message.comDevelopers

Widget namespace Live

The /widget namespace is what the embed bundle subscribes to. Anonymous handshake, identified by workspaceId plus a locally-cached visitorId. The widget bundle wires this for you. The reference here covers each event in case you build a custom client.

For the dashboard side see Agent namespace. For connection mechanics see Socket.io overview.

Handshake

Connect anonymously with the public workspace ID. If you have a cached visitor ID, pass it; otherwise the server issues one on the first visitor:connect ack. Persist that ID in local storage so the visitor reattaches to their conversation history on the next visit.

JavaScript
// The widget bundle does this for you. Shown for reference.
import { io } from "socket.io-client";

const socket = io("wss://app.message.com/widget", {
  auth: {
    workspaceId: "abc123",
    visitorId: localStorage.getItem("msg_visitor") || undefined
  },
  transports: ["websocket"]
});

socket.on("connect", () => {
  socket.emit("visitor:connect", {
    pageUrl: location.href,
    referrer: document.referrer,
    locale: navigator.language
  }, (err, ack) => {
    if (ack?.visitorId) {
      localStorage.setItem("msg_visitor", ack.visitorId);
    }
  });
});

Events received by the embed

EventPayloadFires when
message:receivedMessage with agent detailsAgent sent a reply.
agent:typing{ conversationId, agentId, agentName }Agent typing indicator starts.
agent:typing-stopped{ conversationId, agentId }Agent typing indicator clears.
conversation:assignedConversation with assignedAgentAgent picked up the chat; the widget shows their name and avatar.
conversation:closedConversationThe chat was closed by the agent. The widget surfaces the post-chat survey.
visitor:identifiedVisitor recordServer confirmed an identify call.

Events emitted by the embed

EventPayloadEffect
visitor:connect{ pageUrl, referrer, locale }Initial handshake. Server issues a visitorId if none provided.
visitor:identifyVisitor identification payloadLinks session to a customer record. HMAC-signed in production.
message:send{ body, attachments? }Visitor sends a message. Creates a conversation on the first message.
message:typingNoneBroadcast typing indicator to the assigned agent.
message:typing-stoppedNoneClear the typing indicator.

visitor:identify

Promotes an anonymous visitor to an identified record. In production, the payload must include a userHash signature; without it the server records the identification but flags the record as unverified.

JavaScript
socket.emit("visitor:identify", {
  email: "[email protected]",
  userId: "crm-4567",
  userHash: "<server-side HMAC>",
  name: "Alice Johnson",
  plan: "pro"
});

For HMAC computation, see Identifying visitors.

message:send

Sends a visitor message. The first message:send in a session creates a new conversation; subsequent messages append to the latest open conversation for that visitor.

JavaScript
socket.emit("message:send", {
  body: "Hi, I have a question about my order #4567"
}, (err, msg) => {
  if (err) console.error(err);
  // msg is the persisted visitor message
});

Errors: rate_limited for burst (3 messages per second), workspace_offline if the workspace has been suspended, visitor_blocked if the visitor is on the abuse list.

message:typing

Debounce and emit while the visitor is typing. Stop after 1.5 seconds of inactivity. The widget bundle handles this; if you build a custom client, the pattern is:

JavaScript
let typingTimer;
input.addEventListener("input", () => {
  socket.emit("message:typing");
  clearTimeout(typingTimer);
  typingTimer = setTimeout(() => {
    socket.emit("message:typing-stopped");
  }, 1500);
});

Reconnection and offline behaviour

If the socket disconnects mid-conversation, the widget shows a quiet “reconnecting” chip in the header. Messages composed during a disconnect are queued locally and flushed on reconnect. The server replays any agent messages the visitor missed (up to the 5-minute replay window).

Common pitfalls

  • Not persisting the visitorId. If you do not write the ID to local storage after the first connect ack, every page reload starts a new visitor record. Your inbox fills with duplicates.
  • Identifying without HMAC. Without userHash, anyone with dev tools can pretend to be anyone. Always sign in production.
  • Skipping the ack on message:send. Without the ack you cannot tell if the message was rate-limited or rejected.
  • Spamming typing events. Debounce them. Sending one per keystroke wastes bandwidth and creates flicker on the agent side.