Claude-code-plugins-plus-skills documenso-webhooks-events
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/documenso-pack/skills/documenso-webhooks-events" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-documenso-webhooks-events && rm -rf "$T"
manifest:
plugins/saas-packs/documenso-pack/skills/documenso-webhooks-events/SKILL.mdsource content
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
setupdocumenso-install-auth
Supported Events
| Event | Trigger | Use Case |
|---|---|---|
| New document created | Audit logging |
| Document sent for signing | Start SLA timers |
| Recipient opens the document | Track engagement |
| One recipient completes signing | Progress tracking |
| All recipients have signed | Trigger downstream workflows |
| Recipient rejects | Alert sender, escalate |
| 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
header on every request.X-Documenso-Secret - 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 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.