> ## Documentation Index
> Fetch the complete documentation index at: https://docs.avaluma.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Embed an Avaluma Agent on Your Website

> Embed a shared Avaluma agent in any website with an iframe, and customize its behavior and appearance using query parameters.

Every shared agent can be embedded as an `<iframe>` on any website. The avatar then connects directly from your visitor's browser to the agent — no backend required on your side.

## Prerequisites

<Steps>
  <Step title="Enable embedding">
    Open the agent in the [dashboard](https://avaluma.ai) and turn on **Allow embedding**. Only shared agents can be embedded. Toggling this switch is **saved immediately** — you can test right away without saving the agent manually first.
  </Step>

  <Step title="Allow your origin">
    Under **Allowed origins**, add the address(es) of the page(s) that may run the avatar — for example `https://www.your-domain.com`.

    <Warning>
      If the embedding page's origin is **not** on the list, the browser blocks the iframe (via `frame-ancestors`) and all [messaging](#messaging) commands are rejected. Enter the exact origin (scheme + host + port), without a path.
    </Warning>
  </Step>

  <Step title="Copy the snippet">
    Copy the ready-made embed code from the dashboard, or build it yourself as described below.
  </Step>
</Steps>

## Quickstart

```html theme={null}
<iframe
  src="https://avaluma.ai/agent/AGENT_ID/embed"
  allow="microphone; autoplay"
  style="width:100%;height:600px;border:0"
></iframe>
```

Replace `AGENT_ID` with your agent's UUID (visible in the dashboard).

<Note>
  The `allow="microphone; autoplay"` attribute is required: without `microphone` the visitor cannot speak, and without `autoplay` audio playback will not start automatically in some browsers (the "Enable sound" overlay takes over instead).
</Note>

<Tip>
  Don't want to set up your own page first? Use the built-in **[embedding test page](https://avaluma.ai/embed-test)** at `avaluma.ai/embed-test`: enter an agent UUID, embed it, and try the postMessage bridge (handshake, send text, microphone on/off, event log) right away. Just add `https://avaluma.ai` to the agent's **Allowed origins** once.
</Tip>

## Start screen preview

Before the visitor clicks "Start conversation", the real avatar is not yet connected — no session is established and nothing is spoken. If the agent's **avatar** has a preview video configured, the start screen plays it as a **silent, seamless loop** (back and forth) until the visitor starts. Without a configured video, the start screen stays a solid color (see `border`).

The preview video is configured on the avatar and automatically applies to every agent that uses that avatar — you don't need to change your embed code. We recommend a small **MP4** already exported as a boomerang (forward + reverse).

## Fullscreen displays

Embedding always renders the avatar **inline**, inside the iframe container you place on your page. For a standalone, fullscreen display — for example an unattended kiosk stand — use the dedicated **[Kiosk mode](/en/agents/kiosk)** instead of an iframe. It opens directly as a top-level page (no iframe) and adds a screen wake lock, an idle auto-reset, and a home-screen web app for true fullscreen.

## Query parameters

All options are appended as query parameters to the `/embed` URL. Invalid values fall back to their defaults automatically.

<ParamField query="border" type="string" default="000000">
  Background / letterbox color as a 6-digit hex value **without** `#` (e.g. `f5f5f5`).
</ParamField>

<ParamField query="fit" type="string" default="contain">
  How the avatar preview and live stream are displayed inside the iframe. `contain` shows the full image (possibly with thin bars in the `border` color); `cover` fills the area and crops the edges. Applies to both the start-screen video and the running avatar.
</ParamField>

<ParamField query="lang" type="string" default="en">
  UI language of the controls. `de` or `en`.
</ParamField>

<ParamField query="hideTextChat" type="boolean" default="false">
  With `true` the text-chat input is hidden (voice/avatar-only mode).
</ParamField>

<ParamField query="endCall" type="boolean" default="false">
  With `true` the "Hang up" button is shown.
</ParamField>

<Note>
  The microphone stays open for the duration of a conversation. If you want to mute or re-enable it from your page (e.g. your own push-to-talk button), control it via `postMessage` — see [Messaging](#messaging).
</Note>

### Example with multiple options

```html theme={null}
<iframe
  src="https://avaluma.ai/agent/AGENT_ID/embed?border=f5f5f5&lang=en&hideTextChat=true"
  allow="microphone; autoplay"
  style="width:100%;height:600px;border:0"
></iframe>
```

## Sound & volume

Like the kiosk, the embedded agent optimizes playback automatically: on iPad/iPhone audio plays at full volume, on Android it routes to the loud system speaker. A **gear icon** in the top right of the iframe lets you adjust the audio processing and output device — see the kiosk [Sound & volume](/en/agents/kiosk#sound--volume) section for details.

## Messaging

Beyond simply embedding the avatar, you can control it programmatically and react to its behavior. An embedded agent runs inside an `<iframe>`, so your page and the avatar live in different browser contexts. They communicate through the standardized [`window.postMessage`](https://developer.mozilla.org/docs/Web/API/Window/postMessage) API: you **send messages** to the iframe and **receive events** from it.

Every message carries the `avaluma:` prefix so that foreign messages (e.g. from third-party scripts) are safely ignored.

<Warning>
  Messaging only works if your page's origin is listed under the agent's **Allowed origins** (see [Prerequisites](#prerequisites) above). Messages from or to a disallowed origin are rejected by the browser.
</Warning>

### Get a reference to the iframe

Give your embed an `id` so you can address its `contentWindow`:

```html theme={null}
<iframe
  id="avaluma-agent"
  src="https://avaluma.ai/agent/AGENT_ID/embed"
  allow="microphone; autoplay"
  style="width:100%;height:600px;border:0"
></iframe>
```

```js theme={null}
const frame = document.getElementById("avaluma-agent");
const AVALUMA_ORIGIN = "https://avaluma.ai";
```

### Flow (handshake)

The bridge announces itself with a `ready` beacon on every (re)mount. The parent page confirms with `init`, registering its origin as the target for outbound events.

<Steps>
  <Step title="Receive ready">
    As soon as the bridge inside the iframe is ready, it sends `{ type: "avaluma:ready" }`.
  </Step>

  <Step title="Send init back">
    The parent page replies with `{ type: "avaluma:init" }`. Only then does the bridge know the target origin and can deliver events.
  </Step>

  <Step title="Send & receive">
    From now on the page can send `sendMessage` and receives `connected`, `agentMessage`, and `disconnected`.
  </Step>
</Steps>

<Tip>
  Always respond to **every** `ready` beacon with `init`. After "hang up" and reconnecting, the bridge restarts and sends `ready` again — the repeated `init` keeps events flowing seamlessly.
</Tip>

### Send messages to the avatar

Send a message to the iframe's `contentWindow`. Always pass the Avaluma origin as the second argument so the message is only delivered to the avatar:

```js theme={null}
function sendToAvatar(message) {
  frame.contentWindow.postMessage(message, AVALUMA_ORIGIN);
}

// Complete the handshake (respond to every ready beacon)
sendToAvatar({ type: "avaluma:init" });

// Send text to the agent (requires an active conversation)
sendToAvatar({ type: "avaluma:sendMessage", text: "Hello!" });

// Turn the microphone off / back on
sendToAvatar({ type: "avaluma:setMicrophone", enabled: false });
sendToAvatar({ type: "avaluma:setMicrophone", enabled: true });

// End the conversation programmatically (like "hang up")
sendToAvatar({ type: "avaluma:disconnect" });
```

| Message                 | Payload       | Effect                                                                                                                                                                                                                                                                                                                 |
| ----------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `avaluma:init`          | —             | Registers the parent page's origin and completes the handshake. Send on every `ready` beacon.                                                                                                                                                                                                                          |
| `avaluma:sendMessage`   | `{ text }`    | Sends text to the agent — as if the user had typed it into the chat field. Empty messages are ignored.                                                                                                                                                                                                                 |
| `avaluma:setMicrophone` | `{ enabled }` | Turns the visitor's microphone on (`true`) or off (`false`). Turning it off only mutes the microphone but keeps it open at the device level — the microphone indicator stays on, so re-enabling takes effect immediately. Use this to build your own push-to-talk button on your page, for example.                    |
| `avaluma:disconnect`    | —             | Ends the running conversation immediately — as if the visitor had clicked "hang up". The avatar disconnects and the bridge then emits `avaluma:disconnected`. Useful when you close or minimize the iframe yourself, so the session doesn't keep running in the background. Does nothing if no conversation is active. |

<Note>
  A conversation is started by the visitor via "Start conversation" inside the iframe. `avaluma:sendMessage` only takes effect while a conversation is active.
</Note>

### Receive events from the avatar

Listen for `message` events on `window`. **Always check `event.origin`** before trusting the data:

```js theme={null}
window.addEventListener("message", (event) => {
  if (event.origin !== AVALUMA_ORIGIN) return;

  const data = event.data;
  if (!data || typeof data.type !== "string") return;

  switch (data.type) {
    case "avaluma:ready":
      // Bridge is ready → confirm the handshake
      sendToAvatar({ type: "avaluma:init" });
      break;
    case "avaluma:connected":
      console.log("Conversation started");
      break;
    case "avaluma:disconnected":
      console.log("Conversation ended");
      break;
    case "avaluma:agentMessage":
      // Streamed: same id, growing text; final === true at the end
      if (data.final) console.log("Agent:", data.text);
      break;
  }
});
```

| Event                  | Data                  | Meaning                                                                                                        |
| ---------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------- |
| `avaluma:ready`        | —                     | The bridge is ready. Sent on every (re)mount — respond with `init`.                                            |
| `avaluma:connected`    | —                     | A conversation has been established (avatar connected).                                                        |
| `avaluma:disconnected` | —                     | The conversation has ended.                                                                                    |
| `avaluma:agentMessage` | `{ id, text, final }` | A message from the agent. Transcripts are streamed — the same `id` is sent multiple times with growing `text`. |

<Accordion title="How is &#x22;final&#x22; determined?">
  Typed chat messages are immediately `final: true`. Streamed speech transcripts are considered complete once the text stops changing for about 1 second — then exactly one closing `final: true` event with the full text follows. Multiple events with the same `id` belong to the same message; use the `id` to update the display instead of appending. If you only need the final result, filter for `final === true`.
</Accordion>

### Full test example

<Tip>
  The quickest way without writing your own code: the built-in **[embedding test page](https://avaluma.ai/embed-test)** at `avaluma.ai/embed-test`. It embeds an agent from the avaluma.ai domain and visualizes the full bridge (handshake, send text, microphone on/off, event log). Just add `https://avaluma.ai` to the agent's **Allowed origins**.
</Tip>

A standalone HTML page that embeds the avatar, sends text to it, and shows all events in a small log. Serve it locally with a static server (e.g. `python3 -m http.server 8080`) and add its origin (`http://localhost:8080`) to the agent's **Allowed origins**.

<CodeGroup>
  ```html test.html theme={null}
  <!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Avaluma Embed Test</title>
    <style>
      body { margin: 0; min-height: 100vh; font-family: system-ui, sans-serif;
             background: #0f172a; color: #f8fafc; padding: 1rem; box-sizing: border-box; }
      h1 { font-size: 2rem; }
      .row { display: flex; gap: 0.5rem; margin: 0.75rem 0; }
      input { flex: 1; padding: 0.5rem; border-radius: 6px; border: 0; }
      button { padding: 0.5rem 1rem; border-radius: 6px; border: 0;
               background: #554998; color: #fff; cursor: pointer; }
      #log { background: #1e293b; border-radius: 8px; padding: 0.75rem; height: 140px;
             overflow: auto; font-family: monospace; font-size: 12px; white-space: pre-wrap; }
      iframe { width: 100%; height: 600px; border: 0; margin-top: 1rem; border-radius: 8px; }
    </style>
  </head>
  <body>
    <h1>Avaluma Embed Test</h1>

    <div class="row">
      <input id="text" type="text" placeholder="Message to the avatar…" />
      <button id="send">Send to avatar</button>
    </div>
    <div id="log"></div>

    <iframe
      id="avatar"
      src="https://avaluma.ai/agent/AGENT_ID/embed"
      allow="microphone; autoplay"
    ></iframe>

    <script>
      const AVALUMA_ORIGIN = "https://avaluma.ai";
      const iframe = document.getElementById("avatar");
      const logEl = document.getElementById("log");
      const input = document.getElementById("text");

      function log(msg) {
        logEl.textContent += msg + "\n";
        logEl.scrollTop = logEl.scrollHeight;
      }

      function sendToAvatar(message) {
        iframe.contentWindow.postMessage(message, AVALUMA_ORIGIN);
      }

      // Receive the iframe's events. The bridge sends a ready beacon on every
      // (re)mount; we register our origin via init in response.
      window.addEventListener("message", (event) => {
        if (event.origin !== AVALUMA_ORIGIN) return;
        const data = event.data;
        if (!data || typeof data.type !== "string" || !data.type.startsWith("avaluma:")) return;
        log("← " + JSON.stringify(data));

        if (data.type === "avaluma:ready") {
          log("→ init (register origin)");
          sendToAvatar({ type: "avaluma:init" });
        }

        // Only act on the final agent reply:
        if (data.type === "avaluma:agentMessage" && data.final) {
          // e.g. write into your own UI here:
          // showAgentReply(data.text);
        }
      });

      // Send text to the agent (conversation must be started inside the iframe).
      document.getElementById("send").addEventListener("click", () => {
        const text = input.value.trim();
        if (!text) return;
        log("→ sendMessage: " + text);
        sendToAvatar({ type: "avaluma:sendMessage", text });
        input.value = "";
      });
      input.addEventListener("keydown", (e) => {
        if (e.key === "Enter") document.getElementById("send").click();
      });
    </script>
  </body>
  </html>
  ```
</CodeGroup>

<Warning>
  Replace `AGENT_ID` with your agent's UUID. For sending to work, the test page's origin must be listed under the agent's **Allowed origins**.
</Warning>

### Security checklist

<Steps>
  <Step title="Always specify the target origin">
    Never use `"*"` as the target origin in `postMessage`. Pass `https://avaluma.ai` so messages can't reach other frames.
  </Step>

  <Step title="Always verify the source origin">
    In your `message` listener, reject every event whose `event.origin` is not `https://avaluma.ai`.
  </Step>

  <Step title="Keep allowed origins tight">
    Under **Allowed origins** in the Dashboard, list only the origins that actually host the avatar.
  </Step>
</Steps>
