---
title: "Hosted iframe"
description: "Embed SeatLayer's hosted buyer page, and understand what it can complete on its own and what it never tells your host page."
---

The hosted iframe is the lowest-code way to place a responsive SeatLayer picker
on a page. It loads the published event at `https://app.seatlayer.io/e/:eventKey`
and needs no browser credential.

It is a self-contained buyer page, not a component. That single fact decides
both of the things worth knowing about it.

## What it can complete

The framed page runs the same `SeatPicker` the SDK ships, so the buyer picks and
holds seats for real — those seats are blocked for everyone else while the hold
lasts.

Whether the buyer can also **pay** depends on the organizer's account:

- **Payments enabled, and a gateway connected to the event** — the buyer pays by
  card inside the frame, the money goes to the organizer's own Stripe or
  Razorpay account, and the order is confirmed on the SeatLayer buyer page. The
  frame is served from `app.seatlayer.io`, so that is where the gateway returns
  the buyer. No host code is involved at any point.
- **Otherwise** — at checkout the buyer sees SeatLayer's "finish in the ticketing
  checkout" card. The seats stay held; nothing is charged.

## What it never does

<Aside type="caution" title="The frame tells your page nothing">
  The public `postMessage` contract is resize and fullscreen only. There is no
  checkout, selection, hold, or booking-result message, so a host page cannot
  learn that a hold was made or an order was paid, and cannot run its own payment
  step against a hold created in the frame.
</Aside>

Use the iframe when the frame completing the sale (or completing nothing) is
acceptable — a marketing page, a venue page, a preview, a layout review.

Use [`SeatPicker`](/buyer-sdk/install) when your page needs to know. It works
without a bundler through the hosted script, and gives you either the server
checkout handoff (`onCheckout`) or, with `checkout: "hosted"`, the picker's own
payment plus `onOrderConfirmed` in your own page.

## Responsive embed

```html title="event-page.html"
<iframe
  id="seatlayer"
  title="Choose your seats"
  src="https://app.seatlayer.io/e/ev_9f3a"
  style="
    width: 100%;
    aspect-ratio: 4 / 3;
    max-height: min(85vh, 760px);
    border: 0;
    border-radius: 12px;
  "
  allow="fullscreen"
></iframe>

<script>
  const frame = document.getElementById("seatlayer");
  const seatLayerOrigin = new URL(frame.src).origin;

  addEventListener("message", (event) => {
    if (event.origin !== seatLayerOrigin) return;
    if (event.source !== frame.contentWindow) return;

    if (event.data?.type === "seatlayer:height") {
      frame.style.height = `${event.data.px}px`;
      frame.style.aspectRatio = "auto";
    }

    if (event.data?.type === "seatlayer:fullscreen") {
      const on = event.data.on === true;
      frame.style.position = on ? "fixed" : "";
      frame.style.inset = on ? "0" : "";
      frame.style.zIndex = on ? "9999" : "";
      frame.style.maxHeight = on ? "none" : "";
      document.documentElement.style.overflow = on ? "hidden" : "";
    }
  });
</script>
```

Replace `ev_9f3a` with a published event key. `aspect-ratio` reserves stable
space before the first measurement arrives; the height message then tracks
content changes.

## Message contract

| Message | Payload | Host action |
|---|---|---|
| `seatlayer:height` | `{ type: "seatlayer:height", px: number }` | Set the frame height |
| `seatlayer:fullscreen` | `{ type: "seatlayer:fullscreen", on: boolean }` | Pin or restore the frame |

The picker sends these messages because a framed document cannot resize its own
iframe or escape it for fullscreen. There is no public checkout, selection,
hold, or booking-result message — a sale completed inside the frame is invisible
to the host page by design, because the frame runs on SeatLayer's origin and
must not be able to assert anything to yours.

## Use the frame helper

`@seatlayer/js` exports `attachPickerFrame()` for the same protocol. It validates
the frame source, locks and restores document scrolling, preserves prior styles,
handles height changes received during fullscreen, and exits fullscreen on
Escape.

<PackageManagers pkg="@seatlayer/js" />

```ts title="host-frame.ts"
import { attachPickerFrame } from "@seatlayer/js";

const iframe = document.querySelector<HTMLIFrameElement>("#seatlayer");
if (!iframe) throw new Error("SeatLayer iframe was not found");

const detach = attachPickerFrame(iframe);

// Call when the iframe is removed or the host view unmounts.
detach();
```

By default the helper accepts the origin parsed from `iframe.src`. To pin it
explicitly:

```ts
const detach = attachPickerFrame(iframe, {
  origin: "https://app.seatlayer.io",
});
```

## Security requirements

<Aside type="warning" title="Verify both origin and source">
  A listener that trusts `event.data` alone lets any framed or opened page ask
  your host to resize or cover the viewport.
</Aside>

- Compare `event.origin` with the iframe URL's origin.
- Compare `event.source` with `iframe.contentWindow`.
- Accept only known message types and expected primitive fields.
- Never put a SeatLayer secret key in the iframe URL or host JavaScript.
- Give the iframe an accessible `title`.

## Iframe versus SDK

| Requirement | Iframe | `SeatPicker` |
|---|---:|---:|
| No bundler | Yes | Yes, through hosted script |
| Sells with no backend | Yes, when payments are on | Yes, with `checkout: "hosted"` |
| Buyer finishes on your domain | No, always on `app.seatlayer.io` | In-page gateways yes; a card redirect returns to `app.seatlayer.io` |
| Your payment/order step before booking | No | Yes |
| `onCheckout`, `onOrderConfirmed`, hold callbacks | No | Yes |
| Host pricing overrides | No | Yes |
| Host theme overrides | No | Yes |
| Responsive/fullscreen helper | Required in host | Built into same-document widget |

## Verification checklist

- [ ] The host page does not need to know about holds or orders.
- [ ] The event is published in the expected mode.
- [ ] The listener verifies both origin and window source.
- [ ] The frame has a useful title and keyboard focus is visible.
- [ ] Height changes do not create horizontal page overflow.
- [ ] Fullscreen locks scrolling and restores all prior styles on exit.
- [ ] Mobile Safari and an Android browser have been tested.
- [ ] Anything that must react to a hold or an order uses an SDK surface instead.

Continue with [choose an integration](/start/choose-an-integration) or
[install `SeatPicker`](/buyer-sdk/install).