Skip to content

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.

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 Server
  1. Go to WPsigner → More → Webhooks
  2. Click Add Webhook
  3. Enter your webhook URL (must be HTTPS in production)
  4. Select which events to listen for
  5. Click Save Webhook
SettingDescription
URLThe endpoint that will receive webhook events
SecretA secret key for verifying webhook signatures
EventsWhich events trigger this webhook
StatusActive or paused

These are the events currently emitted by the plugin:

EventDescription
document.createdA new document was created
document.sentDocument was sent for signing
document.viewedA signer viewed the document
document.signedA signer completed their signature
document.completedAll signers have signed
document.declinedA signer declined to sign
document.expiredDocument has expired
signer.remindedA reminder was sent to a signer

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"
}
}
FieldTypeDescription
idstringUnique delivery ID (stable across retries)
eventstringEvent type
timestampstringISO 8601 timestamp (UTC)
attemptintegerDelivery attempt number (1-based)
dataobjectEvent-specific nested objects (document, signer, etc.)
metaobjectSite/plugin context

Each delivery includes both preferred and legacy headers:

HeaderDescription
X-WPS-EventEvent name
X-WPS-DeliveryDelivery ID
X-WPS-TimestampUnix timestamp
X-WPS-AttemptAttempt number
X-WPS-Signaturesha256=<hmac> when a secret is configured
X-ESF-EventLegacy alias of X-WPS-Event
X-ESF-DeliveryLegacy alias of X-WPS-Delivery
X-ESF-TimestampLegacy alias of X-WPS-Timestamp
X-ESF-AttemptLegacy alias of X-WPS-Attempt
X-ESF-SignatureLegacy alias of X-WPS-Signature

Prefer X-WPS-* in new integrations. X-ESF-* remains for backward compatibility.

X-WPS-Signature: sha256=abc123...
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);
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');
});

Return a 2xx status code to acknowledge receipt:

HTTP/1.1 200 OK

If your endpoint returns a non-2xx status or times out:

  1. WPsigner retries the webhook
  2. Retry delays: 1 minute → 10 minutes → 60 minutes
  3. After 4 failed attempts, delivery stops (HTTP 410 from Zapier marks the hook inactive immediately)

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);
});

  1. Always verify X-WPS-Signature (fallback to X-ESF-Signature if needed).
  2. Respond quickly with 2xx, then process asynchronously.
  3. Use id (delivery ID) for idempotency.
  4. Keep your endpoint on HTTPS.
  5. Prefer nested data.document / data.signer fields from the live payload.