Server-side integration
The secret-key backend flow — where sk_live may and may not appear, the public key as the browser render credential, and the two checkout flows (SDK checkout vs headless).
Server-side integration
SeatBuilder has two keys, and getting the split right is the whole of server-side integration. This guide covers the auth model, why the public key is the only credential the browser needs, and how your backend drives the authoritative write calls. It also disambiguates the two checkout flows so you never accidentally double-hold a seat. For the concepts behind holds and bookings, see Core concepts.
The two-key auth model
| Key | Prefix | Lives | Used for |
|---|---|---|---|
| Secret key | sk_live_xxx | Server only | Every write call (hold, book, release, extend), webhook registration. |
| Public key | pk_live_xxx | Browser (safe) | SDK render + reading seat status. |
Your public key is seeded automatically and is ready to use. Your secret key is not — create it on demand from the dashboard API keys page, where its value is shown exactly once at creation. Copy it into your server-side secret store immediately; if you lose it, rotate from the same page to mint a replacement.
Both are sent in the X-Api-Key header. The rule that matters:
The secret key
sk_live_xxxmust never reach the browser. If it ends up in client-side JavaScript, a page source, or a public repo, anyone can book and release seats on your behalf.
The browser needs only the public key. The public key is the browser render credential — it is designed to be exposed, scoped to your workspace and environment, and it authenticates exactly two things: read calls (fetching the event and seat status) and taking holds through the SDK. There is nothing else to mint or inject client-side; hand the SDK your public key and it renders. The long-lived secret never crosses to the untrusted browser.
Render tokens are an internal dashboard mechanism, not part of external integration. The render-token endpoint is guarded by a dashboard session (cookie-JWT) and exists only for the in-app LiveSeatViewer preview. A secret key cannot call it, and the SDK's
render()has no field to accept one — so ignore it entirely when integrating from your own site.
Which flow are you building?
There are two supported checkout flows. Pick one — the difference is
who takes the hold, and mixing them up is the most common integration
bug (re-holding a seat the SDK already holds returns a 409).
SDK checkout
The browser SDK auto-holds each seat the buyer selects and captures a
holdToken client-side. Because the seat is already held by the SDK,
your server must never call POST /seats/hold for it. Your server's
job is only to:
extendthe hold while the buyer moves through your payment window,bookthe selection at checkout, andreleaseanything the buyer abandons.
Pass the SDK's holdToken to your backend (e.g. in your create-order
request) and drive extend / book / release with it — from your
server, with your secret key. See
Hold and book lifecycle for the exact
extend / book / release contracts, and skip the hold step.
Headless / server-authoritative
No JS SDK on the page — your server owns the entire lifecycle. It calls
hold first, then book at checkout (and release for abandoned
seats). This is the flow the
Server-side HTTP reference
serves for backends with no JavaScript SDK (PHP, Python, Ruby, Go), and
it is walked endpoint-by-endpoint in
Hold and book lifecycle.
Driving write calls from the server
Whichever flow you build, the write calls (hold / book / release /
extend) should go out from your server. The API accepts either key on
these routes, but calling them from your server with your secret key
keeps the secret off the browser — that is the whole point of the split.
A typical headless checkout does:
- Your server takes the hold with
POST /api/v1/events/{eventKey}/seats/holdusingX-Api-Key: sk_live_xxx, so the seat is locked immediately. - At checkout your server books the whole selection in one atomic
POST /api/v1/events/{eventKey}/seats/bookcall, again with the secret key. - Anything abandoned is returned with
POST /api/v1/events/{eventKey}/seats/release.
POST /api/v1/events/evt_q3-2026-jazz-night/seats/hold HTTP/1.1
Host: seatbuilder.org
X-Api-Key: sk_live_xxx
Content-Type: application/json
{"objectLabels":["A-12","A-13"],"holdToken":"7f6c3a91-b1ef-4d0a-9c84-3f12a8e0b5d2","extraData":{"orderId":"ord_98a2"}}In the SDK-checkout flow, skip step 1 — the SDK already holds the
seat — and drive only extend / book / release with the SDK's
holdToken.
The full request and response contract for every write and read endpoint lives in Hold and book lifecycle. To react to seat changes your server did not initiate — a hold expiring, a booking from another integration — subscribe to Webhooks.
Webhook signature header
Webhook deliveries are signed with the SeatBuilder-Signature header. See
Webhooks for the HMAC verification steps.
Checklist
- Secret key
sk_live_xxxis only ever read from server-side secrets, never embedded in client code. - The browser renders with the public key alone — it is the render credential; there is no browser-side render token to mint.
- Pick one flow: SDK checkout (server never calls
/seats/hold— onlyextend/book/release) or headless (server callsholdthenbook). - All write calls go out from your server with the secret key.
- Webhook deliveries are verified via the
SeatBuilder-Signatureheader before you trust them (see Webhooks).
Webhooks
The complete webhook reference — the four seat event types, the Stripe-style envelope, and SeatBuilder-Signature HMAC-SHA256 verification.
Server-side HTTP reference
Language-neutral REST reference — base URL, X-Api-Key auth, and copy-paste curl for hold/extend/book/release/status. For any backend (PHP, Python, Ruby, Go).