Skip to main content

Overview

VIZOCHOK uses a WebSocket-based protocol for real-time streaming chat. The protocol is designed around three principles:
  1. Auth via first message — credentials are sent as the first WebSocket message, not as query parameters (which would appear in server logs).
  2. Structured messages — all messages are JSON objects with a type field for routing.
  3. Streaming text — LLM responses are delivered token-by-token via text_delta messages for real-time display.

Connection Lifecycle

1

WebSocket Connect

Client establishes WebSocket connection. Server accepts.
2

Authentication

Client sends {"type":"auth","token":"pk_..."} (must be first message). Server responds with {"type":"auth_ok"}.
3

Session Initialization

New conversation: Server sends {"type":"conversation_started", "conversation_id":"..."}Reconnection: Server sends {"type":"session_restored", "conversation_id":"...", "selected_items":[...]} with cart state and pending tools.
4

Message Exchange

Client sends {"type":"message","text":"..."}. Server responds with status updates, text_delta chunks (repeated), text_end, and response_complete.
5

Disconnect

Client closes connection or disconnects. Session is saved to Redis for reconnection.

Endpoint

wss://api.vizochok.com/api/v1/ws/chat
For self-hosted deployments, replace the host with your API base URL. The SDK automatically converts https:// to wss://.

Client to Server Messages

auth

Must be the first message sent after connection. The server waits up to 10 seconds for this message before closing the connection with code 4001.
FieldTypeRequiredDescription
typestringYesMust be "auth".
tokenstringYesAPI key (public pk_ or secret sk_).
store_idstringNoStore identifier. Used for webhook routing.
user_idstringNoUser identifier. Enables per-user rate limits and personalization.
{
  "type": "auth",
  "token": "pk_live_abc123",
  "store_id": "my-store",
  "user_id": "user_42"
}

message

Send a text message from the user.
FieldTypeRequiredDescription
typestringYesMust be "message".
textstringYesThe user’s message text. Max 64 KB.
store_idstringNoStore identifier.
user_idstringNoUser identifier.
conversation_idstringNoConversation ID from conversation_started or session_restored. Omit to start a new conversation.
languagestringNoLanguage code: "uk", "en", or "ru". Default: "uk".
{
  "type": "message",
  "text": "Find me some milk",
  "store_id": "my-store",
  "user_id": "user_42",
  "conversation_id": "conv_abc123"
}

action

Send a user action in response to an interactive tool (product selection, quick reply, checklist, meal plan).
FieldTypeRequiredDescription
typestringYesMust be "action".
actionstringYesAction type: "select", "checklist_confirm", "meal_plan_approve", "meal_plan_modify".
store_idstringNoStore identifier.
user_idstringNoUser identifier.
conversation_idstringNoCurrent conversation ID.
skustringNoProduct SKU (for "select" action).
quantitynumberNoProduct quantity (for "select" action).
recipe_namestringNoRecipe name (for "checklist_confirm" action).
selectedstring[]NoDeselected ingredient names (for "checklist_confirm" action).
textstringNoModification text (for "meal_plan_modify" action).
{
  "type": "action",
  "action": "select",
  "sku": "milk-001",
  "quantity": 2,
  "store_id": "my-store",
  "conversation_id": "conv_abc123"
}

ping

Client-initiated keepalive message. The SDK sends this every 30 seconds.
{ "type": "ping" }

pong

Response to a server-initiated ping. Must be sent within 60 seconds to avoid heartbeat timeout.
{ "type": "pong" }

Server to Client Messages

auth_ok

Confirms successful authentication. Sent immediately after a valid auth message.
{ "type": "auth_ok" }

conversation_started

Sent when a new conversation is created (first message with no conversation_id).
FieldTypeDescription
typestring"conversation_started"
conversation_idstringThe new conversation’s unique ID.
{
  "type": "conversation_started",
  "conversation_id": "conv_abc123"
}

session_restored

Sent on reconnect when the client provides an existing conversation_id that has an active Redis session. Contains the current cart state and any pending interactive tool.
FieldTypeDescription
typestring"session_restored"
conversation_idstringThe restored conversation ID.
selected_itemsCartItem[]Current cart items (optional, present if cart is non-empty).
cart_totalnumberTotal cart value (optional).
pending_toolobjectPending interactive tool awaiting user input (optional).
The pending_tool object varies by tool type:
Tool NameFields
show_products_to_username, id, products: ProductItem[]
ask_username, id, options: string[]
show_recipe_checklistname, id, recipe_name: string, ingredients: string[]
show_meal_planname, id, title, days, total_ingredients
{
  "type": "session_restored",
  "conversation_id": "conv_abc123",
  "selected_items": [
    { "sku": "milk-001", "name": "Milk 2.5%", "price": 42.90, "quantity": 1 }
  ],
  "cart_total": 42.90
}

text_delta

A single token (or small chunk) of streaming text from the LLM. Multiple text_delta messages form a complete text block, terminated by text_end.
FieldTypeDescription
typestring"text_delta"
deltastringText fragment to append.
{ "type": "text_delta", "delta": "Here " }
{ "type": "text_delta", "delta": "are some " }
{ "type": "text_delta", "delta": "options:" }

text_end

Signals the end of a streaming text block. After this, the streaming cursor should stop.
{ "type": "text_end" }

text

A complete text block (non-streaming). Used for short, pre-formed responses.
FieldTypeDescription
typestring"text"
textstringComplete text content.
{ "type": "text", "text": "Added to cart!" }

status

Progress indicator during tool execution. Replaced in the UI when a new status arrives for the same message.
FieldTypeDescription
typestring"status"
codestringStatus code (mapped to localized text by the SDK).
textstringFallback text if the SDK does not recognize the code.
querystringSearch query (for "searching" status).
productstringProduct name (for "adding_to_cart" status).
Status codes:
CodeEnglishUkrainian
searchingSearching products Шукаю
thinkingThinking…Думаю…
adding_to_cartAdding to cart…Додаю
removing_from_cartRemoving from cart…Видаляю з кошика…
updating_cartUpdating cart…Оновлюю кількість…
suggestingSelecting the best options…Підбираю найкращі варіанти…
generating_planCreating a meal plan…Створюю план харчування…
checking_listChecking the list…Перевіряю список…
updating_profileUpdating preferences…Оновлюю налаштування…
loading_cartLoading cart…Завантажую кошик…
clearing_cartClearing cart…Очищую кошик…
loading_detailsLoading details…Завантажую деталі…
preparing_resultsPreparing results…Готую результати…
completing_sessionCompleting session…Завершую сесію…
processingProcessing…Обробляю…

product_cards

Display a set of products for the user to select from.
FieldTypeDescription
typestring"product_cards"
textstringIntroductory text (e.g., “Here are some options:”).
itemsProductItem[]Array of products to display.
querystringThe search query that produced these results (optional).

quick_replies

Display quick reply buttons for the user to choose from.
FieldTypeDescription
typestring"quick_replies"
optionsstring[]Array of reply option texts.
{
  "type": "quick_replies",
  "options": ["Yes, add all", "Show cheaper options", "No thanks"]
}

confirmation

Confirms a cart operation (add, remove, update).
FieldTypeDescription
typestring"confirmation"
textstringConfirmation message.
productProductItemThe product that was added/removed/updated.

ingredient_checklist

Display a recipe ingredient checklist for user selection.
FieldTypeDescription
typestring"ingredient_checklist"
recipe_namestringName of the recipe.
textstringDescription text.
ingredientsIngredientItem[]Array of ingredients with selection state.
Each IngredientItem:
FieldTypeDescription
idstringUnique ingredient identifier.
namestringIngredient display name.
selectedbooleanWhether the ingredient is pre-selected.

meal_plan

Display a generated meal plan for the user to approve or modify.
FieldTypeDescription
typestring"meal_plan"
titlestringPlan title.
daysMealPlanDay[]Array of days with meals.
total_ingredientsstring[]Combined ingredient list across all days.
messagestringSummary message.

session_summary

Display a summary of the current session’s cart.
FieldTypeDescription
typestring"session_summary"
itemsCartItem[]All items in the session cart.
totalnumberTotal cart value.

cart_changed

Notification that the cart was modified. This is a UI-only event — the actual cart state lives on the client’s backend.
FieldTypeDescription
typestring"cart_changed"
actionstring"item_selected", "item_removed", "quantity_updated", or "cart_cleared".
skustringSKU of the affected product.
namestringName of the affected product.
quantitynumberNew quantity.
pricenumberProduct price.
itemsCartItem[]Complete cart after the change.
totalnumberNew cart total.

response_complete

Signals the end of an agent response. The conversation is ready for the next user message.
FieldTypeDescription
typestring"response_complete"
conversation_idstringCurrent conversation ID.
session_summaryobjectOptional. Contains selected_items, total, item_count.
{
  "type": "response_complete",
  "conversation_id": "conv_abc123",
  "session_summary": {
    "selected_items": [
      { "sku": "milk-001", "name": "Milk 2.5%", "price": 42.90, "quantity": 1 }
    ],
    "total": 42.90,
    "item_count": 1
  }
}

error

An error occurred while processing the message.
FieldTypeDescription
typestring"error"
codestringMachine-readable error code. See Error Codes.
textstringFallback error text (optional).
retry_afternumberSeconds to wait before retrying (for rate_limit_exceeded).
max_sizenumberMaximum allowed message size in bytes (for message_too_large).
limitnumberThe limit that was exceeded (for user limit errors).

event

Internal server events. Currently ignored by the SDK but can be used for analytics.
FieldTypeDescription
typestring"event"
eventstringEvent name.
dataobjectEvent-specific data.

ping / pong

Server-initiated heartbeat. The client must respond with {"type": "pong"} within 60 seconds.
{ "type": "ping" }
Server response to a client-initiated ping:
{ "type": "pong" }

Close Codes

CodeReasonDescription
4001Auth timeout / Invalid authFirst message was not auth, timed out (10s), invalid format, or invalid/expired API key.
4002Missing chat scopeAPI key does not have the chat scope.
4003Origin not allowed / DeactivatedWebSocket origin not in allowed list, or tenant is deactivated.
4004LLM not configuredTenant has no LLM provider configured.
4008Heartbeat timeoutNo pong received within 60 seconds of a ping.
4029Too many connectionsAPI key has more than 3 concurrent WebSocket connections.

Reconnection Strategy

The SDK implements automatic reconnection with exponential backoff and jitter:
ParameterValue
Initial delay1,000 ms
Maximum delay10,000 ms
Backoff formulamin(1000 * 2^attempt, 10000)
Jitter+/- 25% of the computed delay
Maximum attempts30

Reconnection Rules

  • Reconnects on: Network errors, unexpected disconnects, heartbeat timeouts
  • Does not reconnect on: Auth errors (4001, 4002, 4003, 4004), clean close, explicit disconnect() call
  • Attempt counter resets: On successful connection (before auth)
  • Network awareness: Listens for browser online/offline events; reconnects when the network comes back

Session Restore on Reconnect

When the client reconnects with a conversation_id:
  1. Server loads the session from Redis (if still within the 1-hour TTL)
  2. Server sends a session_restored message with cart state and any pending tool
  3. Client can continue the conversation seamlessly
If the Redis session has expired, the server creates a new conversation.

Heartbeat

Both client and server send periodic heartbeat messages to detect dead connections:
SideIntervalTimeoutMessage
Client30s{"type": "ping"}
Server30s60s{"type": "ping"}
The server closes the connection with code 4008 if no pong is received within 60 seconds.

Message Size Limits

LimitValueEnforced By
Client message64 KBSDK (pre-send check) and server
Message queue50 messagesSDK (client-side queue during disconnect)
The SDK checks message size before sending and returns a message_too_large error via onError without transmitting the message. The server also enforces the 64 KB limit and responds with an error if exceeded.

Origin Validation

The server checks the Origin header of the WebSocket connection against the configured allowed origins list:
  • If the origin is not in the allowed list, the connection is closed with code 4003
  • This prevents unauthorized websites from connecting to the WebSocket API
  • The origin list is configured via the CORS_ORIGINS environment variable
Due to a Starlette/FastAPI limitation, the WebSocket must be accepted before the origin can be checked. The connection is accepted first, then immediately closed with a custom code if the origin is not allowed.