Communication ServiceIntegration guide

Realtime integration / v1

One command in.
One response out.

Create an isolated session over HTTP, authenticate a Socket.IO participant, then route every application message through two stable event names. Types carry the behavior; transport wiring stays fixed.

Client → serversession:command Server → clientsession:response
01

Start here

Quickstart

  1. Create a sessionUse the service API token and the tenant that owns the room.
  2. Issue a participant JWTSign an HS256 token with id and roles claims. Other application claims are rejected.
  3. Connect and route by typeSend commands and switch on typed responses.
JavaScript · complete connection
const socket = io(SERVICE_URL, {
  path: "/socket.io",
  auth: {
    protocolVersion: 2,
    sessionId,
    accessToken,
    username,
  },
});

socket.on("session:response", ({ type, payload }) => {
  switch (type) {
    case "session.ready":
      console.log("Protocol", payload.protocolVersion);
      console.log("Supported commands", payload.commandTypes);
      console.log("Chat preference", payload.chatPreference, payload.effectiveChatMode);
      break;
    case "session.state":
      renderRoom(payload);
      break;
    case "server.error":
      console.error(payload.code, payload.message);
  }
});

socket.emit("session:command", {
  type: "message.send",
  payload: { body: "Hello, room", correlationId: "msg-001" },
}, (result) => console.log(result));
02

Session control plane

HTTP API

All session endpoints require Authorization: Bearer <SESSION_API_TOKEN>.

POST/sessionsCreate a canonical session
The type field accepts Support, Live_Chat, CourseChat, or Community. Optional initialState seeds messages, pins, and settings. Only Support sessions use agent reply and read indicators.

Request body

{
  "tenantId": "tenant-acme",
  "type": "CourseChat",
  "initialState": {
    "chatMode": "private",
    "messages": [{
      "id": "welcome-1",
      "body": "Welcome to the course",
      "userId": "instructor-1",
      "username": "Instructor",
      "sentByAdmin": true
    }],
    "pinnedMessageId": "welcome-1"
  }
}

Success · 201

{
  "schemaVersion": 5,
  "sessionId": "…",
  "tenantId": "tenant-acme",
  "type": "CourseChat",
  "lifecycle": "idle",
  "revision": 0,
  "chatEnabled": true,
  "chatMode": "private",
  "knownParticipants": [],
  "participants": [],
  "messages": [{ "id": "welcome-1", "body": "Welcome to the course", "sessionId": "…", "tenantId": "tenant-acme", "userId": "instructor-1", "username": "Instructor", "sentByAdmin": true, "sentAt": "…" }],
  "pinnedMessageId": "welcome-1",
  "lastMessage": "Welcome to the course",
  "lastMessageSentAt": "…",
  "blockedUserIds": [],
  "hasPendingAgentReply": false,
  "hasUnreadUserMessagesForAgent": false,
  "unreadUserMessageCountForAgent": 0
}
initialState accepts chatEnabled, chatMode, knownParticipants, blockedUserIds, messages, pin fields, and a Support agentLastReadMessageId. Message id and sentAt are optional; session and tenant IDs are assigned by the service. Pins and read watermarks must refer to supplied messages. Server-owned and unknown fields return 400.
GET/sessions/:sessionIdRead canonical state
Includes pinnedMessage alongside pinnedMessageId when a message is pinned. Returns 404 when the session does not exist.
GET/admin/sessionsList tenant sessions with filters

Query

?tenantId=tenant-acme&type=Support&read=false&replied=false&page=1&limit=10
lastMessagedFrom=2026-09-01T00:00:00.000Z
lastMessagedTo=2026-09-30T23:59:59.999Z

Success · 200

{
  "threads": [{
    "sessionId": "…",
    "type": "Support",
    "lastMessage": "Need help",
    "lastMessagedDate": "2026-09-22T10:00:00.000Z",
    "participants": ["Ada Lovelace", "Agent Smith"],
    "read": false,
    "unreadUserMessageCountForAgent": 1,
    "replied": false
  }],
  "count": 1,
  "page": 1,
  "limit": 10,
  "total": 1,
  "totalPages": 1
}
tenantId is required. page starts at 1; limit defaults to 10 and cannot exceed 10. count is the current page size, while total and totalPages cover all matching threads. read and replied apply only to Support threads; supplying either without type implies Support. Support records include unique participant usernames, retained after disconnect. See how the unread count works.
Unread count

For a Support session, read unreadUserMessageCountForAgent from each GET /admin/sessions thread or from the session.state payload. It counts user messages after the agent read watermark. read is false when the count is above zero. The count becomes zero when the agent reads through those messages or replies. For other session types, admin thread records return null; their session state returns 0.

POST/sessions/infoRead lightweight info for multiple rooms

Request body

{ "roomIds": ["room-1", "room-2"] }

Success · 200

{
  "rooms": [{
    "roomId": "room-1",
    "type": "Support",
    "lastMessage": "Hello",
    "lastMessageSentAt": "2026-08-19T10:00:00.000Z"
  }],
  "notFound": ["room-2"]
}
Requires a non-empty roomIds array. Unknown IDs are returned in notFound.
03

Realtime data plane

Socket contract

Handshake auth

Pass protocol version 2, the existing session ID, signed socket JWT, and username in Socket.IO's auth object.

{
  protocolVersion: 2,
  sessionId: "session-id",
  accessToken: "participant-jwt",
  username: "Ada Lovelace"
}

Participant JWT

The token is signed with the configured HS256 secret. A non-empty id identifies the participant; roles is an array of non-empty strings.

{
  "id": "user-42",
  "roles": ["user"],
  "iat": 1787300000
}
emitsession:command

{ type, payload? } plus an acknowledgement callback.

listensession:response

{ type, payload } as a discriminated response envelope.

04

Build client logic here

Response types

TypeWhenPayload
session.readyAuthentication and room admission succeeded.{ commandTypes, protocolVersion, chatPreference, effectiveChatMode }
session.stateInitial viewer-filtered snapshot with the visible pinned message.SessionStateResponse
message.createdEvery newly accepted message, routed by effective chat mode.MessageView
message.updatedA message was edited by its author.MessageView
message.read.updatedAn agent advanced the Support read watermark; includes the unread flag and count.MessageReadUpdated
chat.history.replacedVisibility changed; replace cached messages and visible pinned message.ChatHistoryReplaced
chat.history.pageOlder visible messages and the visible pinned message.ChatHistoryPage
chat.mode.updatedAn admin changed the session-wide mode.ChatModeUpdated
chat.preference.updatedThe user's persistent personal preference changed.ChatPreferenceUpdated
participant.joined / participant.leftMembership changed after the initial snapshot.SessionView / ParticipantLeft
server.errorThe server rejected the connection.{ code, message }
hand.updatedA hand request was submitted, accepted, or rejected.HandUpdated
message.pin.updatedAn admin pinned or removed the room's pinned message; pinning includes the message.MessagePinUpdated
message.deletedA visible message was deleted; includes the resulting Support unread flag and count.MessageDeleted
user.block.updatedAn admin changed a user's message-sending permission.UserBlockUpdated
chat.updatedAn admin enabled or disabled room chat.ChatUpdated
05

Supported operation

Commands

message.sendAppend a message to canonical room state.

Payload

body
string · required · 1–4,000 characters
correlationId
string · optional · up to 100 characters
type
optional · audio, video, image, pdf, poll, or live-session
ref
string · optional · reference for the typed content

Example: { body: "Diagram", type: "image", ref: "asset://diagram-42" }

Acknowledgement

// success
{ ok: true, data: { revision: number } }

// failure
{ ok: false, error: {
  code: "COMMAND_FAILED",
  message: "…"
} }
Connected, unblocked members can send while chat is enabled. Admins can still send when chatEnabled is false.
message.readMark Support user messages read through a target message.

Command

{
  type: "message.read",
  payload: { messageId: "message-id" }
}

Behavior

Requires roles: ["admin"] and a Support session. The agent read watermark advances through the target message. The session receives message.read.updated with the resulting unread flag and count.

chat.history.pageLoad older visible messages.

Payload

{ before?: "message-id-or-nextCursor", limit?: 50 }

before accepts a visible message ID or a returned nextCursor. limit is capped at 100.

Response

The requesting socket receives chat.history.page with messages, nextCursor, hasMore, and any visible pinnedMessageId and pinnedMessage. Prepend older pages; the pin is supplied separately from page messages.

hand.updateRequest a hand raise or resolve it as an admin.

Participant

{
  type: "hand.update",
  payload: { status: "requested" }
}

Participants may use requested or rejected for their own hand.

Admin

{
  type: "hand.update",
  payload: {
    participantSocketId: "socket-id",
    status: "accepted"
  }
}

Admins resolve another participant's request with accepted or rejected.

message.pin / message.unpinSet or remove the room's single pinned message.

Pin

{
  type: "message.pin",
  payload: { messageId: "message-id" }
}

The message must exist in this session.

Unpin

{ type: "message.unpin" }

Both commands require roles: ["admin"]. Visible members receive message.pin.updated. Pinning includes pinnedMessage; unpinning sends the old messageId with pinned: false and no message.

message.editEdit your own message in a Support, CourseChat, or Community session.

Command

{
  type: "message.edit",
  payload: { messageId: "message-id", body: "Updated text" }
}

Behavior

Available to the message author in Support, CourseChat, and Community sessions (not Live_Chat), including admins editing their own messages. The response is message.updated; the message keeps its original sentAt and gains editedAt and editedByUserId.

message.deletePermanently remove a message from the current session.

Command

{
  type: "message.delete",
  payload: { messageId: "message-id" }
}

Behavior

In Support sessions, users may delete their own messages and admins may delete any message. In other session types, deletion remains admin-only. Deleting the pinned message also clears the pin atomically. Visible room members receive message.deleted with the resulting unread flag and count.

user.block / user.unblockControl a user's ability to send room messages.

Block

{
  type: "user.block",
  payload: { userId: "student-id" }
}

Unblock

{
  type: "user.unblock",
  payload: { userId: "student-id" }
}

Requires roles: ["admin"]. Blocked users remain connected and continue receiving incoming messages.

chat.toggleEnable or disable member message sending for the room.

Command

{ type: "chat.toggle" }

Behavior

Requires roles: ["admin"]. When disabled, member message.send commands are rejected while admins can still send. Members stay connected and receive chat.updated and state updates.

chat.mode.set / chat.preference.setControl session-wide or personal message visibility.

Admin session mode

{ type: "chat.mode.set", payload: { mode: "private" } }

Requires roles: ["admin"].

Participant preference

{ type: "chat.preference.set", payload: { mode: "private" } }

Persists by session and user. The effective mode is private when either setting is private.

06

Canonical model

State schema

SessionStateRoom snapshot
interface SessionState {
  schemaVersion: 5;
  sessionId: string;
  tenantId: string;
  type: "Support" | "Live_Chat" | "CourseChat" | "Community";
  lifecycle: "idle" | "active" | "draining";
  revision: number;
  createdAt: string;
  updatedAt: string;
  knownParticipants: { userId: string; username: string }[];
  participants: SessionView[];
  messages: MessageView[];
  lastMessage: string | null;
  lastMessageSentAt: string | null;
  pinnedMessageId?: string;
  pinnedAt?: string;
  pinnedByUserId?: string;
  blockedUserIds: string[];
  chatEnabled: boolean;
  chatMode: "all" | "private";
  hasPendingAgentReply: boolean;
  hasUnreadUserMessagesForAgent: boolean;
  unreadUserMessageCountForAgent: number;
  pendingReplyMessageId?: string;
  pendingReplySince?: string;
  agentLastReadMessageId?: string;
  agentLastReadMessageSentAt?: string;
  messagesNextCursor?: string;
  messagesHasMore?: boolean;
}

The session.state snapshot also includes pinnedMessage?: MessageView when the pinned message is visible, even if it is outside the latest 50 messages. Support unread count is the number of user messages after the agent read watermark; the unread flag is true when the count is positive. Other session types have false indicators and a zero count.

CreateSessionRequestOptional initial data
interface CreateSessionRequest {
  tenantId: string;
  type: "Support" | "Live_Chat" | "CourseChat" | "Community";
  initialState?: {
    chatEnabled?: boolean;
    chatMode?: "all" | "private";
    knownParticipants?: { userId: string; username: string }[];
    blockedUserIds?: string[];
    messages?: {
      body: string;
      userId: string;
      username: string;
      sentByAdmin: boolean;
      id?: string;
      sentAt?: string;
      correlationId?: string;
      type?: "audio" | "video" | "image" | "pdf" | "poll" | "live-session";
      ref?: string;
    }[];
    pinnedMessageId?: string;
    pinnedAt?: string;
    pinnedByUserId?: string;
    agentLastReadMessageId?: string; // Support only
  };
}

Message IDs must be unique. A pin must reference an initial message, and private mode permits only an admin-authored pin. A Support read watermark must reference an initial user message. The service assigns message session and tenant IDs, generates missing IDs and timestamps, and derives latest-message and Support status fields.

SessionViewConnected participant
interface SessionView {
  socketId: string;
  sessionId: string;
  tenantId: string;
  userId: string;
  isAdmin?: boolean;
  connectedAt: string;
  handRaiseStatus: "requested" | "accepted" | "rejected";
  handRaiseUpdatedAt?: string;
}
Pin and read responsesState changes
interface MessagePinUpdated {
  messageId: string;
  pinned: boolean;
  pinnedMessage?: MessageView; // present when pinned
  changedByUserId: string;
  changedAt: string;
}

interface MessageReadUpdated {
  messageId: string;
  readByUserId: string;
  readAt: string;
  hasUnreadUserMessagesForAgent: boolean;
  unreadUserMessageCountForAgent: number;
  revision: number;
}

interface MessageDeleted {
  messageId: string;
  userId: string;
  sentByAdmin: boolean;
  deletedByUserId: string;
  deletedAt: string;
  wasPinned: boolean;
  hasUnreadUserMessagesForAgent: boolean;
  unreadUserMessageCountForAgent: number;
}
MessageViewPersisted room message
interface MessageView {
  id: string;
  body: string;
  correlationId?: string;
  type?: "audio" | "video" | "image" | "pdf" | "poll" | "live-session";
  ref?: string;
  sessionId: string;
  tenantId: string;
  userId: string;
  username: string;
  sentByAdmin: boolean;
  sentAt: string;
  editedAt?: string;
  editedByUserId?: string;
}
07

No client project required

Live runner

Credentials stay in this page and are never written to browser storage.

then connect
send a command

Response stream

  1. Runner initialized
    Complete the fields to test the service.