SignalR Groups
How ZapTicket organizes real-time event routing using SignalR groups for tenant-wide and conversation-specific messaging.
Overview
ZapTicket uses SignalR groups to control which clients receive which events. Groups are logical channels that clients join and leave — events sent to a group are delivered to all members of that group.
There are two types of groups:
- Tenant groups — all agents in a workspace
- Conversation groups — participants in a specific conversation
Tenant Group
tenant:{tenantId}
Example: tenant:ws_abc123Every agent is automatically added to their tenant group when they connect to the hub. The tenant ID is extracted from the workspace JWT claims — no client action needed.
Events delivered to tenant group:
ConversationCreated— new conversation started by a visitorConversationUpdated— status change, assignment changeMessageReceived(internal notes only) — internal notes are never sent to conversation groupsPresenceChanged— agent online/offline
Conversation Group
conversation:{conversationId}
Example: conversation:conv_def456Clients join a conversation group explicitly by calling JoinConversation(conversationId). Visitors are automatically joined when they start a conversation.
Events delivered to conversation group:
MessageReceived— new messages (public only, not internal notes)TypingChanged— typing indicatorsConversationUpdated— status/assignment changes for this conversation
Group Membership Rules
┌─────────────────────────────────────────────────────────────────┐
│ Client │ tenant:{id} │ conversation:{id} │
├───────────────┼──────────────┼──────────────────────────────────┤
│ Agent │ Auto-joined │ Joined via JoinConversation() │
│ Visitor │ Never │ Auto-joined on StartConversation │
└─────────────────────────────────────────────────────────────────┘- Agents can join any conversation group in their tenant
- Visitors can only be in their own conversation group
- Visitors are never added to the tenant group
- Agents are removed from conversation groups when they navigate away or disconnect
Internal Notes Routing
Internal notes are a special case. When an agent sends a message with isInternal: true, the MessageReceived event is only broadcast to the tenant group, not the conversation group. This ensures visitors never receive internal notes, even if they're in the conversation group.
SendMessage(isInternal: false)
→ Broadcast to conversation:{id} (all participants see it)
→ Broadcast to tenant:{id} (inbox preview updates)
SendMessage(isInternal: true)
→ Broadcast to tenant:{id} ONLY (agents see it in sidebar)
→ NOT sent to conversation:{id} (visitor never receives it)Presence Tracking
Agent presence (online/offline status) is tracked at the tenant group level. The system monitors connection lifecycle events:
- Connect — Agent marked online,
PresenceChanged(online)sent to tenant group - Disconnect — 15-second grace period for reconnection
- Grace period expires — Agent marked offline,
PresenceChanged(offline)sent - Reconnect within grace — No event, agent stays online
Connect ─────────────────→ Online
│
Disconnect
│
▼
Grace Period (15s)
│ │
Reconnect Timeout
│ │
▼ ▼
Online Offline