SignSecure Sign PadSignSecure Sign Pad

Webhooks

Verify and process real-time SignSecure Sign Pad lifecycle events.

Webhooks deliver signed JSON POST requests when an envelope changes. Use them to advance your application without continuously polling the SignSecure Sign Pad API.

Create a webhook

  1. Open Settings → Webhooks.
  2. Select Create Webhook and choose Personal only or one workspace.
  3. Enter a publicly reachable HTTPS URL.
  4. Select specific events or All Events.
  5. Copy the whsec_... signing secret. It is displayed only once.

Store the signing secret in a secret manager. Never expose it to browser code or log it with incoming request headers.

Available events

EventEmitted when
*Any current or future event occurs
document.createdAn envelope is created
document.updatedEnvelope metadata or workflow configuration changes
document.deletedAn envelope is deleted
document.sentRecipients can begin the signing workflow
document.viewedA recipient opens the signing page
document.signedA signer or approver completes their action
document.completedEvery required recipient has completed the envelope
document.estamp.startedThe e-stamp provider accepts the purchase
document.estamp.completedThe stamped PDF is attached to the envelope
document.estamp.failedE-stamping reaches a terminal failure
workflow.publishedA workspace workflow is published
workflow.run.createdA workflow creates a signing run
workflow.run.approval_requestedA workflow run needs approval before sending
workflow.run.approvedA workflow run receives final approval
workflow.run.rejectedA workflow run is rejected
bulk_send.startedA workspace bulk send is queued
bulk_send.completedEvery row in a bulk send succeeds
bulk_send.partial_failureA bulk send finishes with failed rows

See E-Stamp API Workflow for e-stamp event ordering and complete payload examples.

Request format

Every delivery uses Content-Type: application/json and includes:

HeaderDescription
X-SignSecure-EventEvent name, for example document.estamp.completed
X-SignSecure-DeliveryUnique delivery identifier
X-SignSecure-SignatureTimestamped HMAC-SHA256 signature
X-SignSecure-Scopeworkspace or personal
X-SignSecure-WorkspaceWorkspace ID, or personal for a personal event
User-AgentSignSecure-Webhooks/1.0

All payloads use the same envelope:

{
  "id": "evt_01J...",
  "event": "document.estamp.completed",
  "createdAt": "2026-07-22T10:15:00.000Z",
  "context": {
    "scope": "workspace",
    "workspaceId": "ws_abc123"
  },
  "data": {
    "envelopeId": "env_abc123",
    "status": "completed"
  }
}

Treat id as the event idempotency key. The event-specific data object can grow over time, so ignore unknown properties.

Workspace endpoints receive events only for resources in their workspace. Personal endpoints receive only personal events. Workspace roles with integrations.view can inspect configuration and delivery history; roles with integrations.manage can create, edit, test, replay, and delete endpoints. All accessible endpoints appear together in the webhook ledger, with their Personal or workspace scope shown on each endpoint.

Verify the signature

The signature header has this format:

t=1784715300,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd

SignSecure Sign Pad computes v1 as HMAC-SHA256 over {timestamp}.{raw_json_body}. Verification must use the raw bytes exactly as received, before JSON parsing or re-serialization.

import crypto from "node:crypto";

export function verifySignSecureWebhook(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map((part) => part.split("=", 2)),
  );
  const timestamp = parts.t;
  const received = Buffer.from(parts.v1 ?? "", "hex");
  const expected = Buffer.from(
    crypto
      .createHmac("sha256", secret)
      .update(`${timestamp}.${rawBody}`)
      .digest("hex"),
    "hex",
  );

  if (
    received.length !== expected.length ||
    !crypto.timingSafeEqual(received, expected)
  ) {
    throw new Error("Invalid webhook signature");
  }

  const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!Number.isFinite(ageSeconds) || ageSeconds > 300) {
    throw new Error("Stale webhook timestamp");
  }
}

Express handler

import express from "express";
import { verifySignSecureWebhook } from "./verify-webhook.js";

const app = express();

app.post(
  "/webhooks/signsecure",
  express.raw({ type: "application/json" }),
  async (request, response) => {
    const rawBody = request.body.toString("utf8");
    verifySignSecureWebhook(
      rawBody,
      request.header("X-SignSecure-Signature"),
      process.env.SIGNSECURE_WEBHOOK_SECRET,
    );

    const event = JSON.parse(rawBody);

    // Atomically insert event.id before doing work. If it already exists,
    // acknowledge the duplicate without processing it again.
    await enqueueEventOnce(event.id, event);

    response.sendStatus(200);
  },
);

Delivery and replay behavior

Each event receives one immediate delivery attempt with a 10-second timeout. SignSecure Sign Pad does not run an automatic webhook retry cron, preventing unexpected background redelivery.

Use Settings → Webhooks → Delivery activity to inspect the request payload, HTTP response, latency, attempt count, and error details for each delivery. Delivered and failed events can be replayed after the endpoint is ready:

  1. Select a completed delivery and choose Replay.
  2. SignSecure creates a new delivery record; the original record remains unchanged for auditing.
  3. The replay keeps the same event ID but receives a new delivery ID.
  4. Because replay can repeat an action, receivers must deduplicate using the event id before processing.

Return 2xx as soon as the signature and durable enqueue succeed. Perform slow business logic in your own queue worker.

Processing rules

  • Verify the signature before parsing or trusting any field.
  • Reject timestamps more than five minutes away from your server clock.
  • Deduplicate using id, not X-SignSecure-Delivery.
  • Return 2xx for an already-processed event.
  • Do not assume delivery order. Re-read envelope status when an event arrives out of sequence.
  • Subscribe only to events your application handles, or use * when future events should be delivered automatically.

On this page