Webhooks
Verification
Always verify the signature before acting on a delivery.
Reference helper
The canonical consumer implementation ships in @aegix/webhook-delivery:
import { verifyWebhookRequest } from "@aegix/webhook-delivery";
const result = verifyWebhookRequest({
rawBody, // exact request body text
signature: req.headers["aegix-signature"],
timestamp: req.headers["aegix-timestamp"],
secret, // from the dashboard (shown once)
toleranceSeconds: 300 // 5 minutes clock skew
});
if (!result.valid) return res.status(401).end();Hand-rolled example
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, signature, timestamp, secret) {
if (!signature || !timestamp) return false;
const expected = createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`, "utf8").digest();
const provided = Buffer.from(signature, "hex");
if (provided.length !== expected.length) return false;
const skew = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
if (!/^\d{1,10}$/.test(timestamp) || skew > 300) return false;
return timingSafeEqual(provided, expected);
}Rules
Use crypto.timingSafeEqual on decoded digests, and reject stale or future timestamps. AEGIX performs constant-time comparison and enforces timestamp freshness for replay protection.
Never do JSON.parse(body) → JSON.stringify(body) → verify. Serialization differences silently invalidate the signature.