---
title: "Custom applications"
description: "Integrate SeatLayer into any frontend, backend, checkout, payment provider, or analytics stack without giving up product control."
---

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

# Custom applications

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.

> **Custom to custom is the normal path**
>
> Your framework, database, cart, payment provider, analytics, and deployment
> platform do not need a SeatLayer-specific adapter. Keep a small integration
> boundary around the documented browser and HTTP contracts.

## Reference architecture

```text title="custom-application.txt"
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.

```ts title="server/seatlayer.ts"
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:

```ts title="shared/checkout-contract.ts"
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

### Vanilla JavaScript

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

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

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

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:

```js title="browser/analytics.js"
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](/buyer-sdk/analytics). 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](/buyer-sdk/install),
[integrate a ticketing backend](/integrations/ticketing-backend), or
[build with agents](/agents/overview).

Source: https://docs.seatlayer.io/integrations/custom-applications/index.mdx
