Webhook Verification
Secure your webhook endpoints with HMAC signatures.
Trust but Verify
To ensure that a webhook was actually sent by KiPay, you must verify the X-KiPay-Signature header sent with every request.
Signature Format
The X-KiPay-Signature header contains a timestamp and an HMAC-SHA256 hash, comma-separated: t=<timestamp>,v1=<hash>. The hash is computed over "<timestamp>.<raw request body>" using your Webhook Secret as the key — the timestamp is part of the signed content, not just metadata.
Always use the raw, unparsed request body when computing the hash. Re-serializing parsed JSON will produce a different byte sequence and the signature will never match.
Verification Logic
Node.jsPython
const crypto = require("crypto");
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map(p => p.split("=")));
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}
// req.body must be the raw Buffer, not parsed JSON
const ok = verify(req.body, req.headers["x-kipay-signature"], process.env.KIPAY_WEBHOOK_SECRET);
if (!ok) throw new Error("Invalid signature");