Skip to content

Webhook Security

Every webhook delivery is signed with an HMAC-SHA256 signature using your webhook secret. Always verify signatures to ensure payloads are genuinely from inSigner and haven’t been tampered with.

Each webhook request includes these security headers:

HeaderDescription
X-InSigner-Signaturesha256=<hex_digest> — HMAC-SHA256 of the raw request body
X-InSigner-TimestampUnix timestamp (seconds) when the event was created

The signature is computed as:

HMAC-SHA256(webhook_secret, raw_request_body)
import crypto from 'crypto';
const WEBHOOK_SECRET = process.env.INSIGNER_WEBHOOK_SECRET;
function verifyWebhookSignature(req) {
const signature = req.headers['x-insigner-signature'];
const timestamp = req.headers['x-insigner-timestamp'];
if (!signature || !timestamp) {
throw new Error('Missing signature headers');
}
// Prevent replay attacks: reject events older than 5 minutes
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
throw new Error('Webhook timestamp too old');
}
// Compute expected signature from raw body
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.rawBody) // Use raw body, not parsed JSON
.digest('hex');
// Constant-time comparison to prevent timing attacks
if (!crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)) {
throw new Error('Invalid webhook signature');
}
return true;
}
// Express route handler
app.post('/webhooks/insigner', express.raw({ type: 'application/json' }), (req, res) => {
try {
// Store raw body before parsing
req.rawBody = req.body;
verifyWebhookSignature(req);
const event = JSON.parse(req.body);
console.log(`Received event: ${event.event}`);
// Process the event
switch (event.event) {
case 'document.completed':
handleDocumentCompleted(event.data);
break;
case 'signer.completed':
handleSignerCompleted(event.data);
break;
}
res.status(200).json({ received: true });
} catch (err) {
console.error('Webhook error:', err.message);
res.status(401).json({ error: err.message });
}
});
  1. Always verify signatures. Never process webhook payloads without verifying the HMAC signature.

  2. Use constant-time comparison. Use crypto.timingSafeEqual() (Node.js) or hmac.compare_digest() (Python) to prevent timing attacks.

  3. Check the timestamp. Reject webhooks with timestamps older than 5 minutes to prevent replay attacks.

  4. Use HTTPS. Always use an HTTPS URL for your webhook endpoint so payloads are encrypted in transit.

  5. Respond quickly. Return a 2xx response within 10 seconds. If you need more processing time, acknowledge the webhook immediately and process asynchronously.

  6. Handle duplicates. Use the X-InSigner-Delivery-Id header to deduplicate events — the same event may be delivered more than once during retries.