Skip to content

Holds and checkout handoff

Protect a buyer's selection in the browser, hand an opaque hold to your backend, and release or restore it safely.

Updated View as Markdown

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

browser/seat-picker.jsjs
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:

CheckoutHandoff.tsts
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

browser/headless-checkout.jsjs
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

server/inspect-hold.jsjs
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:

browser/restore-hold.jsjs
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:

browser/extend-hold.jsjs
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:

browser/release-hold.jsjs
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.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close