Agent skill
Documenso Webhooks Skill
Receive and authenticate Documenso webhooks. Use when setting up a Documenso webhook endpoint, verifying the X-Documenso-Secret header, debugging 401s or an empty secret header, or handling document events like DOCUMENT_SIGNED and DOCUMENT_COMPLETED.
Install this skill
npx skills add hookdeck/webhook-skills --skill documenso-webhooks
Documenso is an open-source document signing platform. Its webhooks POST a JSON event to your URL whenever a document is created, sent, opened, signed, completed, rejected or cancelled. Documenso does not sign the payload. Despite the setup page saying the secret "signs" it, the secret is sent as plain text in the X-Documenso-Secret header, so authentication is a shared-token check, not an HMAC.
When to Use This Skill
- How do I receive Documenso webhooks?
- How do I verify the
X-Documenso-Secretheader? - Does Documenso sign webhook payloads with HMAC? (No: it sends the secret itself)
- Why does my Documenso webhook arrive with an empty
X-Documenso-Secretheader? - How do I handle
DOCUMENT_SIGNEDandDOCUMENT_COMPLETEDevents?
Verification (core)
Documenso sends the secret you entered when creating the webhook, unchanged, in X-Documenso-Secret. If the webhook has no secret the header is an empty string, so reject empty values and refuse to run without a configured secret.
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyDocumensoSecret(received, expected = process.env.DOCUMENSO_WEBHOOK_SECRET) {
if (!expected) return false; // never accept when no secret is configured
if (!received) return false; // empty header = webhook has no secret
return safeEqual(received, expected);
}
// req.headers['x-documenso-secret'] -> 401 if verifyDocumensoSecret(...) is false
Return 401 when this fails, 400 for a body that isn't a JSON object, and 200 once handled (Documenso expects a 2xx within 10 seconds). There is no payload signature, so the raw body isn't part of verification, but authenticate before parsing it. In Express use express.raw({ type: '*/*' }) so any content type reaches the handler. Use HTTPS, because the secret travels in every request. Details in references/verification.md.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Payload and Event Types
Every request has the shape { "event", "payload", "createdAt", "webhookEndpoint" }, where payload is the document (or template) with its recipients.
| Event | Triggered When |
|---|---|
DOCUMENT_CREATED | A new document is created |
DOCUMENT_SENT | A document is sent to recipients |
DOCUMENT_OPENED | A recipient opens the document for the first time |
DOCUMENT_SIGNED | A recipient signs the document |
DOCUMENT_ | A recipient completes their required action |
DOCUMENT_COMPLETED | All recipients have completed their actions |
DOCUMENT_REJECTED | A recipient rejects the document |
DOCUMENT_CANCELLED | A pending document is cancelled, or an owner deletes it |
DOCUMENT_REMINDER_SENT | A reminder email is sent to a recipient |
RECIPIENT_EXPIRED | A recipient's signing deadline passes |
TEMPLATE_CREATED / TEMPLATE_UPDATED / TEMPLATE_DELETED / TEMPLATE_USED | Template lifecycle events |
Environment Variables
DOCUMENSO_WEBHOOK_SECRET=replace-with-a-long-random-secret # the webhook's "Secret" field
Local Development
Documenso rejects webhook URLs that resolve to private or loopback addresses, so tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 documenso --path /webhooks/documenso
Use the printed URL as the webhook's Webhook URL.
Resources
- references/overview.md - How Documenso webhooks work, events, payload fields
- references/setup.md - Create the webhook in team settings, set the secret, test and resend
- references/verification.md - The X-Documenso-Secret check, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests