---
title: "Build SeatLayer into your platform"
description: "Add seating to a custom product or multi-organiser platform while keeping your own brand, users, checkout, and business logic."
---

SeatLayer Platform is a composable reserved-seating and inventory layer. Your
product can use only the buyer picker, or combine venue design, event inventory,
checkout holds, server booking, operations, reporting, and webhooks behind your
own interface.

You keep the event catalogue, customer relationship, product UX, accounts,
payments, taxes, commercial Orders, ticket/QR/PDF creation and delivery, email,
refunds, check-in, support, and business rules. SeatLayer provides the
seating-specific geometry, live inventory, concurrency, and inventory booking
ledger.

## Choose an integration shape

<CardGrid>
  <Card title="Add seating to one product" icon="ph:puzzle-piece">
    Embed the picker in an existing booking or commerce flow. Your current backend receives the hold and confirms the order.
  </Card>
  <Card title="Run a multi-organiser platform" icon="ph:buildings">
    Provision one isolated workspace per organiser, store the mapping, and expose SeatLayer through your own CMS.
  </Card>
  <Card title="Build custom venue operations" icon="ph:sliders-horizontal">
    Combine APIs, the embeddable Designer/control room, and webhooks with your own permissions and workflows.
  </Card>
</CardGrid>

The data flow stays the same in every shape:

```text title="platform-boundary.txt"
Your CMS ──secret key──> charts, workspaces, events
Buyer page ──event + public key/scoped token──> render, select, hold
Your checkout ──secret key──> inspect hold, book
SeatLayer ──signed webhook──> reconciliation and downstream work
```

## Map your model to SeatLayer

| Your product | SeatLayer |
|---|---|
| Venue/layout | Reusable chart |
| Performance, departure, or timed session | Event with independent inventory |
| Tenant, organiser, or customer account | Workspace |
| Cart reservation | Hold |
| Inventory committed for a paid/confirmed order | Inventory Booking |
| Your stable resource id | `externalRef` |

`workspaceId` is the enforced isolation boundary. `externalRef` is an opaque reconciliation tag; never use it as authorization.

## Multi-organiser provisioning

<Steps>
  <Step title="Create an isolated workspace">
    Create the mapping when a customer or organiser is provisioned.

    ```bash title="create-workspace.sh"
    curl https://api.seatlayer.io/v1/workspaces \
      -X POST \
      -H "Authorization: Bearer $SEATLAYER_SECRET_KEY" \
      -H "Idempotency-Key: workspace-org_123" \
      -H "Content-Type: application/json" \
      -d '{"name":"Organiser X","externalRef":"org_123"}'
    ```

    Persist the returned workspace id on your organiser record.
  </Step>

  <Step title="Create or duplicate a chart">
    Maintain reusable master venues, then duplicate a master into the organiser workspace.

    ```bash title="duplicate-chart.sh"
    curl https://api.seatlayer.io/v1/charts/c_master_hall/duplicate \
      -X POST \
      -H "Authorization: Bearer $SEATLAYER_SECRET_KEY" \
      -H "Idempotency-Key: org_123-main-hall" \
      -H "Content-Type: application/json" \
      -d '{
        "name":"Organiser X — Main Hall",
        "workspaceId":"ws_7e2d",
        "externalRef":"venue_81"
      }'
    ```

    A duplicate is a draft. Review it, then publish it before creating an event.
  </Step>

  <Step title="Create an event">
    Events inherit the chart workspace and receive independent inventory. Only
    `chartId` is required by the core API; `name` may default from the chart and
    the platform can keep all catalogue content in its own system.

    ```bash title="create-event.sh"
    curl https://api.seatlayer.io/v1/events \
      -X POST \
      -H "Authorization: Bearer $SEATLAYER_SECRET_KEY" \
      -H "Idempotency-Key: event-show_456" \
      -H "Content-Type: application/json" \
      -d '{
        "chartId":"c_7e2d",
        "name":"Spring Gala",
        "externalRef":"show_456"
      }'
    ```

    Store the returned `meta.key` with your event. That event key addresses the
    buyer SDK's inventory and the inventory occurrence is operational
    immediately. It does not publish a Hosted Event Page, Organizer Website
    listing, or SeatLayer-managed checkout, and it does not need hosted event
    details or SeatLayer ticket releases.
  </Step>

  <Step title="Embed under your brand">
    Render the picker in your product. Register the exact buyer-page origin as
    an Embed domain and give the SDK your publishable key. No account secret is
    sent to the browser.

    ```js title="buyer-page.js"
    const picker = new seatlayer.SeatPicker({
      container: "#seat-picker",
      event: seatlayerEventKey,
      publicKey: seatlayerPublicKey,
      theme: {
        accent: "#6d28d9",
        accentInk: "#ffffff",
        fontFamily: "Inter, system-ui, sans-serif",
      },
      onCheckout: (_, __, handoff) => {
        beginYourCheckout({ holdId: handoff.holdId });
      },
    });

    await picker.render();
    ```

    For ordinary Public sale, SeatLayer validates the key, event, mode, and
    origin, then returns an in-memory Public-only session with the chart and
    compact inventory status in one response. Login, presale, partner, and
    channel inventory instead use `buyerAccessTokenProvider` or
    `buyerAccessToken` from your authenticated backend. An explicit token or
    provider always takes precedence over `publicKey`.
  </Step>

  <Step title="Inspect, pay, and book inventory">
    Your backend resolves the hold, derives the trusted order amount, runs your
    payment flow, and commits SeatLayer inventory with your existing order id.

    ```js title="platform-checkout.js"
    const hold = await seatlayerRequest(
      `/v1/events/${event.seatlayerKey}/holds/${holdId}`,
    );

    assertWorkspaceOwnership(event, organiser);
    const order = await createOrderFromTrustedItems(hold.items);
    await authorizePayment(order);

    const booking = await seatlayerRequest(
      `/v1/events/${event.seatlayerKey}/book`,
      {
        method: "POST",
        body: JSON.stringify({
          holdId,
          bookingRef: order.id,
        }),
      },
    );
    ```
  </Step>
</Steps>

## One webhook, many organisers

Account-level webhook traffic can arrive at one endpoint. Route from the SeatLayer event id/key you stored, then verify its immutable workspace matches your business record.

```js title="server/seatlayer-webhook.js"
app.post("/webhooks/seatlayer", rawBody(), async (req, res) => {
  if (!verifySeatLayerSignature(req.rawBody, req.headers)) {
    return res.sendStatus(401);
  }

  const delivery = JSON.parse(req.rawBody);
  const businessEvent = await findBySeatLayerEvent(delivery.payload.eventId);

  if (
    !businessEvent ||
    businessEvent.seatlayerWorkspaceId !== delivery.payload.workspaceId
  ) {
    return res.sendStatus(400);
  }

  await enqueueForOrganiser(businessEvent.organiserId, delivery);
  return res.sendStatus(200);
});
```

<Aside type="caution" title="Verify before routing">
  Verify the signature against the raw body first. An `externalRef` is useful context, but it is not proof that a payload belongs to a tenant.
</Aside>

## White-label boundaries

You can keep your own:

- navigation, account hierarchy, roles, and permissions;
- checkout, payments, tax, refunds, and order history;
- ticket/QR/PDF creation and delivery, email, scanning, and attendance;
- visual theme, surrounding content, and analytics;
- venue onboarding and approval workflow; and
- customer support and operational runbooks.

SeatLayer remains responsible for:

- chart geometry and semantic seating objects;
- selection rules and accessible-seat relationships;
- realtime holds and availability;
- atomic booking transitions;
- booking-reference linkage, inventory history, and sell-through reports;
- buyer 2D/3D visualization; and
- signed event notifications.

## Platform best practices

- Keep one workspace per isolation boundary, not per event.
- Reuse a chart for repeat performances; do not duplicate it for each date unless geometry differs.
- Store SeatLayer ids alongside your own immutable ids at creation time.
- Use deterministic idempotency keys for provisioning retries.
- Enforce ownership in your backend before every cross-tenant action.
- Keep master charts separate from organiser-owned copies.
- Test sandbox and live webhook routing through the same tenant checks.
- Expose only the operations your product is ready to support.
- Treat configured booked value as an inventory performance figure; your
  commerce ledger remains authoritative for money.

<Aside type="caution" title="No SeatLayer fulfilment in Platform mode">
  `POST /book` commits inventory and emits inventory webhooks. It must not create
  a SeatLayer commerce Order, ticket, QR/PDF/email, attendee/door record, or
  refund. Your platform owns those records and experiences end to end.
</Aside>

## Custom-to-custom checklist

Before shipping a new product shape, write down:

1. which system owns venues, events, prices, inventory, commercial Orders,
   tickets, refunds, check-in, and customers;
2. how your ids map to `workspaceId`, chart id, event key, hold id, and `bookingRef`;
3. which surface is embedded and which is called server-to-server;
4. how a hold expiry, booking conflict, and payment failure recover;
5. how tenant isolation is enforced and tested; and
6. which webhooks reconcile your downstream systems.

Then use [integration best practices](/integrations/best-practices) and the [complete checkout example](/examples/complete-checkout) to implement the buyer-to-booking loop.