> documenso-webhooks-events
Implement Documenso webhook configuration and event handling. Use when setting up webhook endpoints, handling document events, or implementing real-time notifications for document signing. Trigger with phrases like "documenso webhook", "documenso events", "document completed webhook", "signing notification".
curl "https://skillshub.wtf/jeremylongshore/claude-code-plugins-plus-skills/documenso-webhooks-events?format=md"Documenso Webhooks & Events
Overview
Configure and handle Documenso webhooks for real-time document lifecycle notifications. Webhooks require a Teams plan or higher. The webhook secret is sent via the X-Documenso-Secret header (not HMAC-signed -- it is a shared secret comparison).
Prerequisites
- Documenso team account (webhooks require teams)
- HTTPS endpoint for webhook reception
- Completed
documenso-install-authsetup
Supported Events
| Event | Trigger | Use Case |
|---|---|---|
document.created | New document created | Audit logging |
document.sent | Document sent for signing | Start SLA timers |
document.opened | Recipient opens the document | Track engagement |
document.signed | One recipient completes signing | Progress tracking |
document.completed | All recipients have signed | Trigger downstream workflows |
document.rejected | Recipient rejects | Alert sender, escalate |
document.cancelled | Sender cancels document | Cleanup, notify recipients |
Instructions
Step 1: Create Webhook via Dashboard
- Log into Documenso, navigate to Team Settings > Webhooks.
- Click Create Webhook.
- Enter your HTTPS endpoint URL.
- Select the events you want to receive.
- (Optional) Enter a webhook secret -- this value will be sent as-is in the
X-Documenso-Secretheader on every request. - Save.
Step 2: Webhook Handler (Express)
// src/webhooks/documenso.ts
import express from "express";
const router = express.Router();
const WEBHOOK_SECRET = process.env.DOCUMENSO_WEBHOOK_SECRET!;
// Middleware: verify the shared secret
function verifySecret(req: express.Request, res: express.Response, next: express.NextFunction) {
const secret = req.headers["x-documenso-secret"];
if (!secret || secret !== WEBHOOK_SECRET) {
console.warn("Webhook rejected: invalid secret");
return res.status(401).json({ error: "Invalid webhook secret" });
}
next();
}
router.post("/webhooks/documenso", express.json(), verifySecret, async (req, res) => {
const { event, payload } = req.body;
console.log(`Received ${event} for document ${payload.id}`);
// Acknowledge immediately -- process async
res.status(200).json({ received: true });
// Route to handler
try {
await handleEvent(event, payload);
} catch (err) {
console.error(`Failed to process ${event}:`, err);
}
});
async function handleEvent(event: string, payload: any) {
switch (event) {
case "document.completed":
// All recipients signed -- download final PDF, update CRM
await onDocumentCompleted(payload);
break;
case "document.signed":
// One recipient signed -- track progress
await onRecipientSigned(payload);
break;
case "document.rejected":
// Recipient rejected -- alert sender
await onDocumentRejected(payload);
break;
case "document.opened":
// Track engagement for SLA
console.log(`Document ${payload.id} opened by recipient`);
break;
default:
console.log(`Unhandled event: ${event}`);
}
}
async function onDocumentCompleted(payload: any) {
const { id, title, recipients } = payload;
console.log(`Document "${title}" (${id}) completed by all ${recipients?.length} recipients`);
// Download signed PDF, store in S3, update database, notify team
}
async function onRecipientSigned(payload: any) {
console.log(`Recipient signed document ${payload.id}`);
// Update progress tracker, send notification
}
async function onDocumentRejected(payload: any) {
console.log(`Document ${payload.id} REJECTED`);
// Alert sender, create follow-up task
}
export default router;
Step 3: Verification in Python
# webhooks/documenso.py
from flask import Flask, request, jsonify
import hmac
app = Flask(__name__)
WEBHOOK_SECRET = os.environ["DOCUMENSO_WEBHOOK_SECRET"]
@app.route("/webhooks/documenso", methods=["POST"])
def handle_webhook():
# Verify shared secret (constant-time comparison)
secret = request.headers.get("X-Documenso-Secret", "")
if not hmac.compare_digest(secret, WEBHOOK_SECRET):
return jsonify({"error": "Unauthorized"}), 401
data = request.json
event = data["event"]
payload = data["payload"]
print(f"Event: {event}, Document: {payload['id']}")
if event == "document.completed":
# Trigger post-signing workflow
pass
elif event == "document.rejected":
# Alert and escalate
pass
return jsonify({"received": True}), 200
Step 4: Local Development with ngrok
# Start your webhook server
npm run dev # listening on port 3000
# Expose via ngrok
ngrok http 3000
# Copy the HTTPS URL (e.g., https://abc123.ngrok.io)
# Add as webhook URL in Documenso dashboard:
# https://abc123.ngrok.io/webhooks/documenso
Step 5: Test with curl
# Simulate a webhook delivery locally
curl -X POST http://localhost:3000/webhooks/documenso \
-H "Content-Type: application/json" \
-H "X-Documenso-Secret: $DOCUMENSO_WEBHOOK_SECRET" \
-d '{
"event": "document.completed",
"payload": {
"id": 42,
"title": "Service Agreement",
"status": "COMPLETED",
"recipients": [
{ "email": "signer@example.com", "name": "Jane Doe", "role": "SIGNER" }
]
}
}'
Step 6: Idempotency and Reliable Processing
// Use a Set or database to deduplicate events
const processedEvents = new Set<string>();
async function handleEventIdempotent(event: string, payload: any) {
const eventKey = `${event}:${payload.id}:${payload.updatedAt}`;
if (processedEvents.has(eventKey)) {
console.log(`Skipping duplicate: ${eventKey}`);
return;
}
processedEvents.add(eventKey);
await handleEvent(event, payload);
}
For production, store processed event IDs in Redis or a database table rather than in-memory.
Webhook Payload Structure
{
"event": "document.completed",
"payload": {
"id": 42,
"externalId": null,
"userId": 1,
"teamId": 5,
"title": "Service Agreement",
"status": "COMPLETED",
"createdAt": "2026-03-22T10:00:00.000Z",
"updatedAt": "2026-03-22T14:30:00.000Z",
"completedAt": "2026-03-22T14:30:00.000Z",
"recipients": [
{
"email": "signer@example.com",
"name": "Jane Doe",
"role": "SIGNER",
"signingStatus": "SIGNED"
}
]
}
}
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| 401 on webhook | Secret mismatch | Verify X-Documenso-Secret matches your stored secret |
| No events received | URL not HTTPS | Use HTTPS endpoint (ngrok for local dev) |
| Duplicate processing | Retry delivery | Implement idempotency with event key deduplication |
| Handler timeout | Slow processing | Acknowledge 200 immediately, process async via queue |
| Events stop arriving | Webhook disabled | Check webhook status in Team Settings |
Resources
Next Steps
For performance optimization, see documenso-performance-tuning.
> related_skills --same-repo
> fathom-cost-tuning
Optimize Fathom API usage and plan selection. Trigger with phrases like "fathom cost", "fathom pricing", "fathom plan".
> fathom-core-workflow-b
Sync Fathom meeting data to CRM and build automated follow-up workflows. Use when integrating Fathom with Salesforce, HubSpot, or custom CRMs, or creating automated post-meeting email summaries. Trigger with phrases like "fathom crm sync", "fathom salesforce", "fathom follow-up", "fathom post-meeting workflow".
> fathom-core-workflow-a
Build a meeting analytics pipeline with Fathom transcripts and summaries. Use when extracting insights from meetings, building CRM sync, or creating automated meeting follow-up workflows. Trigger with phrases like "fathom analytics", "fathom meeting pipeline", "fathom transcript analysis", "fathom action items sync".
> fathom-common-errors
Diagnose and fix Fathom API errors including auth failures and missing data. Use when API calls fail, transcripts are empty, or webhooks are not firing. Trigger with phrases like "fathom error", "fathom not working", "fathom api failure", "fix fathom".