Every frame on the realtime feed is one JSON object with the same envelope. Only some fields are filled for each event type; the rest are null or absent.
The envelope
{
"type": "MESSAGE_CREATED",
"orgId": 12,
"conversationId": 5,
"occurredAt": "2026-11-07T09:00:00Z",
"message": { "id": 1042, "conversationId": 5, "senderType": "<sender>", "messageType": "TEXT", "content": "Hello" },
"ticket": null
}| Field | Type | Notes |
|---|---|---|
type | string | The event type. Ignore types you do not know; more can be added. |
orgId | integer | Your organisation. |
conversationId | integer or null | Set for events about one conversation. |
occurredAt | string | ISO 8601 instant. |
message | object or null | A timeline message. See below. |
ticket | object or null | A ticket summary. Same shape as the Inbox ticket list. |
notification | object or null | NOTIFICATION_CREATED only. |
agentId | integer or null | Presence and typing or viewing frames. |
presenceStatus | string or null | AGENT_PRESENCE_UPDATED only. |
typingSenderType | string or null | TYPING_* only: AGENT or CONTACT. |
userId, displayName | integer, string or null | Who is typing or viewing. Drop frames carrying your own userId. |
nodeId, delta | string or null | AI reply streaming only. |
templateName, templateStatus | string or null | TEMPLATE_STATUS_CHANGED only. |
waitReasons | object or null | WAIT_REASONS_CHANGED only. |
Event types
| Type | Arrives on | Fields filled | Meaning |
|---|---|---|---|
MESSAGE_CREATED | conversation topic; user queues of the ticket's assignee and followers | message | A new message, in or out. Also arrives to Web Chat visitors on their conversation. |
MESSAGE_UPDATED | conversation topic; user queues | message | A message's delivery status changed (message.status). |
PRIVATE_NOTE_CREATED | conversation topic; user queues of assignee, followers and author | message | An internal note. Never sent to Web Chat visitors. Its message has senderType: "AGENT", messageType: "PRIVATE_NOTE" and channelType: "INTERNAL". |
TICKET_CREATED | conversation topic; org tickets topic; user queues | ticket, conversationId | A ticket was created. |
TICKET_ASSIGNED | same | ticket | A ticket was assigned or reassigned. |
TICKET_UPDATED | same | ticket | Any other ticket change. |
AGENT_PRESENCE_UPDATED | org agents topic | agentId, presenceStatus | An agent's presence changed, for example to OFFLINE. |
NOTIFICATION_CREATED | the target user's queue | notification | A persisted notification for that user. |
TYPING_ON, TYPING_OFF | conversation .agents topic for staff; the conversation topic for the customer | conversationId, typingSenderType; when the typer is an AGENT, also agentId, userId and displayName (null for a CONTACT) | Someone started or stopped typing. |
VIEWING_ON, VIEWING_OFF | org agents topic | conversationId, agentId, userId, displayName | An agent opened or left a conversation. Repeated every 20 seconds while open. |
LIVE_METRICS_CHANGED | org tickets and agents topics | none | A live-board figure moved; refetch GET /reports/live. At most once per second per org and topic. |
WAIT_REASONS_CHANGED | org tickets topic | waitReasons | Waiting tickets' reasons changed. One frame per dispatch pass. |
PRIORITY_CASES_CHANGED | org tickets topic | none | The priority scale changed; refetch GET /priority-cases. |
DASHBOARD_METRICS_INVALIDATED | org dashboard topic | none | Dashboard figures are stale; refetch them. |
TEMPLATE_STATUS_CHANGED | org dashboard topic | templateName, templateStatus | A WhatsApp template's approval status changed. |
AI_REPLY_DELTA | conversation topic | conversationId, nodeId, delta | One fragment of an AI reply being written. Append in order; fragments can split a word. |
AI_REPLY_END | conversation topic | conversationId, nodeId | The stream is finished; the full message follows as MESSAGE_CREATED. |
TYPING_* and VIEWING_* are the frames behind the two frames you can send.
waitReasons
{ "tickets": [ { "ticketId": 77, "reason": "<reason code>" } ], "truncated": false }Each entry names a ticket and its new reason, or null when nothing keeps it waiting any more. truncated: true means more tickets changed than the frame lists; refetch the list. The list of reason values is not published here; read them from the ticket list.
The message object
| Field | Type | Notes |
|---|---|---|
id | integer or null | Message id. |
conversationId | integer or null | |
senderType | string | Who sent it, such as the contact, an agent or a bot. |
messageType | string | TEXT, PRIVATE_NOTE, REACTION and the rich types (uppercase). |
channelType | string or null | The channel. |
originType, originId, campaignId | string, integer, integer or null | Where it came from, for example a campaign. |
content | string or null | Plain text. |
contentJson | string or null | Rich content, as a string of JSON. Parse it yourself. |
status | string or null | Delivery status. |
providerMessageId | string or null | The channel's own id. |
createdAt | string or null | ISO 8601. |
privateNote | boolean | true for an internal note. |
noteKind, transferEventId | string, integer or null | TRANSFER_NOTE marks a handover note. |
errorMessage | string or null | Why sending failed, when it did. |
originKind | string or null | MACRO, AUTOMATION_RULE or AUTO_RESOLVE when automation sent it; null for a direct action. |
sourceId | integer or null | The id of the macro or rule that sent it, with originKind. |
attachments | array | Files attached to the message. |
replyToMessageId | integer or null | The message this one replies to. |
The allowed values of senderType and messageType are not listed here because we have not confirmed the full sets; handle unknown values gracefully.
Handling events
function handle(event) {
switch (event.type) {
case 'MESSAGE_CREATED':
return addMessage(event.conversationId, event.message)
case 'TICKET_ASSIGNED':
case 'TICKET_UPDATED':
return upsertTicket(event.ticket)
case 'LIVE_METRICS_CHANGED':
return refetchLiveBoard() // carries no numbers
default:
return // unknown types are safe to ignore
}
}Events carry changes, not state, and are not replayed after a reconnect. Refetch the lists you care about whenever you reconnect.