How do I verify that a webhook really came from Bead?
1 — Header format
x-webhook-signature: t=1752067200,s=9d6309b739a8e5b87e3c2b8d1d4fbc17e5e3c7f3c5b5a9d3e…Component
Meaning
2 — Verification steps
expected = HMAC-SHA256(signingSecret, rawBody)
3 — Code example (Node.js)
import crypto from "crypto";
import express from "express";
const app = express();
// Raw body needed for HMAC
app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } }));
const SIGNING_SECRET = process.env.BEAD_SIGNING_SECRET;
app.post("/bead/webhooks", (req, res) => {
const sigHeader = req.get("x-webhook-signature") || "";
const [tsPart, sigPart] = sigHeader.split(",").map(p => p.split("=")[1]);
const timestamp = Number(tsPart);
const signature = sigPart;
// ❶ Timestamp tolerance (5 min)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > 300) {
return res.status(400).send("Stale webhook");
}
// ❷ Compute expected signature
const expected = crypto
.createHmac("sha256", SIGNING_SECRET)
.update(req.rawBody)
.digest("hex");
// ❸ Constant-time compare
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
return res.status(400).send("Invalid signature");
}
// ✅ Verified — handle the event
const event = req.body;
// ...business logic...
res.sendStatus(200);
});
app.listen(3000);4 — Common pitfalls
Pitfall
Fix
5 — Testing your implementation
Last updated