Agent namespace Live
The /agent namespace is what the dashboard subscribes to. It streams every inbox event for the authenticated agent and accepts emits for actions like claiming the next chat, sending messages, and controlling calls.
For the connection handshake and reconnect rules see Socket.io overview. For the visitor side see Widget namespace.
Connecting
Authenticate with a workspace JWT in auth.token. The server attaches the socket to one room per workspace, plus per-conversation rooms for the conversations the agent has access to (filtered by role and department scope).
import { io } from "socket.io-client";
const socket = io("wss://app.message.com/agent", {
auth: { token: workspaceJwt }
});
// Receive: new conversation in the queue
socket.on("conversation:new", (conv) => {
// shape matches GET /api/v1/conversations:[i]
console.log("new conv", conv.id, conv.channel);
});
// Receive: agent reply landed
socket.on("message:received", (msg) => {
console.log("msg", msg.conversationId, msg.body);
});Events received
Subscribe with socket.on(eventName, handler). Payload shapes match the REST resource shapes one-for-one.
| Event | Payload | Fires when |
|---|---|---|
conversation:new | Conversation | New conversation appears in the queue. |
conversation:updated | Conversation | Status, assignee, or department changed. |
conversation:transferred | Conversation + fromAgentId | Someone transferred a conversation to you. |
conversation:transfer-timeout | { conversationId, fromAgentId } | Pending transfer expired (30s window). |
message:received | Message | Visitor sent something in any of your conversations. |
message:typing | { conversationId, visitorId } | Visitor typing indicator. |
message:typing-stopped | { conversationId, visitorId } | Visitor stopped typing. |
visitor:online | { visitorId, since } | Visitor opened a session. |
visitor:offline | { visitorId, since } | Visitor closed all sessions. |
agent:online | { agentId, status } | Teammate came online. |
agent:offline | { agentId } | Teammate went offline. |
call:ringing | Call event | Incoming call, see Call events. |
call:answered | Call event | Call was picked up. |
call:on_hold | Call event | Call placed on hold. |
call:resumed | Call event | Call resumed from hold. |
call:transferred | Call event | Call transferred to another agent or department. |
call:ended | Call event with duration_ms | Call hung up. |
call:voicemail | Call event with recordingUrl | Voicemail left. |
take-next
Atomic claim of the next available conversation. The server picks the oldest unassigned conversation in the agent's channel and department scope, assigns it to the caller, and acks with the conversation object. If another agent claimed it first, the server returns already_taken. If nothing is queued, queue_empty.
socket.emit("take-next", { channel: "chat" }, (err, conv) => {
if (err === "already_taken") {
// another agent claimed it first
return;
}
if (err === "queue_empty") {
return;
}
if (err) throw new Error(err);
// conv is yours
});message:send
Send an agent message into a conversation. The payload mirrors the REST POST body. The server returns the persisted message in the ack and fans the event out to the visitor over the widget namespace.
socket.emit("message:send", {
conversationId: "uuid",
body: "Thanks, looking into it now.",
expectedVersion: 4
}, (err, msg) => {
if (err) console.error(err);
});Errors: stale_version if expectedVersion mismatches, not_assigned if you do not own the conversation, rate_limited on burst.
agent:status
Set the agent presence flag. The dashboard surfaces these to the team and to routing rules.
socket.emit("agent:status", { status: "available" });
socket.emit("agent:status", { status: "away" });
socket.emit("agent:status", { status: "offline" });See Authentication and presence for the full state machine and how status influences routing.
Other emits
message:typing, broadcast agent typing indicator to the visitor.message:typing-stopped, clear it.call:answer, pick up a ringing call. See Call events.call:hangup, end the active call.call:hold, put the active call on hold.call:resume, resume from hold.call:transfer, transfer to another agent or department.
Rooms and fan-out
Server-side, every authenticated socket is joined to a workspace room plus per-conversation rooms for conversations the agent can access. Events fan out to the matching rooms. You do not need to manage rooms yourself; the server controls membership based on role, department scope, and explicit assignment.
Common pitfalls
- Treating ack errors as exceptions.
already_takenandqueue_emptyare normal states, not errors. Branch on them, do not throw. - Missing
expectedVersiononmessage:send. Without it, two agents typing on the same conversation can stomp each other. Pass the version from the latest GET. - Subscribing twice. Calling
socket.on('message:received', ...)multiple times stacks handlers. In React, register insideuseEffectand unsubscribe in the cleanup. - Polling on top. If you also poll REST you will render duplicate messages. Pick one source per surface.