Skip to content

Custom applications

Integrate SeatLayer into any frontend, backend, checkout, payment provider, or analytics stack without giving up product control.

Updated View as Markdown

SeatLayer is designed to sit inside an existing application rather than replace it. Use the browser SDK for interactive inventory and the HTTP API from any trusted server runtime.

Reference architecture

custom-application.txttext
Your frontend
  ├─ SeatPicker or SeatingChart
  ├─ your cart, account and checkout UI
  └─ your analytics adapter

          │ holdId

Your backend
  ├─ your authentication and authorization
  ├─ your order + payment services
  ├─ SeatLayer HTTP adapter (secret key)
  └─ signed webhook receiver


SeatLayer charts, events and live inventory

Only the browser SDK needs DOM access. The server integration is standard HTTPS, so it works in Node, Bun, Deno, PHP, Ruby, Python, Go, Java, .NET, Workers, and other environments that can send authenticated requests.

Keep the integration boundary small

Create one server module responsible for:

  • loading and validating the SeatLayer secret key;
  • building the API base URL for the current environment;
  • inspecting a hold;
  • booking with a stable bookingRef;
  • cancelling or changing server-managed inventory;
  • normalizing errors into your application’s domain errors; and
  • attaching request ids to logs without recording secrets.
server/seatlayer.tsts
const apiBase = "https://api.seatlayer.io";

async function seatLayerRequest(path: string, init: RequestInit = {}) {
  const response = await fetch(`${apiBase}${path}`, {
    ...init,
    headers: {
      authorization: `Bearer ${process.env.SEATLAYER_SECRET_KEY}`,
      "content-type": "application/json",
      ...init.headers,
    },
  });

  const body = await response.json().catch(() => null);
  return { ok: response.ok, status: response.status, body };
}

export function inspectHold(eventKey: string, holdId: string) {
  return seatLayerRequest(
    `/v1/events/${encodeURIComponent(eventKey)}/holds/${encodeURIComponent(holdId)}`,
  );
}

export function bookHold(
  eventKey: string,
  input: { holdId: string; bookingRef: string },
) {
  return seatLayerRequest(
    `/v1/events/${encodeURIComponent(eventKey)}/book`,
    { method: "POST", body: JSON.stringify(input) },
  );
}

Wrap this module in your existing service, dependency-injection, tracing, and test conventions. Do not spread direct SeatLayer calls across route handlers.

Define the browser-to-server contract

Your checkout route needs an opaque hold id and your own application context:

shared/checkout-contract.tsts
type SeatCheckoutRequest = {
  eventKey: string;
  holdId: string;
  cartId: string;
};

eventKey may instead come from a trusted product record or route parameter. Never accept buyer-supplied price, currency, category, or labels as the booking authority.

Connect any frontend

Mount SeatPicker from the hosted script or ESM build and call your checkout endpoint from onCheckout.

Use @seatlayer/react and keep the event key and callbacks in component state. Hold state remains server-authoritative.

Mount the vanilla SDK in the framework’s client-only lifecycle hook and call destroy() during cleanup. Avoid rendering it during server-side rendering.

Load the buyer surface after the webview is ready, allow WebGL only if using 3D, and bridge the checkout handoff into the native application.

Send analytics anywhere

SeatPicker.onAnalytics is a callback, not a SeatLayer analytics destination. Forward supported events to the system your application already uses:

browser/analytics.jsjs
const picker = new seatlayer.SeatPicker({
  container: "#picker",
  event: "ev_9f3a",
  onAnalytics: (event, properties) => {
    // PostHog
    posthog.capture(`seatlayer_${event}`, properties);

    // Or Google Analytics:
    // gtag("event", `seatlayer_${event}`, properties);

    // Or your own endpoint:
    // navigator.sendBeacon("/analytics", JSON.stringify({ event, properties }));
  },
});

The currently emitted event names cover the 3D buyer journey and are marked Preview in the analytics reference. A throwing sink does not break the picker, but your adapter should still be defensive and avoid sending personal data accidentally.

Adapt errors into product behavior

SeatLayer result Domain behavior
404 hold_not_found Cart is stale; return to selection
Active hold with expired timestamp Return to selection and clear persisted hold
403 mode_mismatch Configuration fault; alert operators, not buyers
Booking 409 Recoverable inventory conflict
Timeout or 5xx after booking request Unknown state; retry with the same bookingRef
Webhook signature failure Reject and log security event

Keep raw provider errors in structured server logs and return stable, buyer-appropriate application errors to the client.

Test without real dependencies

Inject the HTTP transport or mock your server adapter at its boundary. Cover:

  1. successful hold inspection and booking;
  2. expired or missing hold;
  3. payment rejection before booking;
  4. atomic booking conflict;
  5. booking timeout followed by same-reference retry;
  6. webhook duplicate delivery;
  7. missing or wrong-mode credentials; and
  8. browser cleanup during navigation.

An end-to-end test in SeatLayer test mode should complement—not replace—these fast contract tests.

Production checklist

  • SDK code runs client-side and is destroyed on unmount.
  • A single server adapter owns API calls and secret loading.
  • Browser payloads contain no trusted price or booking authority.
  • HTTP errors are mapped to stable product states.
  • Checkout retries reuse the existing order id.
  • Analytics forwards only intended, non-sensitive properties.
  • Unit, contract, and test-mode end-to-end paths pass.
  • Framework upgrades cannot silently duplicate picker instances.

Continue with install the Buyer SDK, integrate a ticketing backend, or build with agents.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close