---
title: "Integration best practices"
description: "Security, inventory, payments, retries, accessibility, observability, and testing practices for production SeatLayer integrations."
---

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

# Integration best practices

Good reserved-seating UX depends on more than rendering a chart. The integration must preserve a clear trust boundary, behave well while inventory changes, and give buyers a useful recovery path when a hold or payment fails.

## Keep one trust boundary

| Browser may | Server must |
|---|---|
| Render public event inventory | Store `SEATLAYER_SECRET_KEY` |
| Let the buyer select and hold | Inspect the hold and its authoritative items |
| Send your backend an opaque `holdId` | Calculate the order total |
| Display availability and pricing | Take payment and confirm the booking |

> **Never trust checkout data from the browser**
>
> Labels and prices in the browser are display data. Resolve the hold on your server and build the charge from the returned items.

## Use an explicit checkout state machine

A practical checkout has these states:

1. `selecting` — no hold exists.
2. `held` — inventory is protected until `expiresAt`.
3. `paying` — payment is in flight; keep the same hold and order.
4. `booked` — SeatLayer confirmed the sale.
5. `recoverable_error` — hold expired, inventory conflicted, or payment failed.

Do not create a new order or `bookingRef` merely because a network request timed out. Retry the original operation with the original reference.

## Make booking idempotent

Use your immutable order or payment identifier as `bookingRef`.

```js title="server/book-with-retry.js"
async function bookHeldSeats({ eventKey, holdId, orderId }) {
  const response = await 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 }),
    },
  );

  if (response.ok) return response.json();
  if (response.status === 409) return { conflict: true };
  throw new Error(`SeatLayer booking failed: ${response.status}`);
}
```

The same `bookingRef` is safe to replay. A different reference for the same sold seats is a real conflict.

## Decide the payment sequence

Most integrations inspect the hold, authorize or capture payment, then book:

```text
hold → inspect trusted items → authorize payment → book → capture/settle
```

If your payment provider captures before booking, define an automatic void/refund path for a booking conflict. If it supports authorization and capture, authorizing before booking reduces refund noise.

> **Do not let a request timeout decide the order state**
>
> Re-inspect your order and retry booking with the same `bookingRef`. A timeout does not prove that the first booking call failed.

## Treat `409` as a product path

Inventory conflicts are expected under load. When booking returns `409`:

- do not partially confirm the order;
- void or refund payment according to your payment sequence;
- explain that the reservation expired or changed;
- reopen the picker with the previous intent where possible; and
- let the buyer choose again without restarting the whole event journey.

## Preserve active holds across navigation

The buyer SDK restores its current hold from `sessionStorage` by default. If your application owns navigation or cart persistence, set `restoreHold: false`, store the `holdId`, and pass `initialHoldId` when the picker mounts again.

```js title="checkout-picker.js"
const picker = new SeatPicker({
  container: "#picker",
  event: eventKey,
  restoreHold: false,
  initialHoldId: cart.seatlayerHoldId,
  onHoldChange: (hold) => {
    saveCart({ seatlayerHoldId: hold?.holdId ?? null });
  },
});
```

## Design for every buyer

- Give the picker container an explicit height and test it at its actual embed width.
- Keep keyboard focus visible and do not trap focus around the embed.
- Preserve accessible-seat metadata and companion-seat rules from the venue.
- Test high zoom, narrow screens, screen-reader labels, and colorblind-safe mode.
- Treat 3D and 360° seat views as progressive enhancement. The 2D map remains the reliable fallback.
- Localize surrounding checkout copy as well as the picker.

## Add useful observability

Record your own correlation fields together:

- SeatLayer `eventKey`;
- `holdId`;
- your `orderId` / `bookingRef`;
- payment reference;
- HTTP status and SeatLayer error code; and
- webhook delivery/event identifier.

Never log secret keys or full authorization headers. Avoid logging buyer personal data in inventory diagnostics.

## Use webhooks for reconciliation

The synchronous booking response drives the buyer journey. Signed webhooks provide asynchronous confirmation and repair.

- Verify HMAC against the **raw request body** before parsing or trusting it.
- Deduplicate deliveries.
- Return a fast `2xx`, then run slow business work asynchronously.
- Route platform traffic by your stored event mapping and verify `workspaceId`.
- Keep test and live traffic separate using `livemode`.

## Test the system, not only the component

Before live credentials:

- two browsers compete for the same seat;
- a hold expires during checkout;
- payment succeeds but booking returns `409`;
- booking succeeds but the HTTP response times out;
- the same booking request is delivered twice;
- the page refreshes with an active hold;
- realtime disconnects and reconnects;
- the event sells out while the picker is open;
- a narrow mobile container completes checkout; and
- missing or mode-mismatched credentials fail safely.

> **Use sandbox events**
>
> Test-mode events exercise the same hold, booking, realtime, and webhook flow without spending booking credits.

## Production checklist

- [ ] Secret and public configuration are separated.
- [ ] CSP allows only the SeatLayer origins your embed needs.
- [ ] Server hold inspection is the source of price and quantity.
- [ ] `bookingRef` comes from an immutable order identifier.
- [ ] Payment conflict compensation is automated.
- [ ] Webhook signatures and duplicate deliveries are tested.
- [ ] Mobile, keyboard, accessibility, and 2D fallback are verified.
- [ ] Logs connect event, hold, order, and payment without leaking secrets.
- [ ] Test and live credentials cannot be mixed.

Use the [complete checkout](/examples/complete-checkout) as a reference implementation.

Source: https://docs.seatlayer.io/integrations/best-practices/index.mdx
