Saga Pattern in AI Booking: Distributed Transactions
Booking a trip spans payment, provider confirmation, and database updates. When any step fails, compensating transactions roll everything back.

Here is a nightmare scenario that keeps me up at night. A user tells our AI agent to book a flight. The agent charges their credit card. Then it sends the booking request to the airline. The airline system responds: "fare no longer available." The user just paid for a flight that does not exist.
This scenario is the reason we use the saga pattern for every booking our platform processes. It is the difference between a user getting charged for a ghost booking and a user seeing "that flight sold out, let me find you an alternative" with no charge on their card.
Why traditional transactions do not work here

In a traditional database application, you wrap related operations in a transaction. Either everything succeeds or everything rolls back. ACID guarantees handle it for you.
Travel booking does not work that way because the operations span multiple independent systems. The payment processor is one system. The airline or hotel provider is another system. Our application database is a third system. No single database transaction can span all three.
You cannot wrap "charge the card on the payment processor" and "create the booking on the airline system" in the same transaction because these are separate companies with separate databases running on separate infrastructure. If the card charge succeeds and the booking fails, no database transaction rollback is going to put the money back on the user's card.
This is a distributed transaction problem, and distributed transactions in heterogeneous systems are one of the genuinely hard problems in software engineering. The saga pattern is how we solve it.
Compensating transactions
A saga is a sequence of local transactions where each step has a corresponding compensating transaction that undoes it if a later step fails. The key word is "compensating," not "rolling back." You cannot undo a credit card charge by rewinding time. You undo it by issuing a refund, which is a new forward action, not a reversal.
Our booking saga has four steps:
- Reserve payment. Authorize (but do not capture) the charge on the user's payment method. The money is held but not transferred.
- Create booking with provider. Send the booking request to the airline or hotel system. Wait for confirmation.
- Capture payment. Convert the authorization to an actual charge now that the booking is confirmed.
- Record booking. Save the confirmed booking details to our database and send the user a confirmation.
Each step has a compensating transaction:
- If the booking fails after payment authorization, release the authorization (the hold disappears from the user's card).
- If payment capture fails after the booking is created, cancel the booking with the provider.
- If the database save fails after payment capture, refund the payment and cancel the booking.
The ordering matters. We authorize payment before creating the booking because an authorization is cheap to release. We create the booking before capturing payment because a cancelled booking is cleaner than a refunded charge on the user's statement.
Orchestration vs. choreography

There are two approaches to implementing sagas. Orchestration uses a central coordinator that manages the sequence of steps and triggers compensations on failure. Choreography uses events, where each step publishes an event when it completes, and the next step listens for that event.
We chose orchestration. The central coordinator pattern is easier to reason about for our use case because the booking flow has a strict sequence. Step 2 cannot start before step 1 completes. Step 3 cannot start before step 2 completes. There is no parallelism to exploit, so the event-driven indirection of choreography adds complexity without benefit.
The orchestrator is a state machine. It tracks which step the saga is on, stores the results of completed steps (needed for compensations), and handles the failure logic for each step. When a step fails, the orchestrator walks backward through the completed steps, executing compensating transactions in reverse order.
We persist the saga state after each step. If our server crashes mid-saga, the orchestrator can pick up where it left off when it restarts. A saga that was in step 2 (booking with provider) when the server died will resume with a status check against the provider when the server comes back. This durability guarantee is what prevents the nightmare scenario of a partial booking with no cleanup.
Timeout handling
Travel provider APIs are not fast. A booking confirmation from an airline can take anywhere from 2 seconds to 30 seconds. Occasionally, it takes longer. Occasionally, it does not respond at all.
Our saga orchestrator has timeout thresholds for each step. If a step has not completed within its timeout, the orchestrator has to decide: is the step still in progress, or did it fail?
This decision is harder than it looks. If we treat a timeout as a failure and trigger compensating transactions, but the booking actually went through (just slowly), we end up with a confirmed booking that we then try to cancel while also refunding the user. The user gets a cancellation for a booking they wanted.
We handle this with a confirmation verification step. After a timeout, instead of immediately compensating, the orchestrator queries the provider to check whether the booking was actually created. If it was, we proceed with payment capture. If it was not, we compensate. If the verification query also times out, we enter a manual review queue.
The manual review queue is the escape hatch for cases where automation cannot determine the state. It is rare (less than 0.1% of bookings), but it exists because the alternative, making automated decisions based on incomplete information about the state of a financial transaction, is unacceptable.
Audit trails that survive partial failures
Every step of the saga writes to an audit log. The log captures what action was taken, what the result was, how long it took, and what the saga state was before and after. When a compensation occurs, the log captures why and records the result of the compensating transaction.
This audit trail has two purposes. First, debugging. When something goes wrong (and in distributed systems, things go wrong), the audit trail lets us reconstruct exactly what happened. "The payment was authorized at 14:32:01, the booking request was sent at 14:32:02, the provider returned a timeout at 14:32:32, the verification check confirmed no booking was created at 14:32:35, and the payment authorization was released at 14:32:36." That level of detail turns a mystery into an explanation.
Second, customer support. When a user says "I was charged for a flight but never got a confirmation," the support team can pull up the saga audit trail and see exactly what happened. Was the charge an authorization that was released? Was a refund issued? Is the booking actually confirmed but the confirmation email failed to send? The audit trail answers these questions in seconds.
Multi-layer idempotency
The saga pattern works in concert with our idempotency guarantees. Every booking attempt has a unique idempotency key. If the user's device sends the booking request twice (because they double-tapped, or their connection was flaky), the second request finds the existing saga and returns its current state rather than starting a new one.
This idempotency operates at three layers. The application layer deduplicates requests by idempotency key. The payment layer uses the same key to ensure the payment processor does not create duplicate charges. The provider layer uses the key to ensure the airline or hotel system does not create duplicate bookings.
Three layers of idempotency might seem like overkill. It is not. I have seen systems where two out of three layers caught duplicates correctly but the third did not, resulting in a duplicate booking that the user had to sort out manually. Each layer is a safety net for the others.
The user never sees any of this
The entire saga executes behind a single message in the chat: "Booking your flight..." followed by either "Your flight is confirmed!" or "That flight is no longer available, but I found alternatives."
The user does not know about payment authorizations, compensating transactions, timeout handlers, or idempotency keys. They should not have to. The complexity exists so that the simple thing, booking a flight through a conversation, works reliably every time.
That is the point of the saga pattern in an AI booking system. Not to be clever about distributed transactions. To make sure the user never gets charged for something they did not get, never gets a duplicate booking, and never ends up in a state that requires them to call someone to sort out.
The nightmare scenario I described at the top? It has never happened to a Nowah user. The saga pattern is why.
Nowah is an AI travel agent that searches and books real flights and hotels through conversation — no filters, no thirty open tabs. Plan your next trip.