Server → Client Events

Events the server pushes to connected clients for real-time updates on messages, conversations, typing, and presence.

Overview

These events are broadcast by the server to connected clients. Register handlers before starting the connection to ensure you don't miss events during the startup window.

Registering Handlers
// Always register handlers BEFORE calling connection.start()
connection.on("MessageReceived", (payload) => { /* ... */ });
connection.on("ConversationCreated", (payload) => { /* ... */ });
connection.on("ConversationUpdated", (payload) => { /* ... */ });
connection.on("TypingChanged", (payload) => { /* ... */ });
connection.on("PresenceChanged", (payload) => { /* ... */ });

await connection.start();

MessageReceived

Fired when a new message is sent in a conversation. Delivered to all members of the conversation:{id} group and the tenant:{id} group.

Payload Shape
{
  "id": "msg_abc123",
  "conversationId": "conv_def456",
  "content": "Hi, I need help with my order",
  "senderType": "visitor",
  "senderName": "John Doe",
  "senderId": null,
  "isInternal": false,
  "createdAt": "2024-01-15T14:30:00Z"
}
  • senderType"visitor" or "agent"
  • senderId — Agent ID if sender is an agent, null for visitors
  • isInternal — Always false for messages sent to visitors. Internal notes are only broadcast to the tenant group.
💡Internal notes (isInternal: true) are only sent to the tenant:{tenantId} group. Visitors never receive events where isInternal is true.

ConversationCreated

Fired when a visitor starts a new conversation. Delivered to the tenant:{id} group so all online agents see it in their inbox.

Payload Shape
{
  "id": "conv_new789",
  "visitorName": "Alice Customer",
  "visitorEmail": "[email protected]",
  "status": "open",
  "lastMessage": {
    "content": "I have a question about pricing",
    "senderType": "visitor",
    "createdAt": "2024-01-15T14:30:00Z"
  },
  "createdAt": "2024-01-15T14:30:00Z"
}

ConversationUpdated

Fired when a conversation's metadata changes (status change, agent assigned, etc.). Delivered to both the tenant:{id} and conversation:{id} groups.

Payload Shape
{
  "id": "conv_abc123",
  "status": "assigned",
  "assignedAgentId": "agt_xyz789",
  "assignedAgent": {
    "id": "agt_xyz789",
    "fullName": "Sarah Agent"
  },
  "updatedAt": "2024-01-15T14:35:00Z",
  "closedAt": null
}

The payload includes only the fields that changed, plus the id for lookup. Clients should merge this into their local state.

TypingChanged

Fired when someone starts or stops typing in a conversation. Delivered to the conversation:{id} group.

Payload Shape
{
  "conversationId": "conv_abc123",
  "userId": "agt_xyz789",
  "userName": "Sarah Agent",
  "userType": "agent",
  "isTyping": true
}
  • userType"visitor" or "agent"
  • isTypingtrue when typing starts, false when stopped

Clients should auto-clear typing indicators after 5 seconds of no update (as a safety net if the isTyping: false event is missed due to disconnection).

PresenceChanged

Fired when an agent comes online or goes offline. Delivered to the tenant:{id} group.

Payload Shape
{
  "agentId": "agt_xyz789",
  "fullName": "Sarah Agent",
  "status": "online",
  "lastSeenAt": "2024-01-15T14:30:00Z"
}
  • status"online" or "offline"
  • lastSeenAt — Timestamp of last activity, updated on disconnect
💡Presence is tracked via connection lifecycle. When an agent's last SignalR connection disconnects (and doesn't reconnect within the grace period), they're marked as offline and a PresenceChanged event fires.

Event Delivery Guarantees

  • Events are delivered at-most-once — if the client is disconnected, missed events are not replayed
  • After reconnection, clients should fetch the latest state via REST API to catch up
  • Events are ordered within a single connection but ordering is not guaranteed across reconnections
  • Typing and presence events are ephemeral — they're never persisted or retried