Skip to main content
GET
/
ws
/
:instance
Connect via WebSocket
curl --request GET \
  --url https://api.example.com/ws/:instance
{
  "success": false,
  "error": {
    "message": "WebSocket is not enabled for this instance. Configure it first using POST /api/events/websocket/$Instance_Name"
  }
}

Documentation Index

Fetch the complete documentation index at: https://docs.ryzeapi.cloud/llms.txt

Use this file to discover all available pages before exploring further.

Auth: 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 with event, data, and instanceData. Configuration (enable, filter events, toggle mediaBase64) is done at POST /api/events/websocket/:instance. This page covers only the connection layer.
Prerequisite: the instance must have WebSocket configured with enabled=true. Without that, the upgrade fails with 400 before becoming a WS connection.

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.
curl -v --include \
  --header "Connection: Upgrade" \
  --header "Upgrade: websocket" \
  --header "Sec-WebSocket-Key: $(openssl rand -base64 16)" \
  --header "Sec-WebSocket-Version: 13" \
  --header "token: $Token_Instance" \
  "https://ryzeapi.cloud/ws/$Instance_Name"

wscat (interactive)

Opens a real interactive WebSocket session with wscat (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.
# npm i -g wscat
wscat -c "wss://api.example.com/ws/$Instance_Name?token=$Token_Instance"

Client examples

Browsers don’t allow custom headers on WebSocket, use ?token=:
const BASE = "wss://api.example.com";
const INSTANCE = "$Instance_Name";
const TOKEN = "a1b2c3d4-..."; // Account or Instance

let ws;
let reconnectDelay = 1000; // 1s, doubles up to 30s

function connect() {
  ws = new WebSocket(`${BASE}/ws/${INSTANCE}?token=${encodeURIComponent(TOKEN)}`);

  ws.addEventListener("open", () => {
    console.log("WS connected");
    reconnectDelay = 1000;
  });

  ws.addEventListener("message", (ev) => {
    try {
      const env = JSON.parse(ev.data);
      switch (env.event) {
        case "message.exchange":  handleMessage(env.data); break;
        case "instance.state":    handleState(env.data);   break;
        // ... other types
      }
    } catch (e) {
      console.error("Non-JSON frame:", ev.data);
    }
  });

  ws.addEventListener("close", (ev) => {
    console.warn(`Closed (code=${ev.code}). Reconnecting in ${reconnectDelay}ms...`);
    setTimeout(connect, reconnectDelay);
    reconnectDelay = Math.min(reconnectDelay * 2, 30000);
  });

  ws.addEventListener("error", (ev) => {
    console.error("WS error:", ev);
    // 'close' fires next
  });
}

connect();

Envelope of received frames

Each text frame is a JSON identical to the webhook:
{
  "event": "message.exchange",
  "data": { /* specific payload */ },
  "instanceData": {
    "baseUrl": "https://api.example.com",
    "instance": "$Instance_Name",
    "token":    "<instance-token>"
  }
}
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.
When ENCRYPTION_KEY is configured, the token comes decrypted in the payload. Filter/redact it in client logs if you log the entire frame.

Path parameters

instance
string
required
Instance name. Must exist and have WebSocket enabled.

Headers

NameRequiredExampleDescription
token or Authorizationyes, unless using ?token=token: a1b2c3...Flexible auth.
UpgradeyeswebsocketRequired by the protocol.
ConnectionyesUpgradeSame.
Sec-WebSocket-Keyyes(generated by client)Same.
Sec-WebSocket-Versionyes13Only accepted value.
Originconditionalhttps://app.example.comValidated against allowlist in browsers. Clients without Origin are accepted.

Query parameters

token
string
Authentication token. Required when the client cannot send token or Authorization in the header (browser case).

Preconditions

  1. There is a config in websocket_configs for the instance (created via POST /api/events/websocket/:instance) with enabled=true.
  2. Valid token, TokenAccount or TokenInstance of the instance (same matrix as REST).
  3. If coming from a browser, the request Origin is in ALLOWED_WS_ORIGINS or is same-origin.
Validation happens before the HTTP→WS upgrade. After the upgrade there is no re-authentication, the TCP session is trusted until closed.

Authentication

ValidateTokenFlexible() accepts the token from three sources:
SourceExample
Header tokentoken: a1b2c3d4-...
Header Authorization: BearerAuthorization: Bearer a1b2c3d4-...
Query param ?token=wss://api.example.com/ws/myinst?token=a1b2c3d4-...
The query param is practically required for browser clients, since the browser’s new WebSocket(url) API doesn’t allow custom headers.Server-side clients (Node, Go, Python, etc.) should prefer the token header, query params leak into proxy/CDN logs.

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_ORIGINSBehavior
Empty / undefinedOnly 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

SideMessageInterval
Server → clientPINGevery ~54s (pingPeriod)
Client → serverPONGwithin 60s (pongWait)
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 (gorilla/websocket, Node ws, Python websockets, native browser) reply PONG automatically, the client almost never needs to implement this manually.

Buffers and backpressure

LimitValueEffect when exceeded
Read buffer (max client message)4096 bytesServer closes the connection.
Send buffer per client256 messagesSlow client is dropped by the hub.
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.

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 (WritePump and ReadPump) 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 at POST /api/events/websocket/:instance applies 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

CriterionWebSocketWebhook
Latencyms (direct push)100-500ms (HTTP + queue)
PersistenceEvents lost if offlineQueue + 5x retry + DLQ
Multi-consumerSeveral clients in broadcastUp to 3 enabled webhooks (labels)
AuthToken on upgradeOptional Authorization header
SetupWS client + enabled configPublic HTTP endpoint + URL
Dev-friendlinessExcellent (wscat, DevTools)Requires tunneling in dev
  • Webhook: server-to-server integrations where loss is unacceptable (CRM, ERP, analytics, log sink).
  • WebSocket: real-time UIs (dashboard, live inbox, support screen) where low latency is the priority and occasional losses are acceptable.
  • Both in parallel: webhook persists state, WS makes the UI pop.

Errors before the upgrade

All occur before 101 Switching Protocols, the client receives a normal HTTP response.
HTTPerror.messageWhen
400WebSocket is not enabled for this instance. Configure it first using POST /api/events/websocket/<instance>No config or enabled=false.
401Missing token in header, Authorization header, or query parameterNone of the 3 sources provided a token.
401Invalid tokenInvalid token.
403— (empty body, from the upgrader)Origin outside the allowlist.
404Instance not found:instance does not exist.
429Rate limit exceeded. Try again later.Global rate-limit.
500Failed to get instance / Failed to get websocket configurationDatabase error.
{
  "success": false,
  "error": {
    "message": "WebSocket is not enabled for this instance. Configure it first using POST /api/events/websocket/$Instance_Name"
  }
}

Errors after the upgrade

After 101, 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/:instance

Event catalog

Schemas of the 6 types.

Events overview

Webhook vs WebSocket, envelope, best practices.

Authentication

Token matrix, token header, ?token=.