Webhooks
Webhooks allow your application to receive real-time HTTP notifications when events occur in WPsigner. Instead of polling the API for changes, you can subscribe to events and receive instant updates.
Overview
Section titled “Overview”When an event occurs (like a document being signed), WPsigner sends an HTTP POST request to your configured webhook URL with details about the event.
Document Signed → WPsigner → HTTP POST → Your ServerConfiguring Webhooks
Section titled “Configuring Webhooks”Via WordPress Admin
Section titled “Via WordPress Admin”- Go to WPsigner → More → Webhooks
- Click Add Webhook
- Enter your webhook URL (must be HTTPS in production)
- Select which events to listen for
- Click Save Webhook
Webhook Settings
Section titled “Webhook Settings”| Setting | Description |
|---|---|
| URL | The endpoint that will receive webhook events |
| Secret | A secret key for verifying webhook signatures |
| Events | Which events trigger this webhook |
| Status | Active or paused |
Webhook Events
Section titled “Webhook Events”Document Events
Section titled “Document Events”| Event | Description |
|---|---|
document.created | A new document was created |
document.sent | Document was sent for signing |
document.viewed | A signer viewed the document |
document.signed | A signer completed their signature |
document.completed | All signers have signed |
document.declined | A signer declined to sign |
document.expired | Document has expired |
document.deleted | Document was deleted |
Signer Events
Section titled “Signer Events”| Event | Description |
|---|---|
signer.added | A new signer was added |
signer.reminded | A reminder was sent to signer |
signer.viewed | Signer opened the document |
signer.signed | Signer completed their signature |
signer.declined | Signer declined to sign |
Webhook Payload
Section titled “Webhook Payload”All webhooks include a standard payload structure:
{ "event": "document.signed", "timestamp": "2024-01-20T15:30:45Z", "webhook_id": "wh_abc123", "data": { "document_id": "44", "document_title": "Employment Contract", "signer_id": "38", "signer_name": "John Doe", "signer_email": "john@example.com", "status": "signed" }, "metadata": { "site_url": "https://your-site.com", "plugin_version": "2.8.0" }}Payload Fields
Section titled “Payload Fields”| Field | Type | Description |
|---|---|---|
event | string | The event type |
timestamp | string | ISO 8601 timestamp |
webhook_id | string | Unique webhook delivery ID |
data | object | Event-specific data |
metadata | object | Additional context |
Event Payloads
Section titled “Event Payloads”document.created
Section titled “document.created”{ "event": "document.created", "timestamp": "2024-01-15T10:00:00Z", "data": { "document_id": "44", "document_title": "Employment Contract", "created_by": "admin", "status": "draft" }}document.sent
Section titled “document.sent”{ "event": "document.sent", "timestamp": "2024-01-15T10:15:00Z", "data": { "document_id": "44", "document_title": "Employment Contract", "signers_count": 2, "signers": [ { "id": "38", "name": "John Doe", "email": "john@example.com" }, { "id": "39", "name": "Jane Smith", "email": "jane@example.com" } ] }}document.signed
Section titled “document.signed”{ "event": "document.signed", "timestamp": "2024-01-20T15:30:45Z", "data": { "document_id": "44", "document_title": "Employment Contract", "signer_id": "38", "signer_name": "John Doe", "signer_email": "john@example.com", "signing_order": 1, "remaining_signers": 1, "ip_address": "203.0.113.50" }}document.completed
Section titled “document.completed”{ "event": "document.completed", "timestamp": "2024-01-21T09:15:00Z", "data": { "document_id": "44", "document_title": "Employment Contract", "completed_at": "2024-01-21T09:15:00Z", "total_signers": 2, "signed_document_url": "https://your-site.com/wp-json/wpsigner/v1/documents/44/file" }}document.declined
Section titled “document.declined”{ "event": "document.declined", "timestamp": "2024-01-18T14:20:00Z", "data": { "document_id": "44", "document_title": "Employment Contract", "signer_id": "38", "signer_name": "John Doe", "signer_email": "john@example.com", "decline_reason": "I need more time to review the terms." }}Verifying Webhook Signatures
Section titled “Verifying Webhook Signatures”All webhooks include a signature header to verify authenticity:
X-WPS-Signature: sha256=abc123...Verifying in PHP
Section titled “Verifying in PHP”function verify_webhook_signature($payload, $signature, $secret) { $expected = 'sha256=' . hash_hmac('sha256', $payload, $secret); return hash_equals($expected, $signature);}
// In your webhook handler$payload = file_get_contents('php://input');$signature = $_SERVER['HTTP_X_WPS_SIGNATURE'] ?? '';$secret = 'your_webhook_secret';
if (!verify_webhook_signature($payload, $signature, $secret)) { http_response_code(401); exit('Invalid signature');}
$event = json_decode($payload, true);// Process the event...Verifying in Node.js
Section titled “Verifying in Node.js”const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) { const expected = 'sha256=' + crypto.createHmac('sha256', secret) .update(payload) .digest('hex');
return crypto.timingSafeEqual( Buffer.from(expected), Buffer.from(signature) );}
// Express middlewareapp.post('/webhooks/wpsigner', (req, res) => { const payload = JSON.stringify(req.body); const signature = req.headers['x-wps-signature']; const secret = process.env.WEBHOOK_SECRET;
if (!verifyWebhookSignature(payload, signature, secret)) { return res.status(401).send('Invalid signature'); }
const event = req.body; // Process the event...
res.status(200).send('OK');});Responding to Webhooks
Section titled “Responding to Webhooks”Success Response
Section titled “Success Response”Return a 2xx status code to acknowledge receipt:
HTTP/1.1 200 OKFailure Handling
Section titled “Failure Handling”If your endpoint returns a non-2xx status or times out:
- WPsigner will retry the webhook
- Retries occur at: 1 min, 5 min, 30 min, 2 hours
- After 4 failed attempts, the webhook is marked as failed
Timeout
Section titled “Timeout”Webhooks have a 30-second timeout. For long-running processes:
app.post('/webhooks/wpsigner', async (req, res) => { // Acknowledge immediately res.status(200).send('OK');
// Process async processWebhookAsync(req.body);});Best Practices
Section titled “Best Practices”1. Always Verify Signatures
Section titled “1. Always Verify Signatures”Never trust webhook payloads without verification:
if (!verifySignature(payload, signature, secret)) { return res.status(401).send('Unauthorized');}2. Process Asynchronously
Section titled “2. Process Asynchronously”Don’t block the response:
// ❌ Bad - blocks responseawait processLongRunningTask(event);res.send('OK');
// ✅ Good - async processingres.send('OK');processLongRunningTask(event);3. Handle Duplicates
Section titled “3. Handle Duplicates”Webhooks may occasionally be delivered more than once:
const processed = new Set();
function handleWebhook(event) { if (processed.has(event.webhook_id)) { return; // Already processed } processed.add(event.webhook_id); // Process event...}4. Log Everything
Section titled “4. Log Everything”Keep detailed logs for debugging:
console.log('Webhook received:', { event: event.event, webhook_id: event.webhook_id, timestamp: new Date().toISOString()});Testing Webhooks
Section titled “Testing Webhooks”Local Development
Section titled “Local Development”Use a tunneling service like ngrok for local testing:
ngrok http 3000# Use the generated URL: https://abc123.ngrok.io/webhooksTest Events
Section titled “Test Events”You can trigger test webhooks from the WordPress admin:
- Go to WPsigner → Webhooks
- Click on your webhook
- Click Send Test Event
- Select an event type
- Click Send
Webhook Logs
Section titled “Webhook Logs”View webhook delivery history in WordPress admin:
- Go to WPsigner → More → Webhooks
- Click on a webhook to view details
- See Recent Deliveries section
Each log entry shows:
- Event type
- Response code
- Response time
- Timestamp
- Retry count (if failed)