# A retry should not create a second business action

When a response is lost, the caller needs a way to repeat the request without repeating its effect. Give the intended action an identity that survives every attempt.

By Cobnex editorial. Published 2026-09-10. Updated 2026-09-11.

## The caller cannot infer the result from a timeout

A booking request reaches the server, which creates the booking and commits it. The response disappears on the way back. The user sees a timeout and tries again. Without a stable request identity, the server may create a second booking.

The payload alone does not reliably identify intent. Two identical bookings might be deliberate, while two slightly different transport representations might be retries of one action. Let the caller or trusted workflow create an idempotency key for the intended operation and reuse it across attempts.

Scope that key to the relevant account and operation. Possessing a key must not grant access to another user's result.

## Bind identity to meaning

Store a normalised representation or fingerprint of the material request with the key. If the same key arrives with a different target, amount or other consequential field, reject the conflict rather than silently executing a new action or returning an unrelated old result.

Define normalisation carefully. Field order in JSON may be irrelevant, while currency, target identity and explicitly supplied defaults can matter. The rule should describe business equivalence, not merely whether two raw byte strings happen to match.

### One intention, several possible attempts

The server recognises the operation before executing and returns its recorded outcome to matching retries.

1. **Receive key**: Authenticate caller and validate the request
2. **Claim intention**: Atomically bind account, operation and payload
3. **Perform effect**: Commit locally or use downstream idempotency
4. **Return result**: Reuse the recorded outcome for matching attempts

## Put the guarantee beside the effect

If the operation record and business mutation share a database, a transaction can commit them together. A unique constraint or equivalent atomic mechanism prevents concurrent requests from both claiming the same intention.

For an external side effect, a local record alone is insufficient. Marking success before the remote call can lose the effect. Marking it after the call leaves a window where the remote service succeeded but the local process stopped. Use the downstream service's idempotency contract or a reliable reconciliation path.

Keep pending and uncertain states explicit. A retry may need to wait, return a status reference or reconcile rather than execute immediately.

## Define the lifetime of the promise

Idempotency records cannot necessarily be retained forever. Choose a retention period based on client retries, offline queues, operator replay and downstream guarantees. Document what happens after expiry.

A key reused after its record is removed may represent a new request under the service contract. Callers need to know that boundary before replaying old work. Do not assume every provider has the same retention or error-caching behaviour.

## Test concurrency and conflict

Send the same key and payload concurrently, then repeat with one material field changed. Verify one intended effect in the first case and a conflict in the second. Also lose the response after commit and retry through the normal client path.

The useful guarantee is about business effects under a defined scope and lifetime. Repeated network requests may still occur, but they should not multiply the action the user intended once.

## Sources

- [AWS Builders' Library: idempotent APIs](https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/)
- [Stripe: idempotent requests](https://docs.stripe.com/api/idempotent_requests)
