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.
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.
-
Configure Allowed websites with the origins that will contain the iframe. See Launch options.
-
Add the iframe. Replace
YOUR_HANDLEandYOUR_CLUSTERwith your AI Agent’s values.The assets service redirects versionless URLs to the current SDK build.
If your page uses a Content Security Policy, allow
https://messaging-assets.ada.supportinframe-src. Keepallow="clipboard-write; microphone; autoplay"for the permissions the chat needs for copying and multimedia features. Do not add asandboxattribute. 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. -
Install the host shim. Its module has no runtime imports or network loads.
-
Create the client after the iframe element exists. Set
origintohttps://messaging-assets.ada.support, without a path or trailing slash. Set the requiredhandleandclusteroptions to the same values used in the iframe URL. -
Subscribe inside
onConnect(signal), then callawait chat.start({...}). Useada:readyto detect readiness.startresolves when SDK startup returns; it does not wait for readiness.
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 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:
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. 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:
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:
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.
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, Settings, and 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.
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 CustomEvents 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.
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
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 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.
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:
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.
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.
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, andtriggerPlaybookshare at most 32 queued or pending requests. - The shim rejects the 33rd such request with
limit_exceededbefore 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.
startreturns when the SDK start call returns, before readiness, so it uses 30 seconds. Subscribe toada:readyfor readiness.
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.
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.
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.