Developer guide
How to verify webhook signatures
Every webhook from Capay Global is signed. Checking the signature takes a few lines and stops anyone from faking a payment completed event.
Updated October 7, 2026
Verify a delivery in Node.js
verify-capay-webhook.js
import crypto from 'node:crypto';
// rawBody: the exact bytes received, as a string. header: X-Capay-Signature.
export function verifyCapayWebhook(rawBody, header, secret) {
const parts = header.split(',').map((p) => p.trim().split('='));
const t = parts.find(([k]) => k === 't')?.[1];
const sigs = parts.filter(([k]) => k === 'v1').map(([, v]) => v);
if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
return sigs.some(
(s) =>
s.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)),
);
}Step by step
- 1Keep the raw bodyVerify against the exact bytes you received. Parsing the JSON first and serialising it again changes the bytes, and the check fails.
- 2Read the signature headerX-Capay-Signature has the form t=<unix time>,v1=<hex>. During a secret rotation it carries two v1 values: accept the delivery if either one matches.
- 3Recompute the HMACCompute HMAC-SHA256 with your endpoint's whsec_ signing secret over the timestamp, a dot and the raw body.
- 4Compare safely and check the ageCompare in constant time and reject any delivery whose timestamp is more than 300 seconds away from now.
- 5Process each event onceFailed deliveries are retried, so the same event can arrive more than once. Keep the X-Capay-Event-Id you have processed and ignore repeats.
Frequently asked questions
Where do I find the signing secret?
In the dashboard under Webhooks. Each endpoint has its own whsec_ secret, separate from your API keys.
What should my endpoint answer when verification fails?
Return an error status and do not act on the event. Only a verified delivery should change an order.
Which headers come with a delivery?
X-Capay-Signature, X-Capay-Event-Id and X-Capay-Event-Type.
Related guides
How to create a payment linkA payment link is a checkout page you can share anywhere: WhatsApp, social media, SMS, email or an invoice. No website and no code.How to integrate a mobile money APICollect mobile money from your own app with one REST API: test with sandbox keys, create a collection, then confirm it with a signed webhook.How to go live with real paymentsThe sandbox opens the moment you sign up. This checklist covers what has to be true before real money moves.
Start in the sandbox today
Free, unlimited and open the moment you sign up. Live payments open after business verification.