DEV Community

jaryn
jaryn

Posted on

Verify Webhook Signatures Before Your Proxy Rewrites the JSON

A webhook arrives with a valid HMAC over its raw bytes. Middleware parses the JSON, changes whitespace and key order, then asks the verifier to authenticate the new serialization. The object is equivalent; the signature is not.

Preserve the signed input

import crypto from "node:crypto";
export function verify(raw, header, secret) {
  const [ts, sig] = header.split(",");
  if (Math.abs(Date.now()/1000-Number(ts)) > 300) return false;
  const expected=crypto.createHmac("sha256",secret).update(`${ts}.`).update(raw).digest("hex");
  return sig.length===expected.length && crypto.timingSafeEqual(Buffer.from(sig),Buffer.from(expected));
}
Enter fullscreen mode Exit fullscreen mode

Capture raw before JSON parsing. Bind the timestamp into the signature and store an event ID or signature digest in a five-minute replay cache. Reject a second delivery after the first succeeds.

Fixtures

Fixture Expected
exact raw body accept once
reordered keys reject
one-byte mutation reject
six-minute-old timestamp reject
duplicate valid request reject as replay

Create one valid fixture with a fixed timestamp and secret, then derive every negative case from it. Never log the production signature or secret. Record only fixture ID, reason class, and request digest.

prevent: verify raw bytes before business logic
detect: reason-coded rejects and replay-cache hits
recover: rotate secret, retain sanitized evidence, replay known-good events deliberately
Enter fullscreen mode Exit fullscreen mode

This example covers HMAC webhooks, not asymmetric signatures or every provider header format. Follow the provider's canonicalization rules. Which middleware in your stack first destroys access to the original bytes?

Top comments (0)