Fictura
Webhooks

Signature verification

One header, one HMAC, constant-time compare — the check that stands between your endpoint and spoofed purchases.

With a signing secret set, every delivery carries X-Growth-Signature: the HMAC-SHA256 of the raw request body, hex-encoded, keyed with your secret. Verify against the exact bytes received, before any JSON parsing — a re-serialized body will never match.

Node
import { createHmac, timingSafeEqual } from "node:crypto";

/** rawBody must be the exact bytes received — verify BEFORE JSON.parse. */
function isFromFictura(rawBody: string, signature: string, secret: string): boolean {
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(signature, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}
  • In Express, capture the raw body with express.raw() (or the verify callback) on the webhook route — a globally applied express.json() consumes the bytes first.
  • The connect-time "type": "test" delivery is signed too, so you can verify your verifier before anything real flows.
  • No secret configured → no header. Setting one is optional but strongly recommended: without it, anyone who learns your URL can mint fake purchases.