First identify what is uncertain

A timeout tells the browser that it did not receive a usable answer. It does not tell the business whether the server created the order. The request might never have arrived, it might have been accepted while the reply was lost, or one service might have completed its work while another was still waiting. Treat those as separate states in the order workflow. A button that simply posts the same basket again can create two order records, two reservation attempts or two staff tasks. The customer sees an error and reasonably tries again, so the interface and the server must share responsibility for handling uncertainty. Start by writing down the single business action being protected: creating one order for one deliberate customer submission.

AWS describes an idempotent API as one in which repeating the same request with the same token has the same effect as making it once. That is a useful design aim, but it is not a magic property of every HTTP request. A new token on every click defeats the protection; reusing one token for a genuinely new purchase incorrectly merges different intentions. Create a stable submission identifier when the customer starts the final order attempt. Keep it through a network retry, browser refresh or temporary connection loss. Issue a new identifier only when the customer starts a new intentional purchase. The identifier is a technical reference, not a replacement for the customer facing order number.

A client side disabled button can reduce accidental double taps, yet it cannot prove that only one order was created. The user can open another tab, the browser can resend a request, or a background job can deliver the same message twice. Put the final decision at the server boundary that owns order creation. Store the submission identifier with the result under a uniqueness rule, and make concurrent attempts resolve to that same result. If the first attempt is still in progress, return a clear pending status and a way to check it. Do not claim success before the committed order can be found by the same identifier. This also makes support work easier: staff can search for the attempt rather than guess from a screenshot.

Separate the order from each side effect

An order can trigger payment, stock allocation, a confirmation email and a pick instruction. One protected order creation request does not automatically make every downstream action safe. Give each consequential action its own stable reference tied to the order and the action type. For example, a payment attempt should be associated with the relevant order and its own provider request identifier; a stock reservation should be linked to the order line and reservation event. When a worker retries a failed queue delivery, the receiver should check whether that particular effect was already recorded. This is an implementation pattern inferred from the AWS idempotency guidance, not a promise about the behaviour of a chosen checkout vendor.

Stripe's API documentation illustrates why provider details must be checked before copying an implementation. Its API v1 saves the first executed response for a key, including certain server errors, and may prune keys after they are at least 24 hours old. It compares retried parameters with the original. Its API v2 describes different retry behaviour for failed or partially failed work. A saved error is therefore not a licence to assume that no payment action occurred; a key retained by the provider is also not a permanent business deduplication record. Read the documentation for the exact API version in use, preserve the provider request identifier and reconcile the provider result with your own order state before telling staff to capture, refund or try a different payment route.

Decide what must be atomic and what can be reconciled. The order record and its submission identifier should be committed together. A payment call to an outside provider cannot usually be placed in the same local database transaction; instead, keep an explicit payment state, record the outgoing request and correlate the callback or query result. Stock must not be silently decremented twice when a message is redelivered. Where an effect fails after the order is created, show a state such as awaiting payment or allocation rather than creating a fresh order. Document which team resolves an unresolved state and how they verify it. This is more useful than a broad promise of exactly once processing across independent systems.

Walk through a lost response

Imagine a small Hong Kong retailer whose customer confirms one basket containing two desk lamps. The browser sends submission reference A. The server commits order 105 with that reference, then the connection drops before the browser receives the order number. The screen says that the result is unknown, offers a status check and keeps A for a retry. The customer taps again. The server finds A already associated with order 105 and returns that order's current state; it does not create order 106. If payment remains pending, the page says so plainly. These numbers are an illustrative test case, not observed merchant data or a prediction of outcomes.

Now change one detail: the customer edits the basket quantity before retrying. The same identifier with different parameters should not silently overwrite the first order. Stripe's v1 parameter comparison is a concrete example of rejecting mismatched retries. In your own order service, decide whether to show the existing order and ask the customer to start a clearly new purchase, or to support an explicit amendment to the existing order where business rules allow. Do not infer that identical baskets always represent the same intent either: a customer may intentionally buy the same item twice. The identifier binds a particular submission attempt, while the order number records the resulting transaction. Basket similarity alone is a weak substitute.

The test plan should cover more than two fast clicks. Simulate a response lost after the database commit, simultaneous requests with the same identifier, a repeated queue delivery, a payment response arriving late and a status query while work is pending. For each, inspect the order count, payment references, inventory movements and staff tasks. The expected outcome is not that every screen instantly says success; it is that the business can identify one submission, avoid unintended additional effects and show the customer an honest state. Record the actual evidence from a test environment before accepting the workflow. A green unit test for a button is not evidence that payment and fulfilment boundaries are protected.

Make the policy usable for staff and customers

The interface needs three distinct messages: an order is confirmed, an order is still being checked, or no order was created and a new submission is needed. Avoid a generic failed message when the outcome is unknown. Give the customer a safe status route based on an authenticated account or an appropriate private reference, without exposing another customer's details. Customer support should look up the submission reference, order number and payment reference before advising a second payment. Staff should have a documented route for mismatched states, such as an order recorded locally but a provider result that has not yet been reconciled. This protects trust while an investigation is underway.

Set the retention period for your own deduplication records from the longest plausible business retry and reconciliation window, rather than assuming a payment provider's key lifetime covers your order history. Agree who may manually release a stuck order and what evidence they must attach. If a genuine second purchase is requested, make that new intent explicit and give it a new submission reference. Finally, review real incidents for patterns: which response was lost, which effect was repeated, and whether the user was misled. The immediate next action for an owner is to trace one timed out checkout from browser request to payment and stock records, then ask the developer to demonstrate the same reference returning the same order when retried.

Before you proceed

  • Can the same submission identifier return the original order after a lost response?
  • Are payment and stock effects independently safe to retry?
  • Can staff reconcile an unknown result before asking for another payment?

Sources & further reading