Verifying Webhook Signatures
Every webhook is signed with a per-endpoint signing secret shown in the dashboard. Verify the signature on every request to guarantee the payload came from DocPeel and was not tampered with.
How the signature is computed
The signature is the HMAC-SHA256 of the string {timestamp}.{raw_body}, keyed with your endpoint secret, hex-encoded.
Node.js example
import crypto from 'node:crypto';
export function verifyDocPeel(req, secret) {
const timestamp = req.headers['x-docpeel-timestamp'];
const signature = req.headers['x-docpeel-signature'];
const raw = req.rawBody.toString('utf8');
// Reject deliveries older than 5 minutes (replay protection).
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (age > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${raw}`)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(signature, 'hex'),
);
}Important
- Always verify against the raw request body, before any JSON parsing.
- Use a constant-time comparison (e.g.
crypto.timingSafeEqual). - Reject deliveries with timestamps more than ±5 minutes from now.
- Treat the secret like a password — store it in env vars, not in code.
Rotating secrets
From the dashboard you can roll a webhook secret. During the rollover window (24 hours) DocPeel sends both the old and new signature; verify against either.