Skip to content

Verifying Webhook Signatures

Anyone who knows (or guesses) your callback URL can send a POST request that looks like a webhook. Verifying the signature is what actually proves a request came from Baiyar — never trust a webhook payload without it.

Every request carries an X-Baiyar-Signature header:

X-Baiyar-Signature: t=1725267120,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
  • t — a Unix timestamp of when the request was signed.
  • v1HMAC-SHA256(webhook_secret, "<t>.<raw request body>"), hex-encoded.
  1. Extract t and v1 from the header.
  2. Recompute the HMAC over "<t>.<raw body>" using your webhook secret (from Configure a Webhook).
  3. Compare it to v1 using a constant-time comparison.
  4. Reject the request if t is more than 300 seconds from your current time, to guard against a captured payload being replayed later.
import crypto from "node:crypto";
function verifyWebhook(rawBody, signatureHeader, secret) {
const [tPart, v1Part] = signatureHeader.split(",");
const timestamp = Number(tPart.split("=")[1]);
const signature = v1Part.split("=")[1];
if (Math.abs(Date.now() / 1000 - timestamp) > 300) {
throw new Error("Signature timestamp too old");
}
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const isValid =
expected.length === signature.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
if (!isValid) {
throw new Error("Invalid webhook signature");
}
}

Handling Webhook Deliveries — what to do once a request is verified: responding correctly, retries, and idempotency.