Events
Connect via WebSocket
Upgrade /ws/:instance, protocol, authentication, Origin validation, heartbeat, and reconnection
GET
Connect via WebSocket
Auth:
Origin validation (
Independent of CORS (which only affects REST), WebSocket has its own allowlist controlled by the env var
No PONG within 60s → the server drops the connection. There is no session resume: the client must reconnect with backoff and events lost during the gap do not return.
Most WebSocket libraries (
The server does not consume messages sent by the client (only PONG and close). Sending JSON payloads from the client to the server has no effect.
TokenAccount or TokenInstance (header or query) • Protocol: WSS/WS • Rate-limit: Global (100/min on the upgrade)
Description
HTTP → WebSocket upgrade endpoint to receive events in real time. Frames sent by the server use the same envelope as webhooks: a JSON text withevent, data, and instanceData.
Configuration (enable, filter events, toggle mediaBase64) is done at POST /api/events/websocket/:instance. This page covers only the connection layer.
cURL examples (handshake)
websocat or wscat give you an interactive experience. cURL is only useful to inspect the handshake.
Handshake (inspection)
Performs the raw HTTP→WebSocket upgrade handshake just to inspect status, headers, and confirm that the instance is accepting connections. It does not keep the channel open, it’s a one-shot probe.wscat (interactive)
Opens a real interactive WebSocket session withwscat (or an equivalent client in each language) and prints every received JSON frame. The most practical way to debug events in real time during development.
Client examples
- Browser (JS)
- Node.js (ws)
- Python (websockets)
- Go (gorilla)
Browsers don’t allow custom headers on
WebSocket, use ?token=:Envelope of received frames
Each text frame is a JSON identical to the webhook:instanceData.token is the instance’s own token, useful when a client consumes multiple instances and needs to identify the source or make REST calls back.
Path parameters
Instance name. Must exist and have WebSocket enabled.
Headers
| Name | Required | Example | Description |
|---|---|---|---|
token or Authorization | yes, unless using ?token= | token: a1b2c3... | Flexible auth. |
Upgrade | yes | websocket | Required by the protocol. |
Connection | yes | Upgrade | Same. |
Sec-WebSocket-Key | yes | (generated by client) | Same. |
Sec-WebSocket-Version | yes | 13 | Only accepted value. |
Origin | conditional | https://app.example.com | Validated against allowlist in browsers. Clients without Origin are accepted. |
Query parameters
Authentication token. Required when the client cannot send
token or Authorization in the header (browser case).Preconditions
- There is a config in
websocket_configsfor the instance (created viaPOST /api/events/websocket/:instance) withenabled=true. - Valid token,
TokenAccountorTokenInstanceof the instance (same matrix as REST). - If coming from a browser, the request
Originis inALLOWED_WS_ORIGINSor is same-origin.
Authentication
ValidateTokenFlexible() accepts the token from three sources:
| Source | Example |
|---|---|
Header token | token: a1b2c3d4-... |
Header Authorization: Bearer | Authorization: Bearer a1b2c3d4-... |
Query param ?token= | wss://api.example.com/ws/myinst?token=a1b2c3d4-... |
Origin validation (ALLOWED_WS_ORIGINS)
Independent of CORS (which only affects REST), WebSocket has its own allowlist controlled by the env var ALLOWED_WS_ORIGINS.
ALLOWED_WS_ORIGINS | Behavior |
|---|---|
| Empty / undefined | Only same-origin (Origin equal to Host) is accepted. |
"https://app.example.com,https://dashboard.example.com" | Explicit allowlist, comma-separated. |
Clients without an
Origin header (curl, Postman, Node/Python/Go libs) are always accepted, Origin is a browser mechanism, not universal. Security for those clients comes from the token.Blocks are logged as WebSocket upgrade blocked from origin <origin> (host <host>). The client receives 403 Forbidden (no body) and the TCP is closed.Heartbeat
| Side | Message | Interval |
|---|---|---|
| Server → client | PING | every ~54s (pingPeriod) |
| Client → server | PONG | within 60s (pongWait) |
gorilla/websocket, Node ws, Python websockets, native browser) reply PONG automatically, the client almost never needs to implement this manually.
Buffers and backpressure
| Limit | Value | Effect when exceeded |
|---|---|---|
| Read buffer (max client message) | 4096 bytes | Server closes the connection. |
| Send buffer per client | 256 messages | Slow client is dropped by the hub. |
Event catalog
The 6 possible types (message.exchange, message.status, call.update, group.flow, instance.state, label.update) share this envelope. Full schemas and examples at /en/api/events/catalog.
Reconnection and resilience
The server does not replay events lost during outages, the WS client is fire-and-forget. For delivery guarantee, use webhook in parallel.Always have a
close handler with automatic reconnection, ideally with exponential backoff and jitter, capped at 30s between attempts.Handle close codes:
1006 (network drop), 1011 (server error), 1008 (policy violation), 4xxx (custom, rare).Catch up via REST after reconnecting, use
GET /api/chat/history/:instance to pull recent messages that may have been missed.Local buffer in the client, never block the
message handler with slow operations; queue and process in another thread/worker.Side effects
- In-memory hub: the handler registers the client in
WebSocketHub(map[instanceName]map[*WebSocketClient]bool). The connection is not persisted. A process restart drops them all. - Goroutines: each connection spawns 2 goroutines (
WritePumpandReadPump) that live until close. - No DB write: the upgrade itself writes nothing. Subsequent broadcasts go through the webhook dispatcher (which touches the DB) in parallel, WS is just an additional fanout.
- Prometheus metrics: counters of active connections per instance (see /en/api/observability/overview).
Notes
- No retry/persistence: a client offline for 5 min loses 5 min of events. For guarantees, use webhook.
- Multi-client: multiple clients can connect to the same instance. All of them receive all events (broadcast). There is no atomicity for “who processed first”.
- Filters are global per instance: the
events[]filter configured atPOST /api/events/websocket/:instanceapplies to all clients, it’s not configurable per connection. - Server frame size: the 4096-byte limit applies only to messages sent by the client. The server sends potentially much larger frames (base64 media easily exceeds 100KB). Read frames without limits in the client.
When to use webhook vs WebSocket
| Criterion | WebSocket | Webhook |
|---|---|---|
| Latency | ms (direct push) | 100-500ms (HTTP + queue) |
| Persistence | Events lost if offline | Queue + 5x retry + DLQ |
| Multi-consumer | Several clients in broadcast | Up to 3 enabled webhooks (labels) |
| Auth | Token on upgrade | Optional Authorization header |
| Setup | WS client + enabled config | Public HTTP endpoint + URL |
| Dev-friendliness | Excellent (wscat, DevTools) | Requires tunneling in dev |
Errors before the upgrade
All occur before101 Switching Protocols, the client receives a normal HTTP response.
| HTTP | error.message | When |
|---|---|---|
| 400 | WebSocket is not enabled for this instance. Configure it first using POST /api/events/websocket/<instance> | No config or enabled=false. |
| 401 | Missing token in header, Authorization header, or query parameter | None of the 3 sources provided a token. |
| 401 | Invalid token | Invalid token. |
| 403 | , (empty body, from the upgrader) | Origin outside the allowlist. |
| 404 | Instance not found | :instance does not exist. |
| 429 | Rate limit exceeded. Try again later. | Global rate-limit. |
| 500 | Failed to get instance / Failed to get websocket configuration | Database error. |
Errors after the upgrade
After101, any protocol failure closes the connection with a standard close code (1001 going away, 1006 abnormal closure, 1011 server error). The server doesn’t write a body, the client handles it via the close code.
Common causes:
- Client frame larger than 4096 bytes.
pongWait(60s) expired without a reply to the PING.- Client send buffer full (slow client), the hub unregisters it.
- Instance was deleted while the client was connected.
References
Configure WebSocket
POST /api/events/websocket/:instanceEvent catalog
Schemas of the 6 types.
Events overview
Webhook vs WebSocket, envelope, best practices.
Authentication
Token matrix,
token header, ?token=.