Skip to Content

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

EventDescription
envelope.sentEnvelope sent for signing
envelope.viewedSigner opened document
envelope.signedSigner completed signing
envelope.completedAll signers completed
envelope.declinedSigner declined to sign
envelope.expiredEnvelope 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', 200

Retry 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

  1. Respond quickly - Return 200 immediately, process async
  2. Verify signatures - Always validate webhook authenticity
  3. Handle duplicates - Use event IDs for idempotency
  4. Log everything - Keep records for debugging
  5. Monitor failures - Alert on delivery failures
  6. 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

Last updated on