Skip to main content
The Chat v1 REST endpoints cover request/response integrations — a gateway sending customer messages and reading replies. For a live UI (a web widget, an embedded support panel) that needs to show messages, mode changes, and session events as they happen, connect directly to the session over a WebSocket instead of polling.
This is for connecting a client — typically a browser — to one specific session, in addition to your server-to-server integration with POST /v1/chat/inbound. It’s not a replacement for the REST API.

1. Mint a token

Call POST /v1/chat/session/{id}/ws_token with your API-Token to get a short-lived token and the URL to connect to:
With an API-Token, stream must be "user" — a 403 is returned for any other value. The session must not be closed (409 session_closed); a dormant session can still be minted for, since the next customer message reactivates it.
The token expires 5 minutes after it’s issued. Mint a fresh one for each new connection, and again before an existing connection’s token expires if you want to keep the connection alive uninterrupted (see Reconnecting).

2. Connect

Open the WebSocket at ws_url exactly as returned (don’t build the host yourself), adding token and your own client_id as query parameters:

3. Send frames

Send JSON text frames. Two shapes are accepted:
  • type defaults to "message" if omitted.
  • message_id is optional — a client-chosen id echoed back on the acknowledgement, useful for matching a send to its ack in your UI.
  • idempotency_key is also accepted on either frame shape and behaves the same as on POST /v1/chat/inbound/async — resending the same key and content is safe.
  • For interaction responses, send option_id (a button/list/carousel choice) or response (free-form input like a date), matching what the interactive message asked for — see Interactive messages.
  • Only text frames are accepted; sending binary data closes the connection.

4. Read acknowledgements and events

Every frame you send gets exactly one acknowledgement back:
When your message completes a turn inline (the common case), payload carries the same fields and state values as the synchronous Send Message response — reply, mode, and help_requested. A few states are specific to the socket: Every ack echoes message_id back if your frame included one, and carries turn_id once a turn exists for it.

Events

Beyond acknowledgements, the connection pushes events as they happen in the session: Every event is wrapped as { type, session_id, account_id, ts, event_id, payload }. Use event_id to de-duplicate — a reconnect can replay a short window of recent events so you don’t miss anything that happened while briefly disconnected.

Close codes

Reconnecting

  • On an unexpected drop, reconnect with exponential backoff — roughly 1s, 2s, 4s, 8s, up to a 30s cap — and give up after a handful of attempts, surfacing an error in your UI.
  • Mint a new token ahead of the 5-minute expiry rather than waiting for the connection to be closed, so an active conversation doesn’t interrupt the customer.
  • Reuse the same client_id across reconnects (persist it locally, e.g. in sessionStorage) so you resume as the same client. Only send claim=1 when the user explicitly wants to take over from another tab/session holding the connection — not on every routine reconnect, or you’ll evict yourself on the next tab.
  • On a 1008 close with the agent inactive, don’t reconnect immediately in a tight loop — poll less aggressively (e.g. every 20s) until the agent is active again. On a 1008 close from a claim conflict, don’t auto-reconnect at all — surface a “Resume here?” action and only reconnect with claim=1 if the customer confirms.