Core rule: a timeout describes the client’s wait, not the server’s outcome. Don’t translate “no response” into “nothing happened”. First give the user a safe way to learn whether the original operation was recorded.
1. The timeout gap
Suppose a customer confirms a one-off payment. The browser sends a request; the server authorises it; then a connection drops before the browser receives the response. From the browser alone, the result is unknown. Sending a brand-new request may create a second charge.
“We lost the reply” is an observation about communication. It is not evidence that the action failed.
Keep a client-side operation reference and expose a status lookup that can recover the original result. The server should persist the outcome before responding, so a repeated request can return the existing result.
2. What idempotency gives you
RFC 9110 describes an idempotent method as one whose intended effect is the same after one or multiple identical requests. For actions such as creating a payment, a service can offer an idempotency key: a unique reference that lets it recognise retries of the same request. When the same key and request details are sent again, the service can return the original result instead of creating a second operation.
The key must be created once for the user’s intent, retained through retries, and scoped appropriately. The server must bind it to the authenticated actor and request parameters, store the operation state, and reject reuse with a materially different payload. A key does not itself make an endpoint safe; the server’s storage and concurrency rules do.
3. A safe recovery flow
- Create one operation identifier when the user confirms.
- Submit the request with that identifier and the same validated payload on every retry.
- If the response times out, show “Checking your request” and query the operation status.
- If the status endpoint is unavailable, preserve the operation reference and offer retrying the check.
- Permit a new operation only when the prior result is known or the user deliberately starts a separate intent.
Idempotency windows can expire. Document the retention period and what the client should do afterwards. For irreversible actions, status reconciliation may need to consult a payment or booking provider rather than relying on a short-lived cache.
Choose the observed condition
This small teaching aid demonstrates wording; it does not send a request or connect to a service.
4. Interface language should match evidence
| Evidence | Useful message | Avoid |
|---|---|---|
| Request sent, no response | “We’re checking whether your request completed.” | “Payment failed.” |
| Provider shows pending | “Your payment is still processing. Last checked at …” | “Payment complete.” |
| Provider confirms success | “Payment confirmed.” | “Guaranteed delivery” unless separately verified. |
| Provider confirms rejection | “Payment wasn’t completed. No charge was recorded.” Only if that is what provider data confirms. | Guessing from a timeout. |
5. Before shipping retries
- Define which action creates the idempotency key and how long it remains valid.
- Persist and deduplicate server-side under concurrent duplicate requests.
- Bind the key to the actor and a canonical request payload.
- Return the same operation result for a recognised retry.
- Offer a status lookup and a route for unresolved cases.
- Test delayed responses, lost replies and simultaneous retries.
Retries are a recovery mechanism, not a guarantee that the network will behave. The useful product promise is that a second attempt will be tied to the first intent and that uncertainty will be shown honestly.
Sources and review date
- IETF RFC 9110, section 9.2.2: Idempotent Methods — semantics and client retry cautions.
- Stripe API: Idempotent requests — one provider’s concrete contract and retention behavior; not a universal standard.
Reviewed 26 October 2026. Provider behaviour and retention windows can change; check the current contract before implementation.
