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

# WebSocket

> Receba eventos em tempo real sem porta pública — autenticação, subscribe com wildcards, ACK, heartbeat e segurança.

Use WebSocket quando sua aplicação **não tem URL HTTPS pública** (bot Discord/Telegram, worker em VPS, processo atrás de NAT). A REST API continua responsável por criar pagamentos e transferências; o WebSocket só notifica que algo aconteceu.

<Note>
  Sem porta pública? Prefira WebSocket. Tem servidor HTTPS? [Webhooks](/api-reference/guides/webhooks) com HMAC e retries automáticos.
</Note>

## Configurar

<Steps>
  <Step title="1. Chave de API">
    Use uma chave `wp_live_...` com as permissões necessárias para as operações que você cria via REST (ex.: `payment-pix/create`).
  </Step>

  <Step title="2. Conectar ao gateway">
    Abra uma conexão WSS em `wss://ws.wovepay.com/v1` a partir do **seu servidor** (nunca no browser).
  </Step>

  <Step title="3. Autenticar">
    Envie a API key na URL, no header do upgrade ou na primeira mensagem (veja [Autenticação](#autenticação)).
  </Step>

  <Step title="4. Inscrever-se">
    Use `subscribe` com pattern (`payment.*`) ou `paymentId` específico.
  </Step>

  <Step title="5. Processar eventos">
    Trate cada mensagem pelo `id` (idempotência) e opcionalmente envie `ack`.
  </Step>
</Steps>

### Escopo por API key

Conexões autenticadas com `wp_live_...` recebem eventos das transações criadas pela **mesma API key**. Eventos de outra chave da mesma conta não são entregues nessa conexão.

Exceção importante (igual aos webhooks): `transfer.internal.received` é emitido para a **conta destino**. Só chega se a API key usada na conexão tiver criado operações relevantes ou se você estiver inscrito na conta correta com a chave que recebe transferências internas.

### Teste

1. Conecte com sua `wp_live_...`
2. Inscreva-se em `payment.*`
3. Crie um PIX com [POST /payment-pix/create](/api-reference/endpoint/payment-pix/create) usando a **mesma API key**
4. Você deve receber `payment.created` e, após pagar, `payment.paid`

Tutorial passo a passo: [WebSocket na prática](/pages/guides/websocket-pratica).

## Endpoint

```
wss://ws.wovepay.com/v1
```

| Requisito | Valor                                                     |
| --------- | --------------------------------------------------------- |
| Protocolo | **WSS** — use sempre `wss://` em produção (nunca `ws://`) |
| Path      | `/v1`                                                     |
| Versão    | `1`                                                       |
| Formato   | JSON em cada frame de texto                               |

## Protocolo

Toda mensagem do **cliente** é um JSON com campo `action`. Toda mensagem do **servidor** usa o campo `event`.

### Mensagens do cliente

| `action`       | Campos                       | Descrição                                            |
| -------------- | ---------------------------- | ---------------------------------------------------- |
| `authenticate` | `apiKey`                     | Autentica com `wp_live_...` (se não usou URL/header) |
| `subscribe`    | `pattern` **ou** `paymentId` | Inscreve em eventos                                  |
| `unsubscribe`  | `pattern` **ou** `paymentId` | Remove inscrição (não fecha a conexão)               |
| `ack`          | `id`                         | Confirma processamento de um evento                  |
| `ping`         | —                            | Responde ao heartbeat do servidor                    |

### Mensagens do servidor

| `event`               | Quando                                                          |
| --------------------- | --------------------------------------------------------------- |
| `authenticated`       | Auth OK — inclui `version`                                      |
| `subscribed`          | Inscrição aceita — ecoa `pattern` ou `paymentId`                |
| `unsubscribed`        | Inscrição removida                                              |
| `ping`                | Heartbeat a cada 30s — responda com `{ "action": "ping" }`      |
| `pong`                | Resposta ao seu `ping`                                          |
| `error`               | Falha — campo `code` (veja [Códigos de erro](#códigos-de-erro)) |
| *(evento de negócio)* | `payment.paid`, `transfer.completed`, etc.                      |

## Autenticação

Escolha **uma** das três formas:

| Modo                   | Exemplo                                                 |
| ---------------------- | ------------------------------------------------------- |
| Query string           | `wss://ws.wovepay.com/v1?token=wp_live_...`             |
| Header no upgrade      | `Authorization: Bearer wp_live_...`                     |
| Mensagem após conectar | `{ "action": "authenticate", "apiKey": "wp_live_..." }` |

Resposta de sucesso:

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

<Warning>
  Rode a conexão **no servidor** (bot, worker, backend). Nunca exponha `wp_live_...` em frontend, app mobile ou repositório público.
</Warning>

Se não autenticar em **10 segundos**, a conexão fecha com código WebSocket `1008`.

Chaves com **restrição de IP** na API key só autenticam se o IP de origem estiver na allowlist configurada na chave.

Ao **revogar** a API key, todas as conexões abertas com ela são encerradas imediatamente.

## Subscribe

### Por pattern (wildcards)

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

Resposta:

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

| Pattern      | Eventos entregues                                                                                                        |
| ------------ | ------------------------------------------------------------------------------------------------------------------------ |
| `payment.*`  | `payment.created`, `payment.paid`, `payment.failed`, `payment.pix.expired`, `payment.refunded`, `payment_link.paid`      |
| `transfer.*` | `transfer.created`, `transfer.completed`, `transfer.failed`, `transfer.internal.completed`, `transfer.internal.received` |
| `refund.*`   | `refund.requested`, `refund.completed`, `refund.failed`                                                                  |
| `invoice.*`  | `invoice.created`, `invoice.processing`, `invoice.issued`, `invoice.failed`, `invoice.cancelled`                         |
| `med.*`      | `med.created`, `med.evidence_sent`, `med.updated`                                                                        |
| `*`          | Todos os eventos ativos (mesmo catálogo dos webhooks)                                                                    |

Regras:

* Pattern válido: letras minúsculas, números, `_`, `.`, `*` — até 64 caracteres
* Máximo **100 inscrições** por conexão (patterns + `paymentId` somados)
* Re-subscribe no mesmo pattern é **idempotente**

### Por pagamento específico

```json theme={null}
{ "action": "subscribe", "paymentId": "clx_transacao" }
```

Use o `id` retornado em [POST /payment-pix/create](/api-reference/endpoint/payment-pix/create) ou [GET /payment-pix/get](/api-reference/endpoint/payment-pix/get).

| Regra            | Valor                                                                                                                                           |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| TTL da inscrição | **30 minutos** (remove automaticamente)                                                                                                         |
| Ownership        | Só pagamentos da conta da API key; se a transação foi criada por outra chave, retorna `FORBIDDEN`                                               |
| Evento terminal  | Ao receber `payment.paid`, `payment.failed` ou `payment.pix.expired`, a inscrição deste `paymentId` é removida — **a conexão permanece aberta** |
| Ordem            | Eventos do mesmo `paymentId` são entregues em ordem na fila interna                                                                             |

### Unsubscribe

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

Resposta:

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

Remover uma inscrição **não** fecha o socket — você pode inscrever-se em outros patterns na mesma conexão.

## Formato do evento

Cada evento de negócio segue o envelope abaixo. O campo `data` é **idêntico** ao corpo JSON dos [webhooks](/api-reference/guides/webhooks#formato-da-entrega) para o mesmo tipo de evento.

```json theme={null}
{
  "id": "wsevt_abc123",
  "event": "payment.paid",
  "createdAt": "2026-07-31T10:00:00.000Z",
  "data": {
    "id": "clx_transacao",
    "type": "PIX_IN",
    "status": "COMPLETED",
    "amount": 1500,
    "feeAmount": 7.5,
    "netAmount": 1492.5,
    "currency": "BRL",
    "externalReference": "pedido-123",
    "referenceId": "ref_pix_abc",
    "endToEndId": "E12345678202505301234567890123456",
    "payer": {
      "name": "João da Silva",
      "document": "12345678909"
    },
    "completedAt": "2026-07-31T10:00:00.000Z",
    "createdAt": "2026-07-31T09:55:00.000Z"
  }
}
```

| Campo       | Descrição                                                                                                               |
| ----------- | ----------------------------------------------------------------------------------------------------------------------- |
| `id`        | ID único da **entrega** — use para idempotência (não confundir com `data.id` da transação).                             |
| `event`     | Tipo do evento (ex.: `payment.paid`).                                                                                   |
| `createdAt` | Momento da entrega (ISO 8601).                                                                                          |
| `data`      | Payload enxuto — mesmo formato `/v1` e webhooks (sem `provider`, `accountId`, `metadata`, `direction` nem `updatedAt`). |

### Tipos de `data`

| Família               | Eventos                                     | Formato de referência                                                                                                    |
| --------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| PIX recebido          | `payment.*`, `refund.*`                     | [GET /payment-pix/get](/api-reference/endpoint/payment-pix/get), [GET /refunds/get](/api-reference/endpoint/refunds/get) |
| PIX enviado           | `transfer.*` (exceto `transfer.internal.*`) | [GET /transfer-pix/get](/api-reference/endpoint/transfer-pix/get)                                                        |
| Transferência interna | `transfer.internal.*`                       | [GET /transfer-internal/get](/api-reference/endpoint/transfer-internal/get)                                              |
| MED                   | `med.*`                                     | [GET /meds/get](/api-reference/endpoint/meds/get/\{id})                                                                  |
| Link de pagamento     | `payment_link.paid`                         | Objeto de sessão — ver [webhooks](/api-reference/guides/webhooks#links-de-pagamento)                                     |
| Nota fiscal           | `invoice.*`                                 | Apenas `invoiceId` no `data`                                                                                             |
| Teste                 | `webhook.test`                              | Metadados do disparo — ver [webhooks](/api-reference/guides/webhooks#teste)                                              |

### Campos úteis em transações PIX

| Campo                                      | Quando aparece                                             |
| ------------------------------------------ | ---------------------------------------------------------- |
| `payer`                                    | `payment.paid` de `PIX_IN` — nome e documento do pagador.  |
| `recipient`                                | `transfer.completed` de `PIX_OUT` — favorecido confirmado. |
| `copyPaste`, `qrCodeBase64`, `qrcodeUrl`   | `payment.created` — dados do QR gerado.                    |
| `expiresAt`                                | Cobrança PIX pendente ou expirada.                         |
| `securityReview`                           | `transfer.created` — saque retido para análise antifraude. |
| `refund`                                   | Eventos `refund.*` e `payment.refunded`.                   |
| `transferKind`, `recipientEmail`, `pairId` | Transferência interna.                                     |

Para exemplos JSON completos de cada evento, consulte a seção [Eventos](/api-reference/guides/webhooks#eventos) do guia de webhooks — os payloads são os mesmos; apenas o transporte muda (WSS em vez de `POST` HTTPS).

## ACK (opcional)

Confirme que processou o evento:

```json theme={null}
{ "action": "ack", "id": "wsevt_abc123" }
```

O ACK é usado para rastreamento de entrega. A WovePay registra o recebimento; **não há reenvio automático** pelo gateway se você não enviar ACK (diferente dos webhooks HTTP com retry).

Recomendações:

* Persista o `id` **antes** de processar o `data`
* Envie `ack` após enfileirar ou concluir o processamento
* Duplicatas do mesmo `id` devem ser ignoradas no seu lado

```typescript theme={null}
const seen = new Set<string>();

function onEvent(msg: { id: string; event: string; data: unknown }) {
  if (msg.event === "ping") {
    ws.send(JSON.stringify({ action: "ping" }));
    return;
  }
  if (!msg.id || seen.has(msg.id)) return;
  seen.add(msg.id);

  void processEvent(msg).then(() => {
    ws.send(JSON.stringify({ action: "ack", id: msg.id }));
  });
}
```

## Heartbeat

| Parâmetro             | Valor                                         |
| --------------------- | --------------------------------------------- |
| Intervalo do servidor | **30s** — envia `{ "event": "ping" }`         |
| Timeout do cliente    | **15s** — responda com `{ "action": "ping" }` |
| Resposta              | `{ "event": "pong" }`                         |

Sem resposta ao `ping` do servidor dentro de 15s, a conexão é fechada. Implemente o handler de `ping` antes de ir para produção.

## Reconexão

O gateway **não** mantém estado entre desconexões. Após queda de rede ou deploy:

1. Reconecte com backoff exponencial: **1s → 2s → 4s → 8s → 16s → 30s** (máximo)
2. Autentique novamente
3. Re-inscreva nos patterns necessários
4. Consulte a REST API para eventos perdidos durante a janela offline (`GET /payment-pix/get/:id`, etc.)

<Warning>
  Não use WebSocket como única fonte de verdade. Combine com consultas REST para recuperar estado após downtime prolongado.
</Warning>

## Rate limits

| Limite                               | Valor     |
| ------------------------------------ | --------- |
| Conexões simultâneas / API key       | **20**    |
| Tentativas de auth / min / IP        | **30**    |
| Mensagens cliente / min / API key    | **100**   |
| Inscrições / conexão                 | **100**   |
| Payload máximo (cliente ou servidor) | **64 KB** |

Ao exceder limites de conexão ou auth, o servidor envia `{ "event": "error", "code": "RATE_LIMIT" }` e pode fechar a conexão (`1008`).

## Segurança

* **Conexão criptografada** — use somente `wss://` em produção (nunca `ws://`)
* **Ownership** — `subscribe` com `paymentId` valida que a transação pertence à conta da API key
* **Revogação** — API key revogada desconecta todas as sockets imediatamente
* **Allowlist de IP** — respeitada na autenticação, igual à REST API
* **Ações permitidas** — cliente só envia: `authenticate`, `subscribe`, `unsubscribe`, `ack`, `ping`
* Payload acima de **64 KB** → close `1009`

### Checklist

* Conexão apenas no **servidor** (nunca browser)
* API key em variável de ambiente (`WovePay_API_KEY`)
* Handler de `ping` implementado
* Idempotência pelo `id` da entrega
* Reconexão com backoff + re-subscribe
* REST API como fallback após downtime
* Patterns mínimos necessários (`payment.*` em vez de `*` se possível)

## Códigos de erro

Erros são enviados como:

```json theme={null}
{ "event": "error", "code": "INVALID_API_KEY" }
```

| Código            | Significado                                                           |
| ----------------- | --------------------------------------------------------------------- |
| `INVALID_API_KEY` | Credencial inválida, revogada, conta bloqueada ou auth expirada (10s) |
| `RATE_LIMIT`      | Limite de conexões, mensagens ou inscrições excedido                  |
| `INVALID_ACTION`  | JSON inválido, `action` desconhecida ou subscribe malformado          |
| `FORBIDDEN`       | `paymentId` de outra conta ou de outra API key                        |

### Códigos de close WebSocket

| Código          | Quando                                    |
| --------------- | ----------------------------------------- |
| `1008`          | Falha de autenticação ou policy violation |
| `1009`          | Mensagem maior que 64 KB                  |
| `1000` / `1001` | Fechamento normal ou heartbeat timeout    |

## Eventos

O catálogo é o **mesmo dos webhooks**. Inscreva-se com patterns (`payment.*`) ou eventos exatos via `*` (todos).

### Catálogo (ativos)

| Evento                        | Família               |
| ----------------------------- | --------------------- |
| `payment.created`             | PIX recebido          |
| `payment.paid`                | PIX recebido          |
| `payment.failed`              | PIX recebido          |
| `payment.pix.expired`         | PIX recebido          |
| `payment.refunded`            | PIX recebido (legado) |
| `refund.requested`            | Estorno PIX           |
| `refund.completed`            | Estorno PIX           |
| `refund.failed`               | Estorno PIX           |
| `transfer.created`            | PIX enviado           |
| `transfer.completed`          | PIX enviado           |
| `transfer.failed`             | PIX enviado           |
| `transfer.internal.completed` | Transferência interna |
| `transfer.internal.received`  | Transferência interna |
| `med.created`                 | MED                   |
| `med.evidence_sent`           | MED                   |
| `med.updated`                 | MED                   |
| `payment_link.paid`           | Link de pagamento     |
| `invoice.created`             | Nota fiscal           |
| `invoice.processing`          | Nota fiscal           |
| `invoice.issued`              | Nota fiscal           |
| `invoice.failed`              | Nota fiscal           |
| `invoice.cancelled`           | Nota fiscal           |
| `webhook.test`                | Teste                 |

<Note>
  Eventos de **cripto** e **boleto** existem no código mas **não estão ativos** na API pública no momento.
</Note>

### PIX recebido

Disparados por [POST /payment-pix/create](/api-reference/endpoint/payment-pix/create). `data.type` = `PIX_IN`.

| Evento                | Quando ocorre               | `data.status` |
| --------------------- | --------------------------- | ------------- |
| `payment.created`     | QR PIX gerado.              | `PENDING`     |
| `payment.paid`        | Depósito confirmado no SPI. | `COMPLETED`   |
| `payment.failed`      | Falha no fluxo.             | `FAILED`      |
| `payment.pix.expired` | QR expirou sem pagamento.   | `CANCELED`    |
| `payment.refunded`    | Estorno concluído (legado). | `REVERSED`    |

Exemplo `payment.created`:

```json theme={null}
{
  "id": "wsevt_001",
  "event": "payment.created",
  "createdAt": "2026-05-24T11:55:00.000Z",
  "data": {
    "id": "clx_transacao",
    "type": "PIX_IN",
    "status": "PENDING",
    "amount": 100,
    "feeAmount": 0.5,
    "netAmount": 99.5,
    "currency": "BRL",
    "externalReference": "pedido-123",
    "referenceId": "ref_pix_abc",
    "copyPaste": "00020101021226820014br.gov.bcb.pix...",
    "qrCodeBase64": "data:image/png;base64,iVBORw0KGgo...",
    "expiresAt": "2026-05-25T11:55:00.000Z",
    "createdAt": "2026-05-24T11:55:00.000Z"
  }
}
```

Mais exemplos: [PIX recebido (webhooks)](/api-reference/guides/webhooks#pix-recebido).

### Estorno PIX

Disparados após [POST /refunds/create](/api-reference/endpoint/refunds/create). O `data` é o depósito original com bloco `refund`.

| Evento             | Quando ocorre                                             |
| ------------------ | --------------------------------------------------------- |
| `refund.requested` | Estorno aceito; saldo reservado.                          |
| `refund.completed` | Devolução confirmada — também dispara `payment.refunded`. |
| `refund.failed`    | Estorno não concluído.                                    |

Exemplos completos: [Estorno PIX (webhooks)](/api-reference/guides/webhooks#estorno-pix-depósito-recebido).

### PIX enviado

Disparados por [POST /transfer-pix/create](/api-reference/endpoint/transfer-pix/create). `data.type` = `PIX_OUT`.

| Evento               | Quando ocorre          | `data.status`            |
| -------------------- | ---------------------- | ------------------------ |
| `transfer.created`   | Saída registrada.      | `PENDING` / `PROCESSING` |
| `transfer.completed` | PIX liquidado.         | `COMPLETED`              |
| `transfer.failed`    | Falha ou cancelamento. | `FAILED` / `CANCELED`    |

Exemplos completos: [PIX enviado (webhooks)](/api-reference/guides/webhooks#pix-enviado).

### Transferência interna

Disparados por [POST /transfer-internal/create](/api-reference/endpoint/transfer-internal/create).

| Evento                        | Conta   | `data.type`             |
| ----------------------------- | ------- | ----------------------- |
| `transfer.internal.completed` | Origem  | `INTERNAL_TRANSFER_OUT` |
| `transfer.internal.received`  | Destino | `INTERNAL_TRANSFER_IN`  |

Exemplos completos: [Transferência interna (webhooks)](/api-reference/guides/webhooks#transferência-interna).

### MED

Disputas no trilho **PADRAO**. `data` segue [GET /meds/get](/api-reference/endpoint/meds/get/\{id}).

| Evento              | Quando ocorre                                                                    |
| ------------------- | -------------------------------------------------------------------------------- |
| `med.created`       | Nova disputa; saldo bloqueado.                                                   |
| `med.evidence_sent` | Defesa enviada via [POST /meds/evidence](/api-reference/endpoint/meds/evidence). |
| `med.updated`       | Resultado final (`ACCEPTED`, `REJECTED`, etc.).                                  |

Exemplos completos: [MED (webhooks)](/api-reference/guides/webhooks#med).

### Links de pagamento

| Evento              | Quando ocorre                      |
| ------------------- | ---------------------------------- |
| `payment_link.paid` | Checkout de link confirmado (PIX). |

Para links pagos via PIX, `payment.paid` também pode ser emitido. Para checkout de link, inscreva-se em **`payment_link.paid`** ou `payment.*`.

Exemplo completo: [Links de pagamento (webhooks)](/api-reference/guides/webhooks#links-de-pagamento).

### Nota fiscal

Emitidos quando a cobrança usa emissão automática. O WebSocket traz apenas `invoiceId` no `data`.

| Evento               | Quando ocorre                    |
| -------------------- | -------------------------------- |
| `invoice.created`    | Nota enfileirada após pagamento. |
| `invoice.processing` | Em processamento no emissor.     |
| `invoice.issued`     | Nota emitida.                    |
| `invoice.failed`     | Falha ou rejeição.               |
| `invoice.cancelled`  | Nota cancelada.                  |

```json theme={null}
{
  "id": "wsevt_inv",
  "event": "invoice.issued",
  "createdAt": "2026-06-02T15:01:00.000Z",
  "data": {
    "invoiceId": "clx_invoice"
  }
}
```

### Fluxos comuns

```mermaid theme={null}
sequenceDiagram
  participant Bot as Seu worker
  participant WS as ws.wovepay.com
  participant API as api.wovepay.com
  participant Pagador

  Bot->>WS: WSS connect + auth (wp_live_...)
  Bot->>WS: subscribe payment.*
  WS-->>Bot: subscribed

  Bot->>API: POST /payment-pix/create
  API-->>Bot: id + QR
  WS-->>Bot: payment.created

  Pagador->>API: Paga PIX
  WS-->>Bot: payment.paid
  Bot->>Bot: Libera pedido
  Bot->>WS: ack wsevt_...

  Note over Bot,WS: Queda de rede
  Bot->>WS: Reconnect + re-subscribe
  Bot->>API: GET /payment-pix/get/:id (catch-up)
```

## Exemplo completo (Node.js)

Cliente de produção com auth, subscribe, heartbeat, ACK e reconexão:

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

const API_KEY = process.env.WovePay_API_KEY;
const WS_URL = "wss://ws.wovepay.com/v1";

const seen = new Set();
let backoffMs = 1000;
let ws;

function connect() {
  ws = new WebSocket(WS_URL, {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });

  ws.on("open", () => {
    backoffMs = 1000;
    ws.send(JSON.stringify({ action: "subscribe", pattern: "payment.*" }));
    ws.send(JSON.stringify({ action: "subscribe", pattern: "transfer.*" }));
  });

  ws.on("message", (raw) => {
    const msg = JSON.parse(String(raw));

    if (msg.event === "ping") {
      ws.send(JSON.stringify({ action: "ping" }));
      return;
    }

    if (msg.event === "authenticated" || msg.event === "subscribed") {
      console.log(msg.event, msg);
      return;
    }

    if (msg.event === "error") {
      console.error("WS error:", msg.code);
      return;
    }

    if (!msg.id || seen.has(msg.id)) return;
    seen.add(msg.id);

    console.log(msg.event, msg.data?.id ?? msg.data);
    ws.send(JSON.stringify({ action: "ack", id: msg.id }));
  });

  ws.on("close", () => {
    setTimeout(connect, backoffMs);
    backoffMs = Math.min(backoffMs * 2, 30_000);
  });

  ws.on("error", (err) => console.error("WS socket error:", err.message));
}

connect();
```

Dependência: `npm install ws`

## Exemplo completo (Python)

```python theme={null}
import asyncio
import json
import os
import websockets

API_KEY = os.environ["WovePay_API_KEY"]
WS_URL = f"wss://ws.wovepay.com/v1?token={API_KEY}"
seen = set()

async def run():
    backoff = 1
    while True:
        try:
            async with websockets.connect(WS_URL) as ws:
                backoff = 1
                await ws.send(json.dumps({"action": "subscribe", "pattern": "payment.*"}))

                async for raw in ws:
                    msg = json.loads(raw)
                    event = msg.get("event")

                    if event == "ping":
                        await ws.send(json.dumps({"action": "ping"}))
                        continue

                    if event in ("authenticated", "subscribed"):
                        print(event, msg)
                        continue

                    if event == "error":
                        print("WS error:", msg.get("code"))
                        continue

                    delivery_id = msg.get("id")
                    if not delivery_id or delivery_id in seen:
                        continue
                    seen.add(delivery_id)

                    print(event, msg.get("data"))
                    await ws.send(json.dumps({"action": "ack", "id": delivery_id}))

        except Exception as exc:
            print("reconnecting:", exc)
            await asyncio.sleep(backoff)
            backoff = min(backoff * 2, 30)

asyncio.run(run())
```

Dependência: `pip install websockets`

## WebSocket vs Webhook vs Polling

|                     | WebSocket                                   | Webhook                   | Polling GET               |
| ------------------- | ------------------------------------------- | ------------------------- | ------------------------- |
| Porta pública HTTPS | Não precisa                                 | Precisa                   | Não precisa               |
| Retries automáticos | Não — reconexão do cliente                  | Sim (5x com backoff)      | N/A                       |
| Assinatura HMAC     | Não                                         | Sim (`whsec_...`)         | N/A                       |
| Latência            | Baixa (\~ms)                                | Baixa                     | Alta / rate limit         |
| Estado offline      | Perde eventos na janela — use REST catch-up | Fila com retries          | Você controla o intervalo |
| Ideal para          | Bots, VPS, workers                          | APIs com endpoint público | Legado / debug            |

## Quando usar cada um

| Cenário                                           | Recomendação                       |
| ------------------------------------------------- | ---------------------------------- |
| Bot Discord/Telegram sem domínio                  | **WebSocket**                      |
| API própria com `https://api.empresa.com/webhook` | **Webhook**                        |
| Integração legada que já faz polling              | Mantenha polling ou migre para WS  |
| Máxima confiabilidade + auditoria HMAC            | **Webhook** (com fila no seu lado) |
| Menor latência sem expor porta                    | **WebSocket**                      |

Veja também: [Tutorial prático](/pages/guides/websocket-pratica) · [Webhooks](/api-reference/guides/webhooks) · [Criar cobrança PIX](/pages/guides/receber-pix)
