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.