Skip to content

Webhooks

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.

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

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.

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) so you can receive real notifications while testing.

If you configured a secret, every notification includes the X-Legaldoc-Secret header carrying that value:

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:

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.

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.

Webhooks are available for individual users and teams.