---
title: "Idempotency and conflicts"
description: "Safely retry booking requests and build a deliberate recovery path for atomic inventory conflicts."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.seatlayer.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency and conflicts

Networks time out, payment webhooks repeat, and workers retry jobs. Use one immutable `bookingRef` for one business order so repeated booking calls cannot create a second sale.

## Safe booking retries

```js title="server/book-order.js"
async function bookOrder({ eventKey, holdId, orderId }) {
  return fetch(
    `https://api.seatlayer.io/v1/events/${eventKey}/book`,
    {
      method: "POST",
      headers: {
        authorization: `Bearer ${process.env.SEATLAYER_SECRET_KEY}`,
        "content-type": "application/json",
      },
      body: JSON.stringify({
        holdId,
        bookingRef: orderId,
      }),
    },
  );
}
```

Calling this again with the same `orderId` is safe. If the first request already booked the seats, an idempotent replay succeeds without a second inventory or credit transition:

```json title="200 replay response"
{
  "ok": true,
  "booked": []
}
```

> **A timeout is an unknown result**
>
> Do not generate a new order or `bookingRef` after a timeout. Reconcile your existing order and retry with the same reference.

## What counts as a conflict

A booking under a different reference, an expired or invalid hold, missing inventory, or an invalid capacity allocation returns `409`.

```json title="409 response"
{
  "error": "conflict",
  "conflicts": [
    {
      "label": "A-12",
      "status": "booked"
    }
  ]
}
```

SeatLayer applies inventory mutations atomically. If any required item conflicts, nothing in that request is newly booked.

## Recovery by operation

| Operation | Likely reason | Correct response |
|---|---|---|
| Buyer hold | Someone else acquired inventory after render | Refresh and let the buyer choose again |
| Booking held items | Hold expired or inventory no longer belongs to it | Void/refund as required, then reselect |
| Direct/box-office booking | Item is not free | Surface the conflicting labels to the operator |
| Blocking | Item is already held, booked, or blocked | Skip intentionally or ask the operator |

Do not retry an actual `409` in a loop. Idempotency protects unknown/repeated delivery; it does not make unavailable inventory available.

## Payment-safe state machine

```text title="order-states.txt"
created
  └─> payment_authorized
        ├─> seatlayer_booked ─> payment_captured ─> confirmed
        ├─> inventory_conflict ─> payment_voided ─> needs_reselection
        └─> booking_unknown ─> retry same bookingRef
```

If your provider captures before booking, replace the void with the appropriate refund or reversal. Persist enough state for a background worker or webhook reconciliation job to finish an uncertain request.

## Why concurrency is safe

Each event processes hold, book, block, and release transitions through one authoritative serialized inventory writer. Two competing requests cannot both transition the same seat from `free`; one commits first and the other observes the new state.

Your application still needs idempotency for its own order creation and payment operations. SeatLayer’s `bookingRef` does not automatically deduplicate your checkout route.

## Test cases

- [ ] Send the same booking request twice; only one sale is recorded.
- [ ] Simulate a lost first response, then retry with the same reference.
- [ ] Try a different reference against the already booked seats; receive `409`.
- [ ] Expire the hold before booking; no partial inventory changes.
- [ ] Compete for the same seat from two browser sessions.
- [ ] Ensure payment compensation runs only for a real conflict.
- [ ] Ensure background retries preserve the original reference.

Read the [booking endpoint](/server-api/booking) and the [complete checkout example](/examples/complete-checkout).

Source: https://docs.seatlayer.io/server-api/idempotency-and-conflicts/index.mdx
