Skip to content

Integration best practices

Security, inventory, payments, retries, accessibility, observability, and testing practices for production SeatLayer integrations.

Updated View as Markdown

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

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.

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

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.

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.

checkout-picker.jsjs
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.

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 as a reference implementation.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close