State and events

Your handle’s Allowed websites list controls custom apps. In your Ada dashboard, go to Channels > Chat and 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
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.errorA send fails (this also clears the sending and generating flags). Cleared only by dismissError.
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.
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.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.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.

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.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.

Survey and End Chat keys

The survey keys implement two correlation patterns. Success and failure each carry a triple (a signal plus a survey type plus a conversation id), and eligibility carries a latched sequence. The operations helpers implement both patterns; the raw keys are documented for completeness.

KeyUpdates when
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.submitErrorA survey submit fails, or a score is invalid for its scale. Cleared to null at the start of every non-partial submit.
csat.submitError.seqCore sets csat.submitError to a message. The counter advances even when the new text is identical, which the text alone cannot surface. Correlate error verdicts on its advance plus the type and conversation match.
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 terminal survey submit succeeds; the counter increments. A partial submit does not bump it. Match survey type and conversation id when it advances; type alone lets a late answer from a dead conversation close the wrong survey.
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.uploadErrorAn upload fails or its error is dismissed.
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. Three preference keys respond to the settings helpers:

KeyUpdates when
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.

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:

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

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. Send it within 15 seconds of frame load, or core unmounts your frame.
app.resetresetApp
app.startNewConversationstartNewConversationThe helper adds the cooldown latch.

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.
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.showntrackCsatShownFire-and-forget analytics.
csat.submitsubmitCsatPass conversationId for in-transcript survey rows; omit it for End Chat and proactive flows.
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.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.