Fix the webhook verifier for payment events

Review the AI's codeEasy12 minStripe style

Your task

An AI implemented webhook signature checks for your payment provider. Valid webhooks verified and the tests passed, but the tests only ever sent valid webhooks. A webhook that is replayed, redelivered or tampered with is what this check exists for. Your team rewrote the decision with every rejection spelled out.

An assistant wrote verify_webhook(header, computed_sig, event_id, now_epoch, tolerance_seconds, seen_event_ids). It is in the editor. It has a bug. Find it and fix it.

Input and output:

  • header: the signature header, a comma-separated list of key=value parts, like t=1700000000,v1=abc123. t is the send time in epoch seconds and v1 is the signature.
  • computed_sig: the signature your server computed over the raw request body.
  • event_id: the event's id.
  • now_epoch: the current time in epoch seconds.
  • tolerance_seconds: how far apart t and now may be.
  • seen_event_ids: the ids of events already processed.
  • Return "malformed_header", "stale_timestamp", "bad_signature", "replayed" or "accepted".

Rules the tests check. Return the first that applies:

  1. "malformed_header": a part has no =, there is no non-empty t or v1, or t is not all digits from 0 to 9. Split each part at its first =, and trim nothing. When a key repeats, its first non-empty value counts. Other keys, such as v0, are ignored.
  2. "stale_timestamp": t and now_epoch are more than tolerance_seconds apart, in either direction.
  3. "bad_signature": v1 is not exactly computed_sig. Compare in constant time, so the time taken does not reveal how much matched.
  4. "replayed": event_id is in seen_event_ids.
  5. Otherwise "accepted".

Examples

verify_webhook(header: str, computed_sig: str, event_id: str, now_epoch: int, tolerance_seconds: int, seen_event_ids: list[str]) -> str
Example 1
Inputverify_webhook("t=1700000000,v1=abc123", "abc123", "evt_1", 1700000010, 300, [])
Output"accepted"
Example 2
Inputverify_webhook("t=1700000000,v1=abc123", "abc999", "evt_2", 1700000010, 300, [])
Output"bad_signature"
Example 3
Inputverify_webhook("t=1700000000,v1=abc123", "abc123", "evt_3", 1700000600, 300, [])
Output"stale_timestamp"