# Iframe embed

The hosted URL works inside an `<iframe>` — drop it into any page that allows iframes and your agent appears, fully isolated from the host page.

## Minimal embed

```html title="page.html"
<iframe
  src="https://livelayer.app/a/your-agent-slug"
  width="100%"
  height="720"
  allow="microphone; camera; autoplay"
  style="border: none; border-radius: 12px;">
</iframe>
```

The `allow="microphone; camera"` attribute is **required** — without it the browser blocks the agent from accessing the mic and the visitor sees a permission error.

## Floating-bubble pattern

To get a corner-docked widget instead of an inline frame, position the iframe with `position: fixed`:

```html title="floating-bubble.html"
<iframe
  src="https://livelayer.app/a/your-agent-slug?mode=widget"
  allow="microphone; camera; autoplay"
  style="
    position: fixed;
    bottom: 16px;
    right: 16px;
    width: 400px;
    height: 600px;
    border: none;
    border-radius: 16px;
    box-shadow: 0 12px 40px rgba(0,0,0,0.15);
    z-index: 999999;
  ">
</iframe>
```

> [!TIP]
> For most floating-widget use cases, the [Script tag](/docs/develop/script-tag/overview) is better — it's a single line, handles open/close, and remembers state. Use an iframe when you need full isolation (the agent can't see anything in your page).

## What an iframe **can't** do

The hosted URL inside an iframe is sandboxed by browser cross-origin policies. The agent inside cannot:

- Read text, headings, or form fields on your host page.
- Fill or submit your forms.
- Navigate your host page (the iframe can navigate its own contents only).
- Click elements outside the iframe.

If you need any of that, reach for the [NPM package](/docs/develop/npm/overview) — it embeds in the same DOM as your app and gets full page-context awareness.

## Cross-frame messaging (advanced)

The hosted page posts a small set of events to the parent window via `postMessage`. Listen for them on your page:

```ts title="parent-listener.ts"
window.addEventListener("message", (e) => {
  if (e.origin !== "https://livelayer.app") return;
  if (typeof e.data !== "object" || e.data === null) return;

  switch (e.data.type) {
    case "livelayer:loaded":
      console.log("Agent ready");
      break;
    case "livelayer:conversation:started":
      analytics.track("agent_conversation_started", { id: e.data.id });
      break;
    case "livelayer:conversation:ended":
      analytics.track("agent_conversation_ended", { duration: e.data.duration });
      break;
  }
});
```

Always validate `e.origin` before trusting message contents.

## Read next

- [URL customization](/docs/develop/hosted/url-customization) — query params for `mode`, theme, greeting.
- [Script tag](/docs/develop/script-tag/overview) — drop-in widget without iframe limitations.
