Chatbot Web Service & WebSocket Integration
1. Purpose
This document describes the externally visible communication protocol of the chatbot frontend so that the same client functionality can be implemented in another language or platform.
The protocol has two communication mechanisms:
- HTTP/REST — for loading chat history.
- WebSocket — for sending messages/audio and receiving chatbot responses, including streaming tokens.
2. Server Configuration
The frontend defines:
let baseUrl = "ai.pezhvak.im";
let chatbotId = "60c0917a-bd94-4b62-8cf6-9d20de375da0";
Therefore, the development WebSocket connection is:
ws://baseUrl/ws/chat/{chatbot_id}/{client_id}/
Example:
ws://baseUrl/ws/chat/60c0917a-bd94-4b62-8cf6-9d20de375da0/550e8400-e29b-41d4-a716-446655440000/
3. Client Identity and Session
The client creates a UUID and stores it in browser local storage under a key derived from the chatbot ID:
if (!localStorage.getItem(`${chatbotId}_client_id`)) {
localStorage.setItem(`${chatbotId}_client_id`, crypto.randomUUID());
}
const clientId = localStorage.getItem(`${chatbotId}_client_id`);
The UUID is then used as both:
- the WebSocket
client_id - the logical chat
sessionIdin the frontend
Equivalent behavior
A non-browser client should:
- Generate a UUID.
- Persist it if conversations should survive application restarts.
- Reuse it when reconnecting to the same chatbot/session.
- Generate a new UUID when starting a new chat.
Example UUID:
550e8400-e29b-41d4-a716-446655440000
New conversation
The frontend starts a new conversation by generating a new UUID:
localStorage.setItem(`${chatbotId}_client_id`, crypto.randomUUID());
The new ID is then passed to selectSession() and used as the session ID.
There is no separate “create session” HTTP request in the supplied frontend flow.
4. HTTP / REST API
The frontend exposes the following URL patterns:
| Purpose | HTTP URL |
|---|---|
| Session messages | /fa/api/chat/session/{session_id}/messages/ |
| Stream endpoint | /fa/api/chat/session/{session_id}/stream/{last_msg_id}/ |
5. Loading Chat History
Endpoint
The frontend calls:
fetch('/fa/api/realtime-chat/session/{session_id}/messages/')
Example:
GET /fa/api/realtime-chat/session/550e8400-e29b-41d4-a716-446655440000/messages/
Response
The response is expected to be JSON containing an array of messages. The frontend iterates over the response:
data.forEach(msg => {
createMessageElement(msg);
});
Each message is expected to contain at least:
{
"sender": "user",
"message_text": "Hello"
}
The sender is interpreted as:
"user"→ display as user / right-side message- anything else → display as chatbot / left-side message
Empty session
HTTP 404 is explicitly treated as “no messages yet”:
if (response.status === 404) {
return [];
}
A compatible client should be prepared for 404 Not Found to mean that the session currently has no messages.
Message history example
[
{
"sender": "user",
"message_text": "What is artificial intelligence?"
},
{
"sender": "bot",
"message_text": "Artificial intelligence is..."
}
]
6. WebSocket Protocol
Connection URL
Template:
ws://{baseUrl}/ws/chat/{chatbot_id}/{client_id}/
Example:
ws://{baseUrl}/ws/chat/60c0917a-bd94-4b62-8cf6-9d20de375da0/550e8400-e29b-41d4-a716-446655440000/
The frontend creates the connection once:
const ws = new WebSocket(
`ws://${baseUrl}/ws/chat/${chatbotId}/${clientId}/`
);
7. Sending a Text Message
Text messages are sent as a JSON WebSocket message:
ws.send(JSON.stringify({message: text}));
Therefore the wire format is:
{
"message": "Hello, how are you?"
}
This is a text WebSocket frame containing JSON. The frontend disables further message sending while waiting for the response.
8. Receiving Responses
Every incoming WebSocket message is expected to be a JSON object:
const data = JSON.parse(e.data);
The protocol uses the type field to identify the event. Known event types are:
audio
token
end
follow_up_questions
If the type is not one of these, the frontend treats it as a normal text response.
9. Streaming Text Response
Event type
{
"type": "token",
"response": "text fragment"
}
Example sequence:
{ "type": "token", "response": "Artificial" }
{ "type": "token", "response": " intelligence" }
{ "type": "token", "response": " is..." }
The frontend accumulates the fragments and renders the accumulated content as Markdown:
buffer += text;
marked.parse(buffer)
response is a fragment, not necessarily a complete answer.
A client should concatenate all response values from token events until the corresponding
{"type":"end"} event is received.
10. End of Streaming
The end-of-stream event is:
{
"type": "end"
}
The frontend handles it by:
- Clearing its streaming buffer.
- Sanitizing links in the rendered message.
buffer = "";
sanitizeLinks(document.getElementById(idStreaming));
Therefore the logical response sequence is:
token → token → token → … → end
A client should consider end the completion marker for a streaming response.
11. Follow-Up Questions
The server can send:
{
"type": "follow_up_questions",
"follow_up_questions": [
"What would you like to know next?",
"Would you like an example?",
"Should I explain this in more detail?"
]
}
The frontend renders each question as a clickable item. When the user selects one, it is sent back through the normal text-message mechanism:
{
"message": "Would you like an example?"
}
Protocol sequence
The supplied frontend does not define whether follow_up_questions must occur before or after
end; its current handler supports both event types independently.
12. Non-Streaming Text Response
If the server sends a WebSocket JSON object without one of the recognized types,
the frontend executes createTextMsgElement(data.response).
A non-streaming response is expected to have at least:
{
"response": "Complete answer"
}
Example:
{
"response": "Python is a general-purpose programming language."
}
This is distinct from streaming responses, where the object contains
{"type":"token","response":"..."}.
13. Audio Response
The server can send an audio event:
{
"type": "audio",
"audioUrl": "https://example.com/audio/response.mp3"
}
The frontend handles it using createAudioMsgElement(data.audioUrl). The URL is assigned directly to an HTML <audio> element:
audio.src = audioUrl;
The supplied frontend defines the field name as audioUrl (camelCase).
14. Sending Microphone Audio
Audio input is sent differently from text. The browser uses:
const audioBlob = new Blob(audioChunks, {type: "audio/webm"});
audioBlob.arrayBuffer().then(buffer => {
ws.send(buffer);
});
Therefore the WebSocket receives a binary frame, not JSON.
The client should not JSON-encode the audio data.
The supplied implementation creates audio/webm, so another client should send WebM audio
to reproduce the browser behavior exactly. The server’s expected codec inside the WebM container is not documented.
15. Complete WebSocket Message Model
A compatible implementation should support these incoming message shapes.
15.1 Streaming token
{
"type": "token",
"response": "partial text"
}
15.2 Stream completion
{
"type": "end"
}
15.3 Follow-up questions
{
"type": "follow_up_questions",
"follow_up_questions": [
"Question 1",
"Question 2"
]
}
15.4 Audio URL
{
"type": "audio",
"audioUrl": "https://example.com/audio.wav"
}
15.5 Complete / non-streaming response
{
"response": "Complete response text"
}
16. Complete Client → Server Model
Text
WebSocket text frame
{
"message": "User message"
}
Audio
WebSocket binary frame
audio/webm binary data
No JSON envelope
17. Typical Text Conversation
A normal streaming conversation can be modeled as:
18. Typical Audio Conversation
The exact server-side ordering for audio, text, and end is not specified by the supplied source.
19. Session Lifecycle
The frontend’s session model is simple:
Starting a new chat means generating another UUID and using that UUID as the new session/client identifier.
20. Minimal Implementation Requirements
A client in Python, Java, Kotlin, C#, Go, Rust, Swift, Dart, JavaScript, or another language needs to implement only the following protocol components to reproduce the realtime chat behavior.
Required
- Generate UUID —
client_id = UUID() - Build WebSocket URL —
ws://HOST/ws/chat/CHATBOT_ID/CLIENT_ID/ - Connect WebSocket
- Send text —
{"message":"..."} - Send audio — raw WebSocket binary data containing WebM audio
- Parse incoming JSON — handle
token,end,audio,follow_up_questions, and the fallbackresponse - Load history —
GET /fa/api/realtime-chat/session/{session_id}/messages/
21. Reference Pseudocode
client_id = load_persisted_uuid_or_create_one()
ws_url =
"ws://" +
host +
"/ws/chat/" +
chatbot_id +
"/" +
client_id +
"/"
ws.connect(ws_url)
history = HTTP_GET(
"/fa/api/realtime-chat/session/" +
client_id +
"/messages/"
)
display_history(history)
function send_text(message):
ws.send_text(
JSON.stringify({
"message": message
})
)
function send_audio(audio_bytes):
ws.send_binary(audio_bytes)
function on_websocket_message(frame):
if frame is binary:
handle_binary_response(frame)
return
data = JSON.parse(frame)
switch data.type:
case "token":
append_to_current_response(data.response)
case "end":
finish_current_response()
case "audio":
play_audio(data.audioUrl)
case "follow_up_questions":
display_follow_up_questions(
data.follow_up_questions
)
default:
if "response" in data:
display_complete_response(data.response)
22. Error and Reconnection Considerations
The supplied frontend does not contain a complete WebSocket error/reconnection protocol. It does not document specific server error message schemas such as:
{
"type": "error",
"message": "..."
}
Therefore an implementation in another language should not assume that such a message is part of the protocol unless the backend implementation confirms it.
For production clients, the implementation should nevertheless handle:
- connection failure
- connection closure
- timeout
- malformed JSON
- unexpected WebSocket binary/text frames
- HTTP history request failure
- server-side errors
- reconnection
These are client robustness recommendations, not protocol elements established by the supplied source.
23. Authentication
The supplied frontend does not show:
- Authorization headers
- Bearer tokens
- API keys
- cookies explicitly added by JavaScript
- WebSocket authentication parameters
The WebSocket URL contains only:
chatbot_id
client_id
Therefore no explicit application-level authentication mechanism can be extracted from the supplied source. If authentication is provided by Django session cookies, reverse-proxy authentication, middleware, or another server-side mechanism, that is outside the visible client protocol.
24. CORS / Origin / CSRF
The supplied frontend does not define CORS, CSRF, or WebSocket-origin configuration. A client implemented outside the browser may therefore have different requirements from the browser client.
The actual server configuration must be checked if a non-browser client receives:
- HTTP 403
- WebSocket handshake rejection
- Origin rejection
- CSRF errors
25. Known Endpoint Summary
Realtime WebSocket
Client → server
| Type | Payload |
|---|---|
| Text | {"message":"..."} |
| Binary | raw audio/webm bytes |
Server → client
| Event | Payload |
|---|---|
| Streaming | {"type":"token","response":"..."} |
| Completion | {"type":"end"} |
| Follow-up | {"type":"follow_up_questions","follow_up_questions":["..."]} |
| Audio | {"type":"audio","audioUrl":"..."} |
| Non-streaming | {"response":"..."} |
Chat history
Response:
[
{
"sender": "user",
"message_text": "..."
},
{
"sender": "bot",
"message_text": "..."
}
]
404 is interpreted by the supplied frontend as an empty history.
26. Important Distinction: REST vs WebSocket
The current realtime-chat frontend does not use REST to send the actual text message. The active message flow is:
This distinction is important when reproducing the functionality in another language.
27. Protocol Compatibility Checklist
A replacement client is compatible with the supplied frontend protocol if it satisfies all of the following:
- Generates a UUID client/session ID.
- Uses the UUID in
/ws/chat/{chatbot_id}/{client_id}/. - Opens a WebSocket connection.
- Sends text as a JSON text frame.
- Uses exactly the
messageJSON property for text input. - Sends microphone audio as a raw binary WebSocket frame.
- Uses WebM audio to reproduce the browser behavior.
- Parses incoming WebSocket frames as JSON when they are text.
- Handles
type = "token". - Concatenates streaming
responsefragments. - Handles
type = "end". - Handles
type = "audio"andaudioUrl. - Handles
type = "follow_up_questions". - Can handle a response object without a
typefield. - Loads existing messages through the history endpoint.
- Treats HTTP 404 from the history endpoint as an empty session.
- Can start a new conversation by generating a new UUID.
28. Information Not Defined by the Supplied Source
The following cannot be reliably specified from the supplied frontend code alone:
- HTTP methods for the unused REST endpoints.
- Request bodies for the unused REST endpoints.
- Exact server-side WebSocket consumer implementation.
- Authentication requirements.
- Authorization rules.
- Rate limits.
- Maximum text message size.
- Maximum audio size/duration.
- Audio codec inside WebM.
- WebSocket ping/pong policy.
- WebSocket reconnect semantics.
- Error event schema.
- Server-side timeout behavior.
- Exact ordering guarantees between
audio,token,end, andfollow_up_questions. - Whether
client_idis permanently associated with a database session or is only a logical session identifier. - Exact response schema for the HTTP history endpoint beyond the fields consumed by the frontend.
These details should be obtained from the backend WebSocket consumer, Django URL configuration/views, serializers, and API implementation before treating them as part of the formal protocol.
29. Source-Derived Core Contract
The most important contract for implementing the same realtime functionality is:
WebSocket:
ws://{host}/ws/chat/{chatbot_id}/{client_id}/
Client → Server:
TEXT {"message":"..."}
BINARY <audio/webm bytes>
Server → Client:
TEXT {"type":"token","response":"..."}
TEXT {"type":"end"}
TEXT {"type":"audio","audioUrl":"..."}
TEXT {"type":"follow_up_questions","follow_up_questions":[...]}
TEXT {"response":"..."}
HTTP:
GET /fa/api/realtime-chat/session/{session_id}/messages/
This is sufficient to implement the core text-chat, streaming-response, follow-up-question, audio-input, audio-output, and history functionality in another programming language.