Technical docs
Chat Widget
Overview
Support reference for the current `window.ChatWidget` loader and public webchat widget runtime.
Global loader API
- `window.ChatWidget` currently exposes:
- `init(options)`
- `open()`
- `close()`
- `destroy()`
- `sendMessage(content)`
- `applySettings(settings)`
- `updateStyle(variables)`
`init(...)` options
- Required:
- `apiKey`
- Optional:
- `apiUrl`
- `wsUrl`
- `eventId`
- `contact`
- `language` = `en` or `ru`
- `isOpen`
- `positioning` = `fixed` or `absolute`
- `container`
- URL resolution order is:
- explicit `init(...)` value
- env URL derived from the widget script host
- build env fallback
- Loader init hard-fails if `apiKey`, `apiUrl`, or `wsUrl` resolve to empty values.
Loader lifecycle
- `init(...)` creates:
- the outer widget container
- the button iframe
- the chat iframe container shell
- The chat iframe is still created lazily on first open.
- Greeting iframe code exists, but greeting is disabled with:
- `GREETING_ENABLED = false`
- In `absolute` mode, the loader appends into the configured container and forces that container to `position: relative` if needed.
- In `fixed` mode, the loader appends to `document.body`.
- When the user opens chat before the chat iframe is ready, the loader now shows a loading shell inside the chat container until the iframe finishes initializing.
- When the widget is closed, the launcher button can now be dragged horizontally to the left or right side.
- The chosen launcher side is persisted in widget local storage and reused on the next load.
Loader messaging and settings
- The loader bootstraps the button iframe first.
- The button iframe fetches widget settings from:
- `GET /api/public/webchat/init`
- When those settings load, the button iframe posts them back to the loader as `settingsLoaded`.
- The chat iframe is initialized separately on first open with:
- `apiUrl`
- `wsUrl`
- `apiKey`
- `eventId`
- `contact`
- current settings
- `language`
- `isOpen`
- optional `parentColorScheme`
- `applySettings(...)` pushes settings into button/chat/greeting iframes.
- `updateStyle(...)` only pushes CSS variable overrides to:
- button iframe
- chat iframe
- For system-themed widgets, the loader now derives parent color scheme from the host page using this order:
- root inline `style.colorScheme`
- root `data-theme`
- root `dark` / `light` class
- computed `color-scheme`
- browser `prefers-color-scheme` fallback
Resize and iframe behavior
- Chat iframe permissions are:
- `microphone`
- `camera`
- `autoplay`
- `display-capture`
- `clipboard-write`
- `clipboard-read`
- Desktop resize handles are attached only on the side opposite the current launcher position:
- launcher on right: `n`, `w`, `nw`
- launcher on left: `n`, `e`, `ne`
- Resize handles are disabled on viewports at `666px` wide and below.
- Minimum chat size is:
- `400 x 400`
- Resize clamping keeps the widget inside the viewport/container with:
- `25px` edge padding
- `110px` bottom padding
- On fixed-position mobile viewports, opening the widget currently locks parent-page scroll until the chat is closed.
Runtime bootstrap and session persistence
- The widget now bootstraps conversation state from:
- `GET /api/public/webchat/conversations`
- Current list query params are:
- `event_id`
- `page`
- `limit`
- optional `status`
- optional `contact_external_id`
- optional `contact_email`
- optional `contact_phone`
- The widget still sends the stored `webchat-token` header when present.
- If a stored token becomes invalid because the request contact scope no longer matches it, the widget clears the token locally and retries the request once without it for recoverable `400`/`401`/`403` responses.
- The current widget stores public-session state in local storage under:
- `alloy-webchat`
- The same storage key is shared by loader state and chat runtime state.
- The current loader also stores launcher-side preference inside that same widget storage state.
Conversation management
- The widget now supports multiple public webchat conversations for the current contact/session scope.
- Welcome state shows recent active conversations before a thread is opened.
- A dedicated history panel lists active and archived conversations.
- The widget currently calls:
- `GET /api/public/webchat/conversations`
- `POST /api/public/webchat/conversations/new`
- `PATCH /api/public/webchat/conversations/{conversation_id}/title`
- `PATCH /api/public/webchat/conversations/{conversation_id}/archive`
- `PATCH /api/public/webchat/conversations/{conversation_id}/unarchive`
- `DELETE /api/public/webchat/conversations/{conversation_id}`
- Conversation mutation requests currently include:
- `event_id`
- optional `contact`
- Archive/unarchive/title/delete currently require a live session token on the backend.
- Deleting a conversation is optimistic in the widget UI.
- If a conversation disappears while the widget is viewing it, the widget clears local thread state and falls back to welcome state.
Message loading and send flow
- Opening a conversation now loads:
- `GET /api/public/webchat/messages`
- Current message query params are:
- `event_id`
- `conversation_id`
- `limit`
- optional `limiter`
- optional `contact_external_id`
- optional `contact_email`
- optional `contact_phone`
- Older-history loading now uses cursor-style `limiter` pagination, not `page`.
- Message send now requires an active conversation and sends:
- `message`
- `conversation_id`
- `event_id`
- optional `contact`
- optional `attachments`
- Voice-session creation also now requires an active conversation and sends:
- `conversation_id`
- optional `contact`
- Reaction updates now send:
- `conversation_id`
- `event_id`
- optional `contact`
- `reaction`
- Current widget reaction path still calls:
- `PUT /api/public/webchat/messages/{message_id}/reaction`
- The backend still also exposes:
- `PUT /api/public/webchat/messages/{message_id}/feedback`
Message behavior
- System events render as centered system-log rows rather than regular assistant or customer bubbles.
- Current system-log icon variants distinguish operator joined, AI rejoined, waiting-for-operator, and generic system notices.
- The widget listens for:
- `webchat.message_sent`
- `webchat.message_updated`
- `webchat.message_deleted`
- `webchat.conversation_updated`
- conversation summary updates and deletes for the history list
- Incoming websocket events are ignored if they do not match the current `conversation_id`.
- The widget hides messages when:
- `status === 'in_progress'`
- content is empty and there are no attachments
- Assistant messages can trigger the typing indicator after a contact message when `aiAgentEnabled` is true.
- The typing indicator is cleared when:
- a non-system assistant-side message arrives
- the conversation updates to `has_assigned_operator = true`
- During assistant typewriter rendering, the widget now preserves the currently streamed partial message instead of clearing it during intermediate re-renders.
- The header title prefers:
- assigned operator name
- and appends AI employee name when both exist
Navigation behavior
- Desktop uses a header action to open the conversation history panel.
- On mobile, the widget currently has three navigation screens:
- `welcome`
- `history`
- `chat`
- On mobile, users can swipe right to go back to the previous screen in that local navigation stack.
- The widget also reacts to the loader's `mobileBrowserBack` message to step back through that same mobile navigation flow.
Theming and dark mode
- The widget theme uses runtime CSS variables such as:
- `--chat-background`
- `--primary-text-color`
- `--secondary-text-color`
- `--customer-text-color`
- `--primary-color`
- Current chat styles also derive neutral overlay colors from `--primary-text-color` so hover, search, button-state, and history-row surfaces stay visible in both light and dark themes.
- In dark theme, the welcome-state avatar icon now contrasts against the brand-colored avatar tile using `--chat-background` instead of a hardcoded light icon color.
- In dark theme, user-bubble timestamps are derived from `--customer-text-color` so they stay readable even when the customer bubble is light.
- Loader and conversation-switch screens now use `--chat-background` instead of a hardcoded white background.
- Welcome-state `View all` text and history-search / header button states now inherit theme-adaptive colors instead of fixed dark-on-light assumptions.
Attachments
- Current widget client supports:
- file picker
- drag and drop
- screen capture
- Client-side limits are:
- up to `5` files
- max `10 MB` per file
- Images render inline.
- PDFs open in a new tab.
- Other files download through a generated anchor click.
Formatting and previews
- Conversation rows now use normalized preview text from the latest message.
- The current widget strips fenced-code language labels out of conversation preview text.
- Strikethrough formatting is rendered again in the current chat message formatter.
Voice behavior
- Voice session creation currently requires `eventId`.
- Voice setup uses:
- `POST /chat/realtime/create-session/{eventId}`
- websocket connect to `/chat/realtime?session={sessionId}`
- Voice websocket can send:
- binary audio
- JSON `user_message` payloads with optional attachments
- Voice event handling currently reacts to:
- `session.created`
- `speech_started`
- `speech_stopped`
- `response.cancelled`
- `error`