message.comDevelopers

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).

JavaScript
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.

EventPayloadFires when
conversation:newConversationNew conversation appears in the queue.
conversation:updatedConversationStatus, assignee, or department changed.
conversation:transferredConversation + fromAgentIdSomeone transferred a conversation to you.
conversation:transfer-timeout{ conversationId, fromAgentId }Pending transfer expired (30s window).
message:receivedMessageVisitor 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:ringingCall eventIncoming call, see Call events.
call:answeredCall eventCall was picked up.
call:on_holdCall eventCall placed on hold.
call:resumedCall eventCall resumed from hold.
call:transferredCall eventCall transferred to another agent or department.
call:endedCall event with duration_msCall hung up.
call:voicemailCall event with recordingUrlVoicemail 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.

JavaScript
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.

JavaScript
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.

JavaScript
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_taken and queue_empty are normal states, not errors. Branch on them, do not throw.
  • Missing expectedVersion on message: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 inside useEffect and unsubscribe in the cleanup.
  • Polling on top. If you also poll REST you will render duplicate messages. Pick one source per surface.