# Webhooks

Register an endpoint in **Developers → Webhooks**, choose the events, and Docustay posts a JSON event to it each time one happens. Each endpoint has a secret that starts `whsec_`, shown once.

## Verify every delivery

Deliveries carry the [Standard Webhooks](https://www.standardwebhooks.com) headers `webhook-id`, `webhook-timestamp` and `webhook-signature` (`v1,<base64 HMAC-SHA256 of "id.timestamp.body">`). Verify against the **raw** body, not re-serialised JSON.

```js
import { verifyWebhook } from "@docustay/sdk";
const event = await verifyWebhook(process.env.DOCUSTAY_WEBHOOK_SECRET, req.headers, rawBody);
```

```python
from docustay import verify_webhook
event = verify_webhook(secret, headers, raw_body)
```

Both refuse a changed body, a wrong secret and a timestamp more than five minutes old.
A receiver using any Standard Webhooks library passes `whsec_` plus the secret with `-` swapped for `+` and `_` for `/`.

## Trying it from your laptop

Docustay can only send to a public `https://` address: `localhost`, `127.0.0.1` and office or home network addresses are refused when you add the endpoint, and the message says so. While you build the receiver, give your laptop a public address with a tunnel tool, add that address as the endpoint, and use **Send again** in the delivery log to replay an event as often as you like. When the receiver is deployed, replace the address.

## Delivery

Answer with any `2xx` quickly. A delivery that fails is retried, and every attempt is in the delivery log under the endpoint, where a delivery can be replayed. Deliveries can arrive more than once and out of order: use the `webhook-id` to ignore a repeat, and fetch the document (`GET /api/v1/documents/{id}`) rather than trusting the event as the final word.

The events carry facts about the document, never a signing link or a code.

## Which events

You choose per endpoint. The ones most receivers want are `documents.document.sent`, `documents.document.opened`, `documents.document.signed` (one person signed), `documents.document.executed` (everyone signed and the sealed copy exists), `documents.document.declined`, `documents.document.expired` and `documents.document.voided`. A receiver that only cares about finished documents subscribes to `documents.document.executed` and nothing else.

## A receiver, step by step

1. Read the **raw** request body as bytes before any JSON parser touches it.
2. Verify the three headers against that body with the SDK, as above. If verification throws, answer `400` and stop.
3. Look at `webhook-id`. If you have processed it, answer `200` and stop; this is a repeat.
4. Queue the work (fetch the document, save the signed copy) and answer `200` at once. Do slow work after you have answered.

```bash
curl https://docustay.app/api/v1/documents/DOCUMENT_ID/signed.pdf \
  -H "Authorization: Bearer $DOCUSTAY_KEY" -o signed.pdf
```

## The delivery log

Each endpoint's page shows a delivery log with the event, the result your server gave and which try it was. **Send again** replays a delivery, so you can fix your receiver and try again without making another document. Repeats carry the same `webhook-id`, which is why your receiver should ignore ids it has already handled.
