Skip to navigation

State and events

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. Until the list allows your origin, a configured appUrl is dropped with a console warning and Ada’s default app mounts. Local development needs no entry when both your app and the embedding page run on loopback hosts. See Local development.

The bridge contract has two halves. Core publishes a display state object (AppDisplayState) that your app renders from. Your app sends typed events (AppEvents) back. The full typed contract ships in the npm package, so treat the package’s TypeScript declarations as the shape reference.

This page documents when each customer-relevant key updates, which events are supported, and which surfaces are reserved. Prefer the operations helpers over raw events; nearly every supported event has one.

Update cadence

Core coalesces state publication:

  • At most one state update per microtask. A synchronous batch of internal changes produces one update, not one per change.
  • The update is skipped entirely when the derived state is unchanged.
  • Unchanged keys keep their reference identity across updates. State crosses the frame boundary by structured clone, so the bridge client restores the previous reference for each structurally unchanged key before it notifies. Object.is per key is therefore a sound change detector, which is what subscribeKey uses.
  • An object key’s identity changes exactly when its content does. For chat.messages, that is every streaming delta: the growing message body changes the array’s content each time.

One consequence: a key that flips on and off inside one synchronous batch is invisible. chat.isSending behaves this way; see below.

Display state

The tables below list the documented keys and the condition that updates each one. Keys not listed here are reserved.

Conversation keys

KeyUpdates when
app.conversationStartPendingTrue while Core resolves an App restart against proactive work, including a completed success awaiting conversation delivery. Core clears it when the request finishes or a newer conversation, session, or accepted user send replaces it. Router destruction also clears it. Optional on older Core versions; absent means false. This transient state does not acknowledge a completed conversation start.
chat.answeredInteractiveIdsThe end user answers an interactive block, or a later user message in the same conversation supersedes one. The set survives a reload. Filter interactive rows on it before enabling them.
chat.canLoadMoreA history load reports whether older pages remain.
chat.composerTextCore sets or clears its owned composer draft. Apply it only when its revision changes. Core clears it the moment a send consumes it.
chat.conversationGenerationA conversation or reset boundary occurs; the counter is monotonic.
chat.conversationIdThe conversation is established server-side. The id arrives asynchronously over the realtime channel, not with the send response, and is null before then.
chat.errorCore reports an error. A rejected send can leave an earlier send’s sending and generating state unchanged. Core clears it when a send or retry starts, or when the service recovers after an outage. A new conversation, a full reset, or dismissError also clears it.
chat.error.seqCore sets chat.error — the counter advances even when the new text is identical to the current one, which chat.error alone cannot surface. Send handles fall back to its advance on cores that predate chat.error.clientKey.
chat.error.clientKeyCore sets chat.error. Carries the failing send’s tempMessageUuid when the error belongs to one keyed send, and null otherwise. Send handles reject only when it matches their own key.
chat.isConversationActiveThe server’s conversation-active verdict changes.
chat.isGeneratingTrue from the moment an optimistic user message lands until the reply arrives or the send fails. This, not chat.isSending, is the “a reply is coming” signal.
chat.isSendingA send starts or completes. On a normal AI Agent send, both happen in one synchronous batch, so the true value is effectively never observable. It stays true only across a genuine failure gap.
chat.lastReadCursorA read report commits (after the 400 ms markRead debounce), or the persisted watermark rehydrates on reload. Not monotonic across tabs by design.
chat.loadingPreviousMessagesA history page load starts or finishes. Core does not set this flag when it ignores a request during the delay after a failed page. Retry on a new end-user scroll, not when this flag clears. See loadOlderMessages.
chat.messagesAny append, upsert, streaming delta, reaction, or history load. An optimistic user row appears immediately on send; its durable echo later reconciles in place, swapping id while carrying clientKey forward.
chat.messages.reactionsCore adds a reaction, restores reactions from history, rolls back a reaction, or clears reactions.
chat.playbookStepExecutingA Playbook step starts or finishes running server-side. While true, the composer must refuse to send: free text would be consumed as the step’s input.
chat.previousMessagesLoadSeqA successful older-history page increments this counter, including a page with no visible rows. Failures do not increment it. When it rises, request the next page if chat.canLoadMore is true. Returns to 0 when the chat is cleared. Optional on older Core versions; absent means 0.
chat.recentMessagesLoadSeqRestored or baseline history (re)loads; the counter increments. Returns to 0 when the chat is cleared. Drives the one-shot “land at the bottom of restored history” behavior.
conversation.stateData.captureThe AI Agent blocks on a capture field. isSubmitting flips true when a submit leaves the browser and false when it resolves; validationErrorMessage carries the verdict. An accepted value removes the whole object. lastSubmitFailure records a submit that failed or was dropped without a server verdict, keyed by the submitter’s own submitId.
conversation.stateData.listSelectionA list-selection block becomes active or resolves. Carries the dataId and cancel response that submit and cancel need.

Render both restart controls as busy while app.conversationStartPending is true, keeping the transcript visible. Read it through useBridgeState() or subscribe with client.subscribeKey("app.conversationStartPending", callback). Keep the controls busy until Core clears it, including after the local start-new-conversation cooldown expires. The typed constant is STATE.APP_CONVERSATION_START_PENDING.

Agent and typing keys

KeyUpdates when
agent.activeAgentAn agent becomes active, changes, or leaves. Prefer a message’s own agentInfo when naming its sender: restored history outlives an agent’s session.
liveAgent.inLiveChatA live-agent handoff connects or ends.
liveAgent.inPendingA handoff request enters or leaves the pending state.
liveAgent.liveChatEndedInConversationA live chat in this conversation ends. The next live chat, a reset, or a new conversation clears it. Reloads do not preserve it.
liveAgent.queueCancelPendingA queue-cancel request (ui.queueCancel.request) goes in flight or resolves. Disable your cancel affordance while true.
liveAgent.queuePositionThe end user’s queue position changes.
ui.botTyping.visibleThe AI Agent’s own typing indicator turns on or off (a request is in flight).
ui.typingIndicator.visibleCore’s combined typing verdict changes: the AI Agent is typing, or any agent is. Drive one indicator from this, or use the isAgentTyping helper.

Composer keys

KeyUpdates when
ui.secretMessage.buttonEnabledWhether to offer the masked-input toggle changes.
ui.secretMessage.isOpenMasked composer mode opens or closes. While true, send with sendMessage(body, { secret: true }).

Survey and End Chat keys

Survey success and terminal failure carry a signal, a survey type, and a conversation ID. Eligibility carries a latched sequence. The operations helpers implement these patterns. Inline failures match by inlineAnswerId alone.

KeyUpdates when
csat.agentSettingsHuman-agent survey settings load or change.
csat.botSettingsAI Agent survey settings load or change.
csat.endChatEligibility.seqAn eligibility answer arrives; the counter increments. There is no request id: latch the value before asking and treat yourself as pending until it advances.
csat.endChatEligibility.statusAn eligibility check starts (loading) or answers (ready).
csat.endChatEligibleThe eligibility answer arrives.
csat.endChatSurveyTargetThe eligibility answer names which survey End Chat should present.
csat.settingsCore selects AI Agent or human-agent survey settings according to handoff state.
csat.submitErrorOnly a failure with uiComplete: true and no inlineAnswerId sets the error text and both scope keys. Only a valid write with that same condition clears them. Silent failures preserve the text, sequence, and both scope keys.
csat.submitError.seqOnly terminal failures advance this counter, including repeated failures with identical text. Silent failures and error clears do not advance it. Match its advance, survey type, and conversation ID. For inline rollback, match the first answer’s inlineAnswerId against the csat.inlineSubmitErrorIds list. It holds the 200 most recent failed IDs, independent of conversation and survey type. Clear the local answer and pending full-survey cache only for that failure, without error text. Disable this rollback before a follow-up save; its failure must preserve the modal and answers. A second tap can then retry.
csat.submitErrorConversationIdTogether with csat.submitError; scopes the failure.
csat.submitErrorSurveyTypeTogether with csat.submitError; scopes the failure.
csat.submitSuccess.conversationIdTogether with the success counter; scopes the success.
csat.submitSuccess.seqA survey write succeeds with uiComplete: true, which defaults to !partial, including after a conversation boundary. submit and endConversation do not control this counter. A first-answer write with submit: true, uiComplete: false does not advance it. Match the survey type and conversation ID.
csat.submitSuccess.surveyTypeTogether with the success counter; scopes the success.
endChat.canEndWhether End Chat is offered changes: the conversation has not ended, the end user has sent in it, and a conversation id exists. Gates the header close affordance.

Transcript and file keys

KeyUpdates when
file.isUploadingA live-agent upload goes in flight or resolves. Core can reject a selection and report an error without ever setting this true, so watch file.uploadError too.
file.transferUpload progress advances.
file.uploadEnabledWhether the file upload control is enabled changes. Use this with file.uploadVisible to control access to startFileUpload.
file.uploadErrorAn upload fails or its error is dismissed.
file.uploadVisibleWhether the file upload control is visible changes. Use this with file.uploadEnabled to control access to startFileUpload.
transcript.download.statusA transcript download moves idle to pending to success or error. A request that core drops during a host-driven reset skips pending and lands on error directly.
transcript.download.status.seqEvery transcript.download.status write; the counter increments. Correlate on its advance to a terminal status, not on the standing text.
transcript.email.statusSame lifecycle, for the email-transcript request.
transcript.email.status.seqSame contract as transcript.download.status.seq, for the email request.
transcript.email.conflict.emailCore rejects an emailTranscript call for a different address while one is in flight. This key carries the rejected address, so only that caller’s promise rejects. The in-flight request is not affected.
transcript.email.conflict.seqEvery transcript.email.conflict.email write; the counter increments.

Configuration and preference keys

config.* keys carry the AI Agent’s resolved configuration and theming: name, avatar, colors, languages, and feature toggles. They change rarely after startup. Five preference keys respond to the settings helpers:

KeyUpdates when
appearance.hostViewportThe host viewport changes. Contains { width, height } in rounded CSS pixels when desktop floating frame styles can apply. It is null before the first update and when frame styles do not apply.
appearance.userTextSizeThe end user’s text-size override changes (setTextSize); core persists it.
appearance.userThemeThe end user’s theme override changes (setTheme); core persists it.
chatter.alertSoundEnabledThe alert-sound preference changes, including after setAlertSound.
chatter.languageThe conversation language changes, including after setLanguage.

appearance.hostViewport.width measures the host page’s layout viewport. Its height measures the visible viewport. Use these values to choose frame sizes at your app’s breakpoints. Call setFrameStyles again when they change. Inline embeds, mobile browsers, and native WebViews receive null.

The host page’s Web Notification permission is mirrored into the state under a legacy-named prefix. It updates when the SDK host reports the permission, and after requestNotificationPermission resolves the prompt. Permission is per-origin, so your frame’s own value is not the governing one:

client.subscribeKey("chatter.notificationPermission", (permission) => {
// "default" | "granted" | "denied"
});

Connectivity

outage.connectivityLost is the sanctioned connectivity signal. It is core’s own verdict, and it updates when core decides connectivity was lost or restored. Use it to disable send UI or to warn before End Chat. The isConnectivityLost helper reads it.

Do not substitute other signals:

  • navigator.onLine reports false positives on VPN and virtual-adapter transitions. Ada’s reference app explicitly refuses it for this reason.
  • Other outage.* keys are dismissible banner state and backoff internals, and they are reserved. A dismissed banner can read as recovered while the outage continues.

Events

Nearly every supported event has an operations helper that carries its guards and correlation logic. Send raw events only for the ones marked “send directly”, or when you need something a helper does not expose.

Lifecycle events

EventOperations helperNotes
app.error.reportreportAppErrorRenders the fallback UI and records telemetry.
app.initializenone (send directly)The required mount handshake. Your app must load and send app.initialize within 15 seconds of frame mounting. This includes document load time. Otherwise, Core unmounts your frame.
app.resetresetApp
app.startNewConversationstartNewConversationThe helper refuses during its cooldown or a published pending restart.

Conversation events

EventOperations helperNotes
capture.cancelcancelCaptureThe helper supplies the block’s cancel response and dataId.
capture.submitsubmitCaptureThe helper supplies the dataId and correlates the verdict.
chat.error.dismissdismissErrorClears chat.error in core.
chat.link.clickreportLinkClick
chat.logs.loadMoreloadOlderMessagesThe helper adds the in-flight guard.
chat.message.reaction.addaddReaction
chat.message.retryretryMessageCore reads only the message id.
chat.message.sendsendMessageWhen tempMessageUuid is an RFC 4122 uuid, core seeds the optimistic message’s clientKey from it, so the sender can find its own send in chat.messages. messageType: "secret" masks the send, and no correlatable row exists.
chat.messages.readmarkReadThe helper adds the debounce, the monotonic watermark, and the per-conversation reset.
chat.option.selectselectOption
chat.quickReply.selectselectQuickReply
chat.selectableList.submitsubmitListSelectionThe helper supplies the dataId.
chat.widget.carouselLinkClicknone (send directly)Send it when a widget message’s carousel reports a link activation, with actionType set to link or web_window and url set to a link that a message can carry: the same link-protocol policy, including the Agent’s allowed protocols, within 2048 characters. Core records a carousel link click for that message and drops a click whose actionType or url fails those checks.
listSelection.cancelcancelListSelectionCore drops a cancel without a response id; the helper guards this.

Survey and End Chat events

EventOperations helperNotes
csat.checkEndChatEligibilitycheckEndChatEligibilityThe helper latches the sequence and resolves on its advance.
csat.closenone, send directlySend { conversationId: message.conversationId ?? null, surveyType: message.surveyType } when the full survey closes, including when the row has no ID. Core ends the current surveyed conversation only for answer_flow and end_chat, without a survey write or ada:close_chat. Other survey types, including live_chat and proactive, keep the conversation open. For null, core uses the saved target from the last unstamped answer in the same generation. If a saved target belongs to an older generation, core ignores Close. Only when no saved target exists does core resolve the survey’s captured or held conversation. Core ignores a close that does not resolve to the current conversation.
csat.showntrackCsatShownFire-and-forget analytics.
csat.submitsubmitCsatPass conversationId for transcript surveys; omit it for End Chat and proactive flows. submit controls the API flag. Successful non-partial POSTs with submit: true emit ada:csat_submitted. uiComplete controls completion results and defaults to !partial. endConversation: "inline" or "endChat" reports the completed surface. Core ends the current surveyed conversation after successful UI completion only for answer_flow and end_chat. Other survey types, including live_chat, keep the conversation open. When a raw event omits both submit and uiComplete, core applies the helper’s defaults.
endChat.confirmnone (send directly)Send it every time the end user confirms End Chat. Core publishes ada:close_chat once, at that point, and not again when endChat.skipCsat or an End Chat csat.submit ends that attempt. Without it, core publishes the event when the conversation ends. A confirm covers one attempt. The next confirm, or a message sent afterward, starts a new one. csat.checkEndChatEligibility does not start a new attempt, so send it before or after the confirm.
endChat.endLiveChatendLiveChatEnds an active or pending human-agent handoff while keeping the conversation open.
endChat.skipCsatskipCsatAndEndChat

Preference, file, and chrome events

EventOperations helperNotes
appearance.setTextSizesetTextSize
appearance.setThemesetTheme
file.upload.errorDismissstartFileUpload handle’s dismissError()
file.upload.retrystartFileUpload handle’s retry()Must replay the exact retained { file, uploadId } pair.
file.upload.startstartFileUpload
outage.retryretryAfterOutage
settings.alertSound.togglesetAlertSound
settings.language.changesetLanguage
settings.notifications.requestPermissionrequestNotificationPermission
settings.transcript.download.requestdownloadTranscript
settings.transcript.email.sendemailTranscript
ui.closeButton.clickclose
ui.composer.textChangednotifyComposerChangedPayload-free by contract; the helper suppresses it in secret mode.
ui.frameStyles.setsetFrameStylesPayload: { styles: CustomAppFrameStyles }. Replaces the outer frame’s position, size, and appearance in desktop floating layouts. { styles: {} } restores defaults. Other layouts ignore it.
ui.hostViewport.setsetHostViewportExpanded
ui.minimizeButton.clickminimize
ui.queueCancel.requestnone (send directly)Leaves a live-chat queue. Payload-free. Gate the affordance on liveAgent.inPending, and disable it while liveAgent.queueCancelPending is true.
ui.secretMessage.toggletoggleSecretMessage
ui.toast.dismissdismissToast

Reserved surfaces

Any key or event this page does not list is reserved: it exists for Ada’s own app, native SDKs, or internal integrations, and it can change or disappear without notice. Do not build on reserved surfaces. This includes:

  • security.* keys and events (challenge state and control).
  • voice.* keys and events. These are annotated as reserved in the package types.
  • zendesk.* and zendeskMessaging.* keys and events (handoff SDK control).
  • liveAgent.* events (liveAgent.handoff.request, liveAgent.message.send, and the rest). Ada’s own app never sends them; handoffs are driven by core. To leave a queue, send ui.queueCancel.request. The documented liveAgent.* state keys above are read-only signals.
  • saml.* keys.
  • outage.* keys other than outage.connectivityLost, and any backoff internals.
  • image.print.request.

The reserved surfaces still appear in the package’s TypeScript declarations, because the types mirror the full wire contract. Presence in the types is not a support commitment; this page is.