> ## 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.

# Conectar via WebSocket

> Upgrade /ws/:instance, protocolo, autenticação, validação de Origin, heartbeat e reconexão

**Auth:** `TokenAccount` ou `TokenInstance` (header ou query) • **Protocolo:** WSS/WS • **Rate-limit:** `Global` (100/min sobre o upgrade)

## Descrição

Endpoint de upgrade HTTP → WebSocket para receber eventos em tempo real. Os frames enviados pelo servidor têm o **mesmo envelope** dos webhooks, texto JSON com `event`, `data` e `instanceData`.

A configuração (habilitar, filtrar eventos, ativar `mediaBase64`) é feita em [`POST /api/events/websocket/:instance`](/pt/api/events/websocket-configure). Esta página cobre apenas a camada de conexão.

<Warning>
  Pré-requisito: a instância precisa ter o WebSocket configurado com `enabled=true`. Sem isso, o upgrade falha com `400` antes de virar conexão WS.
</Warning>

## Exemplos cURL (handshake)

`websocat` ou `wscat` dão experiência interativa. cURL serve só para inspecionar o handshake.

### Handshake (inspeção)

Faz o handshake bruto de upgrade HTTP→WebSocket apenas para inspecionar status, headers e validar que a instância está aceitando conexões. Não mantém o canal aberto, é uma sondagem one-shot.

<CodeGroup>
  ```bash cURL theme={null}
  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"
  ```

  ```javascript JavaScript theme={null}
  // Handshake bruto via Node (sem libs WS) - apenas para inspecionar
  import https from "node:https";
  import crypto from "node:crypto";

  const req = https.request({
    hostname: "ryzeapi.cloud",
    path:     `/ws/${process.env.Instance_Name}`,
    method:   "GET",
    headers: {
      "Connection":            "Upgrade",
      "Upgrade":               "websocket",
      "Sec-WebSocket-Key":     crypto.randomBytes(16).toString("base64"),
      "Sec-WebSocket-Version": "13",
      "token":                 process.env.Token_Instance
    }
  });
  req.on("upgrade", (res, socket) => {
    console.log("Upgraded:", res.statusCode);
    socket.end();
  });
  req.end();
  ```

  ```python Python theme={null}
  # Handshake bruto - apenas para inspecionar
  import os, base64, secrets, http.client

  conn = http.client.HTTPSConnection("ryzeapi.cloud")
  conn.request(
      "GET",
      f"/ws/{os.environ['Instance_Name']}",
      headers={
          "Connection":            "Upgrade",
          "Upgrade":               "websocket",
          "Sec-WebSocket-Key":     base64.b64encode(secrets.token_bytes(16)).decode(),
          "Sec-WebSocket-Version": "13",
          "token":                 os.environ["Token_Instance"]
      }
  )
  resp = conn.getresponse()
  print("Status:", resp.status)
  ```

  ```go Go theme={null}
  package main

  import (
      "crypto/rand"
      "encoding/base64"
      "log"
      "net/http"
      "os"
  )

  func main() {
      key := make([]byte, 16)
      rand.Read(key)
      req, _ := http.NewRequest("GET", "https://ryzeapi.cloud/ws/"+os.Getenv("Instance_Name"), nil)
      req.Header.Set("Connection", "Upgrade")
      req.Header.Set("Upgrade", "websocket")
      req.Header.Set("Sec-WebSocket-Key", base64.StdEncoding.EncodeToString(key))
      req.Header.Set("Sec-WebSocket-Version", "13")
      req.Header.Set("token", os.Getenv("Token_Instance"))
      resp, err := http.DefaultClient.Do(req)
      if err != nil {
          log.Fatal(err)
      }
      log.Println("Status:", resp.Status)
  }
  ```
</CodeGroup>

### wscat (interativo)

Abre uma sessão WebSocket interativa real com `wscat` (ou cliente equivalente em cada linguagem) e fica imprimindo cada frame JSON recebido. Forma mais prática para debugar eventos em tempo real durante o desenvolvimento.

<CodeGroup>
  ```bash cURL theme={null}
  # npm i -g wscat
  wscat -c "wss://api.example.com/ws/$Instance_Name?token=$Token_Instance"
  ```

  ```javascript JavaScript theme={null}
  // Cliente WebSocket nativo (Node.js 22+ ou browser)
  const ws = new WebSocket(`wss://api.example.com/ws/${process.env.Instance_Name}?token=${process.env.Token_Instance}`);
  ws.addEventListener("message", (ev) => console.log(ev.data));
  ```

  ```python Python theme={null}
  import asyncio, os, websockets

  async def main():
      url = f"wss://api.example.com/ws/{os.environ['Instance_Name']}?token={os.environ['Token_Instance']}"
      async with websockets.connect(url) as ws:
          async for raw in ws:
              print(raw)

  asyncio.run(main())
  ```

  ```go Go theme={null}
  package main

  import (
      "log"
      "os"
      "github.com/gorilla/websocket"
  )

  func main() {
      url := "wss://api.example.com/ws/" + os.Getenv("Instance_Name") + "?token=" + os.Getenv("Token_Instance")
      c, _, err := websocket.DefaultDialer.Dial(url, nil)
      if err != nil {
          log.Fatal(err)
      }
      defer c.Close()
      for {
          _, msg, err := c.ReadMessage()
          if err != nil {
              log.Fatal(err)
          }
          log.Printf("%s", msg)
      }
  }
  ```
</CodeGroup>

## Exemplos de cliente

<Tabs>
  <Tab title="Browser (JS)">
    Browsers não permitem customizar headers no `WebSocket`, use `?token=`:

    ```js theme={null}
    const BASE = "wss://api.example.com";
    const INSTANCE = "$Instance_Name";
    const TOKEN = "a1b2c3d4-..."; // Account ou Instance

    let ws;
    let reconnectDelay = 1000; // 1s, dobra até 30s

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

      ws.addEventListener("open", () => {
        console.log("WS conectado");
        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;
            // ... demais tipos
          }
        } catch (e) {
          console.error("Frame não-JSON:", ev.data);
        }
      });

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

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

    connect();
    ```
  </Tab>

  <Tab title="Node.js (ws)">
    Server-side: prefira o header `token`:

    ```js theme={null}
    import WebSocket from "ws";

    const ws = new WebSocket("wss://api.example.com/ws/$Instance_Name", {
      headers: { token: process.env.Token_Instance }
    });

    ws.on("open",    () => console.log("conectado"));
    ws.on("message", (buf) => {
      const env = JSON.parse(buf.toString());
      console.log(env.event, env.data);
    });
    ws.on("close",   (code, reason) => console.warn("fechou", code, reason?.toString()));
    ws.on("error",   (e) => console.error(e));
    ```
  </Tab>

  <Tab title="Python (websockets)">
    ```python theme={null}
    import asyncio, json, os, websockets

    async def consume():
        url = "wss://api.example.com/ws/$Instance_Name"
        headers = {"token": os.environ["Token_Instance"]}
        async with websockets.connect(url, extra_headers=headers) as ws:
            async for raw in ws:
                env = json.loads(raw)
                print(env["event"], env["data"])

    asyncio.run(consume())
    ```
  </Tab>

  <Tab title="Go (gorilla)">
    ```go theme={null}
    package main

    import (
        "log"
        "net/http"
        "github.com/gorilla/websocket"
    )

    func main() {
        h := http.Header{}
        h.Set("token", "a1b2c3d4-...")
        c, _, err := websocket.DefaultDialer.Dial(
            "wss://api.example.com/ws/$Instance_Name", h)
        if err != nil { log.Fatal(err) }
        defer c.Close()

        for {
            _, msg, err := c.ReadMessage()
            if err != nil { log.Fatal(err) }
            log.Printf("%s", msg)
        }
    }
    ```
  </Tab>
</Tabs>

## Envelope dos frames recebidos

Cada frame de texto é um JSON idêntico ao webhook:

```json theme={null}
{
  "event": "message.exchange",
  "data": { /* payload específico */ },
  "instanceData": {
    "baseUrl": "https://api.example.com",
    "instance": "$Instance_Name",
    "token":    "<instance-token>"
  }
}
```

`instanceData.token` é o **token da própria instância**, útil quando o cliente consome múltiplas instâncias e precisa identificar a origem ou fazer chamadas REST de volta.

<Warning>
  Quando `ENCRYPTION_KEY` está configurada, o token vem **descriptografado** no payload. Filtre/redija em logs do cliente se você loggar o frame inteiro.
</Warning>

## Parâmetros de rota

<ParamField path="instance" type="string" required>
  Nome da instância. Deve existir e ter WebSocket habilitado.
</ParamField>

## Headers

| Nome                       |          Obrigatório         | Exemplo                   | Descrição                                                                |
| -------------------------- | :--------------------------: | ------------------------- | ------------------------------------------------------------------------ |
| `token` ou `Authorization` | sim, salvo se usar `?token=` | `token: a1b2c3...`        | Auth flexível.                                                           |
| `Upgrade`                  |              sim             | `websocket`               | Exigido pelo protocolo.                                                  |
| `Connection`               |              sim             | `Upgrade`                 | Idem.                                                                    |
| `Sec-WebSocket-Key`        |              sim             | (gerado pelo cliente)     | Idem.                                                                    |
| `Sec-WebSocket-Version`    |              sim             | `13`                      | Único valor aceito.                                                      |
| `Origin`                   |          condicional         | `https://app.exemplo.com` | Validado contra allowlist em browsers. Clients sem `Origin` são aceitos. |

## Query parameters

<ParamField query="token" type="string">
  Token de autenticação. **Obrigatório** quando o cliente não pode enviar `token` ou `Authorization` no header (caso dos browsers).
</ParamField>

## Pré-condições

1. Existe uma config em `websocket_configs` para a instância (criada via [`POST /api/events/websocket/:instance`](/pt/api/events/websocket-configure)) com `enabled=true`.
2. Token válido, `TokenAccount` ou `TokenInstance` da instância (mesma matriz do REST).
3. Se vier de browser, o `Origin` do request está em `ALLOWED_WS_ORIGINS` ou é same-origin.

A validação acontece **antes do upgrade HTTP→WS**. Após o upgrade não há re-autenticação, a sessão TCP é confiável até ser fechada.

## Autenticação

`ValidateTokenFlexible()` aceita o token em **três fontes**:

| Fonte                          | Exemplo                                             |
| ------------------------------ | --------------------------------------------------- |
| Header `token`                 | `token: a1b2c3d4-...`                               |
| Header `Authorization: Bearer` | `Authorization: Bearer a1b2c3d4-...`                |
| Query param `?token=`          | `wss://api.example.com/ws/minha?token=a1b2c3d4-...` |

<Warning>
  O query param é **praticamente obrigatório para clients de browser**, já que a API `new WebSocket(url)` do navegador não permite customizar headers.

  Clients server-side (Node, Go, Python, etc.) devem **preferir o header `token`**, query param vaza em logs de proxy/CDN.
</Warning>

## Validação de Origin (`ALLOWED_WS_ORIGINS`)

Independente do CORS (que afeta só REST), o WebSocket tem sua própria allowlist controlada pela env var `ALLOWED_WS_ORIGINS`.

| `ALLOWED_WS_ORIGINS`                                      | Comportamento                                           |
| --------------------------------------------------------- | ------------------------------------------------------- |
| Vazio / não definido                                      | Apenas **same-origin** (Origin igual ao Host) é aceito. |
| `"https://app.exemplo.com,https://dashboard.exemplo.com"` | Allowlist explícita, separada por vírgula.              |

<Note>
  **Clientes sem header `Origin`** (curl, Postman, libs Node/Python/Go) **são sempre aceitos**, `Origin` é mecanismo do browser, não universal. A segurança para esses clientes é o token.

  Bloqueios são logados como `WebSocket upgrade blocked from origin <origem> (host <host>)`. O cliente recebe `403 Forbidden` (sem body) e a TCP é fechada.
</Note>

## Heartbeat

| Lado               | Mensagem | Intervalo                   |
| ------------------ | -------- | --------------------------- |
| Servidor → cliente | PING     | a cada \~54s (`pingPeriod`) |
| Cliente → servidor | PONG     | dentro de 60s (`pongWait`)  |

Sem PONG em 60s → o servidor dropa a conexão. **Não há resumo de sessão**: o cliente deve reconectar com backoff e os eventos perdidos no intervalo **não voltam**.

A maioria das libs WebSocket (`gorilla/websocket`, `ws` do Node, `websockets` do Python, browser nativo) responde PONG automaticamente, o cliente quase nunca precisa implementar isso manualmente.

## Buffers e backpressure

| Limite                           | Valor         | Efeito ao estourar                |
| -------------------------------- | ------------- | --------------------------------- |
| Read buffer (max client message) | 4096 bytes    | Servidor fecha a conexão.         |
| Send buffer por cliente          | 256 mensagens | Cliente lento é dropado pelo hub. |

O servidor **não consome** mensagens enviadas pelo cliente (apenas PONG e fechamento). Enviar payloads JSON do cliente para o servidor não tem efeito.

## Catálogo de eventos

Os 6 tipos possíveis (`message.exchange`, `message.status`, `call.update`, `group.flow`, `instance.state`, `label.update`) compartilham este envelope. Schemas e exemplos completos em [/api/events/catalog](/pt/api/events/catalog).

## Reconexão e resiliência

O servidor **não replica** eventos perdidos durante quedas, o cliente WS é fire-and-forget. Para garantia de entrega, use webhook em paralelo.

<Check>**Sempre tenha handler de `close` com reconexão automática**, idealmente com backoff exponencial e jitter, limitado a 30s entre tentativas.</Check>
<Check>**Trate close codes**: `1006` (queda de rede), `1011` (erro do servidor), `1008` (policy violation), `4xxx` (custom, raros).</Check>
<Check>**Catch-up via REST** depois de reconectar, use [`GET /api/chat/history/:instance`](/pt/api/chat/history) para puxar mensagens recentes que possam ter sido perdidas.</Check>
<Check>**Buffer local no cliente**, nunca bloqueie o handler `message` com operações lentas; jogue em fila e processe em outra thread/worker.</Check>

## Efeitos colaterais

* **Hub em memória**: o handler registra o cliente em `WebSocketHub` (`map[instanceName]map[*WebSocketClient]bool`). A conexão **não é persistida**. Reinício do processo derruba todos.
* **Goroutines**: cada conexão lança 2 goroutines (`WritePump` e `ReadPump`) que vivem até o close.
* **Sem DB write**: o upgrade em si não escreve nada. Broadcasts subsequentes passam pelo dispatcher de webhook (que toca DB) em paralelo, WS é apenas fanout adicional.
* **Métricas Prometheus**: contadores de conexões ativas por instância (ver [/api/observability/overview](/pt/api/observability/overview)).

## Notas

<Note>
  * **Sem retry/persistência**: cliente offline 5min = perde 5min de eventos. Para garantia, use webhook.
  * **Multi-cliente**: vários clientes podem conectar na mesma instância. Todos recebem todos os eventos (broadcast). Não há atomicidade de "quem processou primeiro".
  * **Filtros são globais por instância**: o filtro `events[]` configurado em `POST /api/events/websocket/:instance` vale para todos os clientes, não é configurável por conexão.
  * **Frame size do servidor**: o limite de 4096 bytes vale apenas para mensagens **enviadas pelo cliente**. O servidor envia frames potencialmente bem maiores (mídia em base64 passa de 100KB facilmente). Leia frames sem limite no cliente.
</Note>

## Quando usar webhook vs WebSocket

| Critério         | WebSocket                      | Webhook                             |
| ---------------- | ------------------------------ | ----------------------------------- |
| Latência         | ms (push direto)               | 100-500ms (HTTP + fila)             |
| Persistência     | Eventos perdidos se offline    | Fila + retry 5x + DLQ               |
| Multi-consumer   | Vários clientes em broadcast   | Até 3 webhooks habilitados (labels) |
| Auth             | Token no upgrade               | Header `Authorization` opcional     |
| Setup            | Cliente WS + config habilitada | Endpoint HTTP público + URL         |
| Dev-friendliness | Excelente (wscat, DevTools)    | Requer tunneling em dev             |

<Tip>
  * **Webhook**: integrações server-to-server onde perda é inaceitável (CRM, ERP, analytics, log sink).
  * **WebSocket**: UIs em tempo real (dashboard, inbox ao vivo, tela de atendimento) onde latência baixa é prioridade e perdas ocasionais são aceitáveis.
  * **Os dois em paralelo**: webhook persiste estado, WS faz a UI saltar.
</Tip>

## Erros antes do upgrade

Todas ocorrem antes do `101 Switching Protocols`, o cliente recebe um HTTP normal.

| HTTP | `error.message`                                                                                              | Quando                               |
| ---- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------ |
| 400  | `WebSocket is not enabled for this instance. Configure it first using POST /api/events/websocket/<instance>` | Sem config ou `enabled=false`.       |
| 401  | `Missing token in header, Authorization header, or query parameter`                                          | Nenhuma das 3 fontes forneceu token. |
| 401  | `Invalid token`                                                                                              | Token inválido.                      |
| 403  | , (body vazio, do upgrader)                                                                                  | `Origin` fora da allowlist.          |
| 404  | `Instance not found`                                                                                         | `:instance` inexistente.             |
| 429  | `Rate limit exceeded. Try again later.`                                                                      | Rate-limit global.                   |
| 500  | `Failed to get instance` / `Failed to get websocket configuration`                                           | Erro de banco.                       |

<ResponseExample>
  ```json 400 Bad Request theme={null}
  {
    "success": false,
    "error": {
      "message": "WebSocket is not enabled for this instance. Configure it first using POST /api/events/websocket/$Instance_Name"
    }
  }
  ```
</ResponseExample>

## Erros após o upgrade

Depois do `101`, qualquer falha de protocolo fecha a conexão com um **close code** padrão (`1001` going away, `1006` abnormal closure, `1011` server error). O servidor não escreve body, o cliente trata pelo close code.

Causas comuns:

* Frame do cliente maior que 4096 bytes.
* `pongWait` (60s) expirou sem resposta ao PING.
* Buffer de envio do cliente cheio (cliente lento), hub faz unregister.
* Instância foi deletada enquanto o cliente estava conectado.

## Referências

<CardGroup cols={2}>
  <Card title="Configurar WebSocket" icon="plug" href="/pt/api/events/websocket-configure">
    `POST /api/events/websocket/:instance`
  </Card>

  <Card title="Catálogo de eventos" icon="book" href="/pt/api/events/catalog">
    Schemas dos 6 tipos.
  </Card>

  <Card title="Visao geral de Eventos" icon="bell" href="/pt/api/events/overview">
    Webhook x WebSocket, envelope, boas práticas.
  </Card>

  <Card title="Autenticação" icon="key" href="/pt/guide/authentication">
    Matriz de tokens, header `token`, `?token=`.
  </Card>
</CardGroup>
