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:

RoleDescription
userA website visitor in a chat.
adminA human agent managing live chat sessions.
widgetThe 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

  1. Open the socket and send client:join with your chatbotId, sessionId, and as.
  2. The server replies with { "type": "livechat:status", "ok": true } to confirm the join. Admins then receive the message backlog and a current livechat:status snapshot.
  3. Send client:message to post; receive livechat:message for every new message.
  4. Send client:typing / receive livechat:typing for typing indicators.
  5. End with client:end, or receive livechat:ended when 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
}
FieldTypeRequiredDescription
chatbotIdstringYesYour chatbot's UUID. For an admin monitoring all bots, the special value "all" joins without a specific room.
sessionIdstringYesThe chat session ID.
asstringYesuser, admin, or widget.
langstringNoPreferred language code (e.g. en, de, fr). Sets the translation target.
translateEnabledbooleanNoEnable auto-translation for this session (default: enabled).
retranslateBacklogbooleanNoRe-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"
}
FieldTypeRequiredDescription
chatbotIdstringYesYour chatbot's UUID.
sessionIdstringYesThe chat session ID.
senderstringYesuser or admin.
contentstringYesThe message text. An empty/missing content is dropped.
langstringNoMessage 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"
}
FieldTypeDescription
statusstringwaiting (no agent yet), open (agent connected), or closed.
modestringbot or human.
userNamestring | nullVisitor name, if collected via Leads.
userEmailstring | nullVisitor email, if collected.
userLanguagestring | nullDetected 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
}
FieldTypeDescription
idnumberMessage ID (matches the REST history id).
senderstringuser, admin, or system.
contentstringMessage text. For admin clients with translation on, this is already translated into the admin language.
countrystring | nullSender's country code, if detected.
createdAtstringISO 8601 timestamp.
fileobject | nullAttached file metadata (uploadId, name, mimeType, size, url, isImage) when the message is an upload.
audiencestring | null"admin" on the admin-facing copy of a message. The user-facing copy omits it.
historicalbooleantrue for backlog messages replayed on join.

System messages: when sender is system, content is 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

EventPayloadPurpose
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: true and set lang to 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
  }));
}