Getting Started

Vouch sends webhook events to the URL you configure in Settings → Webhooks. Each request is a POST with a JSON body and an x-vouch-signature header you should verify before trusting the payload. See the Webhooks section of the API reference for the exact shape of each event’s payload.

Verifying the signature

Vouch signs every webhook request with HMAC-SHA256, keyed by the webhook secret shown when you create the webhook. The signature is sent in the x-vouch-signature header in the form:

t=<unix timestamp>,v1=<hex hmac of "{t}.{raw body}">

Verify it by recomputing the HMAC over {timestamp}.{raw request body} using your webhook secret, and comparing it to v1 with a constant-time comparison. Reject requests where the timestamp is more than 5 minutes old, to guard against replay attacks.

Example (Node.js)

1const crypto = require('crypto');
2
3function verifyWebhookSignature(rawBody, signatureHeader, secret, toleranceSeconds = 300) {
4 const parts = Object.fromEntries(
5 signatureHeader.split(',').map((part) => {
6 const [k, ...rest] = part.split('=');
7 return [k.trim(), rest.join('=').trim()];
8 })
9 );
10 const timestamp = Number(parts.t);
11 const expected = parts.v1;
12 if (!timestamp || !expected) return false;
13 if (Math.abs(Math.floor(Date.now() / 1000) - timestamp) > toleranceSeconds) {
14 return false;
15 }
16 const signedPayload = `${timestamp}.${typeof rawBody === 'string' ? rawBody : JSON.stringify(rawBody)}`;
17 const signature = crypto.createHmac('sha256', secret).update(signedPayload).digest('hex');
18 return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
19}
20
21// Usage — pass the raw request body string when possible
22const isValid = verifyWebhookSignature(
23 request.rawBody,
24 request.headers['x-vouch-signature'],
25 process.env.WEBHOOK_SECRET
26);

Important: Always use crypto.timingSafeEqual (or an equivalent constant-time comparison) when verifying signatures, to prevent timing attacks.