Overview
Nadoo AI provides WebSocket endpoints for bidirectional real-time communication . While SSE handles unidirectional server-to-client streaming, WebSocket connections enable interactive scenarios such as live chat with streaming responses, workspace collaboration, user notifications, and plugin debugging.
Chat Real-time bidirectional chat with token-by-token AI response streaming, typing indicators, and message cancellation.
Notifications Push notifications to connected users for system events, workflow completions, and workspace activity.
Workspace Collaboration Real-time workspace events — document updates, application changes, and member activity broadcasts.
Plugin Debug Live debug streaming during plugin execution with step-by-step traces, variable inspection, and API call logs.
Endpoints
Endpoint Purpose Auth ws://.../ws/chat/{session_id}Real-time chat streaming JWT token (query param) ws://.../ws/notifications/{user_id}User notifications JWT token (required) ws://.../ws/workspaces/{workspace_id}Workspace collaboration JWT token (required) ws://.../api/v1/ws/plugin/debug/{execution_id}Plugin debug console None (execution ID scoped)
Authentication
WebSocket connections authenticate via a token query parameter containing a valid JWT access token. The server decodes the token before accepting the connection.
ws://localhost:8000/ws/chat/session-abc?token=eyJhbGciOiJIUzI1NiIs...
If the token is invalid or expired, the server closes the connection immediately with code 1008 (Policy Violation) and the reason "Invalid token". Ensure the token is refreshed before establishing the WebSocket connection.
Chat WebSocket
The chat WebSocket provides real-time bidirectional communication for interactive AI conversations.
Connection
Open connection
Connect with a session ID and optional JWT token: ws://localhost:8000/ws/chat/{session_id}?token={jwt_token}
Receive confirmation
The server sends a connection confirmation: {
"type" : "connected" ,
"session_id" : "session-abc" ,
"user_id" : "user-123" ,
"timestamp" : "2025-01-15T10:30:00Z"
}
Send and receive messages
Send chat messages and receive streamed AI responses in real time.
Client-to-Server Messages
Send a chat message to the AI agent: {
"type" : "chat" ,
"content" : "Summarize the Q4 report" ,
"application_id" : "app-uuid-123" ,
"chat_id" : "chat-uuid-456" ,
"user_id" : "user-uuid-789" ,
"timestamp" : "2025-01-15T10:30:00Z"
}
If chat_id is omitted, a new chat session is created automatically.
typing -- Typing indicator
Broadcast a typing indicator to other participants in the session:
Send a ping to keep the connection alive: The server responds with: {
"type" : "pong" ,
"timestamp" : "2025-01-15T10:30:05Z"
}
Server-to-Client Messages
Message Type Description connectedConnection established successfully response_startAI response generation has begun text-deltaA chunk of the AI response text sourcesKnowledge base sources used in the response response_endAI response is complete, includes full text and token usage typingAnother user is typing user_disconnectedA user left the session errorAn error occurred processing the message
Streaming Response Flow
When the server receives a chat message, it streams the AI response as a sequence of events:
// 1. Response starts
{ "type" : "response_start" , "message_id" : "msg_1234" , "timestamp" : "..." }
// 2. Text chunks arrive incrementally
{ "type" : "text-delta" , "text" : "Based on the Q4 report, " , "index" : 0 , "timestamp" : "..." }
{ "type" : "text-delta" , "text" : "revenue increased by 15%." , "index" : 1 , "timestamp" : "..." }
// 3. Knowledge base sources (if applicable)
{ "type" : "sources" , "sources" : [ ... ], "timestamp" : "..." }
// 4. Response complete
{ "type" : "response_end" , "full_response" : "Based on the Q4 report, revenue increased by 15%." , "tokens_used" : 12 , "response_time" : 2.3 , "timestamp" : "..." }
Client Example
const sessionId = 'session-abc-123' ;
const token = 'eyJhbGciOiJIUzI1NiIs...' ;
const ws = new WebSocket (
`ws://localhost:8000/ws/chat/ ${ sessionId } ?token= ${ token } `
);
ws . onopen = () => {
console . log ( 'Connected to chat' );
};
ws . onmessage = ( event ) => {
const data = JSON . parse ( event . data );
switch ( data . type ) {
case 'connected' :
console . log ( 'Session ready:' , data . session_id );
break ;
case 'response_start' :
console . log ( 'AI is responding...' );
break ;
case 'text-delta' :
// Append each text chunk to the UI
appendToResponse ( data . text );
break ;
case 'sources' :
showSources ( data . sources );
break ;
case 'response_end' :
console . log ( `Done. Tokens: ${ data . tokens_used } ` );
break ;
case 'typing' :
showTypingIndicator ( data . user_id );
break ;
case 'error' :
console . error ( 'Error:' , data . message );
break ;
}
};
// Send a message
ws . send ( JSON . stringify ({
type: 'chat' ,
content: 'What were the key Q4 metrics?' ,
application_id: 'app-uuid-123'
}));
// Send typing indicator
ws . send ( JSON . stringify ({ type: 'typing' }));
// Keep alive
setInterval (() => {
if ( ws . readyState === WebSocket . OPEN ) {
ws . send ( JSON . stringify ({ type: 'ping' }));
}
}, 30000 );
import asyncio
import json
import websockets
async def chat_client ():
session_id = "session-abc-123"
token = "eyJhbGciOiJIUzI1NiIs..."
uri = f "ws://localhost:8000/ws/chat/ { session_id } ?token= { token } "
async with websockets.connect(uri) as ws:
# Wait for connection confirmation
response = await ws.recv()
data = json.loads(response)
print ( f "Connected: { data } " )
# Send a message
await ws.send(json.dumps({
"type" : "chat" ,
"content" : "Summarize the Q4 report" ,
"application_id" : "app-uuid-123"
}))
# Receive streamed response
full_response = ""
async for message in ws:
data = json.loads(message)
if data[ "type" ] == "text-delta" :
full_response += data[ "text" ]
print (data[ "text" ], end = "" , flush = True )
elif data[ "type" ] == "response_end" :
print ( f " \n\n Tokens used: { data[ 'tokens_used' ] } " )
break
elif data[ "type" ] == "error" :
print ( f "Error: { data[ 'message' ] } " )
break
asyncio.run(chat_client())
Notifications WebSocket
The notifications endpoint delivers real-time push notifications to authenticated users.
Connection
ws://localhost:8000/ws/notifications/{user_id}?token={jwt_token}
The user_id in the URL must match the sub claim in the JWT token. Mismatched values result in an immediate connection close with code 1008.
{
"type" : "notification" ,
"notification" : {
"title" : "Workflow completed" ,
"body" : "Your data extraction workflow finished successfully." ,
"action_url" : "/workflows/wf-123/results"
},
"timestamp" : "2025-01-15T10:30:00Z"
}
Workspace Collaboration WebSocket
The workspace endpoint enables real-time collaboration features within a shared workspace.
Connection
ws://localhost:8000/ws/workspaces/{workspace_id}?token={jwt_token}
Events
When you join the workspace, all other connected users receive a user_joined event. When you disconnect, they receive user_left.
Supported broadcast event types:
Event Type Description user_joinedA user connected to the workspace user_leftA user disconnected from the workspace document_updatedA document in the knowledge base was modified application_changedAn application’s configuration was updated member_addedA new member was added to the workspace
Example
const ws = new WebSocket (
`ws://localhost:8000/ws/workspaces/ ${ workspaceId } ?token= ${ token } `
);
ws . onmessage = ( event ) => {
const data = JSON . parse ( event . data );
if ( data . type === 'user_joined' ) {
showToast ( ` ${ data . user_id } joined the workspace` );
}
if ( data . type === 'document_updated' ) {
refreshDocumentList ();
}
};
// Broadcast a document update to other workspace members
ws . send ( JSON . stringify ({
type: 'document_updated' ,
document_id: 'doc-456' ,
action: 'edited'
}));
Plugin Debug WebSocket
The plugin debug endpoint streams real-time execution data during plugin runs, enabling live debugging.
Connection
ws://localhost:8000/api/v1/ws/plugin/debug/{execution_id}
This endpoint does not require a JWT token. Access is scoped by the execution_id, which must correspond to an existing plugin execution record.
Message Types
Type Description execution_infoInitial execution metadata (plugin ID, tool name, status) debug_dataPreviously recorded debug data (sent if execution has historical data) logLog output from the plugin traceExecution trace data stepA discrete execution step variableVariable state snapshot api_callAn external API call made by the plugin execution_completePlugin execution finished successfully execution_errorPlugin execution failed errorConnection or validation error
Example
const execId = 'exec-uuid-123' ;
const ws = new WebSocket (
`ws://localhost:8000/api/v1/ws/plugin/debug/ ${ execId } `
);
ws . onmessage = ( event ) => {
const data = JSON . parse ( event . data );
switch ( data . type ) {
case 'execution_info' :
console . log ( `Debugging: ${ data . data . tool_name } ` );
break ;
case 'log' :
appendToDebugConsole ( data . data );
break ;
case 'step' :
highlightStep ( data . data );
break ;
case 'variable' :
updateVariableInspector ( data . data );
break ;
case 'execution_complete' :
console . log ( 'Execution finished:' , data . data . result );
break ;
case 'execution_error' :
console . error ( 'Execution failed:' , data . data . error );
break ;
}
};
// Keep-alive ping
setInterval (() => {
if ( ws . readyState === WebSocket . OPEN ) {
ws . send ( 'ping' ); // Server responds with 'pong'
}
}, 30000 );
Connection Management
Reconnection Strategy
WebSocket connections do not auto-reconnect like SSE. Implement reconnection logic in your client:
Detect disconnect
Listen for the onclose event on the WebSocket.
Apply backoff
Wait with exponential backoff: 1s, 2s, 4s, 8s, up to a maximum of 30s.
Reconnect
Open a new WebSocket connection with a fresh JWT token if the previous one expired.
Restore state
After reconnecting, re-fetch any missed messages from the REST API (e.g., GET /api/v1/chat/{app_id}/messages).
function createReconnectingWebSocket ( url , maxRetries = 10 ) {
let retries = 0 ;
let ws ;
function connect () {
ws = new WebSocket ( url );
ws . onopen = () => {
retries = 0 ; // Reset on successful connection
};
ws . onclose = ( event ) => {
if ( retries < maxRetries ) {
const delay = Math . min ( 1000 * Math . pow ( 2 , retries ), 30000 );
console . log ( `Reconnecting in ${ delay } ms...` );
setTimeout ( connect , delay );
retries ++ ;
}
};
return ws ;
}
return connect ();
}
Connection Statistics
Monitor active WebSocket connections via the stats endpoint:
{
"total_connections" : 42 ,
"active_sessions" : 15 ,
"active_users" : 12 ,
"details" : {
"sessions" : {
"session-abc" : 2 ,
"workspace_ws-123" : 5
},
"users" : {
"user-1" : 3 ,
"user-2" : 1
}
}
}
WebSocket vs SSE
For most chatbot and streaming use cases, SSE is the recommended protocol . It is simpler to implement, reconnects automatically, and works reliably through HTTP proxies and load balancers. Use WebSocket only when you need bidirectional communication.
Feature SSE WebSocket Direction Server to client Bidirectional Protocol HTTP (text/event-stream) ws:// or wss://Reconnection Built-in auto-reconnect Manual implementation required Mid-stream input Not supported Supported (cancel, interrupt, new messages) Typing indicators Not supported Supported Browser support Native EventSource API Native WebSocket API Proxy compatibility Excellent (standard HTTP) May require proxy configuration Best for Chat streaming, workflow progress Interactive chat, collaboration, debugging
Next Steps