DokumentasjonSanntidMarkdown
Realtime
Snakkbar pushes small doorbell events; you fetch the actual content over HTTP. That keeps the stream cheap, makes missed events harmless (just refetch), and means one code path for initial load and updates.
Streams
| Who | Endpoint | Receives |
|---|---|---|
| Visitor | GET /api/v1/visitor/stream?conversationId=<id> | Events for the visitor's own conversations; typing/read for conversationId; presence; watch for this visitor |
| Operator | GET /api/v1/operator/stream | Everything in the tenant, including live visitors (visitor, pointer) |
Both are Server-Sent Events over a normal fetch with the Authorization header (the browser's
EventSource can't send headers, so read response.body yourself or use the SDK).
event: ready
data: {"type":"ready"}
event: message
data: {"type":"message","conversationId":"…","messageId":4812}
event: typing
data: {"type":"typing","conversationId":"…","side":"operator","operator":{"id":"…","handle":"kari","name":"Kari","avatarUrl":null},"typing":true}Events (RealtimeEvent)
type | Fields | Do |
|---|---|---|
ready | Stream is live. Refetch once to cover anything missed while connecting. | |
ping | Every 20 s. Ignore. | |
conversation | conversationId | Status, owner, participants or read state changed → refetch it (and your list). |
message | conversationId, messageId | New message or a new translation → refetch the conversation. |
typing | conversationId, side, operator?, typing | Show/hide a typing indicator. Hide it yourself after 6 s without updates. |
read | conversationId, side, at | Update «Sett» receipts. |
presence | Someone checked in/out or came online → refetch config() (visitor) or team() (operator). | |
visitor | visitorId | Operator stream. A visitor came online, started a session, opened a page, switched tabs or changed their profile → refetch the visitor list (and that visitor's detail if shown). |
pointer | LiveFrame: visitorId, tabId, url, title, visible, viewport, scroll, documentHeight, samples, clicks | Operator stream, only while someone watches that visitor. Draw it in the live view. The one event that carries data rather than a doorbell. |
watch | active | Visitor stream. An operator started (true) or stopped (false) watching this visitor → switch the tracker's pace now. The SDK does this for you. |
Debounce refetches (the stock UIs use 250 ms) — one visitor message can ring several times (message, translation, conversation update).
Live visitors
The visitor's browser (the SDK's startTracking(), used by the stock widget) drives the live
visitor feed with POST /visitor/activity:
| Cadence | |
|---|---|
| Heartbeat | About every 10 s (the response's interval), from every open tab, plus at once on a page load, a single-page-app URL change or a tab switch. |
| Online | A heartbeat within the last 35 s. Nothing rings when a visitor goes offline: re-check lastSeenAt on a timer. |
| Session | Starts with a heartbeat after 30 minutes of silence. |
| Watch | POST /operator/visitors/:id/watch lasts 25 s; renew it every 10 s (the SDK's visitors.watch() does). DELETE ends it at once. |
| While watched | Each tab sends a batch about every 500 ms (pointer positions sampled at most every 40 ms, up to 100 per batch, and up to 20 clicks), or a keep-alive every 2 s when nothing moved. Every batch becomes one pointer event. |
The browser learns about a watch from the watch event if it has the visitor stream open (the
widget does while the chat is open), otherwise from the next heartbeat response
({ watch: true }), so a live view can take up to 10 s to start. It falls back to heartbeats when
a response says watch: false, e.g. 25 s after the last renewal.
Using a pointer event: samples and clicks are [t, x, y], where t is milliseconds since
the batch's first sample and x/y are viewport (client) coordinates in CSS px; replay them over
the batch for smooth motion. Scale them by your view's width divided by viewport.width, and
scroll your copy of the page to scroll. Several tabs of one visitor can send frames; follow the
one with visible: true. pointer events go to every operator's stream, so filter by visitorId.
Lifetime and fallback
The server ends each stream after about 4 minutes; reconnect immediately. On errors, back off
(1 s, 2 s, 5 s). If streaming keeps failing (some proxies buffer SSE), poll the relevant GET
every 5 s and try streaming again every 60 s. The SDK's subscribe() does all of this and, while
polling, emits conversation events — with conversationId: "*" on the operator side, meaning
"refetch everything". Polling can't carry pointer events: a live view only moves while the
operator stream is live (onStatus reports "live").
Turn streams
Sending a visitor message (POST /visitor/conversations/:id/messages) returns its own SSE stream
with the assistant's reply as it is written (TurnEvents, see api.md). Doorbell events
for the same messages also arrive on the realtime stream; dedupe by message id (or clientId).