Chat Service
Multi-tenant real-time chat platform supporting WebSocket and REST-based messaging, AI agent routing, file attachments, and queue management.
Runtime: Spring Boot 3.4.4 / Java 21 · 3 pods · nexivo namespace
Tech Stack
| Component |
Detail |
| Framework |
Spring Boot 3.4.4, Java 21 |
| Database |
PostgreSQL |
| Cache / Pub-Sub |
Redis |
| File Storage |
S3 — DigitalOcean Spaces (lon1) |
| Chat Channels |
Azure Communication Services (ACS), LiveKit SDK 0.10.0 |
| Internal Libraries |
msl-core, multi-tenancy-core, communication-core |
Channel Types
The service supports two channel backends, selected per ChatChannel.type.
| Type |
Backend |
Identity |
Token Type |
WIDGET |
Azure Communication Services (ACS) |
ACS thread ID |
ACS token |
APP |
LiveKit |
Room name |
JWT (LiveKit) |
Key Entities
Chat
The top-level conversation container.
| Field |
Type |
Notes |
chatId |
UUID |
Primary key |
name |
String |
Display name |
status |
Enum |
ACTIVE, ARCHIVE, CLOSED |
teamId |
UUID |
Owning team |
lastMessage |
Embedded |
Snapshot of latest message |
ChatThread
A thread within a Chat (1:M).
| Field |
Type |
Notes |
threadId |
UUID |
Primary key |
topic |
String |
Thread topic/label |
chat |
FK → Chat |
Parent chat |
ChatChannel
A tenant-configured channel entry point.
| Field |
Type |
Notes |
identity |
String |
Channel identifier |
type |
Enum |
WIDGET / APP |
routeConfig |
JSONB |
Routing rules (see below) |
aiAgent |
Boolean |
Enables AI agent routing |
attachments |
Boolean |
Enables file upload |
availabilityConfig |
Embedded |
Hours of operation |
ChatThreadMessage
| Field |
Type |
Notes |
participantId |
UUID |
Sender |
message |
TEXT |
Message body |
type |
Enum |
TEXT, IMAGE, VIDEO, AUDIO, FILE |
status |
Enum |
SENT, DELIVERED, READ |
attachmentUrl |
String |
S3 URL when type ≠ TEXT |
ChatThreadParticipant
| Field |
Type |
Notes |
participantId |
UUID |
|
joinedAt |
Timestamp |
|
leftAt |
Timestamp |
Null while active |
callRating |
Integer |
1–5 post-chat rating |
kind |
Enum |
Agent / Customer / Bot |
RouteConfig (JSONB)
{
"role": "AGENT",
"lang": "en",
"aiAgentId": "uuid",
"contactId": "uuid",
"teamId": "uuid",
"groups": ["sales", "support"]
}
AvailabilityConfig
{
"availability": "SCHEDULED",
"timezone": "Europe/London",
"scheduleConfig": {
"monday": { "from": "09:00", "to": "17:00" },
"tuesday": { "from": "09:00", "to": "17:00" },
"saturday": null
}
}
availability is either ALWAYS or SCHEDULED. When SCHEDULED, only days with a non-null entry are considered open.
Message Flow
sequenceDiagram
participant Client
participant ChatService
participant Redis
participant SSE as SSE Stream
participant LongPoll as Long-Poll Client
Client->>ChatService: POST /chats/{chatId}/threads/{threadId}/messages
ChatService->>PostgreSQL: Persist ChatThreadMessage
ChatService->>Redis: PUBLISH chat-threads:{threadId} (message event)
Redis-->>ChatService: (subscriber callback)
ChatService-->>SSE: push "message" event (text/event-stream)
ChatService-->>LongPoll: wake up blocked request, return message
Note over ChatService,Redis: Typing events via chat-typing:{threadId}
Note over ChatService,Redis: Deletions via chat-delete:{threadId}
REST API
Chats — /chats
| Method |
Path |
Description |
GET |
/chats |
List chats (paginated) |
POST |
/chats |
Create chat |
GET |
/chats/{chatId} |
Get chat detail |
PUT |
/chats/{chatId} |
Update chat |
DELETE |
/chats/{chatId} |
Delete chat |
POST |
/chats/{chatId}/participants |
Add participant |
DELETE |
/chats/{chatId}/participants/{participantId} |
Remove participant |
GET |
/chats/{chatId}/threads |
List threads |
POST |
/chats/{chatId}/threads |
Create thread |
GET |
/chats/{chatId}/threads/{threadId}/messages |
List messages |
POST |
/chats/{chatId}/threads/{threadId}/messages |
Send message |
GET |
/chats/{chatId}/threads/{threadId}/messages/stream |
SSE live stream |
POST |
/chats/{chatId}/threads/{threadId}/typing |
Publish typing indicator |
Chat Threads — /chat-threads
| Method |
Path |
Description |
GET |
/chat-threads/{threadId} |
Get thread |
PUT |
/chat-threads/{threadId} |
Update thread |
DELETE |
/chat-threads/{threadId} |
Delete thread |
GET |
/chat-threads/{threadId}/messages |
List messages; add ?waitMs=5000 for long-poll (max 60 s) |
POST |
/chat-threads/{threadId}/messages |
Send message |
POST |
/chat-threads/{threadId}/attachments |
Upload attachment (max 50 MB) |
GET |
/chat-threads/{threadId}/attachments/{key} |
Download attachment |
Chat Channels — /chat-channels
| Method |
Path |
Description |
GET |
/chat-channels |
List channels |
POST |
/chat-channels |
Create channel |
GET |
/chat-channels/{channelId} |
Get channel |
PUT |
/chat-channels/{channelId} |
Update channel |
DELETE |
/chat-channels/{channelId} |
Delete channel |
POST |
/chat-channels/{channelId}/threads |
Create thread for this channel (entry point) |
Agent Inbox — /me/chats
| Method |
Path |
Description |
GET |
/me/chats/token |
Fetch ACS or LiveKit token |
PUT |
/me/chats/status |
Update agent availability |
GET |
/me/chats/inbox |
SSE inbox stream or long-poll inbox |
GET |
/me/chats/unread |
Unread message count |
GET |
/me/chats/search |
Full-text message search |
LiveKit Webhook — /livekit/chat/webhook
| Method |
Path |
Description |
POST |
/livekit/chat/webhook |
Receive LiveKit room events (join, leave, transfer) |
Server-Sent Events (SSE)
Endpoint: GET /chats/{chatId}/threads/{threadId}/messages/stream
Content-Type: text/event-stream
| Event |
Payload |
Description |
message |
ChatMessageInfo JSON |
New message delivered to thread |
typing |
{ "participantId": "..." } |
Participant is typing |
message-deleted |
{ "messageId": "..." } |
Message was deleted |
keepalive |
(empty) |
Heartbeat every 30 seconds |
Limits:
- Maximum 3 concurrent SSE streams per participant per thread.
- Long-poll fallback: append ?waitMs=5000 (max 60000 ms) to any message list endpoint.
Redis Pub/Sub
| Topic |
Purpose |
Consumers |
chat-threads:{threadId} |
New message events |
SSE push + long-poll wakeup |
chat-typing:{threadId} |
Typing indicator events |
SSE push |
chat-delete:{threadId} |
Message deletion events |
SSE push |
Queue Management
Agents are scheduled via a Redis sorted set (chat-queue) using millisecond timestamps as scores, enabling round-robin assignment. A companion key chat-status tracks each agent's current availability state.
AI Agent Routing
When ChatChannel.aiAgent = true, incoming threads are routed to the configured AI agent before any human agent.
RouteConfig.aiAgentId identifies the target agent.
- chat-service calls
ai-provisioning-service via Feign: GET /tenants/agents/{agentId}.
- Participant resolution:
ContactService is tried first; if no match, AiProvisioningService resolves the AI agent identity.
Key Configuration
| Property |
Default / Example |
Description |
chat.limit |
10 |
Max concurrent active chats per agent |
storage.bucket |
nexivo-alpha |
DigitalOcean Spaces bucket |
storage.endpoint |
https://lon1.digitaloceanspaces.com |
S3-compatible endpoint |
azure.services.acs-endpoint |
(tenant-specific) |
Azure ACS endpoint URL |
livekit.server-url |
(env-specific) |
LiveKit server URL |
livekit.api-key |
(secret) |
LiveKit API key |
livekit.api-secret |
(secret) |
LiveKit API secret |