Skip to content

Idempotency and conflicts

Safely retry booking requests and build a deliberate recovery path for atomic inventory conflicts.

Updated View as Markdown

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

server/book-order.jsjs
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:

200 replay responsejson
{
  "ok": true,
  "booked": []
}

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.

409 responsejson
{
  "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

order-states.txttext
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 and the complete checkout example.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close