Skip to content

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.

  1. RouteConfig.aiAgentId identifies the target agent.
  2. chat-service calls ai-provisioning-service via Feign: GET /tenants/agents/{agentId}.
  3. 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