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
Section titled “How they work”- You configure a webhook URL in Legaldoc.
- When an event occurs, Legaldoc sends a
POSTto that URL with the event type and the document’s data. - Your server processes the event and responds
200 OK.
Available events
Section titled “Available events”Document events
Section titled “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
Section titled “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. 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
Section titled “Payload structure”Every notification shares this shape:
{ "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.
Setting up a webhook
Section titled “Setting up a webhook”From your account or team settings, in the Webhooks section:
- Provide the URL that will receive notifications (must be HTTPS).
- Choose which events you want to subscribe to.
- 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) so you can receive real notifications while testing.
Verifying authenticity
Section titled “Verifying authenticity”If you configured a secret, every notification includes the X-Legaldoc-Secret header carrying that value:
POST /webhooks/legaldoc HTTP/1.1Content-Type: application/jsonX-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:
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
Section titled “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 OKimmediately 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
Section titled “Availability”Webhooks are available for individual users and teams.