# Webhooks > How to receive real-time notifications about your documents' events, and how to verify they come from Legaldoc. Webhooks are HTTP notifications Legaldoc.io sends to a URL of your choice whenever an event occurs on a document or template — without you having to poll the API periodically to find out if something changed. Typical use cases: syncing a document's status with your database, triggering an automated workflow when a signature completes, or integrating Legaldoc with your CRM or other third-party systems. ## How they work 1. You configure a webhook URL in Legaldoc. 2. When an event occurs, Legaldoc sends a `POST` to that URL with the event type and the document's data. 3. Your server processes the event and responds `200 OK`. ## Available events ### Document events | Event | Fires when... | |---|---| | `DOCUMENT_CREATED` | A new document is created. | | `DOCUMENT_SENT` | The document is sent to its recipients. | | `DOCUMENT_OPENED` | A recipient opens the document for the first time. | | `DOCUMENT_SIGNED` | A recipient signs. Fires for each individual signature, not just on completion. | | `DOCUMENT_RECIPIENT_COMPLETED` | A recipient completes their required action (signing, approving, or viewing). | | `DOCUMENT_COMPLETED` | All recipients have completed their action. | | `DOCUMENT_REJECTED` | A recipient rejects the document. | | `DOCUMENT_CANCELLED` | The document owner cancels or deletes it. | | `DOCUMENT_REMINDER_SENT` | A reminder is sent to a pending recipient. | ### Template events | Event | Fires when... | |---|---| | `TEMPLATE_CREATED` | A new template is created. | | `TEMPLATE_UPDATED` | A template is modified (settings, recipients, or fields). | | `TEMPLATE_DELETED` | A template is deleted. | | `TEMPLATE_USED` | A document is created from a template — fires alongside `DOCUMENT_CREATED`. | For a standard signing flow, the events you'll almost always care about are `DOCUMENT_COMPLETED` and `DOCUMENT_REJECTED` — see [Integration Guide](/en/guides/integration-guide/#5-knowing-when-its-done). The rest are useful for finer-grained tracking, for example notifying an internal user when a specific recipient signs within a sequential, multi-signer flow. You can subscribe to all events or only the ones you need. ## Payload structure Every notification shares this shape: ```json { "event": "DOCUMENT_COMPLETED", "payload": { /* document or template, with its recipients */ }, "createdAt": "2024-04-22T11:52:18.277Z", "webhookEndpoint": "https://your-server.com/webhooks/legaldoc" } ``` | Field | Description | |---|---| | `event` | The event type identifier — one of the ones listed above. | | `payload` | The affected document or template, including its list of recipients and each one's current status. | | `createdAt` | When this notification was generated. | | `webhookEndpoint` | The URL this notification is being delivered to. | Inside `payload`, each recipient carries its own status: `signingStatus` (`NOT_SIGNED`, `SIGNED`, `REJECTED`), `readStatus` (`NOT_OPENED`, `OPENED`), and, if rejected, `rejectionReason`. The full detail of each resource is in the [API Reference](/en/api/). ## Setting up a webhook From your account or team settings, in the Webhooks section: 1. Provide the **URL** that will receive notifications (must be HTTPS). 2. Choose which **events** you want to subscribe to. 3. Optionally set a **secret** — you'll need it to verify the authenticity of each notification (see below). Your endpoint must meet these requirements: | Requirement | Detail | |---|---| | Protocol | HTTPS | | Method | Accepts `POST` | | Content-Type | `application/json` | | Response | `2xx` within 30 seconds | | Availability | Publicly reachable from the internet | For local development, expose your server through a tunnel (for example [ngrok](https://ngrok.com)) so you can receive real notifications while testing. ## Verifying authenticity If you configured a secret, every notification includes the `X-Legaldoc-Secret` header carrying that value: ```http POST /webhooks/legaldoc HTTP/1.1 Content-Type: application/json X-Legaldoc-Secret: your_configured_secret {"event": "DOCUMENT_COMPLETED", "payload": { /* ... */ }} ``` Before processing any notification, compare that header against your stored secret using a constant-time comparison (not `===` or `==`), so you don't leak information about the secret through response-time variations: ```javascript const crypto = require('crypto'); function isValid(receivedSecret, expectedSecret) { if (!expectedSecret) return true; // no secret configured if (!receivedSecret) return false; try { return crypto.timingSafeEqual( Buffer.from(receivedSecret), Buffer.from(expectedSecret), ); } catch { return false; // different lengths } } app.post('/webhooks/legaldoc', (req, res) => { const secret = req.headers['x-legaldoc-secret']; if (!isValid(secret, process.env.LEGALDOC_WEBHOOK_SECRET)) { return res.status(401).json({ error: 'Invalid signature' }); } const { event, payload } = req.body; // process the event... res.status(200).json({ received: true }); }); ``` If verification fails, respond with `401` without detailing why, and log the attempt for monitoring — never process the payload of a notification that didn't verify. ## Retries If your endpoint doesn't respond `2xx` in time, Legaldoc retries delivery with exponential backoff: | Attempt | Delay | |---|---| | 1 | Immediate | | 2 | 1 minute | | 3 | 5 minutes | | 4 | 30 minutes | | 5 | 2 hours | After the fifth failed attempt, the notification is marked as failed and isn't retried automatically. That's why your handler should: - **Respond fast**: acknowledge with `200 OK` immediately and process the event asynchronously, instead of doing all the work inside the same request. - **Process idempotently**: the same notification can arrive more than once (retries, or a manual resend) — processing it twice shouldn't cause duplicate effects in your system. ## Availability Webhooks are available for individual users and teams.