๐Ÿ“… let's chat! explore the endless possibilities creating industries that don't exist. click here

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". allowed-tools: Read, Write, Edit, Bash(curl:*), Bash(ngrok:*) version: 1.0.0 license: MIT author: Jeremy Longshore <jeremy@intentsolutions.io>

Allowed Tools

No tools specified

Provided by Plugin

documenso-pack

Claude Code skill pack for Documenso (24 skills)

saas packs v1.0.0
View Plugin

Installation

This skill is included in the documenso-pack plugin:

/plugin install documenso-pack@claude-code-plugins-plus

Click to copy

Instructions

# Documenso Webhooks & Events ## Overview Configure and handle Documenso webhooks for real-time document signing notifications. ## Prerequisites - Documenso team account (webhooks require teams) - HTTPS endpoint for webhook reception - Understanding of webhook security ## Supported Events | Event | Trigger | Description | |-------|---------|-------------| | `document.created` | Document created | New document added to system | | `document.sent` | Document sent | Document sent to recipients | | `document.opened` | Document opened | Recipient opened document | | `document.signed` | Recipient signed | One recipient completed signing | | `document.completed` | All signed | All recipients have signed | | `document.rejected` | Document rejected | Recipient rejected document | | `document.cancelled` | Document cancelled | Document was cancelled | ## Webhook Setup ### Step 1: Create Webhook in Dashboard 1. Log into Documenso dashboard 2. Click avatar -> "Team settings" 3. Navigate to "Webhooks" tab 4. Click "Create Webhook" 5. Configure: - **URL**: Your HTTPS endpoint - **Events**: Select events to subscribe - **Secret**: Optional but recommended ### Step 2: Implement Webhook Endpoint ```typescript import express from "express"; import crypto from "crypto"; const app = express(); // IMPORTANT: Use raw body parser for signature verification app.use("/webhooks/documenso", express.raw({ type: "application/json" })); interface DocumensoWebhookPayload { event: | "document.created" | "document.sent" | "document.opened" | "document.signed" | "document.completed" | "document.rejected" | "document.cancelled"; payload: { id: string; title: string; status: string; createdAt: string; updatedAt: string; documentDataId: string; userId: string; teamId?: string; recipients: Array<{ id: string; email: string; name: string; role: string; signingStatus: string; signedAt?: string; }>; }; createdAt: string; webhookEndpoint: string; } app.post("/webhooks/documenso", async (req, res) => { // Step 1: Verify webhook secret const receivedSecret = req.headers["x-documenso-secret"] as string; const expectedSecret = process.env.DOCUMENSO_WEBHOOK_SECRET; if (expectedSecret && receivedSecret !== expectedSecret) { console.warn("Invalid webhook secret"); return res.status(401).json({ error: "Invalid signature" }); } // Step 2: Parse payload let payload: DocumensoWebhookPayload; try { payload = JSON.parse(req.body.toString()); } catch (error) { console.error("Invalid JSON payload"); return res.status(400).json({ error: "Invalid JSON" }); } // Step 3: Log event console.log(`Webhook received: ${payload.event}`); console.log(`Document: ${payload.payload.id} - ${payload.payload.title}`); // Step 4: Handle event try { await handleWebhookEvent(payload); res.status(200).json({ received: true }); } catch (error) { console.error("Webhook handling error:", error); res.status(500).json({ error: "Internal error" }); } }); async function handleWebhookEvent( webhook: DocumensoWebhookPayload ): Promise { const { event, payload } = webhook; switch (event) { case "document.created": await onDocumentCreated(payload); break; case "document.sent": await onDocumentSent(payload); break; case "document.opened": await onDocumentOpened(payload); break; case "document.signed": await onDocumentSigned(payload); break; case "document.completed": await onDocumentCompleted(payload); break; case "document.rejected": await onDocumentRejected(payload); break; case "document.cancelled": await onDocumentCancelled(payload); break; default: console.log(`Unhandled event: ${event}`); } } ``` ### Step 3: Event Handlers ```typescript async function onDocumentCreated( doc: DocumensoWebhookPayload["payload"] ): Promise { console.log(`Document created: ${doc.title}`); // Track in your system await db.documents.create({ externalId: doc.id, title: doc.title, status: "created", createdAt: new Date(doc.createdAt), }); } async function onDocumentSent( doc: DocumensoWebhookPayload["payload"] ): Promise { console.log(`Document sent: ${doc.title}`); // Update status await db.documents.update({ where: { externalId: doc.id }, data: { status: "sent", sentAt: new Date() }, }); // Notify internal users await notifications.send({ channel: "slack", message: `Document "${doc.title}" sent to ${doc.recipients.length} recipients`, }); } async function onDocumentOpened( doc: DocumensoWebhookPayload["payload"] ): Promise { // Find who opened it const opener = doc.recipients.find( (r) => r.signingStatus === "NOT_SIGNED" // They opened but haven't signed ); console.log(`Document opened by: ${opener?.email}`); // Track for analytics await analytics.track("document_opened", { documentId: doc.id, recipientEmail: opener?.email, }); } async function onDocumentSigned( doc: DocumensoWebhookPayload["payload"] ): Promise { // Find who signed const signer = doc.recipients.find((r) => r.signingStatus === "SIGNED"); console.log(`Document signed by: ${signer?.email}`); // Update tracking await db.signatures.create({ documentId: doc.id, recipientEmail: signer?.email, signedAt: signer?.signedAt ? new Date(signer.signedAt) : new Date(), }); // Check if all have signed const allSigned = doc.recipients.every( (r) => r.role === "CC" || r.signingStatus === "SIGNED" ); if (allSigned) { console.log("All recipients have signed!"); } } async function onDocumentCompleted( doc: DocumensoWebhookPayload["payload"] ): Promise { console.log(`Document completed: ${doc.title}`); // Update status await db.documents.update({ where: { externalId: doc.id }, data: { status: "completed", completedAt: new Date() }, }); // Download signed document const client = getDocumensoClient(); const signedDoc = await client.documents.downloadV0({ documentId: doc.id, }); // Store signed copy await storage.upload(`signed/${doc.id}.pdf`, signedDoc); // Trigger downstream processes await workflows.trigger("document_completed", { documentId: doc.id, title: doc.title, }); } async function onDocumentRejected( doc: DocumensoWebhookPayload["payload"] ): Promise { const rejecter = doc.recipients.find((r) => r.signingStatus === "REJECTED"); console.log(`Document rejected by: ${rejecter?.email}`); // Update status await db.documents.update({ where: { externalId: doc.id }, data: { status: "rejected", rejectedBy: rejecter?.email }, }); // Alert team await notifications.send({ channel: "slack", priority: "high", message: `Document "${doc.title}" rejected by ${rejecter?.email}`, }); } async function onDocumentCancelled( doc: DocumensoWebhookPayload["payload"] ): Promise { console.log(`Document cancelled: ${doc.title}`); // Update status await db.documents.update({ where: { externalId: doc.id }, data: { status: "cancelled", cancelledAt: new Date() }, }); } ``` ### Step 4: Idempotency ```typescript // Prevent duplicate processing const processedWebhooks = new Set(); async function handleWebhookIdempotent( webhook: DocumensoWebhookPayload ): Promise { // Create unique key from event + document + timestamp const key = `${webhook.event}:${webhook.payload.id}:${webhook.createdAt}`; // Check if already processed if (processedWebhooks.has(key)) { console.log(`Duplicate webhook ignored: ${key}`); return false; } // Mark as processed processedWebhooks.add(key); // Cleanup old entries (keep last 10000) if (processedWebhooks.size > 10000) { const oldest = processedWebhooks.values().next().value; processedWebhooks.delete(oldest); } // Process webhook await handleWebhookEvent(webhook); return true; } // For production, use Redis or database import Redis from "ioredis"; const redis = new Redis(process.env.REDIS_URL); async function handleWebhookIdempotentRedis( webhook: DocumensoWebhookPayload ): Promise { const key = `webhook:${webhook.event}:${webhook.payload.id}:${webhook.createdAt}`; // Try to set key with 7 day expiry const result = await redis.set(key, "1", "EX", 604800, "NX"); if (!result) { console.log(`Duplicate webhook: ${key}`); return false; } await handleWebhookEvent(webhook); return true; } ``` ## Local Development ```bash # Start ngrok tunnel ngrok http 3000 # Copy the HTTPS URL (e.g., https://abc123.ngrok.io) # Configure this URL in Documenso webhook settings # Your endpoint: https://abc123.ngrok.io/webhooks/documenso ``` ## Testing Webhooks ```bash # Test webhook endpoint locally curl -X POST http://localhost:3000/webhooks/documenso \ -H "Content-Type: application/json" \ -H "X-Documenso-Secret: your-secret" \ -d '{ "event": "document.completed", "payload": { "id": "doc_test123", "title": "Test Document", "status": "COMPLETED", "createdAt": "2024-01-01T00:00:00Z", "updatedAt": "2024-01-01T01:00:00Z", "recipients": [ { "id": "rec_123", "email": "signer@example.com", "name": "Test Signer", "role": "SIGNER", "signingStatus": "SIGNED" } ] }, "createdAt": "2024-01-01T01:00:00Z", "webhookEndpoint": "https://yourapp.com/webhooks/documenso" }' ``` ## Output - Webhook endpoint configured - All events handled - Idempotency implemented - Local testing ready ## Error Handling | Issue | Cause | Solution | |-------|-------|----------| | 401 Unauthorized | Wrong secret | Check webhook secret | | Webhook not received | URL not HTTPS | Use HTTPS endpoint | | Duplicate processing | No idempotency | Add deduplication | | Timeout | Slow handler | Use async queue | ## Resources - [Documenso Webhooks](https://docs.documenso.com/developers/webhooks) - [ngrok Documentation](https://ngrok.com/docs) - [Webhook Best Practices](https://webhooks.fyi/) ## Next Steps For performance optimization, see `documenso-performance-tuning`.

Skill file: plugins/saas-packs/documenso-pack/skills/documenso-webhooks-events/SKILL.md