← all notes

Idempotency keys, minus the folklore

Safe retries are a protocol, not a magic request header.

A client can lose the response after a server has committed the work. Retrying is correct, but repeating the side effect is not. An idempotency key gives both sides a stable name for that attempt.

Store the result, not only the key

The useful record contains a key, a fingerprint of the relevant request fields, the processing state, and the final response. Returning the original status and body makes the retry indistinguishable from the successful first call.

key           text primary key
request_hash  bytea not null
state         text not null
status_code   integer
response_body jsonb
expires_at    timestamptz not null

Reject accidental reuse

If the same key arrives with a different request fingerprint, return a conflict. Silently attaching a new operation to an old key is worse than rejecting the retry because the client can no longer reason about the outcome.

I keep the transaction boundary close: reserve the key and perform the business write in one transaction where possible. For slow external work, the record moves through an explicit processing state and competing requests receive a retryable response.

Expiration is part of the contract

Keys need a documented retention window. Cleanup can lag without affecting correctness, but deleting earlier than promised can duplicate old operations. The modest version of this design has been more reliable than clever middleware because every state is inspectable in the database.