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

Explore the topic

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