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”These are the events currently emitted by the plugin:
| 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 |
signer.reminded | A reminder was sent to a signer |
Webhook Payload
Section titled “Webhook Payload”All deliveries use this envelope:
{ "id": "6f1d2c3a-....", "event": "document.signed", "timestamp": "2024-01-20T15:30:45+00:00", "attempt": 1, "data": { "document": { "id": 44, "title": "Employment Contract", "status": "sent" }, "signer": { "id": 38, "name": "John Doe", "email": "john@example.com", "status": "signed" } }, "meta": { "site_url": "https://your-site.com", "site_name": "Acme Corp", "plugin_version": "3.0.6", "webhook_id": "wh_abc123" }}Payload Fields
Section titled “Payload Fields”| Field | Type | Description |
|---|---|---|
id | string | Unique delivery ID (stable across retries) |
event | string | Event type |
timestamp | string | ISO 8601 timestamp (UTC) |
attempt | integer | Delivery attempt number (1-based) |
data | object | Event-specific nested objects (document, signer, etc.) |
meta | object | Site/plugin context |
Headers
Section titled “Headers”Each delivery includes both preferred and legacy headers:
| Header | Description |
|---|---|
X-WPS-Event | Event name |
X-WPS-Delivery | Delivery ID |
X-WPS-Timestamp | Unix timestamp |
X-WPS-Attempt | Attempt number |
X-WPS-Signature | sha256=<hmac> when a secret is configured |
X-ESF-Event | Legacy alias of X-WPS-Event |
X-ESF-Delivery | Legacy alias of X-WPS-Delivery |
X-ESF-Timestamp | Legacy alias of X-WPS-Timestamp |
X-ESF-Attempt | Legacy alias of X-WPS-Attempt |
X-ESF-Signature | Legacy alias of X-WPS-Signature |
Prefer X-WPS-* in new integrations. X-ESF-* remains for backward compatibility.
Verifying Signatures
Section titled “Verifying Signatures”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);}
$payload = file_get_contents('php://input');$signature = $_SERVER['HTTP_X_WPS_SIGNATURE'] ?? $_SERVER['HTTP_X_ESF_SIGNATURE'] ?? '';$secret = 'your_webhook_secret';
if (!verify_webhook_signature($payload, $signature, $secret)) { http_response_code(401); exit('Invalid signature');}
$event = json_decode($payload, true);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) );}
app.post('/webhooks/wpsigner', (req, res) => { const payload = req.rawBody; // use the exact raw body string const signature = req.headers['x-wps-signature'] || req.headers['x-esf-signature']; const secret = process.env.WEBHOOK_SECRET;
if (!verifyWebhookSignature(payload, signature, secret)) { return res.status(401).send('Invalid signature'); }
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 retries the webhook
- Retry delays: 1 minute → 10 minutes → 60 minutes
- After 4 failed attempts, delivery stops (HTTP
410from Zapier marks the hook inactive immediately)
Timeout
Section titled “Timeout”Webhooks have a 15-second timeout. For long-running processes, acknowledge first and process asynchronously:
app.post('/webhooks/wpsigner', async (req, res) => { res.status(200).send('OK'); processWebhookAsync(req.body);});Best Practices
Section titled “Best Practices”- Always verify
X-WPS-Signature(fallback toX-ESF-Signatureif needed). - Respond quickly with
2xx, then process asynchronously. - Use
id(delivery ID) for idempotency. - Keep your endpoint on HTTPS.
- Prefer nested
data.document/data.signerfields from the live payload.