Getting started

This guide embeds an AI Agent on a web page with the Messaging Web SDK. Pick one of three paths: a module script, the npm package, or an existing Chat SDK script tag that keeps working unchanged.

You need your AI Agent’s handle. If you are not sure of your handle, ask your Ada team.

For new installations, the module script and the npm package are the recommended paths. The window.adaEmbed and window.adaSettings globals remain fully supported for existing installs, but are deprecated for new installations. See Security best practices for the reasoning and for guidance on scoping your integration code.

Embed with a module script

Import the hosted SDK entry, create the adaEmbed interface, and start the AI Agent.

<script type="module">
import { createAdaEmbedInterface } from "https://messaging-assets.ada.support/sdk.js";
const adaEmbed = createAdaEmbedInterface();
await adaEmbed.start({
handle: "<your-handle>",
});
</script>

You should now see a chat button in the bottom right corner of the page. Click the button to open and close the chat drawer.

The adaEmbed reference stays private to the module. Do not assign it to window; see Keep the client in a private scope.

Notes:

  • handle is required. Most websites need only handle.
  • Set cluster or domain only if Ada gives you a non-default regional configuration.
  • Pass parentElement to render the conversation inline inside your own container. Omit it for the floating launcher and drawer. In parentElement mode, toggle() is not supported.

The SDK supports a rich set of settings, actions, and events. For example, you can set the AI Agent language, pass end-user context through metaFields, or start an identified session with identityToken. The SDK API reference covers all of them.

Install from npm

Use @ada-cx/messaging-sdk when your site is built with a bundler.

npm install @ada-cx/messaging-sdk

@ada-cx/messaging-sdk is live on npm. Install the latest version.

The package carries TypeScript types, test mocks, and a thin loader. It never contains the SDK runtime. The loader imports the runtime from Ada’s CDN at page load, so every page receives the current rollout version. The npm version never pins the runtime. This is the same model as @stripe/stripe-js.

Quick start

Call loadAdaMessaging with your handle. It loads the runtime, creates the interface, starts the AI Agent, and returns the interface.

import { loadAdaMessaging } from "@ada-cx/messaging-sdk";
// Module scope: your code holds the only reference.
const ada = await loadAdaMessaging({ handle: "<your-handle>" });

loadAdaMessaging never assigns the interface to window or any other global. Keep the returned reference in module scope, where only your own code can reach it. See Security best practices.

Use loadMessagingSdk when you want the runtime module without starting the AI Agent, for example to preload it before the page needs the widget:

import { loadMessagingSdk } from "@ada-cx/messaging-sdk";
const sdkModule = await loadMessagingSdk();

loadMessagingSdk memoizes the in-flight import, so concurrent calls share one load. Both loaders reject a failed load with a MessagingSdkLoadError and permit a retry:

import { MessagingSdkLoadError } from "@ada-cx/messaging-sdk";
try {
await loadAdaMessaging({ handle: "<your-handle>" });
} catch (error) {
if (error instanceof MessagingSdkLoadError) {
console.error(error.code); // "invalid_cdn_base" | "invalid_build_sha" | "unstamped_build_sha" | "sdk_import_failed" | "sdk_module_invalid"
}
}

One interface per page

Only one messaging interface can run on a page, and loadAdaMessaging enforces this for you. Repeated calls return the same memoized promise and resolve to the same started interface. The first call’s settings win, and later calls never start a second widget. The call is therefore safe in code that runs more than once, such as React Strict Mode development effects. You do not need to cache the promise yourself. A failed load or start clears the memo, so a later call can retry.

Control the widget through the returned interface. stop() tears the widget down, and a later start(settings) on the same interface starts it again. A loadAdaMessaging() call after stop() returns the same stopped interface. It does not restart the widget.

If you create interfaces yourself with createAdaEmbedInterface, only one can start. A second interface’s start() rejects with the AdaEmbedError code start_owner_conflict.

Use the types

Import any public type directly. Type imports add zero runtime code.

import type {
AdaMessagingInterface,
AdaMessagingSettings,
AdaMessagingStartOptions,
PublicMessage,
ResetParams,
} from "@ada-cx/messaging-sdk";

AdaMessagingInterface is the interface loadAdaMessaging resolves. The AdaEmbedInterface name remains available as its legacy alias.

Runtime symbols such as AdaMessagingClient and createAdaEmbedInterface are exported as types only. Get their implementations from loadMessagingSdk(). createAdaEmbedInterface is the legacy window.adaEmbed compat facade; new integrations use loadAdaMessaging.

Test without the CDN

The ./testing entry provides an in-memory mock of the messaging interface, so your unit tests never touch the network.

import { createMockAdaMessaging } from "@ada-cx/messaging-sdk/testing";
const ada = createMockAdaMessaging();
await ada.sendMessage("hello");
expect(ada.calls).toContainEqual({
method: "sendMessage",
args: ["hello"],
});

The mock records every call in calls, and $emit(key, data) simulates SDK events for your subscribers. Pass overrides to replace any method with your own spy.

Bundler note

The loader imports the runtime with a fully dynamic URL. The import is pre-annotated with /* webpackIgnore: true */ and /* @vite-ignore */, so webpack and Vite leave it for the browser to resolve.

Do not copy the CDN import into your own code without those annotations. Without them, the bundler tries to resolve the https: URL at build time and fails, or rewrites the import so it never reaches Ada’s CDN. If your bundler still rewrites dynamic imports, configure it to ignore imports of https: URLs.

Migrate from the Chat SDK web script

If your site already loads Ada with the Chat SDK script tag, keep it. No code change is required.

<script
id="__ada"
data-handle="<your-handle>"
src="https://static.ada.support/embed2.js">
</script>

The loader keeps the window.adaEmbed global, and Ada switches the runtime behind it per handle. window.adaSettings remains fully supported, and legacy callbacks such as adaReadyCallback, chatterTokenCallback, and conversationEndCallback keep working.

The globals stay supported for this path indefinitely. They are deprecated only for new installations, which should use the module script or the npm package. See Security best practices.

If you replace the legacy script tag with the npm loader, update the page’s CSP. Add https://messaging-assets.ada.support to script-src, connect-src, and frame-src. Alternatively, reuse your existing static.ada.support allowance with cdnBase: "https://static.ada.support/messaging/". Allow the selected host in all three directives.

For new code, prefer event subscriptions over legacy callbacks:

await window.adaEmbed.subscribeEvent("ada:end_conversation", (event) => {
console.log("Conversation ended", event);
});

The transcript-bearing events ada:message:sent and ada:message:received are exact-subscription only, and they require the enableProgrammaticControl setting. They are not delivered through subscribeAll().

Migrate from the embed2 npm package

If your build installs @ada-support/embed2, swap the dependency. This is the one integration path that needs a code change.

npm uninstall @ada-support/embed2
npm install @ada-cx/messaging-sdk

The old package bundles the Ada runtime into your build. The new package does not. It carries types, test mocks, and a loader that imports the runtime from Ada’s CDN at page load. Your users get fixes without a rebuild on your side.

Update the import

@ada-support/embed2 exports a ready-made interface as its default export. You then call start.

// Before
import adaEmbed from "@ada-support/embed2";
await adaEmbed.start({ handle: "<your-handle>" });

@ada-cx/messaging-sdk exports loadAdaMessaging. One call loads the runtime, starts the AI Agent, and returns the interface.

// After
import { loadAdaMessaging } from "@ada-cx/messaging-sdk";
const ada = await loadAdaMessaging({ handle: "<your-handle>" });

Hold the returned interface in module scope. Call your existing actions on it. See the SDK API Reference for the current surface.

Add the CSP directives

The runtime now loads from Ada’s CDN, so your Content Security Policy must allow it. Add the asset-host directives and your AI Agent’s API host, listed in Content Security Policy before you deploy. A missing directive blocks the widget silently.

What carries over

Your AI Agent configuration carries over unchanged. You do not reconfigure the Agent.

window.adaSettings keeps working if your page sets it. Ada honors it for this path. Settings you pass to loadAdaMessaging take precedence.

Validate a specific runtime version

The runtime normally resolves from Ada’s rollout at page load. To validate a specific build on a staging page, append the ada-messaging-version query parameter to the page URL:

https://staging.example.com/checkout?ada-messaging-version=<40-character-sha>

The value is a 40-character hex git SHA of a deployed build. Ada serves each build under an immutable SHA root. The SHA your page loaded is therefore visible in the resolved runtime URL: <asset-host>/<sha>/sdk/index.js. Read it from the network panel or from the loaded module URL.

Use it for regression testing. Record the SHA a page loaded while it worked. Pin that SHA later to compare behavior against the current rollout.

The pin bypasses the rollout while the parameter is present on the page URL. An invalid or unavailable value falls back to the stable rollout.

The SDK reads this parameter from the page URL. Your page cannot disable it. A crafted link can therefore load an older deployed build for any visitor, which can predate a security fix. The value is validated to a deployed Ada build, so this is a downgrade surface and not an injection surface. Treat the parameter as a debugging affordance rather than a production control.

To point the npm loader at a different asset host, pass cdnBase:

await loadAdaMessaging(
{ handle: "<your-handle>" },
{ cdnBase: "<asset-host>" },
);

The preferred production root is https://messaging-assets.ada.support/. For production pages whose CSP already allows static.ada.support, use cdnBase: "https://static.ada.support/messaging/". Without cdnBase, the npm loader automatically retries once from the static root when the default import fails, including CSP and network failures. The retry preserves pinBuildSha and its <sha>/sdk/index.js path. If the default bootstrap rejects after manifest or runtime failures, it evicts its shared window.__AdaMessagingSdkBootstrap promise. The static bootstrap can then resolve independently. If resolution is pending or successful, bootstrap copies share the promise.

An explicit cdnBase disables automatic fallback. Module-invalid and validation errors retain their original codes and messages. If both imports fail, the loader rejects with sdk_import_failed and names both URLs. After a successful fallback, the loader logs one warning per page, without debug logging. The warning includes the original error as its second console argument. A TypeError from the default import produces the fetch-failure warning:

[Ada] Loaded the Messaging SDK from https://static.ada.support/messaging/ because the default root https://messaging-assets.ada.support/ was blocked or unavailable. Add https://messaging-assets.ada.support to script-src, connect-src and frame-src, or pass cdnBase: "https://static.ada.support/messaging/".

For a CSP block, update your host’s policy as shown in the fetch-failure warning.

Other errors produce the evaluation-failure warning:

[Ada] Loaded the Messaging SDK from https://static.ada.support/messaging/ because the module at https://messaging-assets.ada.support/sdk.js failed to evaluate (Error: boom). This is an Ada-side problem, not your host's CSP. Report it to Ada.

For an evaluation-failure warning, report the original error to Ada. Network failures also produce the fetch-failure warning. A module that throws TypeError during evaluation also produces the fetch-failure warning, so the classification does not prove a CSP block.

Successful loads from the requested root produce no fallback warning. For staging validation, Ada can provide another asset root. The cdnBase value must name an Ada-operated host. Ada does not support mirroring the runtime or serving it from your own host. See Why the SDK loads the current build.

Pin the CDN build

Pinning is not recommended for production. A pinned runtime misses Ada’s fixes and rollouts. A pinned runtime can also predate later server contract changes and stop working.

The npm package exports CDN_BUILD_SHA. The value is the git commit SHA of the Ada commit your installed npm version was published from. Ada deploys each commit’s runtime under an immutable SHA root on the CDN, so the value names the CDN build associated with your npm version.

Pass the SHA as the pinBuildSha loader option. The loader then skips the rollout and imports that exact build:

import { CDN_BUILD_SHA, isCdnBuildShaStamped, loadAdaMessaging } from "@ada-cx/messaging-sdk";
if (isCdnBuildShaStamped()) {
await loadAdaMessaging({ handle: "<your-handle>" }, { pinBuildSha: CDN_BUILD_SHA });
}

Use a pin only to debug an issue, to validate a staged build, or to reproduce a report against a known runtime. The option accepts any full 40-character hex git SHA of a deployed Ada build. Only installed npm packages carry a real CDN_BUILD_SHA. In a local checkout the value is a 40-zero placeholder, isCdnBuildShaStamped() returns false, and the loader rejects the placeholder with the unstamped_build_sha error code. In the first minutes after an npm release, a pin can fail with sdk_import_failed until the matching CDN deploy completes. pinBuildSha composes with cdnBase for staged validation.

Content Security Policy

If your site enforces a CSP, allow the Ada asset host in three directives. Also allow your AI Agent’s API host in connect-src:

DirectiveValueWhy
script-srcasset hostLoads sdk.js, which then imports the versioned SDK module
connect-srcasset hostsdk.js fetches the rollout manifest to resolve the runtime version
connect-srcAI Agent API hostThe SDK clears the unread badge and reads notification status from your AI Agent’s API host
frame-src (or child-src)asset hostThe SDK mounts its iframes from the asset host

Current builds position the frames with property writes rather than a style attribute, so a strict style-src does not stop the widget rendering. Builds from before this change set a style attribute instead, which a strict style-src blocks, leaving the frames mispositioned. That matters only if you pin an older build with the options below.

A frame-src limited to the asset host does block one thing: the SDK’s own outage panel, which it shows if the chat never becomes usable. The panel is a data: document, so your policy blocks it and your end user sees an empty frame. Set showFallbackOnTimeout: false and render your own outage state instead. An exact subscribeEvent("ada:chat_frame_timeout", ...) subscription also suppresses the panel. A subscribeAll() listener does not: it receives the event but still gets the panel. Neither option widens your policy.

Printing an attachment adds nothing to this list. When an end user prints an image, the widget opens a separate page that Ada hosts. The image and the print styles load under that page’s own policy, not yours.

The asset host depends on how the SDK loads:

  • Module script or default npm loader: https://messaging-assets.ada.support
  • npm loader fallback or explicit static cdnBase: https://static.ada.support
  • Chat SDK script tag (embed2.js): https://static.ada.support

The recommended allow-list uses https://messaging-assets.ada.support in all three directives. During a migration, allow both production hosts or set cdnBase: "https://static.ada.support/messaging/" to reuse the static root. When CSP blocks the default bootstrap, manifest, or runtime, the npm fallback can load the SDK from the static root. This fallback requires your policy to allow https://static.ada.support in all three directives.

Content-Security-Policy:
script-src 'self' https://messaging-assets.ada.support;
connect-src 'self' https://messaging-assets.ada.support https://<your-ai-agent-api-host>;
frame-src 'self' https://messaging-assets.ada.support;

Your AI Agent’s API host depends on your cluster, not on your handle alone. On the default cluster it is https://<your-handle>.ada.support. On another cluster it takes that cluster’s domain, such as https://<your-handle>.eu.ada.support. If you set the endpoint setting, allow that origin instead. Confirm the value in your browser’s network panel before you enforce the policy.

This policy allows the SDK module. It does not allow an inline <script> block, so the inline example in Embed with a module script is blocked under it. Move that bootstrap into a module file your page serves. Or give the inline block a nonce or hash that your policy lists.

The conversation runtime’s own API and WebSocket traffic runs inside a cross-origin iframe with its own CSP, so your page’s policy does not govern it. The SDK in your page does make a small number of its own API calls, which your policy does govern. They fail silently, with no error on your page. Without your AI Agent’s API host in connect-src the SDK cannot read notification status. A returning end user’s live agent session is then not resumed, and no unread badge appears.

If your policy has no frame-src, framing falls back to child-src and then default-src. A restrictive default-src with neither directive blocks the widget without a frame-src violation to point at.

Roll out CSP changes with Content-Security-Policy-Report-Only first. It surfaces every violation without breaking the page.

Debug a blocked widget

The browser console names the violated directive and the blocked URL. The npm loader always warns once after a successful static fallback. Successful loads from the requested root produce no fallback warning. To enable additional SDK diagnostics, set window.adaSettings.debug = true or run localStorage.setItem("ada.debug", "1").