CODING CHALLENGE · N°57

Verify a Webhook Signature

Medium FDESecurityIntegrations

Every real integration sends signed webhooks — and every FDE has to verify them. Recompute the signature over the payload, compare in constant time, and reject stale events to stop replay attacks. (A provided MAC stands in for HMAC-SHA256 so it runs anywhere; the verify logic is identical.) Solve it in Python or TypeScript, with hidden tests.

The problem

Implement verify(payload, signature, secret, timestamp, now, tolerance). A sender signs "{timestamp}.{payload}" with a shared secret using the provided sign() and sends the result plus the timestamp. You must: (1) reject if the event is too old — |now - timestamp| > tolerance — to block replays; (2) recompute the expected signature; (3) compare it to the received one in constant time. Return True only if it is authentic and fresh.

EXAMPLE 1
Input valid, fresh event
Output True
signature matches and timestamp is within tolerance
EXAMPLE 2
Input tampered payload
Output False
recomputed signature no longer matches
EXAMPLE 3
Input stale timestamp
Output False
older than tolerance → rejected as a possible replay
CONSTRAINTS
  • Use the provided sign(secret, message) — do not invent your own.
  • The signed message is exactly str(timestamp) + "." + payload.
  • Compare with a constant-time equality (accumulate differences over all characters; never early-return on the first mismatch) so an attacker can’t time their way to a valid signature.
  • Reject events whose timestamp differs from now by more than tolerance.
SOLVE IT YOURSELF

Your turn — write it

Edit the stub, hit Run (or ⌘/Ctrl + Enter), and watch the hidden tests. Stuck? the hints are right above and Reveal solution is one click away.

YOUR TASK

Implement verify(...): check the timestamp window first, recompute sign(secret, "{timestamp}.{payload}"), and constant-time-compare it against the received signature.

HINTS — 4 IDEAS
  1. Bail out early only on the timestamp check: if abs(now - timestamp) > tolerance, return False.
  2. Recompute the expected signature with the provided sign over str(timestamp) + "." + payload.
  3. Constant-time compare: if lengths differ return False; otherwise XOR each pair of characters into an accumulator and require it to be 0.
  4. This mirrors how Stripe/GitHub webhooks work — real code just swaps sign for HMAC-SHA256.
CPython · WebAssembly
Approach, complexity & discussion — open after you solve

The approach

The sender signs the payload with a shared secret (an HMAC) and puts the signature — often with a timestamp — in a header. The receiver recomputes the HMAC over the raw request body and compares it to the header using a constant-time comparison. Reject on mismatch, and reject a stale timestamp to block replays.

Complexity

Time O(n) in the payload length to compute the HMAC.

Common mistakes

  • Comparing signatures with == — the early-exit leaks timing; use a constant-time compare.
  • Hashing the parsed-and-re-serialized body instead of the exact raw bytes the sender signed.
  • Skipping the timestamp check, leaving you open to replay of a captured valid request.

Where this shows up

Webhook signature verification is how you trust that an inbound webhook truly came from Stripe, GitHub, or your provider and was not forged or replayed. The raw-body + constant-time-compare + timestamp trio is the standard, and getting any one of the three wrong is a genuine security hole.

Finished this one? 0 / 75 Challenges done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More Challenges