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
- Open Settings → Webhooks.
- Select Create Webhook and choose Personal only or one workspace.
- Enter a publicly reachable HTTPS URL.
- Select specific events or All Events.
- 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
| Event | Emitted when |
|---|---|
* | Any current or future event occurs |
document.created | An envelope is created |
document.updated | Envelope metadata or workflow configuration changes |
document.deleted | An envelope is deleted |
document.sent | Recipients can begin the signing workflow |
document.viewed | A recipient opens the signing page |
document.signed | A signer or approver completes their action |
document.completed | Every required recipient has completed the envelope |
document.estamp.started | The e-stamp provider accepts the purchase |
document.estamp.completed | The stamped PDF is attached to the envelope |
document.estamp.failed | E-stamping reaches a terminal failure |
workflow.published | A workspace workflow is published |
workflow.run.created | A workflow creates a signing run |
workflow.run.approval_requested | A workflow run needs approval before sending |
workflow.run.approved | A workflow run receives final approval |
workflow.run.rejected | A workflow run is rejected |
bulk_send.started | A workspace bulk send is queued |
bulk_send.completed | Every row in a bulk send succeeds |
bulk_send.partial_failure | A 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:
| Header | Description |
|---|---|
X-SignSecure-Event | Event name, for example document.estamp.completed |
X-SignSecure-Delivery | Unique delivery identifier |
X-SignSecure-Signature | Timestamped HMAC-SHA256 signature |
X-SignSecure-Scope | workspace or personal |
X-SignSecure-Workspace | Workspace ID, or personal for a personal event |
User-Agent | SignSecure-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=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdSignSecure 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:
- Select a completed delivery and choose Replay.
- SignSecure creates a new delivery record; the original record remains unchanged for auditing.
- The replay keeps the same event ID but receives a new delivery ID.
- Because replay can repeat an action, receivers must deduplicate using the event
idbefore 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, notX-SignSecure-Delivery. - Return
2xxfor 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.