Set up webhook endpoints to receive real-time notifications about document events in your Documenso workspace.
Prerequisites
A publicly accessible HTTPS endpoint to receive webhook requests
Team owner or admin role in Documenso
Access to the team settings page
Creating a webhook
Navigate to team settings
Go to your team settings and select the Webhooks tab.
Create a new webhook
Click Create Webhook to open the webhook configuration dialog.
Configure webhook settings
Fill in the webhook configuration:
Webhook URL : Your endpoint URL (must be publicly accessible)
Secret : Optional shared secret for signature verification
Event Triggers : Select which events to subscribe to
Enabled : Toggle to enable/disable the webhook
Example configuration: {
"webhookUrl" : "https://your-app.com/webhooks/documenso" ,
"secret" : "your-secret-key" ,
"eventTriggers" : [
"DOCUMENT_SIGNED" ,
"DOCUMENT_COMPLETED"
],
"enabled" : true
}
Test your webhook
After creating the webhook, use the Test button to send a sample payload. This helps verify your endpoint is configured correctly before real events occur.
Webhook endpoint requirements
Your webhook endpoint must:
Accept POST requests with JSON payload
Return a 2xx status code for successful delivery
Respond within 30 seconds
Handle duplicate deliveries (implement idempotency)
Endpoints that consistently fail or timeout may be automatically disabled to prevent performance issues.
Example endpoint implementation
Node.js / Express
Python / Flask
Go
const express = require ( 'express' );
const app = express ();
app . post ( '/webhooks/documenso' , express . json (), async ( req , res ) => {
const { event , payload , createdAt } = req . body ;
const secret = req . headers [ 'x-documenso-secret' ];
// Verify secret (see Security page for full implementation)
if ( secret !== process . env . DOCUMENSO_WEBHOOK_SECRET ) {
return res . status ( 401 ). json ({ error: 'Invalid secret' });
}
// Process the webhook event
console . log ( `Received ${ event } event for document ${ payload . id } ` );
try {
// Handle different event types
switch ( event ) {
case 'DOCUMENT_COMPLETED' :
await handleDocumentCompleted ( payload );
break ;
case 'DOCUMENT_SIGNED' :
await handleDocumentSigned ( payload );
break ;
default :
console . log ( `Unhandled event type: ${ event } ` );
}
// Always return 200 for successful processing
res . status ( 200 ). json ({ received: true });
} catch ( error ) {
console . error ( 'Webhook processing error:' , error );
res . status ( 500 ). json ({ error: 'Processing failed' });
}
});
app . listen ( 3000 );
Managing webhooks
View webhook details
Click on any webhook to view its configuration, event history, and recent calls.
View webhook call logs
Each webhook call is logged with:
Request timestamp
Event type
HTTP status code
Response body and headers
Request payload
Use webhook logs to debug delivery issues and inspect the exact payload sent for each event.
Manually retry webhooks
If a webhook delivery fails, you can manually retry it from the webhook call details page. This resends the exact same payload to your endpoint.
Edit webhook configuration
You can update webhook settings at any time:
Change the webhook URL
Update the secret
Add or remove event subscriptions
Enable or disable the webhook
Delete a webhook
Deleting a webhook immediately stops all event deliveries. This action cannot be undone.
Testing webhooks locally
For local development, use tools like ngrok or localtunnel to expose your local server to the internet.
Start your local server
Run your webhook endpoint locally: node server.js
# or
python app.py
Expose your local server
Use ngrok to create a public URL: Copy the HTTPS forwarding URL (e.g., https://abc123.ngrok.io).
Configure webhook in Documenso
Use the ngrok URL as your webhook URL: https://abc123.ngrok.io/webhooks/documenso
Test the webhook
Trigger events in Documenso or use the test button to verify your local endpoint receives webhooks.
Best practices
Respond with a 200 status code as soon as you receive the webhook. Process the event asynchronously to avoid timeouts. app . post ( '/webhooks/documenso' , async ( req , res ) => {
// Immediately acknowledge receipt
res . status ( 200 ). json ({ received: true });
// Process asynchronously
processWebhookAsync ( req . body ). catch ( console . error );
});
Store processed webhook IDs to avoid processing the same event twice: const processedWebhooks = new Set ();
app . post ( '/webhooks/documenso' , async ( req , res ) => {
const webhookId = req . body . payload . id + req . body . createdAt ;
if ( processedWebhooks . has ( webhookId )) {
return res . status ( 200 ). json ({ received: true , duplicate: true });
}
processedWebhooks . add ( webhookId );
// Process webhook...
});
Keep detailed logs of webhook receipts and processing for debugging: console . log ({
timestamp: new Date (). toISOString (),
event: req . body . event ,
documentId: req . body . payload . id ,
status: 'received'
});
Use environment variables for secrets
Never hardcode webhook secrets in your code: const WEBHOOK_SECRET = process . env . DOCUMENSO_WEBHOOK_SECRET ;
if ( ! WEBHOOK_SECRET ) {
throw new Error ( 'DOCUMENSO_WEBHOOK_SECRET not configured' );
}
Next steps
Event reference Explore all available events and their payloads
Security Implement webhook signature verification