---
title: "How SeatLayer works"
description: "Understand the browser hold, server booking, and realtime inventory boundary behind every SeatLayer integration."
---

> 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.

# How SeatLayer works

Every SeatLayer integration follows one invariant:

> **The browser holds. Your server books.**

The buyer can explore inventory and temporarily reserve a selection without receiving an account secret. Only your trusted backend can turn that hold into a permanent booking.

> **A browser never books production inventory**
>
> Do not place a SeatLayer secret key in client JavaScript, a mobile binary, page HTML, or a client-exposed environment variable.

## The complete journey

1. **Render live inventory**

   Your browser or mobile surface loads a published event through the buyer SDK. The event key identifies the inventory; it does not grant server booking authority.
2. **Select and hold**

   The buyer chooses seats, a table, booth, or GA quantity. The SDK creates a short-lived hold and returns an opaque `holdId` with an expiry time.
3. **Inspect on your server**

   Your backend receives the `holdId` and resolves it with `GET /v1/events/:eventKey/holds/:holdId`. The returned items—not browser-posted prices—become the order source.
4. **Run your order and payment logic**

   Create or reuse your own order. Authorize or capture payment according to your payment provider and recovery model.
5. **Book atomically**

   Your backend calls `POST /v1/events/:eventKey/book` using a secret key and your immutable order id as `bookingRef`.
6. **Confirm and reconcile**

   The synchronous response drives the buyer journey. Realtime updates repaint open pickers, and signed webhooks reconcile downstream systems.

```text title="purchase-flow.txt"
Buyer
  │ selects
  ▼
SeatLayer SDK ── creates hold ──> SeatLayer inventory
  │
  │ sends holdId only
  ▼
Your server ── inspects hold ──> authoritative items and expiry
  │
  ├── runs your order/payment rules
  │
  └── books with secret key + bookingRef ──> atomic success or 409 conflict
```

## Why the split exists

- **Security** — A browser can reserve inventory but cannot create a permanent sale. Booking authority remains in your trusted environment.
- **Correctness** — Each event serializes inventory transitions, so two buyers cannot successfully take the same seat.
- **Product control** — Your application owns identity, pricing rules, payment, tax, orders, refunds, and the final confirmation experience.

## What each system owns

| Responsibility | Browser / SDK | Your server | SeatLayer |
|---|:---:|:---:|:---:|
| Display chart and live availability | Yes | No | Supplies state |
| Let a buyer select | Yes | No | Enforces availability |
| Create and restore holds | Yes | May inspect | Stores authoritative hold |
| Calculate the trusted charge | No | Yes | Supplies trusted line items |
| Process payment and orders | No | Yes | No |
| Permanently book inventory | No | Initiates | Applies atomically |
| Realtime inventory synchronization | Receives | Optional listener | Publishes |
| Signed event notification | No | Receives | Sends |

## Seat lifecycle

| State | Meaning | Typical transition |
|---|---|---|
| `free` | Available to buyers | Initial, released, or expired |
| `held` | Temporarily reserved | Buyer SDK creates a hold |
| `booked` | Permanently sold | Your server books |
| `not_for_sale` | Removed from buyer inventory | Operator or inventory rule |

Holds expire automatically. Booking is all-or-nothing: if any required item cannot be booked, the request returns a conflict without partially completing the sale.

## The browser handoff

```js title="browser/seat-picker.js"
const picker = new seatlayer.SeatPicker({
  container: "#picker",
  event: "ev_9f3a",
  onCheckout: async (_, __, handoff) => {
    await fetch("/api/checkout/seats", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ holdId: handoff.holdId }),
    });
  },
});

await picker.render();
```

The browser sends your backend the hold id. Your backend obtains trusted labels, quantities, tiers, currency, and prices from the hold inspection endpoint.

## Expected failure paths

- **Hold expired:** return the buyer to selection and clear stale cart state.
- **Booking returned `409`:** treat it as a recoverable inventory conflict; do not partially confirm the order.
- **Payment succeeded but booking is uncertain:** retry using the same `bookingRef`.
- **Realtime connection dropped:** reconnect and refresh authoritative inventory.
- **Mode mismatch:** use a test key with test events and a live key with live events.

## Verify your understanding

Before implementation, you should be able to answer:

- Which value crosses from the browser to your server?
- Where is the secret key stored?
- Which response supplies the trusted charge?
- Which identifier makes a retry safe?
- What buyer experience follows a `409`?

Continue to [Authentication](/start/authentication), then [install the Buyer SDK](/buyer-sdk/install).

Source: https://docs.seatlayer.io/start/how-it-works/index.mdx
