WebSocket API
Use the WebSocket API for real-time live chat: instant message delivery, typing indicators, presence, auto-translation, and session lifecycle. The same socket powers visitor widgets and the agent dashboard, so you can build a custom agent console or drive live chat from your own backend.
Endpoint
wss://app.webchatagent.com/api/livechat
All traffic is JSON. You send client:* events; the server sends back livechat:* events. Every message includes a type field.
Connection Roles
When you join, you declare a role with the as field:
| Role | Description |
|---|---|
user | A website visitor in a chat. |
admin | A human agent managing live chat sessions. |
widget | The embedded chat widget in monitoring mode (before a takeover). |
The role decides what you receive: admin clients get the full message backlog and admin-only events; user clients get only new live events (visitors load their own history over the REST history endpoint, not the socket).
Quick flow
- Open the socket and send
client:joinwith yourchatbotId,sessionId, andas. - The server replies with
{ "type": "livechat:status", "ok": true }to confirm the join. Admins then receive the message backlog and a currentlivechat:statussnapshot. - Send
client:messageto post; receivelivechat:messagefor every new message. - Send
client:typing/ receivelivechat:typingfor typing indicators. - End with
client:end, or receivelivechat:endedwhen the other side closes.
Client Events (you send)
client:join
The first event after connecting. Joins the session room (and, for admins, the chatbot room).
{
"type": "client:join",
"chatbotId": "YOUR_CHATBOT_ID",
"sessionId": "SESSION_ID",
"as": "user",
"lang": "en",
"translateEnabled": true,
"retranslateBacklog": false
}
| Field | Type | Required | Description |
|---|---|---|---|
chatbotId | string | Yes | Your chatbot's UUID. For an admin monitoring all bots, the special value "all" joins without a specific room. |
sessionId | string | Yes | The chat session ID. |
as | string | Yes | user, admin, or widget. |
lang | string | No | Preferred language code (e.g. en, de, fr). Sets the translation target. |
translateEnabled | boolean | No | Enable auto-translation for this session (default: enabled). |
retranslateBacklog | boolean | No | Re-translate existing backlog messages into lang when joining (admin). |
client:message
Post a message into the session. The server persists it, translates it if enabled, and broadcasts livechat:message.
{
"type": "client:message",
"chatbotId": "YOUR_CHATBOT_ID",
"sessionId": "SESSION_ID",
"sender": "user",
"content": "I need help with my order",
"lang": "en"
}
| Field | Type | Required | Description |
|---|---|---|---|
chatbotId | string | Yes | Your chatbot's UUID. |
sessionId | string | Yes | The chat session ID. |
sender | string | Yes | user or admin. |
content | string | Yes | The message text. An empty/missing content is dropped. |
lang | string | No | Message language hint for translation. |
The first admin message in a waiting session opens it (status → open) and inserts an "agent joined" system notice automatically.
client:typing
Broadcast a typing indicator to everyone in the session.
{
"type": "client:typing",
"chatbotId": "YOUR_CHATBOT_ID",
"sessionId": "SESSION_ID",
"sender": "user",
"isTyping": true
}
client:meta
Update session metadata (current page) so agents see where the visitor is.
{
"type": "client:meta",
"chatbotId": "YOUR_CHATBOT_ID",
"sessionId": "SESSION_ID",
"pageUrl": "https://example.com/pricing",
"pageTitle": "Pricing - Example",
"referrer": "https://google.com",
"browserLanguage": "en-US"
}
client:agent_takeover
Admin proactively takes over an AI session (sets it to open / human). Admin only.
{
"type": "client:agent_takeover",
"chatbotId": "YOUR_CHATBOT_ID",
"sessionId": "SESSION_ID"
}
client:end
End the session.
{
"type": "client:end",
"chatbotId": "YOUR_CHATBOT_ID",
"sessionId": "SESSION_ID",
"reason": "resolved"
}
client:widget_open / client:widget_close
Sent by the widget to register/unregister an active session for admin visibility (this is what populates livechat:widget_sessions).
{
"type": "client:widget_open",
"chatbotId": "YOUR_CHATBOT_ID",
"sessionId": "SESSION_ID",
"pageUrl": "https://example.com",
"pageTitle": "Home",
"browserLanguage": "en-US"
}
Server Events (you receive)
livechat:status
Two forms: a bare { "type": "livechat:status", "ok": true } join acknowledgement, and a session-status snapshot (below).
{
"type": "livechat:status",
"chatbotId": "YOUR_CHATBOT_ID",
"sessionId": "SESSION_ID",
"status": "open",
"mode": "human",
"userName": "John",
"userEmail": "john@example.com",
"userLanguage": "en"
}
| Field | Type | Description |
|---|---|---|
status | string | waiting (no agent yet), open (agent connected), or closed. |
mode | string | bot or human. |
userName | string | null | Visitor name, if collected via Leads. |
userEmail | string | null | Visitor email, if collected. |
userLanguage | string | null | Detected visitor language. |
livechat:message
A new (or backlog) message.
{
"type": "livechat:message",
"id": 1024,
"sessionId": "SESSION_ID",
"sender": "admin",
"content": "Hello! How can I help you today?",
"country": "DE",
"createdAt": "2025-01-15T10:30:00Z",
"file": null,
"audience": "admin",
"historical": false
}
| Field | Type | Description |
|---|---|---|
id | number | Message ID (matches the REST history id). |
sender | string | user, admin, or system. |
content | string | Message text. For admin clients with translation on, this is already translated into the admin language. |
country | string | null | Sender's country code, if detected. |
createdAt | string | ISO 8601 timestamp. |
file | object | null | Attached file metadata (uploadId, name, mimeType, size, url, isImage) when the message is an upload. |
audience | string | null | "admin" on the admin-facing copy of a message. The user-facing copy omits it. |
historical | boolean | true for backlog messages replayed on join. |
System messages: when
senderissystem,contentis a token your client should localize, not display verbatim:__agent_joined__,__livechat_ended__, or__livechat_timeout__.
livechat:typing
Typing indicator from another participant: { type, chatbotId, sessionId, sender, isTyping }.
livechat:presence
Online/offline status of the visitor in a session: { type, chatbotId, sessionId, online } (boolean).
livechat:meta
Updated session metadata (current page, title, referrer) — admin only.
livechat:ended
The session was closed.
{
"type": "livechat:ended",
"sessionId": "SESSION_ID",
"reason": "admin_closed"
}
reason is one of admin_closed, already_closed, or timeout.
Admin-only events
| Event | Payload | Purpose |
|---|---|---|
livechat:active_count | { chatbotId, count } | Active sessions (waiting + open). |
livechat:waiting_count | { chatbotId, count } | Waiting sessions (badge for unanswered chats). |
livechat:new_request | { chatbotId, sessionId, status, mode, ... } | A new live chat needs an agent. |
livechat:widget_sessions | { chatbotId, chatbotName, sessions } | Current list of active widget sessions. |
livechat:notification | { chatbotId, sessionId, content, sender } | New message while the agent is elsewhere in the dashboard. |
Auto-Translation
The socket can translate between the visitor's and agent's languages automatically:
- Join with
translateEnabled: trueand setlangto your language. - Messages from the other party arrive already translated into your
lang. Each party keeps their original text; the translated copy is delivered to the other side. - Translation runs through Gemini, so an agent can chat with a visitor in any language without translating by hand.
Example: Custom Live Chat Client
const ws = new WebSocket('wss://app.webchatagent.com/api/livechat');
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'client:join',
chatbotId: 'YOUR_CHATBOT_ID',
sessionId: 'SESSION_ID',
as: 'user',
lang: 'en'
}));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'livechat:status':
// Join ack ({ ok: true }) or a session snapshot ({ status, mode, ... })
if (data.ok) console.log('Joined');
else console.log(`Status: ${data.status}, mode: ${data.mode}`);
break;
case 'livechat:message':
if (data.sender === 'system') {
// Localize tokens like __agent_joined__ / __livechat_ended__
console.log('[system]', data.content);
} else {
console.log(`${data.sender}: ${data.content}`);
}
break;
case 'livechat:typing':
console.log(`${data.sender} is typing: ${data.isTyping}`);
break;
case 'livechat:ended':
console.log(`Session ended: ${data.reason}`);
ws.close();
break;
}
};
function sendMessage(text) {
ws.send(JSON.stringify({
type: 'client:message',
chatbotId: 'YOUR_CHATBOT_ID',
sessionId: 'SESSION_ID',
sender: 'user',
content: text,
lang: 'en'
}));
}
function sendTyping(isTyping) {
ws.send(JSON.stringify({
type: 'client:typing',
chatbotId: 'YOUR_CHATBOT_ID',
sessionId: 'SESSION_ID',
sender: 'user',
isTyping
}));
}
