unit U2 — 2 of 4
HMAC verification
timing-safe compares, secret rotation
A signature header proves two things: the sender holds the shared secret, and the body wasn’t altered in transit. The sender computes HMAC-SHA256 over the exact raw bytes of the body with the secret and sends the hex digest; you recompute it and compare. Match means authentic. This is the same mechanism FlowLogic’s own Lemon Squeezy webhook uses to gate tier changes.
Two details make or break it. First, hash the raw body, not a re-serialised object — re-stringifying JSON reorders keys and changes whitespace, so your digest won’t match. Capture the raw payload before any step parses it. Second, compare with a timing-safe function, not `===`. A plain compare returns faster on an early-mismatched byte, and an attacker can measure that to recover the correct signature one byte at a time.
Rotation: secrets leak. Accept a request if it validates against either the current or previous secret during a rotation window, then retire the old one once the provider has switched. Never log the secret; never put it in the URL.
worked example
Verifying an inbound signature in a Code Piece — raw body in, timing-safe compare out.
import { createHmac, timingSafeEqual } from 'node:crypto';
export const code = async (inputs) => {
const raw = inputs.rawBody; // captured before parsing
const sig = inputs.headers['webhook-signature'];
const secret = inputs.signingSecret; // mapped from the flow's stored secret
const digest = createHmac('sha256', secret).update(raw).digest('hex');
const a = Buffer.from(digest);
const b = Buffer.from(sig);
const ok = a.length === b.length && timingSafeEqual(a, b);
if (!ok) throw new Error('bad signature'); // 401 — stop the flow
return inputs;
};field checklist
- Hash the raw request bytes, never a re-serialised object.
- Compare digests with timingSafeEqual, never `===`.
- Length-check both buffers before the timing-safe compare.
- Keep the secret in a Connection, never a step parameter.
- Accept two secrets during rotation, then retire the old.
common failure — String-compare leak on the signature
A verifier used `digest === sig`, which short-circuits on the first differing character. With enough timed requests an attacker recovers the valid signature byte by byte and forges deliveries. `timingSafeEqual` runs in constant time regardless of where the mismatch is — length-check first (unequal lengths throw), then compare the equal-length buffers.
check your understanding
This comparison must not reveal how much of the signature was correct. Which function belongs in the blank?
next unit opens once this is passed
sandbox validation
The check above confirms you followed the unit. Marking the module COMPLETED takes more: build the automation in your own engine and submit the exported flow and its run evidence, signed, to your unique validation URL. See the module page for that spec.