Skip to content

WebSocket events

LeFlux uses Socket.io over a single long-lived WebSocket connection. Mostly an implementation detail — the embed handles all of it transparently — but exposed here for self-hosters + advanced integrations.

Connection

URL: wss://leflux.xrlabs.app/socket.io/ (or your self-hosted equivalent).

Auth: same model as the REST endpoints — the browser’s Origin header on the upgrade handshake is matched against the allowed-host list. Visitor session id is passed as a query param OR via the first join_session event:

wss://leflux.xrlabs.app/socket.io/?sessionId=<uuid>

One socket per session. Server kicks older sockets if a newer one joins the same session room.

Events — client → server

EventPayload
join_session{ sessionId: string }
action_complete{ sessionId, actionId, result: { success, elementId?, description, error? } }
sequence_complete{ sessionId, result: { success, results: StepResult[] } }
update_context{ sessionId, context: { url, title, indexedElements, visibleText, ... } }
continue_task{ sessionId, context: {...} } — fires after each action when in iterative mode
confirmation_response{ sessionId, confirmed: boolean }

Events — server → client

EventPayload
session_joined{ sessionId, restored: boolean, history: Message[] }
message_chunk{ delta: string, streamId: string, chunkIndex: number } — streamed text deltas
message_done{ text: string, streamId: string } — locks in the streaming bubble
message{ text: string, isQuestion?: boolean, isError?: boolean } — non-streamed message
action_plan{ actions: Action[], message: string?, isSequence?: boolean, useUniversalIndexing: true }
ui_block{ block_type, data, message? } — rich card to render
confirmation_required{ message, confirm_label, cancel_label } — high-stakes action gate
task_complete{ summary, message } — multi-step task ended
error{ message } — surface to visitor

Action shape (in action_plan)

Single action:

{
"id": "action-1779723850877",
"type": "click_element",
"elementId": 19,
"description": "submit"
}

Sequence (multi-step):

{
"type": "execute_generic_sequence",
"isSequence": true,
"actions": [
{ "action": "type", "elementId": 12, "inputData": "Ahmed", "description": "name" },
{ "action": "type", "elementId": 13, "inputData": "ahmed@example.com", "description": "email" },
{ "action": "click", "elementId": 19, "description": "submit" }
]
}

Liveness + reconnect

Socket.io’s transport-level ping/pong (default 25s) keeps the connection warm and detects dropped sockets. If the WebSocket disconnects, Socket.io’s built-in reconnect re-establishes within a few seconds and the server-side session resumes seamlessly.

Backpressure

Server enforces a max 30 action_plans per task to prevent runaway loops. The widget enforces a max 5 client-side iteration round-trips per visitor message as defense-in-depth.

Self-hosters

If you’re running your own LeFlux instance:

  • Server stack is Node + Express + Socket.io v4 (server/src/index.js)
  • Default port is 3002, behind nginx for TLS termination
  • Socket.io uses websocket transport with polling fallback (works behind strict proxies)

See Self-hosting.