Chatbot Web Service & WebSocket Integration

Protocol Specification HTTP/REST + WebSocket External client implementation guide

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:

  1. HTTP/REST — for loading chat history.
  2. 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:

Equivalent behavior

A non-browser client should:

  1. Generate a UUID.
  2. Persist it if conversations should survive application restarts.
  3. Reuse it when reconnecting to the same chatbot/session.
  4. 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

GET /fa/api/realtime-chat/session/{session_id}/messages/

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:

Empty session

HTTP 404 is explicitly treated as “no messages yet”:

if (response.status === 404) {
    return [];
}
Note

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

WS ws://{base_url}/ws/chat/{chatbot_id}/{client_id}/

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?"
}
Frame type

This is a text WebSocket frame containing JSON. The frontend disables further message sending while waiting for the response.

Client | | WebSocket TEXT | {"message":"What is Python?"} v Server

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)
Important

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:

  1. Clearing its streaming buffer.
  2. 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

Server | | {"type":"token","response":"..."} | {"type":"token","response":"..."} | {"type":"end"} | {"type":"follow_up_questions","follow_up_questions":[...]} v Client

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;
Field name

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.

Client | | WebSocket BINARY | raw audio/webm bytes v Server
Wire format

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:

1. Connect Client → WebSocket ws://host/ws/chat/{chatbot_id}/{client_id}/ 2. Send message Client → Server TEXT: {"message":"Explain RAG"} 3. Receive token Server → Client TEXT: {"type":"token","response":"RAG "} 4. Receive token Server → Client TEXT: {"type":"token","response":"stands for..."} 5. Receive completion Server → Client TEXT: {"type":"end"} 6. Optional follow-up questions Server → Client TEXT: {"type":"follow_up_questions","follow_up_questions":["Would you like an example?"]} 7. User selects follow-up Client → Server TEXT: {"message":"Would you like an example?"}

18. Typical Audio Conversation

1. Connect Client → Server WebSocket connection 2. Record microphone audio 3. Encode/collect audio as WebM 4. Send binary frame Client → Server BINARY: <audio/webm bytes> 5. Wait for server response 6. Optional audio response Server → Client TEXT: {"type":"audio","audioUrl":"https://..."} 7. Optional text response Server → Client TEXT: {"type":"token","response":"..."} 8. End Server → Client TEXT: {"type":"end"}

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:

┌─────────────────────┐ │ Generate client UUID │ └──────────┬──────────┘ │ v ┌─────────────────────┐ │ Connect WebSocket │ └──────────┬──────────┘ │ v ┌─────────────────────┐ │ Load HTTP history │ └──────────┬──────────┘ │ v ┌─────────────────────┐ │ Send text / audio │ └──────────┬──────────┘ │ v ┌─────────────────────┐ │ Receive response │ └──────────┬──────────┘ │ v ┌─────────────────────┐ │ Continue session │ └─────────────────────┘

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

  1. Generate UUID — client_id = UUID()
  2. Build WebSocket URL — ws://HOST/ws/chat/CHATBOT_ID/CLIENT_ID/
  3. Connect WebSocket
  4. Send text — {"message":"..."}
  5. Send audio — raw WebSocket binary data containing WebM audio
  6. Parse incoming JSON — handle token, end, audio, follow_up_questions, and the fallback response
  7. 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:

Note

These are client robustness recommendations, not protocol elements established by the supplied source.

23. Authentication

The supplied frontend does not show:

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:

25. Known Endpoint Summary

Realtime WebSocket

WS ws://{host}/ws/chat/{chatbot_id}/{client_id}/

Client → server

TypePayload
Text {"message":"..."}
Binary raw audio/webm bytes

Server → client

EventPayload
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

GET /fa/api/realtime-chat/session/{session_id}/messages/

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:

HTTP │ │ GET history v ┌───────────┐ │ REST API │ └───────────┘ WebSocket │ ┌─────────┴─────────┐ │ │ v v Send message Receive tokens Send audio Receive audio Receive follow-ups

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:

28. Information Not Defined by the Supplied Source

The following cannot be reliably specified from the supplied frontend code alone:

  1. HTTP methods for the unused REST endpoints.
  2. Request bodies for the unused REST endpoints.
  3. Exact server-side WebSocket consumer implementation.
  4. Authentication requirements.
  5. Authorization rules.
  6. Rate limits.
  7. Maximum text message size.
  8. Maximum audio size/duration.
  9. Audio codec inside WebM.
  10. WebSocket ping/pong policy.
  11. WebSocket reconnect semantics.
  12. Error event schema.
  13. Server-side timeout behavior.
  14. Exact ordering guarantees between audio, token, end, and follow_up_questions.
  15. Whether client_id is permanently associated with a database session or is only a logical session identifier.
  16. 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/
Sufficient for core functionality

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.