> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.ada.cx/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.ada.cx/_mcp/server.

# iOS SDK reference

Use the iOS SDK [settings](#settings), [events](#events), and [actions](#actions) to customize the behavior of your AI Agent. This page covers the public API of `AdaWebHost` in the `AdaMessaging` framework.

## Settings

Configure `AdaWebHost` with input parameters at initialization. The SDK builds the WebView and its URL inside `init`, so all settings must be set at initialization. To change values later, use the [actions](#actions).

### appScheme

`appScheme: String = ""`

Use this setting to pass the scheme name of the host app. This allows for more robust handling of universal links.

### appUrl

`appUrl: String = ""`

Overrides the app frame URL that the Messaging runtime mounts.

> **Note**
>
> Your handle's **Allowed websites** list controls custom apps. In your dashboard, go to **Channels** > **Messaging**. Before your transition, use **Channels** > **Chat**. Add your app's origin. If the list does not allow the origin, the runtime drops the URL with a logged warning and mounts the default app.

The value must be an absolute `https:` URL. The runtime drops invalid values with a logged warning and mounts the default app instead. If the custom app fails to load or complete its handshake, the runtime falls back to the default app.

For local development against a simulator, expose your development server through an `https:` tunnel, and add the tunnel origin to Allowed websites. The dashboard accepts only `https://` entries. On the production and pre-production environments, the WebView's host page is an Ada asset origin, not a loopback page. The web SDK's implicit loopback allowance therefore does not apply there.

The exception is the local asset-server environment ([`.local`](#adaenvironment)), used for SDK development. Its WebView host page is `https://localhost:4900`, a loopback origin, so the implicit loopback allowance applies and a loopback `appUrl` such as `http://localhost:5173` needs no entry.

The value is delivered to the web runtime through an injected configuration object, not through the WebView URL. It applies only when [`webSdk`](#websdk) is `.messaging`. The legacy runtime ignores it.

### cluster

`cluster: String = ""`

Specifies the cluster your AI Agent runs on.
**Set this only if your Agent is hosted on a non-default cluster** (for example, `us2`, `maple`, `eu`).
If the Agent is on the default cluster, leave this unset.

> **Info**
>
> Do not change this value unless instructed by your Ada team.

### deviceToken

`deviceToken: String = ""`

Pass the APNs device token at startup. See [`setDeviceToken`](#setdevicetoken) for storage and push notification guidance.

| Runtime   | Device token registration                                                                                |
| --------- | -------------------------------------------------------------------------------------------------------- |
| Legacy    | At initialization, before the first message.                                                             |
| Messaging | Once the runtime is ready. The iOS SDK excludes it from the initialization payload. Android includes it. |

The device token never appears in a URL. To set or rotate it after initialization, use [`setDeviceToken`](#setdevicetoken).

### domain

`domain: String = ""`

The Ada domain your AI Agent is served from.

> **Info**
>
> Do not set this value unless instructed by your Ada team.

### embedVersion

`embedVersion: String = ""`

Pins the legacy runtime's host page to a specific embed build. Used for pre-release verification with your Ada team. Leave blank for the stable rollout. Ignored when [`webSdk`](#websdk) is not `.legacy`.

### enableProgrammaticControl

`enableProgrammaticControl: Bool = false`

Opts this host into the Messaging runtime's programmatic-control API. When set to `true`, your app can:

* Call [`selectQuickReply`](#selectquickreply), [`sendMessage`](#sendmessage) and [`triggerPlaybook`](#triggerplaybook).
* Receive the `ada:message:sent` and `ada:message:received` [events](#events) in `eventCallbacks`.

While the flag is `false` (the default), `selectQuickReply`, `sendMessage` and `triggerPlaybook` are rejected with `ProgrammaticControlNotEnabled` and those events are not delivered.

> **Warning**
>
> With this flag on, full message bodies flow into your app code through `eventCallbacks`, including any PII your end users type. Do not write message bodies to logs, analytics, or crash reports. Have your product and security teams accept that exposure explicitly before turning this on.

This setting applies only when [`webSdk`](#websdk) is `.messaging`. The legacy runtime ignores it.

### environment

`environment: AdaEnvironment? = nil`

The deployment environment that hosts the WebView entry page and SDK assets. See [`AdaEnvironment`](#adaenvironment). Most production apps set `.production`.

> **Note**
>
> The Messaging runtime requires an explicit `environment`. If you set `webSdk: .messaging` but leave `environment` unset, the host falls back to the legacy path.

### eventCallbacks

`eventCallbacks: [String: (_ event: [String: Any]) -> Void]? = nil`

A dictionary of callbacks keyed by event name. See [Events](#events) for the delivery contract and the list of event keys.

**`Swift`**

```swift Swift
let adaWebHost = AdaWebHost(
    handle: "my-agent",
    eventCallbacks: [
        "ada:message:received": { event in
            // A message arrived from the AI Agent or a human agent.
        },
        "*": { event in
            // Every event, keyed by event["event_name"].
        }
    ],
    environment: .production,
    webSdk: .messaging,
    enableProgrammaticControl: true
)
```

### greeting

`greeting: String = ""`

Use this setting to customize the greeting messages that new end users see. This is useful for setting view-specific greetings across your app. The greeting should correspond to the ID of the Answer you would like to use, which you can find in the URL of the corresponding Answer in the dashboard.

**Example**

![](/_fern-img/a7fea47cff8a7e0931f1f7b88be30130fc81ec7c6be9d662433b766b8e037e0b.webp)

> **Note**
>
> This setting is only applicable if you're using a **scripted AI Agent**.

### handle

`handle: String`

The handle for your AI Agent. This is a required field.

### headless

`headless: Bool = false`

When set to `true`, the Messaging runtime connects to your AI Agent without rendering its default chat UI. Use this when your app renders its own conversation UI with native components and drives the conversation through [`sendMessage`](#sendmessage) and [`eventCallbacks`](#eventcallbacks).

Headless hosts are typically paired with [`enableProgrammaticControl: true`](#enableprogrammaticcontrol). Without it, the methods and events needed to drive a custom UI are rejected at runtime.

`headless` suppresses only the runtime's UI. To run the WebView without presenting it, launch with [`launchHeadlessWebSupport`](#launchheadlesswebsupport). That action forces this setting to `true`, and rebuilds the WebView when the host was created without it, so set `headless: true` at initialization to avoid the extra load. See [Headless session lifecycle](#headless-session-lifecycle) for the constraints.

> **Note**
>
> Headless sessions run with no visual indicator. The runtime creates and persists session state inside the WebView the same way as a visible session. Treat enabling headless as a meaningful trust decision.

This setting applies only when [`webSdk`](#websdk) is `.messaging`. The legacy runtime ignores it.

### identityToken

`identityToken: String = ""`

A short-lived identity token that authenticates the end user before the session starts. Your backend mints the token with the Ada Platform API, then your app passes it here. See [Authenticate end users](/messaging/ios/getting-started#authenticate-end-users-with-an-identity-token) for the setup flow.

Your server can supply optional `verified_email` and `name` values when it mints the token.
Ada links only when the mint request includes `verified_email`.
Without either field, mint, exchange, and handoff keep their existing behavior.
Ada stores the values encrypted on the session's end user.
Ada uses them only in the first 7 days after the exchange.
Ada deletes them after the first Zendesk Messaging handoff uses them and Sunshine initialization succeeds.
If no handoff uses them, Ada deletes them at the next handoff or token exchange after the 7 days end.

Token handling rules:

* Tokens are **single use** and expire after 15 minutes. Mint a fresh token for each new `AdaWebHost`.
* The SDK delivers the token to the web runtime through a one-time injected configuration object at document start. The token never appears in a URL, so it stays out of request logs.
* The SDK holds the token in memory only. It is never persisted to disk.
* The SDK remembers when the runtime has consumed the token. If the host rebuilds or reloads its WebView, the spent token is not delivered again. Create a new host with a newly minted token to re-authenticate.
* Exchange failures surface as the `ada:identity_token:error` and `ada:identity_token:expired` [events](#events).

> **Warning**
>
> Never log the identity token. Never store it in `UserDefaults`, files, or analytics payloads.

This setting applies only when [`webSdk`](#websdk) is `.messaging`. The legacy runtime ignores it.

### language

`language: String = ""`

Sets the initial display language. To use the same language for AI Agent replies, first turn languages on in your Ada dashboard. Go to **Customization** > **Languages**. See [Support multiple languages in the same AI Agent](/docs/setup/languages/about-multilingual-support) for more information.

With the Messaging runtime, this setting applies on every WebView launch and takes precedence over a saved language choice. A language picker or [`setLanguage`](#setlanguage) change during that launch is still saved when session persistence is enabled. This setting takes precedence again on the next launch. Omit this setting to allow a saved choice to restore.

Later language updates from the Agent or another widget sharing the session can change the display language. With session persistence enabled, the widget saves its current display language.

When activation restores an existing session, it synchronizes the configured language if the Agent supports it. If the reply language changes, other open widgets using the same session apply the update live, even during a conversation.

If the Agent does not support the display language, or the language update fails, the display language can differ from the Agent's reply language.
An unsupported configured value can still replace a supported saved choice when session persistence is enabled.

> **Note**
>
> Language codes use the [ISO 639-1 language format](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes).

### metafields

`metafields: [String: Any] = [:]`

Use metafields to pass information about a user to Ada at initialization. This can be useful for tracking information about your customers, as well as personalizing their experience.

To change these values after initialization, use the [`setMetaFields`](#setmetafields) action.

**`Swift`**

```swift Swift
lazy var adaWebHost = AdaWebHost(handle: "my-agent", metafields: ["tier": "pro"])
```

### navigationBarOpaqueBackground

`navigationBarOpaqueBackground: Bool = false`

When set to `true`, the modal presentation uses a full-screen style with an opaque, light-gray navigation bar and status bar. Applies to [`launchModalWebSupport`](#launchmodalwebsupport) only.

### openWebLinksInSafari

`openWebLinksInSafari: Bool = false`

External web links open by default in-app, via `SFSafariViewController`. To open external links in the Safari browser, pass `openWebLinksInSafari: true`.

### sensitiveMetafields

`sensitiveMetafields: [String: Any] = [:]`

Use this parameter to pass sensitive meta information about an end user. This works like [metafields](#metafields) but provides an added layer of security.

The SDK forwards these fields at initialization, so an identified session carries them from the first message. On the legacy runtime they ride the start config. On the Messaging runtime they ride a document-start injection. The values never appear in a URL.

To change these values after initialization, use the [`setSensitiveMetaFields`](#setsensitivemetafields) action.

### styles

`styles: String = ""`

Passes style overrides to the web runtime. The value's meaning depends on [`webSdk`](#websdk):

* `.legacy`: a CSS style override string.
* `.messaging`: no effect. The runtime ignores the value and logs a warning.

### version

`version: String = ""`

Pins the legacy runtime's chat bundle to a specific build. Used for pre-release verification with your Ada team. Leave blank for the stable rollout. Ignored when [`webSdk`](#websdk) is not `.legacy`.

### webSdk

`webSdk: AdaWebSdk = .legacy`

Selects which web runtime the WebView mounts. See [`AdaWebSdk`](#adawebsdk).

* `.legacy` (the default) keeps the runtime the previous `AdaEmbedFramework` SDK loaded, so migrating apps see no behavior change.
* `.messaging` mounts the Messaging runtime. [`enableProgrammaticControl`](#enableprogrammaticcontrol), [`headless`](#headless), [`identityToken`](#identitytoken), [`selectQuickReply`](#selectquickreply), and [`sendMessage`](#sendmessage) require it.

Set an explicit [`environment`](#environment) when you select `.messaging`.

### webViewLoadingErrorCallback

`webViewLoadingErrorCallback: ((Error) -> Void)? = nil`

Called when the WebView fails to load. Receives an [`AdaWebHostError`](#adawebhosterror): `.webViewTimeout` when loading exceeds [`webViewTimeout`](#webviewtimeout), or `.webViewFailedToLoad` for navigation failures.

### webViewTimeout

`webViewTimeout: Double = 30.0`

The number of seconds the SDK waits for the WebView to load before it stops loading and calls [`webViewLoadingErrorCallback`](#webviewloadingerrorcallback) with `.webViewTimeout`.

### zdChatterAuthCallback

`zdChatterAuthCallback: ((@escaping (_ token: String) -> Void) -> Void)? = nil`

Use `zdChatterAuthCallback` to authenticate Zendesk Chat and Zendesk Messaging Handoffs.
For Zendesk Messaging, select the Messaging runtime. Request a JWT from your server and pass it to the callback.

**`Swift`**

```swift Swift
lazy var adaWebHost = AdaWebHost(handle: "my-agent", zdChatterAuthCallback: { callback in
    // Request JWT from your API
    // Then...
    callback("your.JWT")
})
```

For Zendesk Messaging, return your Zendesk Messaging authentication JWT. Sign it with a Zendesk Messaging signing key using HS256.
Include the signing key ID in the `kid` header. Include the `scope: "user"`, `external_id`, and `exp` claims.
Set `exp` to an expiry time in Unix seconds. Keep the signing secret on your server.

The `external_id` must be a non-empty string of at most 255 characters.
It must not contain `/`, `?`, `#`, `%`, whitespace, or control characters.
Send the required `external_id` claim and the optional `name`, `email`, and `email_verified` claims.

Verified email linking requires the Messaging SDK.

> **Warning**
>
> Ada links to or creates a Zendesk user from the email.
> A wrong email links the conversation to another person's Zendesk user.
>
> [Read all risks](/docs/handoffs/zendesk/zendesk-messaging#authenticate-end-users)

Ada uses `name` and `email` for display and linking only when the JWT has an `email` with boolean `email_verified: true`.
Without a verified email, Ada uses only `external_id`.
With a verified email, Ada writes META `jwt_name` and `jwt_email`, as it does for Zendesk Chat.
Ada updates `name` only when it is empty or still equals the previous JWT's name.
Ada never writes META `email` from these claims.
This preserves the email that your page or a **Capture** block sets.

With a verified email, Ada first uses a Zendesk end user with the same `external_id` as the JWT.
With `email_verified: true`, Ada next matches exactly one Zendesk end user by primary email.
Ada trims whitespace and converts the email to lowercase before matching.
Ada uses the matched user's external ID or sets it to the JWT's `external_id` if missing.
When Ada sets a missing external ID on the Zendesk user, Zendesk keeps that link for later conversations.

If a verified email has no match, Ada creates a Zendesk end user with the JWT's name and email.
If the JWT has no name, Ada uses the email as the name.

If Ada cannot select or create a Zendesk end user, Ada uses the JWT's `external_id`.

Ada uses the JWT's email to match or create a Zendesk user only when `email_verified` is `true`.
Ada never links agents or admins, or a match that is not exact.
See [Authenticate end users](/docs/handoffs/zendesk/zendesk-messaging#authenticate-end-users) for the full matching rules.

The SDK requests one token at startup, including when both Zendesk platforms are configured.
Zendesk Messaging authentication does not require the Zendesk Chat feature. Existing Zendesk Chat callbacks need no changes.

After successful verification, the server stores the identity and its validity window on the end user record.
Each successful startup check renews the window for 24 hours.
Browser metadata cannot supply or change these values.

If no valid token or signing secret is available, the Handoff uses an anonymous Sunshine user unless another identity mapping applies.
When a user signs out, call `adaWebHost.reset()` so the next user starts a new conversation and a new Zendesk user.

## Session persistence

The web runtime keeps the working copy of session state inside the WebView's web storage. From version 1.3.0, the SDK also stores a session mirror in the Keychain. If the end user force-quits the app, the SDK restores the session from the session mirror on the next launch. The restored session includes active live agent handoff context. The native side also caches a small allowlist of cosmetic state, with a 10 minute expiry, to smooth the next launch.

Session persistence has these properties:

* **The session mirror follows your AI Agent's persistence setting.** The `normal` persistence mode writes the session mirror. The `session` and `private` persistence modes clear the session mirror, so the SDK writes no session mirror in these modes.
* **The session mirror is this-device-only.** The Keychain entry does not sync to iCloud and does not transfer to another device.
* **The session does not survive an app uninstall.** This behavior is deliberate. After a reinstall, a first-run check wipes old Keychain entries, and the SDK starts a fresh session.
* **An [`identityToken`](#identitytoken) always takes precedence.** If you supply a token at initialization, the SDK performs the identity exchange first. The resolved session replaces any previously persisted session. To change the identified user, create a new `AdaWebHost` with a newly minted token.

Session persistence shows no end-user prompts, permission dialogs, or biometric sheets. It requires no new host-app permissions. The SDK never persists identity tokens to disk.

> **Warning**
>
> **Clear the session when an end user signs out of your app.** Sessions now survive an app restart. If your sign-out flow does not clear the session, the next user of the device can resume the previous user's conversation. Call [`reset`](#reset) or [`deleteHistory`](#deletehistory) when the end user signs out. These clear the conversation and the session mirror. `clearPersistedState` alone does not clear the conversation in web storage. To confirm the Keychain wipe, also call [`clearPersistedStateDurably(completion:)`](#clearpersistedstatedurablycompletion).

## Events

Pass an [`eventCallbacks`](#eventcallbacks) dictionary at initialization to receive runtime events.

The delivery contract:

* Each callback receives one `[String: Any]` dictionary.
* `event["event_name"]` is the event key as a `String`.
* `event["data"]` carries the event payload when the runtime sends one.
* The callback registered under the exact event key fires first, then the callback registered under `"*"` fires with the same event.
* The `"*"` callback receives every event the runtime emits, including keys not listed below.

The dictionary holds one closure per key. To register several subscribers for the same key, or to add and remove subscribers after initialization, use [`addEventCallback`](#addeventcallback) and [`addSdkEventCallback`](#addsdkeventcallback) instead. Subscribers registered that way also receive the `ada.bridge.error` and `ada.webview.*` events under their own keys, not only under `"*"`.

> **Note**
>
> The `ada:message:sent` and `ada:message:received` events are delivered only when the host was created with [`enableProgrammaticControl: true`](#enableprogrammaticcontrol).

Common event keys, in alphabetical order:

| Event key                              | Fires when                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ada.bridge.error`                     | The native bridge hits an error, such as an SDK load failure. Delivered to the `"*"` callback only, with the message in `event["error"]`.                                                                                                                                                                                                                                                                                                                          |
| `ada.sessionMirror.diagnostic`         | The SDK cannot complete a [session mirror](#session-persistence) operation. `event["data"]["reason"]` carries one reason string and no session data. The SDK reports each reason once per loaded document, except `adapter-removeItem-failed`, which it reports every time. Reasons: `adapter-getItem-failed`, `adapter-removeItem-failed`, `adapter-setItem-failed`, `entries-not-allowlisted`, `legacy-rollout-unresolved`, `origin-mismatch`, `scope-mismatch`. |
| `ada.webview.contentProcessTerminated` | The WebView content process ends after two recoveries in a row. The count resets after the page stays loaded for 60 s. Reported once per terminal state, with `url` when known. A later successful trusted load on the displayed WebView clears this state immediately. Delivered to event subscribers and the `eventCallbacks["*"]` callback.                                                                                                                     |
| `ada.webview.loaded`                   | The WebView finishes loading its page, with the loaded `url`. Delivered to the `"*"` callback only.                                                                                                                                                                                                                                                                                                                                                                |
| `ada.webview.loadFailed`               | The WebView navigation fails or the load times out, with the reason in `event["error"]`. [`webViewLoadingErrorCallback`](#webviewloadingerrorcallback) also fires once. Delivered to event subscribers and the `"*"` dictionary callback.                                                                                                                                                                                                                          |
| `ada.webview.subresourceLoadFailed`    | A resource inside the Messaging runtime page fails to load, with `url`, `error`, and the lowercase tag name in `element`. Reported once per URL per document. The main frame is intact, so `webViewLoadingErrorCallback` does not fire. Delivered to the `"*"` callback only.                                                                                                                                                                                      |
| `ada:agent:joined`                     | A human agent joins the conversation.                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `ada:agent:left`                       | A human agent leaves the conversation.                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `ada:chatter_token`                    | The runtime issues or adopts an end user token.                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `ada:connection:change`                | The runtime's connection state changes.                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `ada:conversation:change`              | The active conversation changes.                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `ada:csat_submitted`                   | The end user submits a satisfaction survey.                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `ada:end_conversation`                 | The conversation ends.                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `ada:identity_token:error`             | The [`identityToken`](#identitytoken) exchange fails. `event["data"]["reason"]` names the cause, such as `identity_token_already_used`, `identity_token_expired`, or `invalid_identity_token`.                                                                                                                                                                                                                                                                     |
| `ada:identity_token:expired`           | The [`identityToken`](#identitytoken) is expired. This event follows `ada:identity_token:error`.                                                                                                                                                                                                                                                                                                                                                                   |
| `ada:message:received`                 | A message arrives from the AI Agent or a human agent. Requires `enableProgrammaticControl`.                                                                                                                                                                                                                                                                                                                                                                        |
| `ada:message:sent`                     | The end user sends a message. Requires `enableProgrammaticControl`.                                                                                                                                                                                                                                                                                                                                                                                                |
| `ada:typing:start`                     | The other party starts typing.                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `ada:typing:stop`                      | The other party stops typing.                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `sdk.ready`                            | The runtime is ready to accept commands. The event also carries `event["web_sdk"]` with the active runtime name. For production integrations, select `.messaging` and `.production`. The default Legacy path emits no `sdk.ready`.                                                                                                                                                                                                                                 |

On `ada.webview.contentProcessTerminated`, the SDK has stopped in-place recovery after two reloads.
To restore chat, release the current `AdaWebHost`.
Create a new `AdaWebHost` with your settings.
Present it with one of the [launch actions](#actions).

## Headless session lifecycle

A [`headless`](#headless) host is an offscreen integration, not a background service. The following constraints are firm:

* **No background execution.** When iOS suspends the app, it also suspends the WebView's content process and event delivery. Use [webhooks and your push provider](/docs/channels/messaging/push-notifications) to send push notifications outside the app lifecycle.
* **App termination ends the runtime.** When the app terminates, the WebView and its in-memory state are gone. On the next launch, the session rehydrates from the runtime's own web persistence, or from the [session mirror](#session-persistence).
* **Tokens are never persisted natively.** [`identityToken`](#identitytoken) is held in memory and consumed once by the runtime. Mint a fresh token for each new host.
* **One WebView per host.** Each `AdaWebHost` owns one WebView. Retain the host for as long as you need the session. Releasing it stops event delivery.
* **Keep the container out of user interaction.** A hidden or zero-sized container is fine. WebKit can throttle rendering for offscreen views; command dispatch and event delivery still work.

To show the Ada UI later, create a new `AdaWebHost` without `headless` and present it with one of the launch actions. The conversation continues because the runtime persists session state in web storage.

## Actions

Use the actions below in conjunction with settings to customize the behavior of your AI Agent in an iOS app. On the Messaging runtime, actions called before the runtime is ready are queued and dispatched when `sdk.ready` fires.

### addEventCallback

`addEventCallback(_ eventName: String = "*", callback: @escaping (_ event: [String: Any]) -> Void) -> AdaEventSubscription`

Registers a callback for one event key, or for every event with the default `"*"` key. Unlike the [`eventCallbacks`](#eventcallbacks) dictionary, which holds one closure per key, any number of callbacks can subscribe to the same event, and you can subscribe after initialization. Returns an [`AdaEventSubscription`](#adaeventsubscription) token. Pass the token to [`removeEventCallback`](#removeeventcallback) to unsubscribe.

**`Swift`**

```swift Swift
let subscription = adaWebHost.addEventCallback("ada:message:received") { event in
    // A message arrived from the AI Agent or a human agent.
}
```

Subscribers registered this way also receive the `ada.bridge.error` and `ada.webview.*` [events](#events) under their own keys.

### addSdkEventCallback

`addSdkEventCallback(_ callback: @escaping (_ key: String, _ data: String?) -> Void) -> AdaEventSubscription`

Registers a raw sink that receives every event as its key plus its JSON-encoded data. Use it to forward events without knowing the keys in advance. Returns an [`AdaEventSubscription`](#adaeventsubscription) token for [`removeSdkEventCallback`](#removesdkeventcallback).

**`Swift`**

```swift Swift
let subscription = adaWebHost.addSdkEventCallback { key, data in
    analytics.track(key, payload: data)
}
```

### clearPersistedState

`clearPersistedState()`

Removes the natively persisted state. This includes the allowlisted, non-sensitive startup and branding state in `UserDefaults`. From version 1.3.0, it also includes the persisted session mirror in the Keychain. Call it when an end user signs out. The next WebView session starts without rehydrated state. See [Session persistence](#session-persistence).

The method returns no result. To confirm the wipe, use [`clearPersistedStateDurably`](#clearpersistedstatedurably).

The call does not touch the web runtime's own storage. To clear the conversation itself, use [`deleteHistory`](#deletehistory) or [`reset`](#reset).

**`Swift`**

```swift Swift
adaWebHost.clearPersistedState()
```

### clearPersistedStateDurably

`clearPersistedStateDurably() -> Bool`

Removes the same natively persisted state as [`clearPersistedState`](#clearpersistedstate), and returns the result. The method returns `false` when the SDK could not confirm the session-mirror wipe in the Keychain. If the method returns `false`, call it again.

This method blocks the calling thread. `AdaWebHost` runs on the main actor, so the blocked thread is the main thread. The block lasts until the wipe completes or a timeout of about 3 seconds elapses. Use [`clearPersistedStateDurably(completion:)`](#clearpersistedstatedurablycompletion) instead, unless your sign-out flow needs the result inline. The method does not touch the web runtime's own storage.

**`Swift`**

```swift Swift
let cleared = adaWebHost.clearPersistedStateDurably()
```

### clearPersistedStateDurably(completion:)

`clearPersistedStateDurably(completion: @escaping @MainActor @Sendable (Bool) -> Void)`

Removes the same natively persisted state as [`clearPersistedState`](#clearpersistedstate). The SDK calls `completion` on the main actor with the result. The method does not block. The result is `false` when the SDK could not confirm the session-mirror wipe in the Keychain. If the result is `false`, call the method again. Prefer this form for a sign-out on the main actor.

**`Swift`**

```swift Swift
adaWebHost.clearPersistedStateDurably { cleared in
    if !cleared {
        // Retry the sign-out wipe.
    }
}
```

### deleteHistory

`deleteHistory()`

Deletes the record used to fetch conversation logs for an end user from local storage. When the user opens a new chat window, a new user record will be created.

**`Swift`**

```swift Swift
adaWebHost.deleteHistory()
```

### launchHeadlessWebSupport

`launchHeadlessWebSupport(in hostView: UIView? = nil)`

Runs the web runtime without presenting any Ada UI. Attaches the WebView to `hostView` when one is supplied (keep it hidden or zero-sized), or to an internal hidden zero-sized container otherwise.

Requirements and behavior:

* Requires [`webSdk: .messaging`](#websdk). Calling it on a legacy host stops the program with a precondition failure, matching the Android SDK's headless factory.
* Forces [`headless`](#headless) to `true`. When the host was created with `headless: false`, the SDK rebuilds the WebView so the runtime loads in headless mode. Set `headless: true` at initialization to avoid the extra load.

Drive the conversation natively through [`eventCallbacks`](#eventcallbacks) and [`sendMessage`](#sendmessage). See [Headless session lifecycle](#headless-session-lifecycle).

**`Swift`**

```swift Swift
// Offscreen: the SDK creates and retains a hidden container.
adaWebHost.launchHeadlessWebSupport()

// Or attach to your own container. Keep it hidden or zero-sized.
adaWebHost.launchHeadlessWebSupport(in: myHiddenContainerView)
```

### launchInjectingWebSupport

`launchInjectingWebSupport(into view: UIView)`

Launches Ada chat into a specified subview.

**`Swift`**

```swift Swift
adaWebHost.launchInjectingWebSupport(into: injectingView)
```

### launchModalWebSupport

`launchModalWebSupport(from viewController: UIViewController)`

Launches Ada chat in a modal view over top of your current view.

**`Swift`**

```swift Swift
adaWebHost.launchModalWebSupport(from: self)
```

### launchNavWebSupport

`launchNavWebSupport(from navController: UINavigationController)`

Pushes a view containing Ada chat to the top of your navigation stack.

**`Swift`**

```swift Swift
adaWebHost.launchNavWebSupport(from: navigationController)
```

### removeEventCallback

`removeEventCallback(_ subscription: AdaEventSubscription)`

Removes exactly the callback that [`addEventCallback`](#addeventcallback) returned the subscription for. Unknown subscriptions are a no-op.

### removeEventCallbacks

`removeEventCallbacks(_ eventName: String = "*")`

Removes every callback registered through [`addEventCallback`](#addeventcallback) for one event key, or for `"*"` by default.

### removeSdkEventCallback

`removeSdkEventCallback(_ subscription: AdaEventSubscription)`

Removes the raw sink that [`addSdkEventCallback`](#addsdkeventcallback) returned the subscription for.

### reset

**`Swift`**

```swift Swift
reset(
    language: String? = nil,
    greeting: String? = nil,
    metaFields: MetaFields.Builder,
    sensitiveMetaFields: MetaFields.Builder,
    resetChatHistory: Bool? = true
)
```

Creates a new end user and refreshes the chat, optionally with a new language, greeting, and metadata. Overloads exist with only `metaFields`, only `sensitiveMetaFields`, or neither.

With `resetChatHistory: true`, the SDK clears omitted caller-supplied sensitive fields. Pass them again to retain them. Resets that preserve history merge supplied fields. Registered device tokens survive.

`resetChatHistory` is tri-state: `true` and `false` are sent to the runtime explicitly, while `nil` omits the value so the runtime's own default applies. The parameter defaults to `true`.

**`Swift`**

```swift Swift
adaWebHost.reset(
    language: "en",
    metaFields: publicFields,
    sensitiveMetaFields: sensitiveFields,
    resetChatHistory: true
)
```

> **Note**
>
> A `reset` overload that takes plain dictionaries still exists for backward compatibility but is deprecated. Use the `MetaFields.Builder` overloads for all new code.

### selectQuickReply

`selectQuickReply(messageId: String, index: Int)`

Selects a quick reply through the same send path as a tap in the built-in UI.
Requires the Messaging runtime and [`enableProgrammaticControl: true`](/messaging/ios/sdk-api-reference#enableprogrammaticcontrol).
Calls queue until the runtime is ready. The method returns no result. The legacy runtime does not support it.

| Parameter   | Type     | Description                                                                      |
| ----------- | -------- | -------------------------------------------------------------------------------- |
| `messageId` | `String` | The non-empty message `id` from `ada:message:received`.                          |
| `index`     | `Int`    | A non-negative integer index into `message.data.quickReplies`, starting at zero. |

Selection starts engagement and activates the chat, like `sendMessage`.
The runtime starts if needed. In visible mode, it opens a closed chat or reloads the timeout fallback.
The selected user message carries `origin: "programmatic"` in `getMessages()` and `ada:message:sent`.

`SEND_FAILED` means the server confirmed rejection with HTTP 4xx.
The runtime withdraws that answer and releases the block in the app display state and host selection checks.
The host can select the same message again if it remains current and passes the rate limit and other checks.
`SEND_UNCONFIRMED` means HTTP 5xx, a transport error, or another unconfirmed outcome.
The block stays answered because the selection may already have taken effect.
Another selection of that message rejects with `ALREADY_ANSWERED`.

`SEND_DROPPED` means the runtime could not start the send.
`INVALID_QUICK_REPLY` means the stored chip has no required target or usable label or value.
If the conversation or session changes during the request, the runtime treats the selection as accepted, as `sendMessage` does.
It does not apply the send result to the replacement state or publish `ada:message:sent` for that request.
See the [Web rejection codes](/messaging/web/sdk-api-reference#selectquickreply) for all selection checks.

The host sends only the message ID and index. The runtime reads the reply fields from its own transcript.
`addEventCallback` receives the message at `event["data"]["message"]`.
Quick-reply messages have `id`, `type: "quick_replies"`, and `data.quickReplies`.

This example reads the received replies and selects the first reply.

**`Swift`**

```swift Swift
let subscription = adaWebHost.addEventCallback("ada:message:received") { [weak adaWebHost] event in
    guard let payload = event["data"] as? [String: Any],
          let message = payload["message"] as? [String: Any],
          let messageId = message["id"] as? String,
          message["type"] as? String == "quick_replies",
          let data = message["data"] as? [String: Any],
          let quickReplies = data["quickReplies"] as? [[String: Any]],
          !quickReplies.isEmpty else { return }
    adaWebHost?.selectQuickReply(messageId: messageId, index: 0)
}
```

### sendMessage

`sendMessage(_ body: String)`

Sends a message from the end user into the conversation. Use this to drive the conversation from your own native UI.

**`Swift`**

```swift Swift
adaWebHost.sendMessage("Where is my order?")
```

Requirements and behavior:

* Requires [`webSdk: .messaging`](#websdk). On the legacy runtime the call is dropped with a debug log.
* Requires [`enableProgrammaticControl: true`](#enableprogrammaticcontrol). Without it, the runtime rejects the command with `ProgrammaticControlNotEnabled`.
* Calls made before the runtime is ready are queued and dispatched when `sdk.ready` fires.
* `sendMessage` returns no result. Runtime rejections, including `ProgrammaticControlNotEnabled`, produce `ada.bridge.error`. `event["error"]` carries the message.

### setDeviceToken

`setDeviceToken(deviceToken: String)`

Stores or updates the APNs device token with the end user's sensitive metadata. To send push notifications, use webhooks and your push provider. See [Push notifications](/docs/channels/messaging/push-notifications).

You can call this before the runtime is ready. The SDK stores the latest device token when the runtime signals `sdk.ready`.

**`Swift`**

```swift Swift
adaWebHost.setDeviceToken(deviceToken: "apns-device-token")
```

### setLanguage

`setLanguage(language: String)`

Changes the language in chat programmatically. Use this action, rather than the [`language`](#language) setting, to change the chat language without clearing the chat history. Language codes must use a lowercase, two-letter code, in [ISO 639-1 language format](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes).

**`Swift`**

```swift Swift
adaWebHost.setLanguage(language: "fr")
```

Before using `setLanguage`, you must turn languages on in your Ada dashboard. Go to **Customization** > **Languages**. See [Support multiple languages in the same AI Agent](/docs/setup/languages/about-multilingual-support) for more information.

### setMetaFields

`setMetaFields(builder: MetaFields.Builder)`

Sets metadata for an end user after initialization. This is useful if you need to update user data after Ada has already launched. See also [metafields](#metafields).

**`Swift`**

```swift Swift
let publicFields = MetaFields.Builder()
    .setField(key: "firstName", value: "Jane")
    .setField(key: "tier", value: "pro")

adaWebHost.setMetaFields(builder: publicFields)
```

> **Note**
>
> A `setMetaFields(_:)` overload that takes a plain dictionary still exists for backward compatibility but is deprecated. Use `MetaFields.Builder` for all new code.

### setSensitiveMetaFields

`setSensitiveMetaFields(builder: MetaFields.Builder)`

Sets sensitive metadata for an end user after initialization. This works like [`setMetaFields`](#setmetafields) and is useful for passing more private and sensitive information. See also [sensitiveMetafields](#sensitivemetafields).

**`Swift`**

```swift Swift
let sensitiveFields = MetaFields.Builder()
    .setField(key: "authToken", value: "secure-session-token")

adaWebHost.setSensitiveMetaFields(builder: sensitiveFields)
```

> **Note**
>
> A `setSensitiveMetaFields(_:)` overload that takes a plain dictionary still exists for backward compatibility but is deprecated. Use `MetaFields.Builder` for all new code.

### triggerPlaybook

**`Swift`**

```swift Swift
func triggerPlaybook(
    _ playbookId: String,
    metaFields: [String: Any]? = nil,
    sensitiveMetaFields: [String: Any]? = nil,
    completion: ((AdaBridgeRequestResult) -> Void)? = nil
)
```

Starts a specific Playbook in the current conversation.

> **Note**
>
> This method is in Early Access.
> It requires [`enableProgrammaticControl`](#enableprogrammaticcontrol) and an active v2 Playbook marked as startable from the SDK.
> Set `startable_from_sdk: true` through the dashboard API, or ask the Ada team to mark the Playbook.
> This Playbook setting defaults to `false`.

Pass the Playbook ID as a 24-character hexadecimal string.
The SDK sends `metaFields` and `sensitiveMetaFields` with the trigger.
For a valid Playbook ID within the rate limit, the API stores metadata before it checks Playbook eligibility.
It also applies a supported `language`.
It also binds the push device token when push notifications are enabled.
These updates persist even when a later check refuses the trigger. A rate-limited request stores nothing.

If the API returns `language_not_supported`, it keeps the current language and skips push device binding and Playbook activation.
Other metadata and email fields remain stored under the same rules as the metadata setters.

If either metadata field has a non-null value that is not an object, the API returns HTTP 409 with `failure_reason: "invalid_metadata"`.
The API stores no metadata and does not activate the Playbook for this refusal.

Use strings, numbers, booleans, or null values for metadata, as with [`setMetaFields`](#setmetafields).
The Playbook must allow standalone use and pass its availability rules for the end user.
The API refuses requests during a live handoff or when another Playbook is running.
Each end user can make three valid-ID attempts with valid metadata objects per minute per AI Agent.

The completion receives `.success(nil)` when the API accepts the trigger.
It receives `.failure(String)` on refusal, invalid input, metadata write failure, or timeout.
The failure includes the reason, such as `triggerPlaybook rejected: playbook_running`.
Use `NSNull()` for a null metadata value.

**`Swift`**

```swift Swift
adaWebHost.triggerPlaybook(
    "507f1f77bcf86cd799439011",
    metaFields: ["orderId": "order-123"],
    sensitiveMetaFields: ["accountToken": "<account-token>"]
) { result in
    if case let .failure(reason) = result {
        print(reason)
    }
}
```

HTTP 503 with `activation_indeterminate` means the API cannot confirm whether the Playbook started.
The rejection does not mean the Playbook did not start.
Timeouts and network errors can also leave the result unknown. Do not retry automatically.
The Legacy runtime reports this method as unsupported.

## Curated bridge requests

`AdaWebHost` exposes a fixed set of methods that ask the running chat for state or drive it. Each method takes a completion closure. The closure receives one `AdaBridgeRequestResult`. That result is `.success(Any?)`, `.unsupported`, or `.failure(String)`.

For production integrations, select [`.messaging`](#websdk) and [`.production`](#environment) to use these requests and `sdk.ready`. The default Legacy path returns `.unsupported` and emits no `sdk.ready`.

A method reports `.unsupported` when the running runtime build does not implement it. A method reports `.failure` in these cases:

* Already 64 requests wait for the runtime.
* The runtime returns an error, such as a bad argument.
* No answer arrives before the request times out.
* The runtime sends a malformed response that carries none of a result, an error, or an unsupported outcome.

A method never silently does nothing.

Reads return public models only. No read returns sensitive meta fields, the internal stores, or persisted session data. Writes and triggers succeed with a `nil` value.

Calls made before the runtime is ready are queued and dispatched when the runtime becomes ready. Reads take a required completion closure. Writes and triggers take an optional completion closure. `triggerPlaybook` and the message reads `getConversation` and `getMessages` require [`enableProgrammaticControl`](#enableprogrammaticcontrol). That setting defaults to `false`. The other reads, writes, and triggers do not. The default differs per platform. React Native defaults it to `true`. There the same two reads resolve without the flag.

| Method                                                          | Result           | Description                                                             |
| --------------------------------------------------------------- | ---------------- | ----------------------------------------------------------------------- |
| `close(completion:)`                                            | `.unsupported`   | The SDK cannot close a parent-hosted webview.                           |
| `closeCampaign(completion:)`                                    | `.success(nil)`  | Dismisses the active campaign.                                          |
| `getConversation(completion:)`                                  | `.success(Any?)` | The public conversation model.                                          |
| `getInfo(completion:)`                                          | `.success(Any?)` | The public AI Agent and session info.                                   |
| `getMessages(completion:)`                                      | `.success(Any?)` | The public message list.                                                |
| `getMetaFields(completion:)`                                    | `.success(Any?)` | The non-sensitive meta fields set on the session.                       |
| `isOpen(completion:)`                                           | `.success(Any?)` | Whether the chat window is open.                                        |
| `open(completion:)`                                             | `.unsupported`   | The SDK cannot open a parent-hosted webview.                            |
| `setTheme(_:completion:)`                                       | `.success(nil)`  | Sets the runtime theme to `.light`, `.dark`, or `.auto`.                |
| `toggle(completion:)`                                           | `.unsupported`   | The SDK cannot toggle a parent-hosted webview.                          |
| `triggerAnswer(answerId:completion:)`                           | `.success(nil)`  | Triggers a specific answer by id.                                       |
| `triggerGreeting(handle:completion:)`                           | `.success(nil)`  | Triggers the greeting, optionally for a specific handle.                |
| `triggerPlaybook(_:metaFields:sensitiveMetaFields:completion:)` | `.success(nil)`  | Starts a Playbook. Early Access. Requires programmatic control.         |
| `triggerProactive(messageKey:params:completion:)`               | `.success(nil)`  | Shows a proactive message by key, with optional variable substitutions. |

**`Swift`**

```swift Swift
adaWebHost.getConversation { result in
    if case let .success(conversation) = result {
        // Use the public conversation model.
    }
}

adaWebHost.setTheme(.dark)
```

## Types

### AdaBridgeRequestResult

The result of a [curated bridge request](#curated-bridge-requests).

| Case               | Description                                                 |
| ------------------ | ----------------------------------------------------------- |
| `.failure(String)` | Carries the error message.                                  |
| `.success(Any?)`   | Carries the decoded value, or `nil` for a write or trigger. |
| `.unsupported`     | The loaded runtime does not implement the method.           |

### AdaChatTheme

The theme for `setTheme(_:completion:)`: `.auto`, `.dark`, or `.light`.

### AdaEnvironment

`AdaEnvironment` selects the deployment environment that hosts the WebView entry page and SDK assets.

| Case                                | Description                                                        |
| ----------------------------------- | ------------------------------------------------------------------ |
| `.production`                       | Production assets. Use this for production apps.                   |
| `.preprod(branch: String = "main")` | Pre-production assets, for pre-release testing with your Ada team. |
| `.local(port: Int = 4900)`          | Local development against a local assets server.                   |
| `.custom(assetsOrigin: URL)`        | A custom CDN origin that serves the SDK assets.                    |

### AdaEventSubscription

An opaque token returned by [`addEventCallback`](#addeventcallback) and [`addSdkEventCallback`](#addsdkeventcallback). Keep it to remove the subscription later with [`removeEventCallback`](#removeeventcallback) or [`removeSdkEventCallback`](#removesdkeventcallback).

### AdaWebHostError

`AdaWebHost.AdaWebHostError` is the error type passed to [`webViewLoadingErrorCallback`](#webviewloadingerrorcallback).

| Case                   | Description                                                                            |
| ---------------------- | -------------------------------------------------------------------------------------- |
| `.webViewFailedToLoad` | The WebView navigation failed.                                                         |
| `.webViewTimeout`      | The WebView did not finish loading within [`webViewTimeout`](#webviewtimeout) seconds. |

### AdaWebSdk

`AdaWebSdk` selects which web runtime the WebView mounts. See [webSdk](#websdk).

| Case         | Description                                                                         |
| ------------ | ----------------------------------------------------------------------------------- |
| `.legacy`    | The runtime the previous `AdaEmbedFramework` SDK loaded. The default.               |
| `.messaging` | The Messaging runtime. Required for `headless`, `identityToken`, and `sendMessage`. |

### MetaFields.Builder

`MetaFields.Builder` builds the metadata payload for [`setMetaFields`](#setmetafields), [`setSensitiveMetaFields`](#setsensitivemetafields), and [`reset`](#reset). `setField(key:value:)` accepts `String`, `Bool`, `Int`, `Float`, and `Double` values and returns the builder for chaining.

**`Swift`**

```swift Swift
let fields = MetaFields.Builder()
    .setField(key: "plan", value: "pro")
    .setField(key: "signedIn", value: true)
    .setField(key: "seats", value: 5)
```