> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wyzecash.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# WebSocket

> Receba os eventos da sua conta na hora, sem precisar de um endereço HTTPS seu: entrada com a chave, inscrições, ping, reenvio do que perdeu, erros e limites.

Use o WebSocket quando o seu sistema **não tem um endereço HTTPS público** para receber webhooks: um bot do Discord ou do Telegram, um worker numa VPS, um processo atrás de NAT ou um script. Ele entrega os **mesmos eventos dos webhooks**, com o mesmo `data`, assim que acontecem.

A API continua sendo quem cria cobranças, saques e envios. O WebSocket só avisa que algo aconteceu.

> [card] Sem endereço público?
>
> Use o WebSocket. O seu servidor abre a conexão para a WyzeCash, então não precisa de porta aberta nem de domínio.

> [card] [Tem um servidor HTTPS?](https://docs.wyzecash.com.br/webhooks/configurar.md)
>
> Prefira os webhooks: assinatura `X-Bukio-Signature` e reenvio automático quando a sua URL cai.

> [!WARNING]
>
> Conecte **só do seu servidor**. A chave de API vai junto, então nunca use o WebSocket no navegador, no app ou em código público. Guarde a chave numa variável de ambiente.

## Começar

**1. Tenha uma chave de API**

Crie em **Integrações e API › Chaves de API** uma chave `bk_live_…` com a permissão de consulta dos eventos que você quer ouvir (veja a tabela em Inscrições, mais abaixo). Ex.: `payments:get` para ouvir as cobranças.

**2. Conecte no endereço**

Abra uma conexão em `wss://api.wyzecash.com.br/v1/ws` a partir do **seu servidor**.

**3. Entre com a chave**

No cabeçalho `Authorization` ao conectar ou na primeira mensagem, em até 10 segundos (veja [Entrar com a chave](#entrar-com-a-chave)).

**4. Inscreva-se**

Mande `subscribe` com um padrão (`payment.*`) ou com o código de uma cobrança (`BKY-…`).

**5. Trate os eventos**

Responda o `ping`, guarde o `id` de cada evento para não processar duas vezes e, se cair, conecte de novo mandando o último `id` em `since`.

### Testar em 1 minuto

1. Conecte com a sua chave e inscreva-se em `payment.*`.
2. Crie uma cobrança Pix pequena com `POST /v1/payments` (pela API ou pelo painel).
3. Chega `payment.created`. Pague com o seu banco e chega `payment.paid`.

> [!NOTE]
>
> Os eventos são **da conta**, não da chave: uma conexão recebe os eventos de tudo o que acontece na conta (criado pela API, pelo painel, pelo app ou por um link de pagamento), desde que a chave tenha a permissão de consulta daquele tipo de evento.

## Endereço

```text theme={null}
wss://api.wyzecash.com.br/v1/ws
```

| Item | Valor |
| - | - |
| Protocolo | `wss://` (conexão criptografada; `ws://` não funciona) |
| Caminho | `/v1/ws` |
| Versão do protocolo | `1` (vem na resposta `authenticated`) |
| Formato | um JSON por mensagem de texto |

## Como as mensagens funcionam

Toda mensagem **sua** é um JSON com o campo `action`. Toda mensagem **do servidor** tem o campo `event`.

### O que você manda

| `action` | Campos | Para quê |
| - | - | - |
| `authenticate` | `apiKey` | Entrar com a chave, se ela não foi no cabeçalho |
| `subscribe` | `pattern` **ou** `paymentId`, e `since` (opcional) | Começar a ouvir eventos |
| `unsubscribe` | `pattern` **ou** `paymentId` | Parar de ouvir (a conexão continua aberta) |
| `ping` | nenhum | Responder o `ping` do servidor (ou testar a conexão) |
| `ack` | `id` | Opcional: só para o seu controle. Não muda nada no servidor |

### O que o servidor manda

| `event` | Quando |
| - | - |
| `authenticated` | A chave foi aceita. Traz `version` |
| `subscribed` | A inscrição foi aceita. Repete o `pattern` ou o `paymentId` |
| `unsubscribed` | A inscrição foi tirada |
| `ping` | A cada 30 segundos. Responda com `{ "action": "ping" }` |
| `pong` | A resposta ao seu `ping` |
| `error` | Algo deu errado. Traz `code` e `message` (veja [Erros](#erros)) |
| `payment.paid`, `withdrawal.completed`... | Um evento da conta (veja [Eventos](#eventos)) |

## Entrar com a chave

Escolha **um** dos dois jeitos:

> [tab] Cabeçalho ao conectar
>
> ```http theme={null}
> GET /v1/ws HTTP/1.1
> Host: api.wyzecash.com.br
> Upgrade: websocket
> Authorization: Bearer bk_live_...
> ```
>
> Use quando a sua biblioteca deixa mandar cabeçalhos (o pacote `ws` do Node e o `websockets` do Python deixam).

> [tab] Primeira mensagem
>
> ```json theme={null}
> { "action": "authenticate", "apiKey": "bk_live_..." }
> ```
>
> Use quando a biblioteca não manda cabeçalhos (o WebSocket nativo do Node 22+, do Bun e do Deno). Mande em até **10 segundos** depois de conectar.

A chave **nunca vai no endereço** (nada de `?token=`): endereço aparece em log de proxy e de servidor. Quando a chave é aceita, chega:

```json theme={null}
{ "event": "authenticated", "version": "1" }
```

| Regra | O que acontece |
| - | - |
| Sem entrar em 10 segundos | Erro `INVALID_API_KEY` e a conexão fecha (código `1008`) |
| Chave errada, apagada ou vencida | Erro `INVALID_API_KEY` e a conexão fecha (`1008`) |
| Chave com **IP liberado** e conexão de outro IP | Erro `IP_NOT_ALLOWED` e a conexão fecha (`1008`), como na API |
| Conta bloqueada | Erro `ACCOUNT_BLOCKED` e a conexão fecha (`1008`) |
| Conta sem a verificação de identidade, quando ela é obrigatória | Erro `KYC_REQUIRED` e a conexão fecha (`1008`) |
| Mandar `subscribe` antes de entrar | Erro `INVALID_API_KEY` (a conexão continua; entre e tente de novo) |
| Mandar `authenticate` de novo na mesma conexão | Erro `INVALID_ACTION` |

**Conexão aberta continua sendo conferida.** A cada minuto o servidor confere a chave e a conta: chave apagada ou vencida, conta bloqueada ou verificação obrigatória pendente derrubam a conexão. Chave apagada no painel derruba as conexões dela **na hora**.

## Inscrições

### Por padrão

```json theme={null}
{ "action": "subscribe", "pattern": "payment.*" }
```

Resposta:

```json theme={null}
{ "event": "subscribed", "pattern": "payment.*" }
```

| Padrão | O que chega | Permissão da chave (uma das duas) |
| - | - | - |
| `*` | Tudo o que a chave pode consultar | As de cada linha abaixo |
| `payment.*` | `payment.created`, `payment.paid`, `payment.expired`, `payment.refunded` | `payments:get` ou `payments:list` |
| `withdrawal.*` | `withdrawal.requested`, `withdrawal.completed`, `withdrawal.failed` | `withdrawals:get` ou `withdrawals:list` |
| `pix.*` | `pix.sent`, `pix.failed` | `pix:get` ou `pix:list` |
| `transfer.*` | `transfer.received`, `transfer.sent` | `account:transactions` ou `account:statement` |
| `subaccount.*` | `subaccount.credited`, `subaccount.debited` | `subaccounts:get` ou `subaccounts:list` |

Regras:

* Também vale **um evento só**: `{ "action": "subscribe", "pattern": "payment.paid" }`.
* Com `*`, chegam só os tipos que a chave pode consultar; os outros ficam de fora, sem erro.
* Pedir um padrão sem a permissão (ex.: `withdrawal.*` numa chave só de cobranças) dá `FORBIDDEN`.
* Padrão fora da lista dá `INVALID_ACTION`.
* Inscrever-se duas vezes no mesmo padrão não duplica nada.
* No máximo **100 inscrições** por conexão (padrões e cobranças somados).
* Prefira o mínimo que você usa (`payment.*` em vez de `*`).

### Por cobrança

Para acompanhar **uma cobrança** (ex.: a tela de "aguardando pagamento" do seu bot), mande o código dela:

```json theme={null}
{ "action": "subscribe", "paymentId": "BKY-6H99YBDMMGBY" }
```

Resposta:

```json theme={null}
{ "event": "subscribed", "paymentId": "BKY-6H99YBDMMGBY" }
```

| Regra | Valor |
| - | - |
| Código | O `id` da cobrança (`BKY-` + 12 caracteres), que vem no `POST /v1/payments` |
| Dona | Só cobrança da sua conta; de outra conta dá `FORBIDDEN` |
| Permissão | `payments:get` ou `payments:list` |
| Validade | **30 minutos**; depois disso a inscrição sai sozinha |
| Fim | Ao chegar `payment.paid` ou `payment.expired`, a inscrição daquela cobrança sai sozinha. **A conexão continua aberta** |

### Parar de ouvir

```json theme={null}
{ "action": "unsubscribe", "pattern": "payment.*" }
```

```json theme={null}
{ "action": "unsubscribe", "paymentId": "BKY-6H99YBDMMGBY" }
```

Resposta: `{ "event": "unsubscribed", "pattern": "payment.*" }`. A conexão continua aberta e você pode se inscrever em outra coisa.

## O evento

Todo evento chega assim:

```json theme={null}
{
  "id": "evt_9f1c2b7d4e8a0b3c5d6e7f80",
  "event": "payment.paid",
  "createdAt": "2026-11-05T21:02:11.000Z",
  "data": {
    "id": "BKY-6H99YBDMMGBY",
    "status": "paid",
    "method": "pix",
    "amount": 15000,
    "fee": 50,
    "description": "Pedido 1842",
    "paidAt": "2026-11-05T21:02:10.000Z",
    "endToEndId": "E12345678202611052102abcdef123456",
    "createdAt": "2026-11-05T21:00:00.000Z"
  }
}
```

| Campo | O que é |
| - | - |
| `id` | Código do evento (`evt_…`). É **o mesmo** do webhook do mesmo acontecimento. Use para não processar duas vezes |
| `event` | Qual evento (ex.: `payment.paid`). No webhook, este campo se chama `type` |
| `createdAt` | Quando aconteceu (ISO 8601, UTC) |
| `data` | **Igual ao `data` do webhook.** `data.id` é sempre o código com prefixo (`BKY-`, `SAQ-`...). Valores em centavos |

> [!NOTE]
>
> O evento só sai depois de gravado de vez. Uma operação que voltou atrás no meio do caminho nunca gera evento.

O provedor por trás de cada operação **nunca** aparece no evento, igual aos webhooks.

## Eventos

São os mesmos 13 eventos dos webhooks. O `webhook.test` (botão "Enviar teste") não vai pelo WebSocket.

| Evento | Quando | `data.id` | Campos do `data` |
| - | - | - | - |
| `payment.created` | Cobrança criada (API, painel, app ou link de pagamento) | `BKY-` | [Cobranças](https://docs.wyzecash.com.br/webhooks/cobrancas.md) |
| `payment.paid` | Pix caiu, cartão aprovado ou cripto confirmada | `BKY-` | [Cobranças](https://docs.wyzecash.com.br/webhooks/cobrancas.md) |
| `payment.expired` | Passou da validade sem pagamento | `BKY-` | [Cobranças](https://docs.wyzecash.com.br/webhooks/cobrancas.md) |
| `payment.refunded` | Um estorno (total ou parcial) foi concluído | `BKY-` | [Cobranças](https://docs.wyzecash.com.br/webhooks/cobrancas.md) |
| `withdrawal.requested` | Saque pedido (API, painel, app ou saque automático) | `SAQ-` / `SAQ-API-` | [Saques](https://docs.wyzecash.com.br/webhooks/saques.md) |
| `withdrawal.completed` | O Pix do saque caiu na chave (com `receiptUrl`) | `SAQ-` / `SAQ-API-` | [Saques](https://docs.wyzecash.com.br/webhooks/saques.md) |
| `withdrawal.failed` | Saque não realizado; valor e tarifa voltaram ao saldo | `SAQ-` / `SAQ-API-` | [Saques](https://docs.wyzecash.com.br/webhooks/saques.md) |
| `pix.sent` | só da rota antiga `/v1/pix/payments`, que vai ser removida (use o saque) | `ENV-` | [Saques](https://docs.wyzecash.com.br/webhooks/saques.md) |
| `pix.failed` | só da rota antiga `/v1/pix/payments`, que vai ser removida (use o saque) | `ENV-` | [Saques](https://docs.wyzecash.com.br/webhooks/saques.md) |
| `transfer.received` | Transferência recebida de outra conta WyzeCash | `TRF-` | [Transferências](https://docs.wyzecash.com.br/webhooks/transferencias.md) |
| `transfer.sent` | Transferência enviada para outra conta WyzeCash (com `receiptUrl`) | `TRF-` | [Transferências](https://docs.wyzecash.com.br/webhooks/transferencias.md) |
| `subaccount.credited` | Entrou dinheiro numa subconta (venda, envio, transferência) | `SUB-` | [Subcontas](https://docs.wyzecash.com.br/webhooks/subcontas.md) |
| `subaccount.debited` | Saiu dinheiro de uma subconta (saque, devolução, transferência, estorno, MED) | `SUB-` | [Subcontas](https://docs.wyzecash.com.br/webhooks/subcontas.md) |

> [accordion] Exemplo: payment.created
>
> ```json theme={null}
> {
>   "id": "evt_1a2b3c4d5e6f7a8b9c0d1e2f",
>   "event": "payment.created",
>   "createdAt": "2026-11-05T21:00:00.120Z",
>   "data": {
>     "id": "BKY-6H99YBDMMGBY",
>     "status": "pending",
>     "method": "pix",
>     "amount": 15000,
>     "description": "Pedido 1842",
>     "expiresAt": "2026-11-05T21:30:00.000Z",
>     "customerId": null,
>     "createdAt": "2026-11-05T21:00:00.000Z"
>   }
> }
> ```
>
> O Pix Copia e Cola e o QR Code **não** vêm no evento: eles estão na resposta do `POST /v1/payments`.

> [accordion] Exemplo: payment.paid no cartão
>
> ```json theme={null}
> {
>   "id": "evt_2b3c4d5e6f7a8b9c0d1e2f3a",
>   "event": "payment.paid",
>   "createdAt": "2026-11-05T21:06:30.410Z",
>   "data": {
>     "id": "BKY-8RP3N2WQ5XZA",
>     "status": "paid",
>     "method": "card",
>     "amount": 15000,
>     "fee": 1249,
>     "description": "Pedido 1843",
>     "paidAt": "2026-11-05T21:06:29.000Z",
>     "card": { "brand": "visa", "last4": "4242" },
>     "availableAt": "2026-11-20T21:06:29.000Z",
>     "createdAt": "2026-11-05T21:00:00.000Z"
>   }
> }
> ```
>
> Cartão aprovado já é venda: libere o pedido. `availableAt` diz quando o valor entra no saldo disponível.

> [accordion] Exemplo: payment.refunded
>
> ```json theme={null}
> {
>   "id": "evt_3c4d5e6f7a8b9c0d1e2f3a4b",
>   "event": "payment.refunded",
>   "createdAt": "2026-11-05T22:10:40.002Z",
>   "data": {
>     "id": "BKY-6H99YBDMMGBY",
>     "refundId": "EST-9Q2W8M4Z3F7K",
>     "amount": 5000,
>     "endToEndId": "E12345678202611052210fedcba654321"
>   }
> }
> ```
>
> `amount` é o valor devolvido **neste** estorno, que pode ser parcial.

> [accordion] Exemplo: withdrawal.completed
>
> ```json theme={null}
> {
>   "id": "evt_4d5e6f7a8b9c0d1e2f3a4b5c",
>   "event": "withdrawal.completed",
>   "createdAt": "2026-11-05T21:12:30.410Z",
>   "data": {
>     "id": "SAQ-API-3F7K9Q2W8M4Z",
>     "status": "completed",
>     "method": "pix",
>     "amount": 50000,
>     "fee": 50,
>     "channel": "api",
>     "destination": { "key": "***.456.789-**", "name": "Ana S.", "institution": "Banco Exemplo" },
>     "crypto": null,
>     "endToEndId": "E12345678202611052112abcdef654321",
>     "reason": null
>   }
> }
> ```
>
> `withdrawal.requested` com `status: "processing"` quer dizer "saindo". Só `withdrawal.completed` confirma.

## Ping

| Item | Valor |
| - | - |
| O servidor manda `{ "event": "ping" }` | a cada **30 segundos** |
| Você responde | `{ "action": "ping" }` (o servidor devolve `{ "event": "pong" }`) |
| A conexão cai | sem **nenhuma** mensagem sua por **45 segundos** (erro `PING_TIMEOUT`, código `1001`) |

Qualquer mensagem sua conta, mas o jeito simples é responder todo `ping`. Faça isso antes de ir para produção.

## Caiu? Pegue o que perdeu

A conexão pode cair (rede, reinício do seu servidor ou do nosso). O que aconteceu enquanto ela estava fora **não se perde**:

**1. Guarde o id do último evento**

A cada evento recebido, guarde o `id` (`evt_…`) num lugar que sobreviva ao reinício (banco, Redis, arquivo).

**2. Conecte de novo esperando um pouco mais a cada vez**

1 s, 2 s, 4 s, 8 s, 16 s e no máximo 30 s. Volte para 1 s quando conectar.

**3. Entre e inscreva-se de novo mandando o since**

```json theme={null}
{ "action": "subscribe", "pattern": "payment.*", "since": "evt_9f1c2b7d4e8a0b3c5d6e7f80" }
```

Chega de novo, em ordem, tudo o que veio **depois** desse evento e entra nessa inscrição: até **24 horas** e até 500 eventos.

| Regra | Valor |
| - | - |
| Janela | 24 horas |
| Máximo por pedido | 500 eventos (passou disso, consulte a API) |
| `since` de outra conta ou com mais de 24 h | Erro `UNKNOWN_SINCE`; a inscrição vale, mas nada é reenviado |
| Mandou em mais de uma inscrição | Cada `subscribe` com `since` reenvia o que entra nele; seu controle pelo `id` evita processar duas vezes |

> [!WARNING]
>
> O servidor não reenvia nada sozinho e o `ack` não muda isso: quem pede o que perdeu é você, com o `since`. Ficou fora mais de 24 horas? Consulte a API (`GET /v1/payments`, `GET /v1/withdrawals`, `GET /v1/statement`) para pôr o seu sistema em dia.

## Fluxo completo

```mermaid theme={null}
sequenceDiagram
  participant Bot as Seu servidor
  participant WS as WebSocket WyzeCash
  participant API as API WyzeCash
  participant Cliente

  Bot->>WS: Conecta + Authorization: Bearer bk_live_...
  WS-->>Bot: authenticated
  Bot->>WS: subscribe payment.*
  WS-->>Bot: subscribed

  Bot->>API: POST /v1/payments
  API-->>Bot: BKY-... + Pix Copia e Cola
  WS-->>Bot: payment.created

  Cliente->>API: Paga o Pix
  WS-->>Bot: payment.paid
  Bot->>Bot: Libera o pedido e guarda o evt_

  Note over Bot,WS: A conexão cai
  Bot->>WS: Conecta de novo + subscribe com since
  WS-->>Bot: O que chegou enquanto estava fora
```

## Exemplos

> [tab] Node.js (SDK)
>
> O [SDK `wyzecash`](https://docs.wyzecash.com.br/sdk/node) entra com a chave, responde o ping, conecta de novo sozinho (esperando cada vez mais, até 30 s) e pede o que perdeu com o `since`. Chave inválida ou apagada (código `1008`) não reconecta.
>
> ```bash theme={null}
> npm install wyzecash
> ```
>
> ```javascript theme={null}
> import WyzeCash from "wyzecash";
>
> const wyzecash = new WyzeCash(process.env.WYZECASH_API_KEY);
>
> const ws = wyzecash.realtime({
>   events: ["payment.*", "withdrawal.*"], // padrão: ["*"]
>   // payments: ["BKY-6H99YBDMMGBY"],     // ouvir só estas cobranças
>   // since: "evt_...",                   // último evento que você já processou
> });
>
> ws.on("open", () => console.log("conectado"));
> ws.on("payment.paid", (evento) => liberarPedido(evento.data));
> ws.on("withdrawal.*", (evento) => console.log(evento.event, evento.data.id));
> ws.on("error", (erro) => console.error(erro.code, erro.message));
> ws.on("close", ({ code, reconnecting }) => console.log("caiu", code, reconnecting));
>
> // Para desligar: ws.close();
> ```
>
> No Node 22+, Bun e Deno o SDK usa o WebSocket que já vem. No Node 20, passe o do pacote `ws`: `wyzecash.realtime({ WebSocket })`.

> [tab] Node.js (ws)
>
> ```bash theme={null}
> npm install ws
> ```
>
> ```javascript theme={null}
> import WebSocket from "ws";
>
> const URL = "wss://api.wyzecash.com.br/v1/ws";
> const vistos = new Set();
> let ultimo = null; // guarde num banco para sobreviver ao reinício
> let espera = 1000;
>
> function conectar() {
>   const ws = new WebSocket(URL, {
>     headers: { Authorization: `Bearer ${process.env.BUKIO_API_KEY}` },
>   });
>
>   ws.on("message", (raw) => {
>     const msg = JSON.parse(String(raw));
>
>     if (msg.event === "ping") return ws.send(JSON.stringify({ action: "ping" }));
>     if (msg.event === "authenticated") {
>       espera = 1000;
>       return ws.send(JSON.stringify({ action: "subscribe", pattern: "payment.*", since: ultimo }));
>     }
>     if (msg.event === "error") return console.error(msg.code, msg.message);
>     if (!msg.id || vistos.has(msg.id)) return; // subscribed, pong ou repetido
>
>     vistos.add(msg.id);
>     ultimo = msg.id;
>     if (msg.event === "payment.paid") liberarPedido(msg.data);
>   });
>
>   ws.on("close", (code) => {
>     if (code === 1008) return console.error("Chave recusada: confira a chave e as permissões.");
>     setTimeout(conectar, espera);
>     espera = Math.min(espera * 2, 30_000);
>   });
>
>   ws.on("error", (erro) => console.error(erro.message));
> }
>
> conectar();
> ```

> [tab] Node.js 22+ (sem pacote)
>
> O WebSocket nativo não manda cabeçalhos, então a chave vai na primeira mensagem.
>
> ```javascript theme={null}
> const ws = new WebSocket("wss://api.wyzecash.com.br/v1/ws");
>
> ws.onopen = () => {
>   ws.send(JSON.stringify({ action: "authenticate", apiKey: process.env.BUKIO_API_KEY }));
> };
>
> ws.onmessage = ({ data }) => {
>   const msg = JSON.parse(data);
>   if (msg.event === "ping") return ws.send(JSON.stringify({ action: "ping" }));
>   if (msg.event === "authenticated") {
>     return ws.send(JSON.stringify({ action: "subscribe", pattern: "payment.*" }));
>   }
>   if (msg.id) console.log(msg.event, msg.data.id);
> };
> ```

> [tab] Python
>
> ```bash theme={null}
> pip install websockets
> ```
>
> ```python theme={null}
> import asyncio, json, os
> import websockets
>
> URL = "wss://api.wyzecash.com.br/v1/ws"
> HEADERS = {"Authorization": f"Bearer {os.environ['BUKIO_API_KEY']}"}
>
> async def main():
>     ultimo = None  # guarde num banco para sobreviver ao reinício
>     vistos = set()
>     espera = 1
>     while True:
>         try:
>             async with websockets.connect(URL, additional_headers=HEADERS) as ws:
>                 async for raw in ws:
>                     msg = json.loads(raw)
>                     evento = msg.get("event")
>                     if evento == "ping":
>                         await ws.send(json.dumps({"action": "ping"}))
>                     elif evento == "authenticated":
>                         espera = 1
>                         await ws.send(json.dumps({"action": "subscribe", "pattern": "payment.*", "since": ultimo}))
>                     elif evento == "error":
>                         print("erro:", msg["code"], msg["message"])
>                     elif msg.get("id") and msg["id"] not in vistos:
>                         vistos.add(msg["id"])
>                         ultimo = msg["id"]
>                         print(evento, msg["data"]["id"])
>         except websockets.ConnectionClosed as fechou:
>             if fechou.rcvd and fechou.rcvd.code == 1008:
>                 raise SystemExit("Chave recusada: confira a chave e as permissões.")
>         except OSError as erro:
>             print("sem conexão:", erro)
>         await asyncio.sleep(espera)
>         espera = min(espera * 2, 30)
>
> asyncio.run(main())
> ```

## Erros

Chegam assim:

```json theme={null}
{ "event": "error", "code": "FORBIDDEN", "message": "Esta chave não tem permissão para ver withdrawal.*." }
```

| Código | O que aconteceu | Fecha a conexão? |
| - | - | - |
| `INVALID_API_KEY` | Chave errada, apagada ou vencida, ou não entrou em 10 segundos | Sim, `1008`. Não adianta reconectar com a mesma chave |
| `IP_NOT_ALLOWED` | A chave tem IP liberado e a conexão veio de outro IP | Sim, `1008` |
| `ACCOUNT_BLOCKED` | A conta está bloqueada | Sim, `1008` |
| `KYC_REQUIRED` | A verificação de identidade é obrigatória e ainda não foi aprovada | Sim, `1008` |
| `RATE_LIMIT` | Tentativas de entrar demais, conexões demais na conta ou mensagens demais por minuto | Sim, `1008` (no limite de inscrições, não fecha) |
| `FORBIDDEN` | A chave não tem a permissão daqueles eventos ou a cobrança é de outra conta | Não |
| `UNKNOWN_SINCE` | O `since` é de outra conta ou passou de 24 horas | Não |
| `INVALID_ACTION` | JSON inválido, ação desconhecida, padrão inválido ou mensagem maior que 64 KB | Só na mensagem grande demais (`1009`) |
| `PING_TIMEOUT` | Nenhuma mensagem sua em 45 segundos | Sim, `1001` |
| `SERVER_CLOSING` | O servidor está reiniciando | Sim, `1001`. Conecte de novo com o `since` |

### Códigos de fechamento

| Código | Quando | Reconectar? |
| - | - | - |
| `1008` | Chave, conta, IP ou limite | Não com a mesma chave; corrija o motivo antes (no `RATE_LIMIT`, espere um minuto) |
| `1009` | Mensagem maior que 64 KB | Sim, mandando mensagens menores |
| `1001` | Sem resposta ao ping ou servidor reiniciando | Sim, com o `since` |
| `1006` | A rede caiu sem aviso | Sim, com o `since` |

## Limites

| Limite | Valor |
| - | - |
| Conexões abertas por conta (somando todas as chaves) | **20** |
| Tentativas de entrar por minuto, por IP | **30** |
| Mensagens suas por minuto, por chave | **100** (o `ping` conta) |
| Inscrições por conexão | **100** |
| Tamanho de uma mensagem | **64 KB** |
| Tempo para entrar | **10 segundos** |
| Inscrição por cobrança | **30 minutos** |
| Janela do `since` | **24 horas**, até 500 eventos |

## Segurança

* **Só do servidor.** A chave nunca vai para navegador, app ou repositório.
* **Conexão criptografada.** Sempre `wss://`.
* **A chave nunca no endereço.** Só no cabeçalho `Authorization` ou na mensagem `authenticate`.
* **Permissão por tipo de evento.** Uma chave só de cobranças não ouve saques. Dê à chave do bot só o que ele precisa.
* **IP liberado vale aqui também.** Se a chave tem IPs liberados, a conexão só entra desses IPs.
* **Apagou a chave, a conexão cai na hora.**

### Antes de ir para produção

* [ ] A conexão roda no servidor e a chave está numa variável de ambiente.
* [ ] O `ping` é respondido.
* [ ] O `id` de cada evento é guardado e repetidos são ignorados.
* [ ] Ao cair, conecta de novo esperando cada vez mais e manda o `since`.
* [ ] O código `1008` para de reconectar e avisa você.
* [ ] O pedido é liberado só em `payment.paid`, conferindo o `data.amount`.
* [ ] Depois de mais de 24 horas fora, o sistema consulta a API.

## WebSocket, webhook ou consulta?

| | WebSocket | Webhook | Consultar a API |
| - | - | - | - |
| Precisa de endereço HTTPS público | Não | Sim | Não |
| Chega na hora | Sim | Sim | Só quando você consulta |
| Se você ficar fora | Pede o que perdeu com `since` (até 24 h) | A WyzeCash reenvia sozinha | Você consulta de novo |
| Prova de origem | A conexão entra com a sua chave | Assinatura `X-Bukio-Signature` | A resposta da API |
| Mesmo `id` de evento | Sim, o mesmo `evt_` do webhook | Sim | Não se aplica |
| Melhor para | Bots, workers, VPS sem domínio | Sistemas com URL pública | Conferir uma operação |

| Cenário | Use |
| - | - |
| Bot do Discord ou do Telegram sem domínio | **WebSocket** |
| Site ou sistema com `https://seusite.com/webhook` | **Webhook** |
| Tela de "aguardando pagamento" no seu servidor | **WebSocket** com `paymentId` |
| Conferir se uma cobrança foi paga agora | **API**: `GET /v1/payments/{id}` |

Pode usar os dois juntos: o `id` do evento é o mesmo, então o seu controle de repetidos vale para os dois.

## No painel

Em **Integrações e API › WebSocket** você vê as conexões abertas agora (chave, IP, há quanto tempo, inscrições e eventos entregues), quantos eventos saíram hoje, quantos foram reenviados pelo `since` e o último evento.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.