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:
- Create a chart (the reusable venue layout).
- Publish the chart so events can render it.
- Create an event against the published chart.
- Render the SDK in the browser.
- Hold a seat server-side when the buyer commits to a selection.
- Extend the hold at checkout to start the payment window.
- Book the seat at checkout.
- 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_xxxThe 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.
| Field | Default | What it does |
|---|---|---|
holdDurationSeconds | — | Browse-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. |
showHoldCountdown | false | When 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.