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
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 inventoryOnly 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.
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:
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:
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:
- successful hold inspection and booking;
- expired or missing hold;
- payment rejection before booking;
- atomic booking conflict;
- booking timeout followed by same-reference retry;
- webhook duplicate delivery;
- missing or wrong-mode credentials; and
- 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.