KiPay

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");