Idempotency in booking and payment APIs: how to never charge or book twice
Timeouts and retries are normal in distributed systems. Idempotency keys, state machines and reconciliation make booking and payment flows safe to retry, without duplicate charges or orphaned reservations.

An operation is idempotent when doing it twice has the same effect as doing it once. In booking and payment systems, that property is what stops a timeout from becoming a double charge or a duplicate reservation. Networks fail, clients retry and queues redeliver; idempotency makes all of that safe.
Why retries are dangerous
Consider a checkout calling a supplier to book a hotel. The request is sent, the supplier confirms the booking, and the response is lost to a timeout. The client sees an error and retries. Without protection, the supplier creates a second booking, and the customer may be charged twice.
The failure is not rare. Mobile networks drop, load balancers time out, and message queues deliver at least once. Any operation that changes money or inventory must assume it will be attempted more than once.
Idempotency keys
The standard technique is an idempotency key: a unique value the client generates for each logical operation and sends with every attempt. Payment providers popularised the Idempotency-Key HTTP header, and the IETF has worked on standardising it for HTTP APIs.
On the server:
- Look up the key before doing any work.
- If it is new, record it as in progress, perform the operation and store the result.
- If it exists and completed, return the stored result.
- If it exists and is still in progress, return a conflict or wait, rather than starting again.
- If the same key arrives with a different request body, reject it.
Store the key with a fingerprint of the request, the response and a timestamp. A database unique constraint on the key makes the check race-safe.
Model the flow as a state machine
Idempotency keys protect individual calls. A checkout involves several, so model the whole flow as explicit states:
created → priced → payment_authorised → supplier_booked → payment_captured → confirmed
with failure states such as supplier_failed and pending_reconciliation. Each transition is recorded, and each external call carries a key derived from the order and the step. A crash in the middle leaves the order in a known state that a worker can resume.
Authorise first, capture last
For card payments, separating authorisation from capture limits damage:
- Authorise to reserve the funds.
- Book with the supplier using an idempotent reference.
- Capture only after the supplier confirms.
- Void the authorisation if the booking definitively fails.
This sequencing is part of the checkout design described in anatomy of a travel booking engine.
When the supplier is not idempotent
Many supplier APIs do not support idempotency keys. Compensate by:
- sending a unique client reference with every booking,
- treating timeouts as unknown and checking with a retrieve or search-by-reference call before retrying,
- running reconciliation jobs that compare your orders with supplier booking reports,
- routing unresolved cases to an operations queue instead of guessing.
The same pattern applies to hotel suppliers, discussed in hotel API integration.
Idempotent consumers
Events and webhooks are delivered at least once. Consumers should record processed event IDs and skip duplicates, or make their effects naturally idempotent — setting a status rather than incrementing a counter. Reliable webhooks covers this from the sender’s side.
Test the failure paths
Idempotency bugs hide in paths that rarely run. Add tests that:
- send the same request twice concurrently,
- drop the response after the operation succeeds,
- crash a worker between two steps and resume,
- replay a batch of events.
The takeaway
Assume every request will be retried and every event delivered twice. Idempotency keys, an explicit order state machine and routine reconciliation turn those retries from incidents into non-events — which, in payments and bookings, is exactly what customers expect.
Frequently asked questions
What is an idempotent API?
An API operation is idempotent when performing it several times has the same effect as performing it once. For booking and payment endpoints, that means a retried request returns the original result instead of creating a second booking or charge.
How do idempotency keys work?
The client generates a unique key for each logical operation and sends it with the request, often in an Idempotency-Key header. The server stores the key with the outcome and, if the same key arrives again, returns the stored result instead of repeating the operation.
How long should idempotency keys be stored?
Long enough to cover every realistic retry, including delayed retries from queues and mobile clients. Twenty-four hours is a common minimum for payment flows; booking systems often keep the key with the order permanently as a reference.