Webhooks
Receive signed event notifications, verify their signatures, and handle retries.
Webhooks notify your systems when signing events happen, so you don't have to poll the API.
Register an endpoint and ESign will POST a JSON event to it. Manage endpoints via the
/webhooks API.
Events
| Event | When |
|---|---|
document.sent | A document was sent for signature. |
document.opened | A recipient opened the signing page. |
recipient.completed | A recipient finished signing their fields. |
recipient.declined | A recipient declined to sign. |
document.completed | All signers are done; the sealed PDF is ready. |
document.voided | The document was voided (e.g. a decline or expiry). |
Payload
Payloads carry IDs and summary fields only — never file bytes. Fetch protected content via the API using the IDs.
{
"id": "evt_019...",
"type": "document.completed",
"createdAt": "2026-06-17T01:23:45.000Z",
"data": {
"documentId": "019...",
"title": "NDA",
"sealedFileHash": "…",
"status": "completed"
}
}Verifying signatures
Each delivery is signed with your endpoint's secret in an X-Esign-Signature header:
X-Esign-Signature: t=1718590000,v1=<hex HMAC-SHA256>v1 is HMAC-SHA256(secret, "<t>.<raw request body>"). To verify, recompute it from the raw
body and the timestamp, then compare in constant time:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(rawBody: string, header: string, secret: string): boolean {
const m = /t=(\d+),v1=([a-f0-9]+)/.exec(header);
if (!m) return false;
const expected = createHmac('sha256', secret).update(`${m[1]}.${rawBody}`).digest('hex');
return timingSafeEqual(Buffer.from(expected), Buffer.from(m[2]));
}Delivery & retries
- Respond with
2xxquickly to acknowledge receipt; do heavy work asynchronously. - Failed deliveries are retried with staged backoff: 1m → 5m → 30m → 2h → 8h.
- After 20 consecutive failures the endpoint is automatically disabled.
- Events may be delivered more than once — use the event
idto deduplicate.