Client → Server Methods

Methods that clients invoke on the SignalR hub to send messages, join conversations, and signal typing.

Overview

These are the methods clients call on the server hub. Agents call them from the dashboard, and visitors call them from the widget. The server validates permissions, persists data, and broadcasts events to the appropriate groups.

JoinConversation

Subscribe to real-time updates for a specific conversation. Adds the caller to the conversation:{id} SignalR group.

Client Invocation
await connection.invoke("JoinConversation", conversationId);
Payload
{
  "conversationId": "conv_abc123"
}

Who can call: Agents (any conversation in their tenant), Visitors (only their own conversation)

Server response: None (fire-and-forget). The client will start receiving events for that conversation.

💡Agents typically call JoinConversation when opening a conversation in the dashboard UI. The tenant group already receives high-level events (new conversations, status changes), but joining a specific conversation enables message-level events.

StartConversation

Create a new conversation (visitor only). The server creates the conversation record, adds the visitor to the conversation group, and broadcasts ConversationCreated to the tenant group.

Client Invocation
const result = await connection.invoke("StartConversation", {
  visitorName: "John Doe",
  visitorEmail: "[email protected]",
  initialMessage: "Hi, I need help with my order"
});
// result: { conversationId: "conv_new123" }
Payload Shape
{
  "visitorName": "John Doe",
  "visitorEmail": "[email protected]",
  "initialMessage": "Hi, I need help with my order",
  "pageUrl": "https://example.com/orders",
  "metadata": {
    "browser": "Chrome 120",
    "os": "macOS"
  }
}
  • visitorName — required
  • initialMessage — required, the first message content
  • visitorEmail — optional (required if workspace has requireEmail: true)
  • pageUrl — optional, the page the visitor is on
  • metadata — optional, arbitrary key-value pairs

Returns: { conversationId: string }

SendMessage

Send a message in a conversation. The server persists the message and broadcasts MessageReceived to all members of the conversation group.

Client Invocation
await connection.invoke("SendMessage", {
  conversationId: "conv_abc123",
  content: "Sure, I can help with that!",
  isInternal: false
});
Payload Shape
{
  "conversationId": "conv_abc123",
  "content": "Sure, I can help with that!",
  "isInternal": false
}
  • conversationId — required
  • content — required, message text (max 5000 characters)
  • isInternal — optional, defaults to false. When true, the message is an internal note only visible to agents
⚠️Internal notes (isInternal: true) are only broadcasted to the tenant:{tenantId} group, not to the visitor. Visitors can never see or receive internal notes through any channel.

SendTyping

Signal that the user is typing. The server broadcasts TypingChanged to the conversation group. This is a lightweight event — it is not persisted.

Client Invocation
await connection.invoke("SendTyping", {
  conversationId: "conv_abc123",
  isTyping: true
});
Payload Shape
{
  "conversationId": "conv_abc123",
  "isTyping": true
}
  • conversationId — required
  • isTyping — required, true when typing starts, false when it stops

Clients should debounce typing signals — send isTyping: true on the first keystroke, then isTyping: false after 3 seconds of inactivity or when the message is sent.

💡Typing events are ephemeral and best-effort. They're not retried on failure and have no persistence. If the event is lost, the worst case is a missing typing indicator.

Error Handling

If a hub method fails (validation error, permission denied), SignalR throws a HubException which the client receives as a rejected promise:

Handling Errors
try {
  await connection.invoke("SendMessage", payload);
} catch (error) {
  // error.message: "You do not have access to this conversation"
  console.error("Hub method failed:", error.message);
}