SeatBuilderSeatBuilder Docs

Integration walkthrough

End-to-end integration — create a chart, publish it, run an event, render the SDK, hold a seat, book it, and verify the webhook delivery.

Integration walkthrough

This page walks through the canonical end-to-end SeatBuilder flow on one long scroll page so you can copy each step into your own integration:

  1. Create a chart (the reusable venue layout).
  2. Publish the chart so events can render it.
  3. Create an event against the published chart.
  4. Render the SDK in the browser.
  5. Hold a seat server-side when the buyer commits to a selection.
  6. Extend the hold at checkout to start the payment window.
  7. Book the seat at checkout.
  8. Receive and verify the webhook delivery on your backend.

All requests are authenticated with an X-Api-Key header — see Authentication for the key model.

1. Create a chart

Send a POST /api/v1/charts with the chart name. The draftVersion object is optional and accepts the editor's chart payload (sections, rows, tables, GA areas, decorative shapes). Most integrators omit it here and use the visual editor to build the layout.

POST /api/v1/charts HTTP/1.1
Host: api.your-host.example
X-Api-Key: sk_live_xxx
Content-Type: application/json

{ "name": "Demo Venue" }
{
  "chartKey": "ch_01HXYZ...",
  "name": "Demo Venue",
  "draftVersion": null,
  "publishedVersion": null,
  "createdAt": "2026-05-17T12:00:00Z"
}

See Charts API for the full payload reference.

2. Publish the chart

Events can only render charts that have a publishedVersion. Promote the current draft with POST /api/v1/charts/{chartKey}/publish — no body.

POST /api/v1/charts/ch_01HXYZ.../publish HTTP/1.1
Host: api.your-host.example
X-Api-Key: sk_live_xxx

The response echoes the chart with publishedVersion populated. From this point on, re-publishing the chart updates the layout live for every existing event that references this chart (see Versioning for the snapshot policy).

3. Create an event

Each event has independent seat availability. Pass the published chartKey and a human-readable name.

POST /api/v1/events HTTP/1.1
Host: api.your-host.example
X-Api-Key: sk_live_xxx
Content-Type: application/json

{
  "chartKey": "ch_01HXYZ...",
  "name": "Opening night, 2026-09-12"
}
{
  "eventKey": "evt_01HXYZ...",
  "chartKey": "ch_01HXYZ...",
  "name": "Opening night, 2026-09-12",
  "createdAt": "2026-05-17T12:00:01Z"
}

See Events API for the full payload reference.

4. Render the SDK

Mount the JavaScript SDK in the buyer's browser. The publicKey is a pk_* (publishable) key — safe to expose. See Render a seat map for the standalone recipe, or copy the snippet from Quickstart.

import SeatsIO from '@seats/sdk';

const chart = SeatsIO.render({
  publicKey: 'pk_live_xxx',
  eventKey: 'evt_01HXYZ...',
  container: 'seats-container',
  apiUrl: 'https://api.your-host.example',
  maxSelectedObjects: 4,
  // Deferred-countdown config (both opt-in — see below):
  holdDurationSeconds: 300, // opt-in short browse TTL (omit → server default 900s)
  showHoldCountdown: false, // keep the built-in banner off (default)
  onObjectSelected: ({ objectLabel, holdToken }) => {
    // Persist `holdToken` alongside the buyer's cart — you'll use it
    // for the hold, extend, and book calls below.
    console.log('Selected', objectLabel, 'with hold', holdToken);
  },
});

Render your own countdown (SDK config)

The SDK exposes exactly two opt-in config fields that control hold lifetime and the built-in countdown. Both have safe defaults — existing integrations behave unchanged except that the built-in countdown banner no longer auto-shows.

FieldDefaultWhat it does
holdDurationSecondsBrowse-hold lifetime in seconds, passed as ttlSeconds on the initial /hold. No client default — omit to keep the server default (900s); set e.g. 300 for a short browse window.
showHoldCountdownfalseWhen true, re-enables the legacy built-in 2-minute warning banner + auto-deselect timer. Leave false to own the visible countdown on your checkout page.

With the default showHoldCountdown: false, the SDK no longer auto-shows a countdown banner, and an expired browse-hold reconciles automatically in the holder's view via the realtime seat.hold_expired broadcast (no polling). The integrator owns the visible timer on the checkout page, driven by the holdExpiresAt returned from /seats/extend (step 6 below). See the full flow in Hold seats and confirm at checkout.

Choose a holdDurationSeconds shorter than your payment window (ttlSeconds on /extend, default 600s) so abandoned selections free up faster than active checkouts.

5. Hold a seat (server-side)

The SDK call in step 4 already places a transient hold inside the buyer's session. When the buyer adds the seat to a server-side cart, mirror the hold to your backend with POST /api/v1/events/{eventKey}/seats/hold so the seat remains locked even if the buyer's tab closes.

POST /api/v1/events/evt_01HXYZ.../seats/hold HTTP/1.1
Host: api.your-host.example
X-Api-Key: sk_live_xxx
Content-Type: application/json

{
  "objectLabel": "A-12",
  "holdToken": "<the holdToken from onObjectSelected>"
}
{
  "objectLabel": "A-12",
  "status": "held",
  "holdToken": "...",
  "expiresAt": "2026-05-17T12:10:01Z"
}

See Seats API for the full payload reference and the hold-conflict / hold-expired error envelopes.

6. Extend the hold at checkout

When the buyer advances to the payment screen, refresh the browse holds to the longer payment window with POST /api/v1/events/{eventKey}/seats/extend, passing the chart's holdToken. This is a batch, partial-success call (it never returns 409): seats still held by the token come back in extended[] with a fresh holdExpiresAt; any seat the token no longer holds comes back in failed[] with a generic not_held_by_token reason.

POST /api/v1/events/evt_01HXYZ.../seats/extend HTTP/1.1
Host: api.your-host.example
X-Api-Key: sk_live_xxx
Content-Type: application/json

{
  "holdToken": "<the chart holdToken>",
  "labels": ["A-12"],
  "ttlSeconds": 600
}
{
  "extended": [
    { "objectLabel": "A-12", "holdExpiresAt": "2026-05-17T12:20:01Z" }
  ],
  "failed": []
}

Render your visible payment countdown from the returned holdExpiresAt (the SDK does not show it when showHoldCountdown is false — see SDK config). Drop any failed[] seats and re-prompt the buyer for those before booking.

7. Book seats

At checkout, promote the holds to permanent bookings with a single all-or-nothing call to POST /api/v1/events/{eventKey}/seats/book. Pass every seat label in objectLabels[] and the shared holdToken; all the labels must currently be held by that token. If any one of them is no longer held, the whole request fails with 409 and nothing is booked.

POST /api/v1/events/evt_01HXYZ.../seats/book HTTP/1.1
Host: api.your-host.example
X-Api-Key: sk_live_xxx
Content-Type: application/json

{
  "objectLabels": ["A-12", "A-13"],
  "holdToken": "<same holdToken>",
  "extraData": { "orderId": "ord_98a2" }
}

Success returns 200 with a booked[] array — one object per confirmed seat:

{
  "booked": [
    { "chartKey": "chart_8a2b1c", "eventKey": "evt_01HXYZ...", "objectLabel": "A-12",
      "status": "booked", "bookedAt": "2026-05-17T12:09:00Z" },
    { "chartKey": "chart_8a2b1c", "eventKey": "evt_01HXYZ...", "objectLabel": "A-13",
      "status": "booked", "bookedAt": "2026-05-17T12:09:00Z" }
  ]
}

If a seat is no longer held, the call returns 409 with a failed[] array — nothing was booked:

{
  "error": "One or more seats are no longer held by this token",
  "failed": [{ "objectLabel": "A-13", "reason": "not_held_by_token" }]
}

The end-to-end e-commerce variant — including 409 (one or more seats lost) recovery — lives in Hold seats and confirm at checkout.

8. Receive the webhook

Register a webhook endpoint in the dashboard (or via POST /api/v1/webhooks) for seat.booked. Each delivery carries a Seats-Signature header in the Stripe-style format t=<unix_seconds>,v1=<hex>. Compute HMAC-SHA256 over `${t}.${rawBody}` with your endpoint secret and reject mismatches.

The full verification recipe — including replay protection — lives in Verify a webhook.