Skip to content

Verification

Each delivery is signed with the endpoint’s secret using HMAC-SHA256. The signature is the lowercase hex digest of JSON.stringify(payload) — the exact JSON body sent.

import crypto from "node:crypto";
app.post("/bizzle/webhook", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.get("X-Webhook-Signature");
const expected = crypto
.createHmac("sha256", process.env.BIZZLE_WEBHOOK_SECRET)
.update(req.body) // the RAW body bytes, exactly as received
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(400).end();
}
const event = JSON.parse(req.body);
// …
res.status(200).end();
});

Important: sign/verify the raw body, not a re-serialized object. Any difference in key order or whitespace changes the digest.

Bizzle does not send a timestamp or enforce a freshness window. To protect against duplicate or replayed deliveries:

  1. Always verify the signature.
  2. Deduplicate by X-Webhook-Id — the event id is stable across retries.
  3. Return 2xx quickly and process asynchronously.

Secrets are whsec_ followed by 32 hex characters. Rotate at any time with POST /v1/webhooks/:id/rotate-secret and update your server immediately.