Webhooks
Receive real-time notifications when E-Sign events occur in Penvio.
API access requires a Business plan or higher.
Overview
Webhooks allow you to receive HTTP POST requests when E-Sign events happen:
- Envelopes sent, viewed, signed, completed
- Signers decline to sign
- Envelopes expire
Configuration
Per-Envelope Webhooks
Configure webhooks when creating an envelope via the API:
{
"templateId": "tmpl_abc123",
"signers": [...],
"webhookUrl": "https://api.example.com/webhooks/esign",
"webhookSecret": "your-secret-key-min-16-chars"
}The webhookSecret is used for HMAC signature verification.
Webhook Endpoint Requirements
Your endpoint must:
- Accept POST requests
- Return 2xx status within 30 seconds
- Be HTTPS in production
Event Types
| Event | Description |
|---|---|
envelope.sent | Envelope sent for signing |
envelope.viewed | Signer opened document |
envelope.signed | Signer completed signing |
envelope.completed | All signers completed |
envelope.declined | Signer declined to sign |
envelope.expired | Envelope expired |
Payload Format
{
"id": "evt_abc123",
"type": "envelope.completed",
"createdAt": "2024-01-15T10:30:00Z",
"data": {
"envelopeId": "env_xyz789",
"status": "COMPLETED",
"completedAt": "2024-01-15T10:30:00Z",
"signers": [
{
"id": "signer_1",
"email": "john@example.com",
"status": "SIGNED",
"signedAt": "2024-01-15T10:25:00Z"
}
],
"documentStorageKey": "esign/envelopes/org_abc/env_xyz789/signed.pdf",
"certificateStorageKey": "esign/envelopes/org_abc/env_xyz789/certificate.pdf"
}
}Signature Verification
All webhooks include an HMAC-SHA256 signature in the X-Penvio-Signature header. Always verify this signature before processing the webhook.
import crypto from 'crypto';
function verifyWebhook(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expected}`)
);
}
// In your webhook handler
app.post('/webhooks/esign', (req, res) => {
const signature = req.headers['x-penvio-signature'];
const isValid = verifyWebhook(
JSON.stringify(req.body),
signature,
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).send('Invalid signature');
}
// Process the webhook
const { type, data } = req.body;
switch (type) {
case 'envelope.completed':
console.log(`Envelope ${data.envelopeId} completed!`);
// Download signed document from your BYOB bucket
break;
case 'envelope.declined':
console.log(`Envelope ${data.envelopeId} declined`);
break;
}
res.status(200).send('OK');
});Python Example
import hmac
import hashlib
def verify_webhook(payload: str, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, f'sha256={expected}')
# In your webhook handler (Flask example)
@app.route('/webhooks/esign', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Penvio-Signature')
payload = request.get_data(as_text=True)
if not verify_webhook(payload, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.get_json()
if event['type'] == 'envelope.completed':
envelope_id = event['data']['envelopeId']
# Download signed document from your BYOB bucket
return 'OK', 200Retry Policy
Failed deliveries are retried with exponential backoff:
- 3 retry attempts
- Delays: 1 minute, 5 minutes, 30 minutes
- After 3 failures, the delivery is marked as failed
Check delivery status via the audit trail in your envelope details.
Idempotency
Webhooks may be delivered more than once. Use the event id to ensure idempotent processing:
const processedEvents = new Set();
app.post('/webhooks/esign', async (req, res) => {
// ... verify signature ...
const { id, type, data } = req.body;
// Skip if already processed
if (processedEvents.has(id)) {
return res.status(200).send('Already processed');
}
// Process the event
await handleEvent(type, data);
// Mark as processed (in production, use persistent storage)
processedEvents.add(id);
res.status(200).send('OK');
});Best Practices
- Respond quickly - Return 200 immediately, process async
- Verify signatures - Always validate webhook authenticity
- Handle duplicates - Use event IDs for idempotency
- Log everything - Keep records for debugging
- Monitor failures - Alert on delivery failures
- Use queues - Process webhooks via a message queue for reliability
Webhook payloads include storage keys for documents. Use these keys to access files directly from your BYOB bucket.
Next Steps
- API Endpoints - Browse all available endpoints
- Authentication - Manage API keys
- Error Handling - Handle webhook delivery errors