> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.ada.cx/llms.txt.

# Iframe-only embed

Use iframe-only embed when your page must not load a script from Ada.
Your code controls the Messaging SDK inside an iframe through a `MessagePort`.
Install the host shim from npm. Choose an inline chat or a floating widget.

The embed uses the SDK's methods, settings, and events. Only the communication medium changes, with the [differences below](#differences-from-the-sdk).

In inline mode, a hidden iframe creates no conversation until it becomes visible in the top-level viewport or the end user interacts with chat. A session-dependent SDK call also counts as interaction. If the browser lacks `IntersectionObserver`, startup uses document visibility instead.

## Choose a mode

Use `mode: "inline"` to place and size your own iframe. Chat fills that iframe.
Use `mode: "floating"` for the standard launcher, greeting cards, campaigns, drawer, and optional mask. The shim creates and clips a transparent iframe. Clicks outside the widget reach your page. Floating startup follows the standard SDK behavior.

## Inline setup

You need your AI Agent's handle and cluster. Ask your Ada team for these values.

1. Configure **Allowed websites** with the origins that will contain the iframe.
   See [Launch options](/docs/channels/messaging/messaging-configuration/launch-options).

2. Add the iframe. Replace `YOUR_HANDLE` and `YOUR_CLUSTER` with your AI Agent's values.

   ```html
   <iframe
     id="ada-chat"
     title="Chat with support"
     src="https://messaging-assets.ada.support/sdk/embed.html?ada_handle=YOUR_HANDLE&amp;ada_cluster=YOUR_CLUSTER"
     referrerpolicy="origin"
     allow="clipboard-write; microphone; autoplay"
     width="400"
     height="640"
   ></iframe>
   ```

   The assets service redirects versionless URLs to the current SDK build.

   If your page uses a Content Security Policy, allow `https://messaging-assets.ada.support` in `frame-src`.
   Keep `allow="clipboard-write; microphone; autoplay"` for the permissions the chat needs for copying and multimedia features.
   Do not add a `sandbox` attribute. The SDK applies the required restrictions to its inner frames.
   Do not delegate camera, fullscreen, or local-network access. A loopback custom app is unsupported in embed mode.

3. Install the host shim. Its module has no runtime imports or network loads.

   ```sh
   npm install @ada-cx/messaging-embed
   ```

4. Create the client after the iframe element exists. Set `origin` to `https://messaging-assets.ada.support`, without a path or trailing slash.
   Set the required `handle` and `cluster` options to the same values used in the iframe URL.

5. Subscribe inside `onConnect(signal)`, then call `await chat.start({...})`.
   Use `ada:ready` to detect readiness. `start` resolves when SDK startup returns; it does not wait for readiness.

```ts
import { createAdaEmbedClient } from "@ada-cx/messaging-embed";

const iframe = document.querySelector<HTMLIFrameElement>("#ada-chat");
if (!iframe) throw new Error("Chat iframe not found.");

const chat = createAdaEmbedClient({
  iframe,
  mode: "inline",
  origin: "https://messaging-assets.ada.support",
  handle: "YOUR_HANDLE",
  cluster: "YOUR_CLUSTER",
  onConnect: async (signal) => {
    try {
      await chat.subscribeEvent("ada:ready", () => {
        if (!signal.aborted) iframe.dataset.chatState = "ready";
      });
      if (signal.aborted) return;
      await chat.start({
        language: "en",
        metaFields: { user_id: "user-123" },
        sensitiveMetaFields: { email: "alex@example.com", name: "Alex" },
      });
    } catch {
      if (!signal.aborted) iframe.dataset.chatState = "error";
    }
  },
});

function removeSupportChat() {
  chat.destroy();
  iframe.remove();
}
```

The helper sends your page URL before startup and tracks navigation automatically.
The SDK keeps the reported URL across `stop` and restart. Each new connection sends the host URL again.
Set `trackPageUrl: false` in the helper options to disable navigation tracking.

The helper queues requests until it connects and confirms the iframe document's identity.
It retries connections for up to 30 seconds. See [Error codes](#error-codes) if connection fails.
`onConnect` runs after each connection, including after an iframe reload.
Register subscriptions and start the SDK inside that callback. Its signal aborts when the connection ends.

`chat.disconnect()` and `chat.destroy()` end the shim client. Both close ports, reject pending requests, close notifications, and remove listeners.
They send `disconnect` to destroy the SDK and release its subscriptions. Your inline iframe remains in place.
Create a new client to reconnect. If the document has ended, the shim reloads the iframe once at its own embed URL.
It connects after the fresh document loads. A second `ended` rejection fails with `ended`; create a new iframe in that case.
Other connection rejections or timeout also end the client. Create a new client to reconnect after a terminal error.
Use `await chat.stop()` followed by `await chat.start()` for an SDK restart through the same client.
An SDK `destroy` request sent directly over the protocol permits a later SDK `start`; it does not end the shim.

## Floating setup

Configure **Allowed websites** with the same origins that you use for the standard SDK.
Allow `https://messaging-assets.ada.support` in your page's `frame-src` policy.
Install the shim and call it after `<body>` exists:

```sh
npm install @ada-cx/messaging-embed
```

```ts
import { createAdaEmbedClient } from "@ada-cx/messaging-embed";

const chat = createAdaEmbedClient({
  mode: "floating",
  handle: "YOUR_HANDLE",
  cluster: "YOUR_CLUSTER",
  title: "Ada chat",
  zIndex: 2147483000,
  onConnect: async (signal) => {
    if (signal.aborted) return;
    await chat.start({
      align: "right",
      hideMask: false,
      language: "en",
      toggleCallback: (isOpen) => {
        document.body.dataset.chatOpen = String(isOpen);
      },
    });
  },
});
```

The shim appends one fixed iframe to `<body>`. It covers your layout viewport and clips itself to the visible widget regions.
It sets `allow="clipboard-write; microphone; autoplay"`, `lang="en"`, and no `sandbox`.
An empty clip hides the iframe from pointer input, keyboard focus, and assistive technology.
Use `title` to change its accessible name. The default is `Ada chat`.

`zIndex` controls the whole widget's position in your page's stacking order. Its default is `2147483000`.
Give host overlays a higher stacking order when they must cover chat. A modal in the browser's top layer remains above this iframe.
Do not set `transform`, `filter`, `perspective`, or containing-block `contain` values on `<html>` or `<body>`.
These styles can move or resize a fixed iframe. The shim logs one warning when it detects them.

If your host page is itself framed, the widget covers that frame's viewport, not the outer page.
Every ancestor must pass [the controller rules](#security-model). Browsers without `location.ancestorOrigins`, including Firefox, support only a host at the top level.

Use one floating embed per page. Do not load the normal SDK on that page.
A second floating client, or a floating client created after an active normal SDK, throws `conflict`.
`disconnect()`, `destroy()`, a connection rejection, or a connection timeout remove the created iframe and release ownership.
Create a new client after any of these terminal states. `stop()` and a later `start()` reuse the client.

The drawer follows the SDK's keyboard behavior. It is not modal: Tab can reach host controls while the mask shows.
Calling `open()` focuses the outer iframe before the request. Closing returns focus to the launcher.
A live embed can cover any part of the viewport while it sends heartbeats, including all host controls.
There are no host-enforced coverage bounds. This accepted residual matches the authority of a script on the page.

If layout confirmation fails, the requested surface stays hidden. The shim releases every exposed region and scroll lock after six visible seconds without a heartbeat, including a launcher-sized region.

## Navigation and notifications

The shim announces `navigate.v1` and `notifications.v1` by default.
Inline clients can set `capabilities: []` to disable both, or pass a subset of these names.
Floating clients also announce `layout.v1` by default and must keep it. For layout without navigation or notifications, use `capabilities: ["layout.v1"]`.

With `navigate.v1`, same-window links navigate your page through a hidden link with `rel="noopener noreferrer"`.
This `host.navigate` path sends no referrer.
The SDK validates destinations against its safe protocols and the AI Agent's configured `allowedProtocols`.
The shim requires an absolute URL. By default, it allows only `http:`, `https:`, `mailto:`, and `tel:`.
Pass `navigation` to `createAdaEmbedClient` to control this policy:

```ts
const navigation = {
  allowedProtocols: ["https:", "mailto:", "myapp:"],
  beforeNavigate: (url: string) => {
    const destination = new URL(url);
    return destination.protocol !== "https:" || destination.origin === location.origin;
  },
};
```

`allowedProtocols` replaces the default list. Include a colon in each protocol name. An empty list refuses every destination.
Default HTTP(S) navigation needs no user gesture, matching the SDK. This is an accepted residual.
Restrict destinations with `navigation.beforeNavigate` and schemes with `navigation.allowedProtocols`.

`beforeNavigate` receives the absolute URL. Returning `false` or throwing refuses navigation.
Executable and local-resource schemes remain blocked even if you list them, including `javascript:`.
Custom schemes also need the AI Agent's startup `allowedProtocols` setting before the SDK relays them.
A refusal drops the URL and reports `navigation_refused` through `ada:error`. It does not open a new tab.

A compromised embed can still navigate within your allowed set, as a script on your page can.
The default allows any HTTP(S) destination. Use `beforeNavigate` when you need to restrict destinations.
Without this capability, same-window targets open in a new tab.

New-tab link cards and CTAs use `noopener,noreferrer` and send no referrer.
Message-text links use `rel="noopener"` and `referrerPolicy="origin"`.
Article links open with `noopener` only.
Message-text links and article links send Ada's origin as the referrer.
They open from the Ada frame and do not use `host.navigate`.

Call `requestNotifications()` from a user gesture on your page:

```ts
document.querySelector("#enable-notifications")?.addEventListener("click", async () => {
  await chat.requestNotifications();
});
```

The shim reports host permission on connection and when it changes. Chat offers no enable action before permission is granted.
Notifications appear only with granted permission while the page is hidden or chat is closed. At most 20 remain open.
A click focuses your page, closes the notification, opens chat, and emits `ada:web_notification:click` with conversation and message IDs.
Notification text reaches the shim only. It never reaches SDK event subscribers or relayed window events.
The shim forwards service-worker messages with `type: "ada:web_notification:click"` to [handleNotification](/messaging/web/sdk-api-reference#handlenotification).
Without `notifications.v1`, notifications remain unsupported.
A direct SDK `requestNotifications` protocol request returns current permission without prompting.

## How it maps to the SDK

Use the SDK's [Actions](/messaging/web/sdk-api-reference#actions), [Settings](/messaging/web/sdk-api-reference#settings), and [Type signatures](/messaging/web/sdk-api-reference#type-signatures) as the API reference.
Invoke each available SDK method as `await chat.<method>(...)`, with the SDK's arguments and results.
Subscribe through `await chat.subscribeEvent(eventName, callback)` using the SDK's [events and payloads](/messaging/web/sdk-api-reference#subscribeevent).
Use `await chat.unsubscribeEvent(id)` to remove a subscription.

Callbacks run on your page with the SDK's behavior and permission checks.
For `zdChatterAuthCallback`, return a token, `null`, or a promise for either value instead of accepting a response callback.
Register subscriptions before `start`. Wait for `ada:ready` before using methods that require readiness.

### Window events

The helper re-dispatches these two public events as `CustomEvent`s on the host `window`.
Read them with `window.addEventListener(name, ...)`. The event's `detail` contains the payload.
Window events are not relayed while the helper is disconnected or checking document identity after a load.
Internal integration commands and results stay inside the embed.
The bridge and shim each rebuild event details from the same allowlist, including when the embed sends extra fields.
They read only own data properties on plain objects, without invoking getters. They drop unlisted fields and values with the wrong type.
Unknown string categories become `other`. Identifiers permit 1 to 64 characters from `[A-Za-z0-9_.:-]`.
Queue positions must be finite numbers from 0 through 100000. Invalid identifiers and numbers are omitted.
Message text, agent identity, credentials, and free-form payload objects are never relayed.

| Name                    | Payload                                                                                                                                                            |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ada:error`             | Optional strings: `code`, `type`, `frame`. Required `message`: the fixed category `"embed_error"`. No source message, stack, or URL.                               |
| `ada:integration:event` | Optional strings: `integration`, `event`, `frameId`, `type`, `connectionStatus`. Optional finite numbers: `position`, `queuePosition`. Optional boolean: `resume`. |

Integration state fields come from the internal `payload` and appear directly in the host event's `detail`.
The core receives the full integration payload so handoffs keep their existing behavior.

## Differences from the SDK

| Area                             | Difference                                                                                                                                                                                                                         |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Settings fixed by the iframe URL | `cluster`, `domain`, `endpoint`, `handle`, and `rolloutOverride` cannot be supplied to [start](/messaging/web/sdk-api-reference#start), including after a restart.                                                                 |
| Settings the embed owns          | In inline mode, `align`, `hideMask`, `lazy`, `onAdaEmbedLoaded`, `parentElement`, and `toggleCallback` cannot be supplied to [start](/messaging/web/sdk-api-reference#start). Your page controls the outer iframe.                 |
| Floating settings                | Floating mode accepts `align`, `hideMask`, `branding.launcher`, and the `toggleCallback` slot. It still owns `lazy`, `onAdaEmbedLoaded`, and `parentElement`.                                                                      |
| Launcher branding                | Inline mode rejects `branding.launcher`. Floating mode uses the SDK launcher settings. Other `branding` fields use the SDK's behavior.                                                                                             |
| Browser methods                  | The shim owns permission prompting. See [Navigation and notifications](#navigation-and-notifications).                                                                                                                             |
| Internal methods                 | `acknowledgeSessionMirrorClear`, `deliverSessionMirrorSeed`, and `setTestModeBanner` return [unsupported](#error-codes), even though they appear on the SDK client.                                                                |
| Host context                     | The helper sends the page URL before startup and tracks navigation. Only HTTP(S) URLs on the bound parent origin are accepted. Without a valid URL, `initialURL` stays `null`, `_ada_url` is omitted, and URL triggers do not run. |
| Proactive messages and greetings | Inline mode suppresses proactive surfaces and card greetings. Floating mode renders them as the standard SDK does.                                                                                                                 |
| Custom apps                      | The localhost exception does not apply because the SDK runs on Ada's origin. Ada-origin custom apps remain rejected.                                                                                                               |
| Navigation                       | Same-window links use the host capability and navigation policy. Refused URLs are dropped. Without the capability, links open in a new tab.                                                                                        |
| Storage                          | Ada storage is partitioned by host site. Frequency limits, greeting deduplication, and drawer continuity reset when the host site changes and differ from a normal SDK install on the same site.                                   |
| Host settings                    | `window.adaSettings` and window callbacks are unsupported. Send settings through `start` and use declared callback slots.                                                                                                          |
| Host styles                      | Host CSS for Ada frame IDs does not reach the widget. Size the outer iframe in inline mode. Use SDK settings for floating mode.                                                                                                    |
| Zendesk Chat                     | Zendesk sees the Ada embed document as its referrer.                                                                                                                                                                               |

Rejected settings return `invalid_params`. The SDK defaults remain `privateMode: false` and `crossWindowPersistence: true`.

## Passing user data

Pass plain values through `metaFields` for personalization. Use `sensitiveMetaFields` for sensitive values needed by your configured tools.
Use strings, numbers, or booleans as field values. The SDK discards nested metadata objects and arrays.
`getMetaFields()` returns only plain metadata.

Sensitive meta fields expire after 24 hours. Ada does not store them long-term.
End-user deletion by email does not find a user whose email was sent only through `sensitiveMetaFields`.

Send a stable identifier, such as your own user ID, as a plain `metaFields` value.
Use an identifier that contains no contact information. Keep email, name, and similar data in `sensitiveMetaFields`.

Delete end users with the [Bulk End-User Deletion API](/reference/end-users/bulk-deletion/overview#identifiers) and its `variable_match` selector.
Set `variable` to the identifier's field name and `value` to its stored string value.
The selector matches the latest value exactly. It does not match global or session variables.

Data that your page sets is not proof of identity.
If the AI Agent must trust who the end user is, use an [identity token](/messaging/identity/getting-started).
Mint the token on your backend. Pass it as `identityToken` in `start` or `reset`. Keep tokens out of URLs and logs.

After startup, update data with `await chat.setMetaFields({ user_id: "user-123" })` or `await chat.setSensitiveMetaFields({ email: "alex@example.com" })`.

## Logout and shared devices

Reset the session when an end user signs out or another person uses the device:

```ts
await chat.reset({
  metaFields: {},
  resetChatHistory: true,
  sensitiveMetaFields: {},
});
```

Wait for the reset before applying data for another end user.
The SDK defaults remain `privateMode: false` and `crossWindowPersistence: true`. A reload does not guarantee a new session.
The example relies on the default `allowMetaFieldsInReset: true`.
If you set it to `false`, full resets restore baseline plain metadata instead; review the SDK's [reset rules](/messaging/web/sdk-api-reference#reset).
Pass a fresh identity token to `reset` when switching to an identified session.
A session reset does not replace server-side end-user deletion.

## Error codes

Rejected helper operations provide `code` and `message`. Use `code` to handle failures, rather than matching message text.

| Code                 | Meaning and action                                                                                                                                                      |
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `already_connected`  | Another helper owns this iframe document. Use one helper per iframe; reload the iframe before replacing its owner.                                                      |
| `conflict`           | A floating embed or normal SDK already owns this page. Destroy that client before creating a floating embed.                                                            |
| `connect_timeout`    | Connection was not established or confirmed within 30 seconds. Check the final origin, handle, cluster, and [Allowed websites rules](#security-model), then reload.     |
| `disconnected`       | The connection ended or a reply-bearing callback exceeded its deadline. Restore subscriptions after reconnect; check callback completion.                               |
| `ended`              | The document has ended. The shim reloads an inline iframe once. If the fresh document also returns `ended`, create a new iframe.                                        |
| `internal`           | An internal failure or result serialization failure occurred. Record the code and operation, without sensitive data, and contact support.                               |
| `invalid`            | The connect envelope is invalid. Check its exact v1 fields and limits.                                                                                                  |
| `invalid_params`     | Arguments, settings, wire values, or callback placement are invalid. Check the SDK signature and the differences table.                                                 |
| `limit_exceeded`     | A message or pending-work limit was exceeded. Reduce payload size or concurrent work and release unused subscriptions.                                                  |
| `missing_capability` | The requested mode needs a capability that the host did not announce.                                                                                                   |
| `not_started`        | The SDK client does not exist yet. Use `start` first; event subscriptions and `setPageUrl` may precede it.                                                              |
| `sdk_error`          | The SDK or a host callback failed. Check `sdkCode` for the original `AdaEmbedError.code` and follow the SDK method's requirements.                                      |
| `timeout`            | A posted request exceeded its execution deadline. After a timeout, the outcome is unknown. The operation can still complete. Do not retry side-effecting calls blindly. |
| `unsupported`        | The method is unknown or unavailable through the embed. Check its name.                                                                                                 |
| `unsupported_mode`   | The requested mode is not supported. Use `inline` or `floating` with a compatible embed build.                                                                          |

## Limits

* Use JSON-compatible values: `null`, booleans, finite numbers, strings, arrays, and plain objects. Functions work only in SDK callback positions.
* Each envelope permits 1,048,576 bytes of serialized UTF-8 JSON, depth 32, and 50,000 value nodes.
* Each connection permits 256 callback registrations, 64 pending callback replies, and 64 pending requests.
* The shim counts queued requests within the 64-request limit. `deleteHistory`, `reset`, `selectQuickReply`, `sendMessage`, and `triggerPlaybook` share at most 32 queued or pending requests.
* The shim rejects the 33rd such request with `limit_exceeded` before it takes a slot, leaving 32 slots for other methods.
* A reply or timeout frees the request's slot in both limits.
* The page buffers four connection candidates. Request and callback IDs must be non-empty strings of at most 128 characters.
* The connection deadline is 30 seconds, including waits for document confirmation after a load. Repeated loads do not extend this deadline.
* Execution deadlines start when the shim posts the request to the iframe. Time queued for connection does not consume the execution deadline.
* During document confirmation, the shim buffers up to 64 incoming messages and, separately, 64 callback completions and navigation replies.
* Confirmation of the same document sends buffered completions in order. A replacement document discards them. The shim never invokes a host callback again.
* Request expiry returns `timeout`, frees its slot, and releases callbacks and subscriptions on both sides. It ignores late answers.
* After a timeout, the outcome is unknown. The operation can still complete. Do not retry side-effecting calls blindly.
* `start` returns when the SDK start call returns, before readiness, so it uses 30 seconds. Subscribe to `ada:ready` for readiness.

| Method             | SDK maximum                                                                               | Execution deadline |
| ------------------ | ----------------------------------------------------------------------------------------- | ------------------ |
| `deleteHistory`    | 125 seconds plus 600 seconds of visibility waiting                                        | 730 seconds        |
| `getInfo`          | 35 seconds queued inside the SDK plus 15 seconds for the response                         | 55 seconds         |
| `reset`            | 125 seconds plus 600 seconds of visibility waiting                                        | 730 seconds        |
| `selectQuickReply` | 120 seconds plus 600 seconds of visibility waiting                                        | 725 seconds        |
| `sendMessage`      | 5 seconds for `beforeSend`, then 120 seconds plus 600 seconds of visibility waiting       | 730 seconds        |
| `triggerPlaybook`  | 120 seconds plus 600 seconds of visibility waiting                                        | 725 seconds        |
| Other RPC methods  | Synchronous, immediate, or at most 5 seconds for asynchronous reads and `setComposerText` | 30 seconds         |

The longer deadlines include five seconds beyond the SDK maximum. Visibility waiting allows an operation to wait while its chat is hidden.
`getConversation`, `getMessages`, `getMetaFields`, and `setComposerText` have five-second SDK bounds; `isOpen` resolves immediately.
`requestNotifications` runs on your page and waits for browser permission. The SDK sets no maximum for this wait; the shim adds none.

Over-limit requests fail with `limit_exceeded`. The embed drops invalid or oversized events and callbacks and reports the first drop through `ada:error`.

## Message protocol

Use the helper above unless your team needs its own transport.
The types-only `@ada-cx/messaging-sdk/embed-protocol` export defines the envelope shapes.
Every envelope includes `protocol: "ada-embed"` and `v: 1`.
Only `connect` uses `window.postMessage`, with the exact final iframe origin and one transferred `MessagePort`.
All subsequent messages use that port.

| Kind                      | Purpose                                                                                                                                                                                                                        |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `already_connected`       | Returns the attempt's `nonce` and the `documentId` already owned by another port, then closes the candidate port.                                                                                                              |
| `callback`                | Invokes a registered `callbackId` with `args` and an `invocationId`.                                                                                                                                                           |
| `callback_result`         | Settles a reply-bearing callback by `invocationId`, with `ok` and `result` or `error`.                                                                                                                                         |
| `connect`                 | Starts an attempt with a fresh 128-bit random `nonce`, encoded as 22 base64url characters without padding. Required fields also include `pageUrl`, `mode`, `capabilities`, and `shimVersion`. Invalid URL strings are ignored. |
| `connect_rejected`        | Returns `nonce`, `documentId`, `handle`, `cluster`, `sdkVersion`, and `reason`, then closes the port.                                                                                                                          |
| `connected`               | Confirms `nonce`, `documentId`, `handle`, `cluster`, `sdkVersion`, `mode`, and accepted `capabilities`.                                                                                                                        |
| `disconnect`              | Ends the connection and destroys the SDK. Releases all subscriptions, callbacks, and pending replies.                                                                                                                          |
| `host.escape_listen`      | Sets the current generation's host Escape relay with `gen` and `active`.                                                                                                                                                       |
| `host.key`                | Relays an unprevented host `Escape` key with `gen` and `key`.                                                                                                                                                                  |
| `host.navigate`           | Sends a validated absolute `url` and runtime generation `gen` to the shim.                                                                                                                                                     |
| `host.navigate_result`    | Returns `gen`, `url`, and `ok`. A refusal or failure returns `false` and reports an error without opening a new tab.                                                                                                           |
| `host.notification`       | Sends `gen`, `id`, `title`, optional `body` and `icon`, plus `conversation_id` and `message_id`.                                                                                                                               |
| `host.notification_click` | Returns `gen` and `id` for a clicked notification.                                                                                                                                                                             |
| `host.notification_state` | Reports `permission`: `default`, `granted`, `denied`, or `unsupported`.                                                                                                                                                        |
| `host.resume`             | Reports a back-forward cache restore with `gen` so the embed resends its state.                                                                                                                                                |
| `host.scroll_lock`        | Sets the current generation's single scroll lock on both document roots with `gen` and `locked`.                                                                                                                               |
| `host.viewport`           | Reports `rev`, layout width and height, and visual width, height, offsets, and scale.                                                                                                                                          |
| `layout.applied`          | Confirms the current `gen` and `seq` after the clip is set and one animation frame passes.                                                                                                                                     |
| `layout.clip`             | Sends the complete `gen`, `seq`, and `rects` state, with optional `full: true`.                                                                                                                                                |
| `layout.heartbeat`        | Confirms liveness with `gen` every two seconds while any applied clip region is exposed or scroll lock is held.                                                                                                                |
| `layout.reset`            | Reports a local rollback with `gen` after six visible seconds without a heartbeat.                                                                                                                                             |
| `release_callbacks`       | Releases the registrations named in `callbackIds`.                                                                                                                                                                             |
| `release_request`         | Releases an abandoned request by `id` and its registrations by `callbackIds`. Removes matching subscriptions. The operation may still complete.                                                                                |
| `request`                 | Invokes an SDK `method` with positional `args` and a unique pending request `id`.                                                                                                                                              |
| `response`                | Settles a request by `id`, with `ok` and `result` or `error`; void results use `null`.                                                                                                                                         |
| `window_event`            | Relays a window event's `name` and `detail` for dispatch as a host `CustomEvent`.                                                                                                                                              |

Connect accepts exactly the listed fields. Capabilities are the only v1 extension point; unknown capability names are ignored.
Send at most eight capability strings of up to 32 characters. `shimVersion` is a semver string of up to 32 characters.
The known names are `layout.v1`, `navigate.v1`, and `notifications.v1`. Inline mode accepts the last two. Floating mode requires `layout.v1` and can accept all three.
The mode cannot change during the embed document's lifetime.

Check protocol, version, nonce, and the 22-character base64url document ID before interpreting an answer.
Handle `connect_rejected` before success validation. For `connected`, verify handle, cluster, and mode against your options.
After each iframe load, the shim quarantines the connection until a new attempt confirms the current `documentId`.
It buffers at most 64 validated old-port messages and queues unsent requests within the 64-request limit.
It hides the floating layer and restores host scrolling immediately. A same-document answer processes buffered messages in order, then sends unsent requests once.
A new document discards the buffer. Buffer overflow ends the connection with `limit_exceeded`.
The shim never resends a request already sent. It requests fresh layout state after confirmation.
A new document aborts the old signal, rejects held and pending requests with `disconnected`, and releases callbacks before binding.
Keep confirmed ports by document ID until a later load proves them stale.
Replace functions only in declared callback positions with `{ "$adaCallback": "unique-id" }`.
Process callbacks independently of pending requests so callbacks can invoke SDK methods.
The host shim implements connection recovery, callback deadlines, and cleanup.

### Security model

The embed checks **Allowed websites** with these rules. Missing or invalid configuration refuses control.

| Entry or condition                                    | Rule                                                                                                                 |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Empty list                                            | Allows any HTTP or HTTPS page.                                                                                       |
| `*` or `http://*`                                     | Allows any HTTP or HTTPS page.                                                                                       |
| `https://*`                                           | Allows only HTTPS pages, on any port.                                                                                |
| Bare host, such as `example.com`                      | Uses HTTPS. Entries are trimmed and converted to lowercase.                                                          |
| `*.example.com`                                       | Matches subdomains, including nested subdomains, but not the apex `example.com`.                                     |
| Scheme                                                | An HTTPS host entry never allows HTTP. An HTTP host entry also allows HTTPS, subject to the port rule.               |
| Port                                                  | A portless entry matches only the page scheme's default port. An explicit port must match; `host:*` allows any port. |
| Path, query, fragment, credentials, or trailing colon | The embed ignores the entry. A bare trailing slash is allowed.                                                       |
| Non-empty list with no usable entries                 | Allows nothing.                                                                                                      |
| Opaque, `null`, or `file:` parent                     | Never connects, even with an empty list or wildcard.                                                                 |
| Ancestor frames                                       | The parent and every ancestor frame must match.                                                                      |

Without `location.ancestorOrigins`, including Firefox, only a direct child of the top window can connect.
The embed grants no more access than the SDK; keep the client and port private within your module.

## Troubleshooting

| Symptom                                       | Check                                                                                                                                                                         |
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| The frame receives HTTP 403                   | Restricted Agents use the same edge policy as the SDK's core page. The Referer check compares Allowed websites entries verbatim. Use the same entries that work with the SDK. |
| The frame loads but never connects            | Check the controller and ancestor rules above. Config errors and invalid Allowed websites lists refuse control.                                                               |
| Floating chat is misplaced                    | Remove containing-block styles on the document roots and check your host overlay stacking order.                                                                              |
| Notifications do not appear                   | Call the shim permission method from a host user gesture. Permission must be granted, and chat must be closed or the page hidden.                                             |
| The client fails after a timeout or rejection | Create a new shim client. Reload the iframe first if its document still belongs to another connection.                                                                        |