# 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.

Source: https://signwith.co/docs/api/webhooks · Updated 2026-10-02

## 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`](https://signwith.co/docs/api/webhooks/events#signature_request-created) | A signature request was sent |
| [`signature_request.completed`](https://signwith.co/docs/api/webhooks/events#signature_request-completed) | Every signer has signed (on by default) |
| [`signature_request.canceled`](https://signwith.co/docs/api/webhooks/events#signature_request-canceled) | A signature request was canceled |
| [`signer.viewed`](https://signwith.co/docs/api/webhooks/events#signer-viewed) | A signer opened the document |
| [`signer.started`](https://signwith.co/docs/api/webhooks/events#signer-started) | A signer started filling in fields |
| [`signer.signed`](https://signwith.co/docs/api/webhooks/events#signer-signed) | A signer finished signing (on by default) |
| [`signer.declined`](https://signwith.co/docs/api/webhooks/events#signer-declined) | A signer declined to sign |
| [`template.created`](https://signwith.co/docs/api/webhooks/events#template-created) | A template was created |
| [`template.updated`](https://signwith.co/docs/api/webhooks/events#template-updated) | A template was changed |
| [`template.archived`](https://signwith.co/docs/api/webhooks/events#template-archived) | A template was archived |
| [`webhook.test`](https://signwith.co/docs/api/webhooks/events#webhook-test) | Test event from the dashboard |

Each event's payload and an example are on the [webhook events](https://signwith.co/docs/api/webhooks/events) page.

## The payload

```json
{
  "id": "evt_7Qm2Vt9sKd3LpXa8RzYw1bNc",
  "type": "signer.signed",
  "created_at": "2026-09-27T10:15:00Z",
  "data": { "object": "signer", "id": 9121, ... }
}
```

- `id` is unique per event and stays the same across retries. Store it and ignore events you've already handled.
- `type` is the event name, also sent in the `X-SignWith-Event` header.
- `data` uses 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](https://signwith.co/docs/api/webhooks/verify-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:

1. Subscribe to `signature_request.completed`.
2. Verify the signature, check you haven't seen the event `id` before, and respond `200`.
3. Read `data.documents` and `data.audit_trail_url` from the payload. If a link is `null`, fetch the signature request again shortly afterwards with [`GET /signature_requests/{id}`](https://signwith.co/docs/api/signature-requests#get-a-signature-request).

## 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.
