E-signature webhooks for signing events
Webhooks tell your server the moment something happens to a signature request: a signer opens it, signs it or declines, or the last person signs. No polling needed.
Add an endpoint
In SignWith, open Settings → Webhooks, add your URL and choose the events you want. SignWith then sends a POST with a JSON body to that URL for each event.
Webhook URLs must be public http:// or https:// addresses. URLs that point at private, loopback or link-local networks (such as localhost, 10.0.0.0/8 or 192.168.0.0/16) are rejected when you save the endpoint and again at delivery time. To test on your own machine, expose it with a tunnel such as ngrok.
Events
| Event | When it's sent |
|---|---|
| signature_request.created | A signature request was sent |
| signature_request.completed | Every signer has signed (on by default) |
| signature_request.canceled | A signature request was canceled |
| signer.viewed | A signer opened the document |
| signer.started | A signer started filling in fields |
| signer.signed | A signer finished signing (on by default) |
| signer.declined | A signer declined to sign |
| template.created | A template was created |
| template.updated | A template was changed |
| template.archived | A template was archived |
| webhook.test | Test event from the dashboard |
Each event's payload and an example are on the webhook events page.
The payload
{
"id": "evt_7Qm2Vt9sKd3LpXa8RzYw1bNc",
"type": "signer.signed",
"created_at": "2026-09-27T10:15:00Z",
"data": { "object": "signer", "id": 9121, ... }
}
idis unique per event and stays the same across retries. Store it and ignore events you've already handled.typeis the event name, also sent in theX-SignWith-Eventheader.datauses the same shapes as API responses and shows the object as it is at delivery time. If you need the latest state, fetch it with the API.
Headers
| Header | Example | Meaning |
|---|---|---|
X-SignWith-Event | signer.signed | Event type (same as type). |
X-SignWith-Delivery | 2b1e… | Unique ID of this delivery attempt. |
X-SignWith-Signature | t=1790503200,v1=5d41… | Timestamp and HMAC-SHA256 signature. |
User-Agent | SignWith Webhook |
Any custom headers you set on the endpoint are sent too.
Verify every request before you trust it: anyone can send a POST to a public URL. Verifying webhook signatures has the code for Node and Python.
Respond quickly, and expect retries
Return any 2xx status within 30 seconds. Do slow work, such as downloading the signed PDF, after you respond, for example in a background job.
Other responses, timeouts and connection errors are retried with exponential back-off: after 1, 2, 4, 8 minutes and so on. Each event is delivered at most 10 times in total, the first attempt plus up to 9 retries. Because an event can arrive more than once, use its id to handle it only once.
A typical handler
When a document is completed, download the signed files:
- Subscribe to
signature_request.completed. - Verify the signature, check you haven't seen the event
idbefore, and respond200. - Read
data.documentsanddata.audit_trail_urlfrom the payload. If a link isnull, fetch the signature request again shortly afterwards withGET /signature_requests/{id}.
Send a test event
The Send test event button on a webhook's settings page sends a webhook.test event straight away, whatever events the endpoint is subscribed to. It's not retried. Use it to check that your endpoint is reachable and that signature verification works.
Start building
The API and MCP server come with every account. 3 free documents a month, then pay per document.