---
title: "Socket.IO"
description: "A message received, a session dropped, a QR code scanned, credit added, an invoice paid: it all arrives over the same open connection, the moment it happens. And you send over that same connection — without opening an HTTP request per dispatch."
lang: en
canonical: https://doc.apibrasil.io/socket-io
markdown: https://doc.apibrasil.io/socket-io.md
source: apibrasil-documentation
---

# Socket.IO

> A message received, a session dropped, a QR code scanned, credit added, an invoice paid: it all arrives over the same open connection, the moment it happens. And you send over that same connection — without opening an HTTP request per dispatch.

- **Address:** `https://socket.apibrasil.com.br`

## The connection

Address: https://socket.apibrasil.com.br
The values go in the connection QUERY, not in an HTTP header. The Socket.IO handshake here does not read `Authorization`.

**Query parameters**

| Parameter | Type |  | Description | Where to find it |
| --- | --- | --- | --- | --- |
| `bearer` | string (JWT) | required | The same Bearer Token you use on the API. It is the only parameter checked at the door: without it the connection is refused with “Token de autenticação não fornecido”, and with a token that does not validate, with “Token de autenticação inválido”. | The APIBrasil dashboard, or the response of POST /api/v2/login. |
| `channelName` | string | required | The room you join. It is how the service decides what to deliver to you, and it is your Profile ID — the same value the gateway uses when publishing. Without it the connection COMES UP but you join no room at all: no event arrives, and no error is shown. | Profile ID, in the APIBrasil dashboard. |
| `deviceToken` | string | optional | It filters nothing and does not affect what you receive. It is stored and only read when you emit `send`, to become the `DeviceToken` header of the call to the gateway. Required only if you are going to send through an API that requires a device. | The device token, on the dashboard's devices screen. |
| `action` | string | optional | The API path that `send` will call — `whatsapp/sendText`, for example. It is fixed for the connection: to send through two different APIs, open two connections. Without it, `send` returns `{ error: “URL não fornecida” }` on `receive`. | The path after /api/v2/ in the API's documentation. |

**When the connection is refused**

| Message in connect_error | Cause | Fix |
| --- | --- | --- |
| `Token de autenticação não fornecido` | The query went up without `bearer`. | Pass the token in `query.bearer`. It goes in the connection's query string, not in the `Authorization` header — the Socket.IO handshake does not read headers here. |
| `Token de autenticação inválido` | The `bearer` arrived but did not validate: expired, truncated, or from another environment. | Renew the token. Reconnecting with the SAME rejected token only repeats the refusal — the customer dashboard goes as far as marking the token invalid so it does not fall into a reconnection loop. |
| `Erro de configuração` | The token arrived, but the service came up with no secret to check it against. | This is not on your side: it is the server's. Open a ticket. |

## What arrives (7)

Register listeners under the EXACT names below. Do not invent `message`, `status`, `qr` or `connection`: the service emits none of those four, and whoever registered them waited forever.

### `events` — Everything that happens on your devices (only in your channel)

⚠ **Caveat:** Server status arrives HERE, with `data.wook` equal to SERVER_STATUS — not on the `server` event, despite the name.

**When it fires:** On every device webhook for your Profile ID: a message received, a connection change, a QR code scan, an incoming call, a server alert, an invoice paid. It is the channel's busiest frame.

**Comes from:** POST /api/webhook/{canal}, called by apibrasil-webhook (ProcessWebhookJob, uptime alerts, payment callbacks).

**Fields**

| Name | Type | Description |
| --- | --- | --- |
| `session` | string | The token of the device that originated the event — it is how you know WHICH handset it came from. Not every producer fills it in: billing notifications arrive without `session`. |
| `channelName` | string | Your Profile ID, repeated inside the body. Useful for checking, not for routing. |
| `data` | object | The event's content. Its shape changes with the producer — the clue is `data.wook`. |
| `data.wook` | string | The event type, uppercase. This is what you route on. Values observed in the code: STATUS_CONNECT, PARTICIPANTS_CHANGED, INCOMING_CALL, SERVER_STATUS, INVOICES, RECHARGE. |

**Example**

```json
{
  "session": "d4f1c2e0-8b3a-4f7e-9c11-2a6d5e0b1f43",
  "channelName": "9f8e7d6c-5b4a-3210-fedc-ba9876543210",
  "data": {
    "wook": "SERVER_STATUS",
    "heartbeat": "down",
    "name": "cluster-01",
    "maxretries": "3",
    "interval": "60",
    "accepted_statuscodes": "200-299",
    "dns_resolve_server": "1.1.1.1"
  }
}
```

### `platform` — Notifications for your account (only in your channel)

⚠ **Caveat:** Route on `data.type`, not on `event`: `data.type` is filled in by the gateway on every notification, while `event` is missing from some producers.

**When it fires:** When something happens to the account: credit added, a plan activated, an invoice issued, a device created or removed, a login, a ticket answered. It is the same notification that shows as the bell in the dashboard.

**Comes from:** POST /api/platform/{canal}, called by the gateway (SocketClient::send).

**Fields**

| Name | Type | Description |
| --- | --- | --- |
| `event` | string | The type, lowercase and dot-separated (`user.balance.added`). It does not always come: official WhatsApp notifications publish without this field. |
| `title` | string | A title ready to display, already in Portuguese. |
| `message` | string | The full sentence, with the values already formatted. |
| `data` | object | The event's data, plus four keys the gateway always adds: `type`, `action_url`, `url` and `icon`. |

**Example**

```json
{
  "event": "user.balance.added",
  "title": "Credit added",
  "message": "R$ 50.00 was added to your balance. Current balance: R$ 173.40",
  "data": {
    "type": "user.balance.added",
    "action_url": "/financeiro",
    "url": "/financeiro",
    "icon": "/icons/wallet.png",
    "amount": 50,
    "new_balance": 173.4
  }
}
```

### `receive` — The response to what you sent (only in your channel)

⚠ **Caveat:** It goes to the ROOM, not to whoever sent it. Two tabs on the same account receive each other's response — include an identifier of your own in the body if you need to tell them apart.

**When it fires:** After every `send`. It carries the gateway's response body — the same one you would get calling the API over HTTP.

**Comes from:** The service itself, on finishing the call your `send` triggered.

**Fields**

| Name | Type | Description |
| --- | --- | --- |
| `(corpo da API)` | object | The body returned by the gateway, with no envelope. On an HTTP error, the error body arrives on this same event — there is no separate `error`. |

**Example**

```json
{
  "error": false,
  "message": "Mensagem enviada com sucesso!",
  "response": {
    "id": "true_5531999999999@c.us_3EB0F1C2D3",
    "status": "PENDING"
  }
}
```

### `wppserver` — Raw events from the WPP server (only in your channel)

**When it fires:** When the WPPConnect server forwards an event from the WhatsApp session.

**Comes from:** POST /api/wppconnect/{canal}.

**Fields**

| Name | Type | Description |
| --- | --- | --- |
| `(corpo inteiro)` | object | The envelope arrives whole, not unwrapped — unlike `evolution`, which loses its envelope along the way. |

**Example**

```json
{
  "session": "d4f1c2e0-8b3a-4f7e-9c11-2a6d5e0b1f43",
  "data": {
    "wook": "STATUS_CONNECT",
    "status": "CONNECTED"
  }
}
```

### `evolution` — Raw events from the Evolution server (only in your channel)

⚠ **Caveat:** This event arrives with NO device identification. The route forwards only `body.data`, and the producer's `session` does not survive — with two Evolution devices on the same channel, there is no way to tell which one it came from by the payload. When that matters, listen to `events`.

**When it fires:** When the Evolution server forwards an event from the instance.

**Comes from:** POST /api/evolution/{canal}, called by apibrasil-webhook (ProcessEvolutionWebhookJob).

**Fields**

| Name | Type | Description |
| --- | --- | --- |
| `(conteúdo de data)` | object | Only the envelope's `data`. The `session` and `channelName` the producer sent are left behind. |

**Example**

```json
{
  "event": "messages.upsert",
  "instance": "minha-instancia",
  "data": {
    "key": {
      "remoteJid": "5531999999999@s.whatsapp.net"
    },
    "message": {
      "conversation": "oi"
    }
  }
}
```

### `server` — Global broadcast (global broadcast: reaches everyone connected, from any account)

⚠ **Caveat:** It is not the servers' status, despite the name — that arrives on `events` with `data.wook` equal to SERVER_STATUS. And since the broadcast is global, never treat what arrives here as your account's data.

**When it fires:** When someone publishes to POST /api/server. None of the four repositories examined calls that route — in practice, this event does not fire today.

**Comes from:** POST /api/server. The only path that does NOT use a room: the service calls `io.emit`, to everyone connected.

**Fields**

| Name | Type | Description |
| --- | --- | --- |
| `(corpo inteiro)` | object | Whatever is published to the route, untransformed. |

**Example**

```json
{
  "status": "up",
  "ts": 1756339200
}
```

### `chat` — Someone disconnected (global broadcast: reaches everyone connected, from any account)

⚠ **Caveat:** Broadcast to everyone connected, not just your channel — so it fires on other accounts' disconnections. Of no practical use; it is documented so nobody mistakes it for an event of their own.

**When it fires:** Whenever ANY client connected to the service disconnects.

**Comes from:** The service itself, on each socket's `disconnect`.

**Fields**

| Name | Type | Description |
| --- | --- | --- |
| `(texto)` | string | The fixed phrase “Cliente desconectado”. It says neither who, nor from which channel. |

**Example**

```json
{
  "(payload)": "Cliente desconectado"
}
```

## What you send

The socket does NOT implement sending: it forwards whatever you emit to https://gateway.apibrasil.io/api/v2, putting your Bearer and your DeviceToken in the headers. The answer comes back on the `receive` event.

Any /api/v2/ path that accepts POST works as an action — whatsmeow, waba and the lookup APIs included. The list beside it is the most-used ones, not the allowed ones.

| Action |  | Engine |  |
| --- | --- | --- | --- |
| `whatsapp/sendText` | Text over WhatsApp (WPP) | WPPConnect | requires DeviceToken |
| `whatsapp/sendFile` | File over WhatsApp (WPP) | WPPConnect | requires DeviceToken |
| `evolution/message/sendText` | Text over Baileys (Evolution) | Evolution API | requires DeviceToken |
| `evolution/message/sendMedia` | Base64 file (Evolution) | Evolution API | requires DeviceToken |

**Body of the send — `whatsapp/sendText`**: A plain text message through the WPP engine.

```json
{
  "number": "5531999999999",
  "text": "Your delivery is on its way to the registered address.",
  "time_typing": 0,
  "options": {
    "createChat": true
  }
}
```

**Body of the send — `whatsapp/sendFile`**: Media or a document by public URL, through the WPP engine.

```json
{
  "number": "5531999999999",
  "path": "https://exemplo.com.br/nota-fiscal.pdf"
}
```

**Body of the send — `evolution/message/sendText`**: A text message through the Baileys engine, with typing control.

```json
{
  "number": "5531999999999",
  "options": {
    "delay": 1200,
    "presence": "composing"
  },
  "textMessage": {
    "text": "Your delivery is on its way to the registered address."
  }
}
```

**Body of the send — `evolution/message/sendMedia`**: Media sent as Base64, for when the file has no public URL. The content travels whole over the connection.

```json
{
  "number": "5531999999999",
  "options": {
    "delay": 1200,
    "presence": "composing"
  },
  "mediaMessage": {
    "mediatype": "image",
    "caption": "Delivery receipt",
    "media": "/9j/4AAQSkZJRgABAQAAAQABAAD…"
  }
}
```

## Pitfalls: what breaks without warning

These behaviours were established from the service's own source. In every one of them the symptom points at the wrong place, which is exactly why they are here:

1. **A network failure on send returns no response at all** (high severity)
   When the call to the gateway fails at the NETWORK level — connection refused, timed out — the service's error handling tries to read a response body that does not exist and throws before emitting. No `receive` arrives, not even an error one. The symptom is a send that hangs forever. Always put a timeout on your side; do not wait on `receive` indefinitely.
2. **Connecting without channelName looks like it works** (high severity)
   The connection is accepted, the client says “connected”, and you join no room at all — nothing arrives, and no error shows. If the socket connected and the silence lasts, the first suspect is `channelName`.
3. **Responses go to the whole room** (high severity)
   `receive` is delivered to the channel, not to whoever sent it. Two tabs open on the same account receive each other's response. If you match request to response, put an identifier of your own in the `send` body and check it on the way back.
4. **One connection serves one action only** (medium severity)
   `action` is read from the handshake, not from `send`. Switching API means opening another connection — sending a `sendFile` body on a connection opened with `sendText` calls `sendText` with the wrong body.
5. **The evolution frame does not say which device it came from** (medium severity)
   The route forwards only the envelope's `data`, and the device identification is left behind. With more than one Evolution device on the same channel, use `events` — `session` arrives there.
6. **subscribe swaps rooms, it does not add one** (medium severity)
   Emitting `subscribe` makes the service LEAVE the current channel before joining the new one. You cannot listen to two channels on one connection; for that, open two.

## Destinations with a page of their own

- [Server](https://doc.apibrasil.io/socket-io/server.md): The `server` event exists in the service, but no producer was found in the repositories — it does not fire today. The server status you are looking for arrives on `events`.
- [Platform](https://doc.apibrasil.io/socket-io/platform.md): Your account's notifications in real time: balance, plans, invoices, devices, logins and tickets. It is what the dashboard uses to light up the bell.
- [Send text (WPP)](https://doc.apibrasil.io/socket-io/send-text-wpp.md): Fire a text message through the WPP engine without opening an HTTP request per send.
- [Send files (WPP)](https://doc.apibrasil.io/socket-io/send-files-wpp.md): Send media or a document by public URL, through the WPP engine.
- [Send text (Baileys)](https://doc.apibrasil.io/socket-io/send-text-baileys.md): Fire a text message through the Baileys engine, with presence and delay control.
- [Send files (Base64)](https://doc.apibrasil.io/socket-io/send-files-base64.md): Send a file with no public URL, embedded as Base64 in the body itself — useful for content generated on the fly.
- [All devices](https://doc.apibrasil.io/socket-io/all-devices.md): Follow ALL the devices under your Profile ID over a single connection. Each frame carries `session` with the originating device's token.
- [Single device](https://doc.apibrasil.io/socket-io/unique-device.md): Receive the events of ONE device only. The filter is not in the connection: the service emits a frame whose NAME is the device token, so you listen for that name.

## See also

- Documentation index: https://doc.apibrasil.io/llms.txt
- HTML version of this page: https://doc.apibrasil.io/socket-io
