A hold temporarily protects inventory while a buyer completes checkout. It is not a sale. The Buyer SDK creates the hold; your server inspects and books it.
SeatPicker handoff
const picker = new seatlayer.SeatPicker({
container: "#picker",
event: "ev_9f3a",
onCheckout: async (hold, seats, handoff) => {
await fetch("/api/checkout/seats", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ holdId: handoff.holdId }),
});
},
});
await picker.render();The third callback argument is the stable checkout handoff:
interface CheckoutHandoff {
holdId: string;
expiresAt: number;
currency: string;
lineItems: CheckoutLineItem[];
total: number;
}Browser totals are useful for presentation, but your server must inspect the hold before creating the trusted order amount.
Headless SeatingChart flow
const chart = new seatlayer.SeatingChart({
container: "#chart",
event: "ev_9f3a",
onSelectionChange: (seats) => updateYourCart(seats),
onHold: ({ holdId, expiresAt }) => {
saveCheckoutHandoff({ holdId, expiresAt });
},
onError: (error) => reportPickerError(error),
});
await chart.render();
document.querySelector("#continue").addEventListener("click", async () => {
const hold = await chart.hold();
if (!hold) {
showMessage("One of those seats is no longer available. Please choose again.");
return;
}
await beginCheckout({ holdId: hold.holdId });
});hold() resolves to:
| Field | Meaning |
|---|---|
holdId |
Opaque capability identifying this hold |
expiresAt |
Absolute expiry timestamp in epoch milliseconds |
seats |
Held selection for buyer-side UI |
items |
Resolved hold line items |
The public method returns null when the hold cannot be created and sends structured unexpected errors to onError.
Inspect from your server
const response = await fetch(
`https://api.seatlayer.io/v1/events/${eventKey}/holds/${holdId}`,
{
headers: {
authorization: `Bearer ${process.env.SEATLAYER_SECRET_KEY}`,
},
},
);
if (!response.ok) {
return handleInactiveOrInvalidHold(response.status);
}
const hold = await response.json();
const order = await createOrderFromTrustedItems(hold.items);Do not accept buyer-posted labels, tiers, quantities, prices, or currency as authoritative checkout input.
Restore a hold
SeatPicker restores its active hold from sessionStorage by default. When your application owns cart persistence:
const picker = new seatlayer.SeatPicker({
container: "#picker",
event: eventKey,
restoreHold: false,
initialHoldId: cart.seatlayerHoldId,
onHoldChange: (hold) => {
persistCart({
seatlayerHoldId: hold?.holdId ?? null,
expiresAt: hold?.expiresAt ?? null,
});
},
onHoldRestored: (hold) => {
renderCountdown(hold.expiresAt);
},
});For a headless chart, call resumeHold(holdId). Restoring verifies the hold and does not extend its expiry.
Extend a hold
The SDK can request more time for the current hold:
const extended = await chart.extendHold();
if (!extended) {
returnBuyerToSelection();
} else {
renderCountdown(extended.expiresAt);
}An active hold may be extended at most three times. A refusal is a normal outcome when the hold is missing, inactive, expired, or has reached the renewal limit.
Release a hold
Release inventory explicitly when the buyer abandons checkout or chooses to start over:
await chart.release();If you do nothing, the server releases the hold at expiresAt.
Conflict behavior
| Moment | Signal | Product response |
|---|---|---|
| Hold creation | hold() returns null |
Refresh selection and explain that availability changed |
| Hold inspection | 404 or inactive hold |
Do not create a new charge; return to selection |
| Booking | 409 conflict |
Void/refund according to payment state and let the buyer reselect |
| Restore | null / callback not fired |
Clear stale cart hold state |
| Extend | null |
Continue expiry recovery; do not promise more time |
Verification checklist
- A selection produces one hold and one stored
holdId. - A competing browser cannot hold the same seat.
- Refresh restores the active hold only when configured.
- Release returns inventory to other buyers.
- Expiry clears the buyer cart and selection state.
- The backend calculates the charge from inspected hold items.
- No secret key appears in client code.
Continue to Booking held seats and Handling hold expiry.