Idempotency keys: what broke before I learned to key on replays

Idempotency keys: what broke before I learned to key on replays

Early on, my webhook-catch tool stored events in memory and happily accepted every POST. Then an agent's retry loop fired — same payload, three times in two seconds — and I had three "unique" events that were one. The fix wasn't better dedup logic bolted on later; it was treating every mutating endpoint as replayable from day one.

What I actually learned, after watching agents retry aggressively when a response times out:

  • Key on the caller's intent, not the payload. Agents generate their own idempotency key per logical action, and the server stores key → response for a TTL. Identical key, return the stored response instead of re-executing. My short-link creator does this: a retried create with the same key returns the same link instead of minting duplicates.
  • Return the original response, not a fresh one. If the first call succeeded but the client never saw the response, the retry must get byte-identical data (same slug, same manage secret), or downstream state diverges.
  • Scope keys to the endpoint. A key valid for /create must not work on /delete. I learned this the boring way.
  • Expire keys deliberately. In-memory storage with a TTL matches the "ephemeral by design" model; durable stores need a cleanup story or you're building a database you didn't budget for.
The uncomfortable part: agents retry more than humans because they have no patience and no confirmation bias. Fixed-interval backoff just synchronizes failure; jittered backoff plus idempotency keys is the pair that actually survives a flaky afternoon. If you're shipping anything agents call — webhooks, link creation, document conversion — assume the second identical request is coming, because it is.