Commerce CensusContactRequest access

Verifying webhooks

Your endpoint is public, so anyone can POST to it. Verify the signature on every request before you trust a single field.

Headers
webhook-id:        evt_01JQ8Z3M4K
webhook-timestamp: 1789012345
webhook-signature: v1,K5f9r…

Verify

TypeScript
import crypto from "node:crypto";

function verify(headers, rawBody, secret) {
  const id = headers["webhook-id"];
  const ts = headers["webhook-timestamp"];

  // Reject anything older than five minutes — a valid signature
  // replayed a week later is still an attack.
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;

  const expected = "v1," + crypto
    .createHmac("sha256", secret)
    .update(`${id}.${ts}.${rawBody}`)
    .digest("hex");

  // Constant-time: a plain === leaks the signature byte by byte.
  const a = Buffer.from(expected), b = Buffer.from(headers["webhook-signature"]);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Three mistakes

Verifying against a parsed and re-serialised body rather than the raw bytes — key order changes and the signature fails. Comparing signatures with `===`, which is timing-attackable. And skipping the timestamp check, which leaves you open to replay.

Rotation

An endpoint can hold two secrets at once. Add the new one, deploy your verifier to accept either, then remove the old one.