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".
What this skill does
# 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-auth` setup
## 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
1. Log into Documenso, navigate to **Team Settings > Webhooks**.
2. Click **Create Webhook**.
3. Enter your **HTTPS endpoint URL**.
4. Select the events you want to receive.
5. (Optional) Enter a **webhook secret** -- this value will be sent as-is in the `X-Documenso-Secret` header on every request.
6. Save.
### Step 2: Webhook Handler (Express)
```typescript
// 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
```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
```bash
# 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
```bash
# 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": "[email protected]", "name": "Jane Doe", "role": "SIGNER" }
]
}
}'
```
### Step 6: Idempotency and Reliable Processing
```typescript
// 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
```json
{
"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": "[email protected]",
"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
- [Documenso Webhooks](https://docs.documenso.com/developers/webhooks)
- [Webhook Verification](https://docs.documenso.com/docs/developers/webhooks/verification)
- [ngrok Documentation](https://ngrok.com/docs)
## Next Steps
For performance optimization, see `documenso-performance-tuning`.
Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.