skip to content

Why does an integration contract for a 'create order' operation typically need to define an idempotency key, and what breaks in production if it doesn't?

level: seniorimportance: should knowfreq 60%

answer

  1. ambiguous timeout problem
  2. Idempotency-Key header (Stripe convention)
  3. dedup store with retention window
  4. retry-safe by construction
  5. at-least-once redelivery duplicates

basics

~20 s

An idempotency key lets a caller safely retry a request without accidentally doing it twice - like writing a unique order number on a form so resubmitting it doesn't create a second order. Without it, network retries can cause duplicate orders, charges, or emails.

solid answer

~50 s

Networks fail ambiguously: a caller sends a 'create order' request, the server processes it successfully, and the response is lost before the caller sees it - the caller can't distinguish that from the request never arriving, so a naive retry re-sends the 'create' operation and risks a duplicate order or charge. An idempotency key is a unique value the caller generates once per logical operation and includes on every retry; the server stores the key alongside the result of the first successful processing and, on a repeated key, returns the stored result instead of reprocessing. This turns an inherently non-idempotent operation into one safe to retry any number of times. Contracts should specify the key's format, its scope, and how long the server retains dedup records, because without it any client retry logic - or an at-least-once broker redelivering a request - becomes a duplicate-processing bug waiting to happen.

go deeper

for a junior

Understands that retrying a 'create' request can create duplicates and that a unique key can prevent that, at a conceptual level.

for a middle

Can implement or consume an idempotency-key convention correctly (generate once, reuse on retry) and explain why plain POST retries are risky.

for a senior

Designs the server-side dedup mechanism (storage, scope, retention window, mismatched-body handling) as part of an API contract, and applies the same reasoning to at-least-once message consumers.

for a principal

Sets idempotency as a required contract element for a class of operations platform-wide (e.g., all payment/provisioning APIs), and makes the retention/storage trade-off call balancing dedup-store cost against duplicate-processing risk across many services.

## What idempotency means here Idempotency, in the integration sense, means that performing the same operation more than once produces the same effect as performing it exactly once. - A `GET` request is naturally idempotent — reading data repeatedly doesn't change anything. - A 'create order' `POST` is naturally not idempotent — calling it twice, by default, creates two orders. An **idempotency key** is the mechanism that makes a non-idempotent operation safe to repeat: the caller generates a unique token (commonly a UUID) once per logical business operation, attaches it to the request (often as an `Idempotency-Key` header, a convention popularized by Stripe), and resends the exact same key on every retry of that same logical operation. The server, on receiving a request, checks whether it has already processed that key: - **if not**, it processes normally and stores the key alongside the result - **if it has**, it skips reprocessing and returns the previously stored result, so the caller gets a consistent answer regardless of how many times the request was sent ## The ambiguous timeout The reason this mechanism exists is that networks fail in a specific, unavoidable way: **ambiguous timeout**. When a caller doesn't get a response before its timeout fires, there are at least three possible underlying realities: 1. the request never reached the server 2. it reached the server and failed 3. or it reached the server, succeeded, and the response was lost on the way back From the caller's side these are indistinguishable, yet the correct retry behavior differs completely: retrying is safe in the first two cases and dangerous in the third. Without an idempotency key, a caller has no way to make retrying universally safe, so teams either accept occasional duplicates, or avoid retries and accept leaving genuinely failed operations unretried — both bad trade-offs. With an idempotency key, retrying is always safe: the server-side dedup check absorbs the ambiguity. ## What it costs the server The trade-off is server-side state and a policy decision about scope and retention. The server now durably stores, per idempotency key, at least the outcome of the operation for as long as a retry might plausibly still arrive — commonly 24 hours, a window Stripe itself documents. This is genuine cost: - extra storage - an extra lookup on every write - and a design decision about key scope (unique per endpoint, per customer, or globally) and about what counts as 'the same request' — should the server also verify the retried request's body matches the original, in case a caller reuses a key for a different payload by mistake, which the contract should define behavior for (typically: reject with a conflict error rather than silently using either payload) ## Failure modes when it is left out of the contract Failure modes when this isn't in the contract are common and often expensive because they touch money or physical fulfillment. - **Duplicate charges** are the clearest example: a payment request times out client-side, the caller's retry logic fires a second identical charge request, and without an idempotency key the payment processor has no way to know these are 'the same' logical charge — the customer is billed twice, and the fix (refund, support ticket, reconciliation) is far more expensive than preventing it upfront. - **A similar failure mode shows up in message-driven systems**: a broker redelivering a message after a slow or missed acknowledgment (a normal consequence of at-least-once delivery, not a bug) causes the same 'create order' handler to run twice, producing a duplicate order row unless the handler checks for an idempotency key or an equivalent dedup key before inserting. ## A concrete worked scenario A concrete worked scenario: a mobile checkout app on a flaky connection sends `POST /orders` with `Idempotency-Key: 8f14e...`. 1. The request reaches the server, the order is created, but the response never makes it back before the app's timeout fires. 2. The app resends the identical request with the same key. 3. Without the key, the server would create a second order and — if payment capture is tied to order creation — charge the customer twice. 4. With the key, the server recognizes it's already processed, returns the original order's confirmation without creating anything new or charging anything again, and the app displays a single successful order regardless of how many times the network forced it to retry. ## What the contract should document This is why serious API contracts for any create/mutate operation that might reasonably be retried — payments being the canonical example, but also order creation, resource provisioning, or any 'do this exactly once' business action — explicitly document the idempotency key: - its required/optional status - format constraints - scope - retention window - and what error the server returns if the same key is reused with a materially different request body

  • Why can't a caller just avoid duplicates by never retrying a request that might have already succeeded?
    Because the caller genuinely cannot tell, from a timeout alone, whether the request succeeded, failed, or never arrived — refusing to retry means real failures go unretried and the operation the user wanted just silently doesn't happen. An idempotency key removes the need to guess by making retrying safe in all three cases.
  • What should a server do if it receives a request with an idempotency key it has already processed, but the new request's body is different from the original?
    The contract should define this explicitly, and the common answer is to reject it with a conflict-style error rather than silently processing either payload, since a mismatched body with a reused key usually indicates a client bug. Silently picking one payload would hide a bug that could cause the wrong operation to be treated as 'already done.'
  • How does an idempotency key help with duplicate message delivery in an event-driven consumer, not just with client-side HTTP retries?
    At-least-once message delivery means a consumer can receive and process the same message more than once as a normal consequence of redelivery after a slow acknowledgment, not a broker bug. If the message carries or the consumer derives an idempotency key, the handler can perform the same dedup check an HTTP endpoint would, making message processing safe to repeat regardless of how many times the broker redelivers it.

An idempotency key is like writing a unique confirmation number on a mail-in rebate form before you send it. If the post office loses the reply and you resend the same form with the same number, the company recognizes it's the same claim and doesn't pay you twice.

saying these in an interview costs you the question

  • Assumes retries are inherently safe for any POST/create operation
  • Doesn't recognize the ambiguous-timeout problem as the root cause
  • Thinks idempotency keys are only relevant to payment APIs
  • Has no answer for what happens when the same key arrives with a different body
  • Forgets the server needs to store dedup state somewhere, with a retention policy
  • Confuses idempotency keys with simple request IDs used only for logging/tracing

context