16 KiB
Streaming Companion -- Technical Specification
Last Updated: 2026-02-27
Architecture, API surface, WebSocket protocol, and data model for the Streaming Companion feature.
Service Architecture
The Streaming Companion runs as a dedicated NestJS microservice within the Lilith platform's service mesh.
| Component | Technology | Address |
|---|---|---|
| Backend API | NestJS | Port 3130 |
| Database | PostgreSQL | Port 25468 |
| Cache / WebSocket Adapter | Redis | Port 26398 |
| Frontend | React (styled-components, TanStack Query, Socket.IO client) | Served via deployment domain |
The backend bootstraps with @lilith/service-nestjs-bootstrap and registers with @lilith/service-registry. Health checks are provided by @lilith/nestjs-health.
Redis serves two purposes: it backs the Socket.IO adapter for horizontal scaling of WebSocket connections, and it provides caching for frequently accessed data (active session state, chatbot config).
REST API
All endpoints are prefixed with /api and require authentication via the platform's SSO token unless otherwise noted.
Sessions -- /api/sessions
| Method | Path | Description |
|---|---|---|
POST |
/api/sessions |
Start a new session |
GET |
/api/sessions |
List sessions for the authenticated creator (paginated, filterable) |
GET |
/api/sessions/:id |
Get session detail with tips, goals, and notes |
PATCH |
/api/sessions/:id |
Update session (title, notes, viewer count) |
POST |
/api/sessions/:id/end |
End an active session |
POST |
/api/sessions/:id/cancel |
Cancel an active session |
GET |
/api/sessions/:id/export |
Export session data (query param: `format=csv |
Tips -- /api/tips
| Method | Path | Description |
|---|---|---|
POST |
/api/tips |
Record a tip for the active session |
GET |
/api/tips |
List tips (filterable by session, date range) |
DELETE |
/api/tips/:id |
Delete a tip (correction) |
POST |
/api/tips/import |
Bulk import tips from CSV |
Goals -- /api/goals
| Method | Path | Description |
|---|---|---|
POST |
/api/goals |
Create a tip goal for a session |
GET |
/api/goals |
List goals for a session |
PATCH |
/api/goals/:id |
Update goal (title, target, sort order) |
POST |
/api/goals/:id/cancel |
Cancel a goal |
DELETE |
/api/goals/:id |
Delete a goal (only if no tips attributed) |
Analytics -- /api/analytics
| Method | Path | Description |
|---|---|---|
GET |
/api/analytics/summary |
Aggregated stats across sessions (date range, platform filter) |
GET |
/api/analytics/trends |
Time-series data (tips per hour, session duration, etc.) |
GET |
/api/analytics/export |
Bulk export all session data (query param: `format=csv |
Chatbot -- /api/chatbot
| Method | Path | Description |
|---|---|---|
GET |
/api/chatbot/config |
Get chatbot configuration for the authenticated creator |
PUT |
/api/chatbot/config |
Update chatbot configuration (enabled, rate limits, personas JSONB) |
GET |
/api/chatbot/templates |
List response templates |
POST |
/api/chatbot/templates |
Create a response template |
PATCH |
/api/chatbot/templates/:id |
Update a response template |
DELETE |
/api/chatbot/templates/:id |
Delete a response template |
Tip Menu -- /api/menu
| Method | Path | Description |
|---|---|---|
GET |
/api/menu |
Get tip menu for the authenticated creator |
POST |
/api/menu |
Create a menu item |
PATCH |
/api/menu/:id |
Update a menu item |
DELETE |
/api/menu/:id |
Delete a menu item |
PATCH |
/api/menu/reorder |
Reorder menu items (body: { items: [{ id, sortOrder }] }) |
WebSocket Protocol
The Streaming Companion uses Socket.IO for real-time communication. Two namespaces serve different access levels.
Authenticated Namespace -- /streaming
Requires a valid SSO token. Used by the creator's dashboard.
Client-to-Server Events:
| Event | Payload | Description |
|---|---|---|
join_session |
{ sessionId: string } |
Subscribe to real-time updates for a session |
leave_session |
{ sessionId: string } |
Unsubscribe from session updates |
update_viewers |
{ sessionId: string, count: number } |
Report current viewer count from the cam platform |
add_tip |
{ sessionId: string, amountCents: number, tipperName?: string, message?: string } |
Record a tip via WebSocket (alternative to REST) |
chatbot_message |
{ sessionId: string, personaName: string, userMessage: string, userName: string } |
Route a chat message through the chatbot system |
Server-to-Client Events:
| Event | Payload | Description |
|---|---|---|
session_updated |
{ session: StreamSession } |
Session state changed (viewer count, duration tick) |
tip_received |
{ tip: StreamTip, sessionTotals: { totalCents, count, avgCents } } |
A tip was recorded |
goal_progress |
{ goal: TipGoal } |
A goal's progress was updated |
goal_completed |
{ goal: TipGoal } |
A goal reached its target |
chatbot_response |
{ personaName: string, responseText: string, triggeredBy: string } |
Chatbot generated a response |
animation_trigger |
{ type: string, data: object } |
Trigger an overlay animation (tip notification, goal completion) |
menu_updated |
{ menu: TipMenuItem[] } |
Tip menu was modified |
Anonymous Overlay Namespace -- /streaming/overlay
No authentication required. Used by OBS browser source overlays. Read-only -- clients cannot emit events that modify state.
Server-to-Client Events (subset of authenticated namespace):
| Event | Payload | Description |
|---|---|---|
tip_received |
{ tipperName, amountCents, message } |
Tip notification (sanitized, no internal IDs) |
goal_progress |
{ title, targetCents, currentCents, percentComplete } |
Goal progress update |
goal_completed |
{ title, targetCents } |
Goal completion trigger |
animation_trigger |
{ type, data } |
Animation event |
menu_updated |
{ items: { title, priceCents, category }[] } |
Menu update (public fields only) |
The overlay namespace joins a session by room ID passed as a query parameter: /streaming/overlay?session=<sessionId>. The session ID is embedded in the overlay URL that the creator copies from the dashboard.
Data Model
StreamSessionEntity
The root entity for a streaming session.
| Column | Type | Description |
|---|---|---|
id |
uuid |
Primary key |
creatorId |
uuid |
FK to the creator's user account |
status |
enum |
active, ended, cancelled |
platform |
enum |
chaturbate, stripchat, myfreecams, bongacams, camsoda, other |
platformCustom |
varchar |
Custom platform name when platform is other |
title |
varchar |
Optional session title |
startedAt |
timestamptz |
When the session was started |
endedAt |
timestamptz |
When the session was ended or cancelled (null if active) |
durationSeconds |
integer |
Computed duration (null if active) |
peakViewers |
integer |
Highest viewer count observed |
lastViewerCount |
integer |
Most recently reported viewer count |
totalTipsCents |
integer |
Denormalized sum of all tips (cents) |
tipCount |
integer |
Denormalized count of all tips |
notes |
text |
Free-form session notes (post-session) |
metadata |
jsonb |
Extensible metadata (platform-specific data, tags) |
createdAt |
timestamptz |
Record creation timestamp |
updatedAt |
timestamptz |
Record update timestamp |
Indexes: creatorId, status, platform, startedAt
StreamTipEntity
Individual tip records within a session.
| Column | Type | Description |
|---|---|---|
id |
uuid |
Primary key |
sessionId |
uuid |
FK to StreamSessionEntity |
creatorId |
uuid |
FK to creator (denormalized for query performance) |
amountCents |
integer |
Tip amount in cents |
currency |
varchar |
Currency code (default: USD) |
tipperName |
varchar |
Display name of the tipper (as entered by creator) |
message |
text |
Message attached to the tip |
source |
enum |
manual, platform_api, import |
receivedAt |
timestamptz |
When the tip was received |
paymentTransactionId |
uuid |
Optional FK to platform payment transaction (for API-sourced tips) |
createdAt |
timestamptz |
Record creation timestamp |
Indexes: sessionId, creatorId, receivedAt
TipGoalEntity
Tip goals within a session.
| Column | Type | Description |
|---|---|---|
id |
uuid |
Primary key |
sessionId |
uuid |
FK to StreamSessionEntity |
creatorId |
uuid |
FK to creator |
title |
varchar |
Goal description |
targetCents |
integer |
Target amount in cents |
currentCents |
integer |
Current accumulated amount in cents |
status |
enum |
active, completed, cancelled |
sortOrder |
integer |
Display order within the session |
completedAt |
timestamptz |
When the goal was completed (null if not) |
createdAt |
timestamptz |
Record creation timestamp |
updatedAt |
timestamptz |
Record update timestamp |
Indexes: sessionId, status
SessionNoteEntity
Checklist items and notes within a session.
| Column | Type | Description |
|---|---|---|
id |
uuid |
Primary key |
sessionId |
uuid |
FK to StreamSessionEntity |
creatorId |
uuid |
FK to creator |
noteType |
enum |
checklist, note, reminder |
content |
text |
Note text |
sortOrder |
integer |
Display order |
completed |
boolean |
Whether the checklist item is checked off |
createdAt |
timestamptz |
Record creation timestamp |
updatedAt |
timestamptz |
Record update timestamp |
Indexes: sessionId
ChatbotConfigEntity
Per-creator chatbot configuration.
| Column | Type | Description |
|---|---|---|
id |
uuid |
Primary key |
creatorId |
uuid |
FK to creator (unique) |
enabled |
boolean |
Master toggle for the chatbot |
personas |
jsonb |
Array of persona definitions: [{ name, enabled, description }] |
rateLimitPerMinute |
integer |
Max bot responses per minute |
perUserCooldownSeconds |
integer |
Min seconds between responses to the same user |
createdAt |
timestamptz |
Record creation timestamp |
updatedAt |
timestamptz |
Record update timestamp |
Indexes: creatorId (unique)
ChatbotResponseTemplateEntity
Response templates assigned to chatbot personas.
| Column | Type | Description |
|---|---|---|
id |
uuid |
Primary key |
configId |
uuid |
FK to ChatbotConfigEntity |
personaName |
varchar |
Which persona this template belongs to (matches personas[].name) |
category |
varchar |
Template category (schedule, pricing, rules, faq, custom) |
triggerPhrases |
text[] |
Array of phrases that activate this template |
responseText |
text |
The response the bot sends |
priority |
integer |
Higher priority templates match first |
enabled |
boolean |
Whether this template is active |
createdAt |
timestamptz |
Record creation timestamp |
updatedAt |
timestamptz |
Record update timestamp |
Indexes: configId, personaName
TipMenuItemEntity
Persistent tip menu items (not session-specific).
| Column | Type | Description |
|---|---|---|
id |
uuid |
Primary key |
creatorId |
uuid |
FK to creator |
title |
varchar |
Menu item title |
description |
text |
Optional description |
priceCents |
integer |
Price in cents |
category |
enum |
performance, request, interaction, custom |
isActive |
boolean |
Whether the item appears in the menu |
sortOrder |
integer |
Display order |
createdAt |
timestamptz |
Record creation timestamp |
updatedAt |
timestamptz |
Record update timestamp |
Indexes: creatorId, isActive
Domain Events
The Streaming Companion emits domain events through @lilith/domain-events for cross-service integration:
| Event | Payload | Consumers |
|---|---|---|
streaming.session.started |
{ sessionId, creatorId, platform, startedAt } |
Analytics pipeline, activity feed |
streaming.session.ended |
{ sessionId, creatorId, durationSeconds, totalTipsCents, tipCount } |
Analytics pipeline, earnings dashboard |
streaming.tip.received |
{ tipId, sessionId, creatorId, amountCents, source } |
Earnings dashboard, achievement system |
streaming.goal.completed |
{ goalId, sessionId, creatorId, title, targetCents } |
Achievement system, notification service |
Frontend Architecture
The Streaming Companion frontend is a React application within the Lilith deployment system:
- Routing:
@lilith/ui-router(wrapper around react-router) - Styling:
@lilith/ui-styled-components(wrapper around styled-components, ensuring single ThemeProvider instance) - Data fetching: TanStack Query for REST API calls with automatic cache invalidation
- Real-time: Socket.IO client connecting to
/streamingnamespace, with events wired into TanStack Query cache updates - Motion:
@lilith/ui-motionfor overlay animations (wrapper around framer-motion) - State: Local component state for UI concerns; server state via TanStack Query; WebSocket events for real-time synchronization
The overlay pages (/overlay/tips, /overlay/goals, /overlay/menu, /overlay/celebration) are separate lightweight routes designed for OBS browser source embedding. They connect to the /streaming/overlay namespace and render with transparent backgrounds.
Authentication and Authorization
- Creator dashboard: Authenticated via platform SSO token (JWT). The token includes the creator's user ID, which scopes all queries to their data.
- Overlay pages: Unauthenticated. The session ID in the URL acts as a capability token. Session IDs are UUIDs and are not guessable, but they are not secret -- anyone with the overlay URL can see the public overlay data.
- API authorization: All REST endpoints validate that the authenticated creator owns the requested resource. A creator cannot access another creator's sessions, tips, or chatbot configuration.
Scaling Considerations
- WebSocket horizontal scaling: Redis-backed Socket.IO adapter allows multiple backend instances to share WebSocket state. A creator's dashboard and their overlay can connect to different backend instances and still receive the same events.
- Database denormalization:
totalTipsCentsandtipCountonStreamSessionEntityare denormalized to avoid aggregate queries on the tips table during live sessions. These are updated atomically when a tip is recorded. - Overlay connection limits: The anonymous overlay namespace is rate-limited by IP to prevent abuse. A single session typically has at most 4-5 overlay connections (one per OBS scene element).
Related Documentation
- README.md -- Feature overview and user stories
- overview.md -- Product context and ecosystem fit
- creator-guide.md -- How creators use the dashboard
- Domain events:
docs/architecture/event-flows.md - Service registry:
@lilith/service-registrypackage documentation
Maintained By: The Collective Domain: Performer Suite