---
title: "Webhook de WhatsApp Wpp"
description: "Mensagens que chegam e que saem, confirmação de leitura, chamadas, e o ciclo de vida da sessão."
lang: pt-BR
canonical: https://doc.apibrasil.io/webhooks/api-whatsapp-wpp
markdown: https://doc.apibrasil.io/webhooks/api-whatsapp-wpp.md
source: apibrasil-documentation
---

# Webhook de WhatsApp Wpp

> Mensagens que chegam e que saem, confirmação de leitura, chamadas, e o ciclo de vida da sessão.

Toda mensagem que entra no seu número chega no seu endpoint em tempo real — com o texto, o autor e o id. E cada mensagem que você envia avisa quando foi entregue e quando foi lida.

## Como receber

As quatro URLs são registradas no dispositivo, não no envio, e quem chama é a APIBrasil — com POST JSON, user-agent APIBRASIL/1.0, um cabeçalho queue dizendo de qual fila veio e 10 segundos de paciência. O esqueleto abaixo roteia por wook e confere a assinatura antes de confiar no corpo.

**Onde registrar a URL e o que cai nela**

| Campo | O que recebe | Eventos |
| --- | --- | --- |
| `wh_message` | Mensagens, confirmações de leitura, chamadas e eventos de conversa. | `SEND_MESSAGE`, `RECEIVE_MESSAGE`, `MESSAGE_STATUS`, `INCOMING_CALL`, `REACTION_MESSAGE`, `REVOKED_MESSAGE`, `MESSAGE_EDIT`, `POLL_RESPONSE`, `ADDED_TO_GROUP`, `PARTICIPANTS_CHANGED`, `LIVE_LOCATION`, `NOTIFICATION_MESSAGE`, `ORDER_STATUS_UPDATE`, `INTERFACE_CHANGED` |
| `wh_connect` | O ciclo de vida da sessão: conectou, caiu, foi removida. | `STATUS_CONNECT`, `SESSION_REMOVED` |
| `wh_qrcode` | O QR Code e o código de pareamento, para você exibir na sua tela. | `QRCODE`, `CODE` |
| `wh_status` | Mudanças de estado da conexão relatadas pelo próprio WhatsApp. | `STATUS_CONNECTION` |

Os eventos deste canal são independentes: não há ordem entre eles.

## Eventos (19)

### `RECEIVE_MESSAGE` — Mensagem recebida

**Quando dispara:** Quando alguém manda mensagem para o seu número. Chega com o texto, o nome de quem escreveu e o id — e é o mesmo id que você usa para responder citando.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | O evento. É por ele que se roteia — SEND_MESSAGE, RECEIVE_MESSAGE, MESSAGE_STATUS… |
| `status` | string | A direção, não o evento: SENT quando a mensagem é sua, RECEIVED quando é do contato. |
| `type` | string | text, image, audio, ptt, video, document, location, sticker, link, vcard, list… |
| `fromMe` | boolean | Verdadeiro quando quem mandou foi você. |
| `id` | string | O id da mensagem no WhatsApp. Amarra com o MESSAGE_STATUS. |
| `session` | string | O nome da sessão (o seu dispositivo). |
| `isGroupMsg` | boolean | Verdadeiro quando veio de um grupo. |
| `author` | string \| null | Em grupo, quem de fato escreveu. Nulo na conversa individual. |
| `name` | string | O nome de exibição do contato, ou string vazia. |
| `from` | string | Remetente SEM o sufixo — o @c.us é cortado antes do envio. |
| `to` | string | Destinatário, também sem o sufixo. |
| `content` | string | O texto da mensagem. |
| `quotedMsg` | object \| string | A mensagem citada, ou "" quando não há. |
| `quotedMsgId` | string | O id da citada, ou "". |
| `timestamp` | number | Unix, em segundos. |
| `datetime` | string | O mesmo instante em DD-MM-YYYY HH:mm:ss. |
| `data` | object | A mensagem crua do WPPConnect, com dezenas de campos. Os sensíveis são removidos. |
| `number` | string \| null | Acrescentado no envio: o número já normalizado. Vem junto de fromNumber e toNumber. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "RECEIVE_MESSAGE",
    "status": "RECEIVED",
    "type": "text",
    "fromMe": false,
    "id": "true_5531999999999@c.us_3EB0C15F8C2A1B9D7E4A",
    "session": "apibrasil-financeiro",
    "isGroupMsg": false,
    "author": null,
    "name": "Maria Souza",
    "from": "5531999999999",
    "to": "5531988888888",
    "content": "Boa tarde! Consegue emitir a segunda via?",
    "quotedMsg": "",
    "quotedMsgId": "",
    "timestamp": 1756400930,
    "datetime": "28-08-2026 14:28:50",
    "number": "5531999999999",
    "fromNumber": "5531999999999",
    "toNumber": "5531988888888",
    "data": {
      "…": "a mensagem crua do WPPConnect"
    }
  }
]
```

### `SEND_MESSAGE` — Mensagem enviada

**Quando dispara:** Quando o seu número manda uma mensagem — inclusive pelo aparelho, não só pela API. É como você mantém o seu sistema em dia com o que foi respondido no celular.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | O evento. É por ele que se roteia — SEND_MESSAGE, RECEIVE_MESSAGE, MESSAGE_STATUS… |
| `status` | string | A direção, não o evento: SENT quando a mensagem é sua, RECEIVED quando é do contato. |
| `type` | string | text, image, audio, ptt, video, document, location, sticker, link, vcard, list… |
| `fromMe` | boolean | Verdadeiro quando quem mandou foi você. |
| `id` | string | O id da mensagem no WhatsApp. Amarra com o MESSAGE_STATUS. |
| `session` | string | O nome da sessão (o seu dispositivo). |
| `isGroupMsg` | boolean | Verdadeiro quando veio de um grupo. |
| `author` | string \| null | Em grupo, quem de fato escreveu. Nulo na conversa individual. |
| `name` | string | O nome de exibição do contato, ou string vazia. |
| `from` | string | Remetente SEM o sufixo — o @c.us é cortado antes do envio. |
| `to` | string | Destinatário, também sem o sufixo. |
| `content` | string | O texto da mensagem. |
| `quotedMsg` | object \| string | A mensagem citada, ou "" quando não há. |
| `quotedMsgId` | string | O id da citada, ou "". |
| `timestamp` | number | Unix, em segundos. |
| `datetime` | string | O mesmo instante em DD-MM-YYYY HH:mm:ss. |
| `data` | object | A mensagem crua do WPPConnect, com dezenas de campos. Os sensíveis são removidos. |
| `number` | string \| null | Acrescentado no envio: o número já normalizado. Vem junto de fromNumber e toNumber. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "SEND_MESSAGE",
    "status": "SENT",
    "type": "text",
    "fromMe": true,
    "id": "true_5531999999999@c.us_9F1D3A7C2E5B8046",
    "session": "apibrasil-financeiro",
    "isGroupMsg": false,
    "author": null,
    "name": "",
    "from": "5531988888888",
    "to": "5531999999999",
    "content": "Claro! Já vou gerar para você.",
    "quotedMsg": "",
    "quotedMsgId": "",
    "timestamp": 1756401002,
    "datetime": "28-08-2026 14:30:02",
    "number": "5531999999999",
    "fromNumber": "5531988888888",
    "toNumber": "5531999999999",
    "data": {
      "…": "a mensagem crua do WPPConnect"
    }
  }
]
```

### `MESSAGE_STATUS` — Entregue, lida, ou falhou

**Quando dispara:** A cada mudança de estado de uma mensagem SUA. O id é o mesmo do SEND_MESSAGE — é por ele que se junta. Os estados são CLOCK, SENT, RECEIVED, READ e PLAYED no caminho feliz; FAILED, EXPIRED, INACTIVE, CONTENT_GONE, CONTENT_TOO_BIG, CONTENT_UNUPLOADABLE e MD_DOWNGRADE quando dá errado.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "MESSAGE_STATUS" neste aviso. |
| `status` | string | O estado. RECEIVED é o aparelho ter recebido; READ é o contato ter aberto; PLAYED só vale para áudio. |
| `type` | string | O tipo da mensagem cujo estado mudou. |
| `id` | string | O id da mensagem. O MESMO do aviso de envio. |
| `from` | string | Sem o sufixo do WhatsApp. |
| `to` | string | Sem o sufixo do WhatsApp. |
| `session` | string | O nome da sessão. |
| `dateTime` | string | Com T MAIÚSCULO — diferente do datetime dos avisos de mensagem. |
| `data` | object | O ack cru, com o código numérico original. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "MESSAGE_STATUS",
    "status": "READ",
    "type": "text",
    "id": "true_5531999999999@c.us_9F1D3A7C2E5B8046",
    "from": "5531988888888",
    "to": "5531999999999",
    "session": "apibrasil-financeiro",
    "dateTime": "28-08-2026 14:31:15",
    "data": {
      "ack": 3
    }
  }
]
```

### `QRCODE` — QR Code para parear

**Quando dispara:** Quando a sessão precisa ser autenticada. Chega em wh_qrcode, com a imagem já em base64 pronta para virar um <img src>.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "QRCODE". |
| `result` | number | 200 nos avisos de conexão. |
| `session` | string | O nome da sessão. |
| `state` | string | "QRCODE_RECEIVED". |
| `status` | string | "awaitReadQrCode" — esperando alguém ler. |
| `qrcode` | string | Data URL PNG em base64. É a imagem inteira, não o texto do código. |
| `attempts` | number | Qual tentativa de geração é esta. |
| `urlCode` | string | O conteúdo do código, para gerar a imagem você mesmo. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "QRCODE",
    "result": 200,
    "session": "apibrasil-financeiro",
    "state": "QRCODE_RECEIVED",
    "status": "awaitReadQrCode",
    "qrcode": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAA…",
    "attempts": 1,
    "urlCode": "2@Xk9…"
  }
]
```

### `STATUS_CONNECT` — A sessão mudou de estado

**Quando dispara:** A cada mudança do ciclo de vida da sessão: INITIALIZING, CONNECTED, browserClose, autocloseCalled, desconnectedMobile. Chega em wh_connect — é por aqui que você descobre que o número caiu antes do cliente reclamar.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "STATUS_CONNECT". |
| `result` | number | 200. |
| `session` | string | O nome da sessão. |
| `state` | string | O estado novo. É o campo que você lê para decidir se precisa reconectar. |
| `status` | string | O status do cliente no momento do aviso. |
| `number` | string | O número da sessão, ou vazio se ainda não pareou. |
| `message` | object | Uma explicação em inglês do que aconteceu. |
| `body` | array \| object | Contexto extra do evento. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "STATUS_CONNECT",
    "result": 200,
    "session": "apibrasil-financeiro",
    "state": "browserClose",
    "status": "browserClose",
    "number": "",
    "message": {
      "message": "If the browser is closed this parameter is returned."
    },
    "body": []
  }
]
```

### `INCOMING_CALL` — Chamada recebida

**Quando dispara:** Quando alguém liga para o seu número. Chega em wh_message, e o formato é diferente do de mensagem — traz phone com o sufixo do WhatsApp, e não from.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "INCOMING_CALL". |
| `id` | string | O id da chamada. |
| `phone` | string | Quem ligou, COM o sufixo @c.us — este aviso não corta, diferente dos de mensagem. |
| `offer_time` | string | Quando a chamada foi oferecida, em DD-MM-YYYY HH:mm:ss. |
| `isVideo` | boolean | Chamada de vídeo ou de voz. |
| `isGroup` | boolean | Chamada de grupo. |
| `participants` | array \| null | Quem está na chamada, quando é de grupo. |
| `session` | string | O nome da sessão. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "INCOMING_CALL",
    "id": "3EB0A1B2C3D4E5F60718",
    "phone": "5531999999999@c.us",
    "offer_time": "28-08-2026 14:35:11",
    "isVideo": false,
    "isGroup": false,
    "participants": null,
    "session": "apibrasil-financeiro",
    "data": {
      "…": "a chamada crua do WPPConnect"
    }
  }
]
```

### `SESSION_REMOVED` — A sessão foi removida

**Quando dispara:** Quando a sessão deixa de existir no serviço. É o aviso terminal do canal: nenhum outro evento virá desta sessão até ela ser criada de novo.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "SESSION_REMOVED". |
| `session` | string | A sessão que saiu. |
| `state` | string | "REMOVED". |
| `status` | string | "REMOVED". |
| `reason` | string | O motivo registrado da remoção. |
| `removed_at` | string | ISO 8601, em UTC. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "SESSION_REMOVED",
    "result": 200,
    "session": "apibrasil-financeiro",
    "state": "REMOVED",
    "status": "REMOVED",
    "reason": "removed",
    "removed_at": "2026-08-28T17:40:03.512Z"
  }
]
```

### `STATUS_CONNECTION` — Estado da conexão

**Quando dispara:** Quando o próprio WhatsApp relata mudança de estado — CONNECTED, DISCONNECTED, TIMEOUT, CONFLICT. É o único evento que sai por wh_status, e o payload é o menor do canal: state e status trazem o MESMO valor.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "STATUS_CONNECTION". |
| `result` | number | 200. |
| `session` | string | O nome da sessão. |
| `state` | string | O estado relatado pelo WhatsApp. |
| `status` | string | O MESMO valor de state — o emissor preenche os dois com a mesma variável. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "STATUS_CONNECTION",
    "result": 200,
    "session": "apibrasil-financeiro",
    "state": "CONNECTED",
    "status": "CONNECTED"
  }
]
```

### `CODE` — Código de pareamento

**Quando dispara:** Quando você pareia por CÓDIGO em vez de QR Code. Sai pela MESMA URL do QR Code (wh_qrcode), então o receptor precisa distinguir os dois pelo wook.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "CODE". |
| `result` | number | 200. |
| `session` | string | O nome da sessão. |
| `state` | string | "CODE_RECEIVED". |
| `status` | string | "awaitReadCode" — esperando alguém digitar. |
| `code` | string | O código para digitar no aparelho. |
| `number` | string | O número que pediu o pareamento. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "CODE",
    "result": 200,
    "session": "apibrasil-financeiro",
    "state": "CODE_RECEIVED",
    "status": "awaitReadCode",
    "code": "1A2B-3C4D",
    "number": "5531988888888"
  }
]
```

### `REACTION_MESSAGE` — Reação a uma mensagem

**Quando dispara:** Quando alguém reage com emoji a uma mensagem. Chega em wh_message — a mesma URL das mensagens —, e não é uma mensagem: não tem content nem type.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "REACTION_MESSAGE" neste aviso. |
| `session` | string | O nome da sessão (o seu dispositivo). |
| `reaction` | object | A reação: o emoji, o id da mensagem reagida e quem reagiu. |
| `data` | object | O MESMO objeto, aninhado outra vez sob reaction. data.reaction é igual a reaction. |
| `number` | string \| null | Acrescentado no envio, junto de fromNumber, toNumber, lid e phoneNumberByLid. Nulo quando o evento não tem número. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "REACTION_MESSAGE",
    "session": "apibrasil-financeiro",
    "reaction": {
      "…": "o objeto de reaction do WPPConnect"
    },
    "data": {
      "reaction": {
        "…": "o mesmo objeto, repetido"
      }
    },
    "number": null
  }
]
```

### `REVOKED_MESSAGE` — Mensagem apagada

**Quando dispara:** Quando alguém apaga uma mensagem para todos. É o aviso que permite refletir o "esta mensagem foi apagada" no seu lado — sem ele, a sua cópia fica dizendo o que o WhatsApp já não mostra.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "REVOKED_MESSAGE" neste aviso. |
| `session` | string | O nome da sessão (o seu dispositivo). |
| `revoked` | object | A mensagem revogada e quem a revogou. |
| `data` | object | O MESMO objeto, aninhado outra vez sob revoked. data.revoked é igual a revoked. |
| `number` | string \| null | Acrescentado no envio, junto de fromNumber, toNumber, lid e phoneNumberByLid. Nulo quando o evento não tem número. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "REVOKED_MESSAGE",
    "session": "apibrasil-financeiro",
    "revoked": {
      "…": "o objeto de revoked do WPPConnect"
    },
    "data": {
      "revoked": {
        "…": "o mesmo objeto, repetido"
      }
    },
    "number": null
  }
]
```

### `MESSAGE_EDIT` — Mensagem editada

**Quando dispara:** Quando uma mensagem já enviada é editada. O texto que você guardou no RECEIVE_MESSAGE deixou de ser o texto atual.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "MESSAGE_EDIT" neste aviso. |
| `session` | string | O nome da sessão (o seu dispositivo). |
| `editedMessage` | object | A mensagem depois da edição. |
| `data` | object | O MESMO objeto, aninhado outra vez sob editedMessage. data.editedMessage é igual a editedMessage. |
| `number` | string \| null | Acrescentado no envio, junto de fromNumber, toNumber, lid e phoneNumberByLid. Nulo quando o evento não tem número. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "MESSAGE_EDIT",
    "session": "apibrasil-financeiro",
    "editedMessage": {
      "…": "o objeto de editedMessage do WPPConnect"
    },
    "data": {
      "editedMessage": {
        "…": "o mesmo objeto, repetido"
      }
    },
    "number": null
  }
]
```

### `POLL_RESPONSE` — Resposta de enquete

**Quando dispara:** Quando alguém vota numa enquete que você enviou.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "POLL_RESPONSE" neste aviso. |
| `session` | string | O nome da sessão (o seu dispositivo). |
| `poll` | object | O voto: qual opção, em qual enquete, de quem. |
| `data` | object | O MESMO objeto, aninhado outra vez sob poll. data.poll é igual a poll. |
| `number` | string \| null | Acrescentado no envio, junto de fromNumber, toNumber, lid e phoneNumberByLid. Nulo quando o evento não tem número. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "POLL_RESPONSE",
    "session": "apibrasil-financeiro",
    "poll": {
      "…": "o objeto de poll do WPPConnect"
    },
    "data": {
      "poll": {
        "…": "o mesmo objeto, repetido"
      }
    },
    "number": null
  }
]
```

### `LIVE_LOCATION` — Localização ao vivo

**Quando dispara:** Enquanto alguém compartilha localização em tempo real. Repete enquanto durar o compartilhamento — é o único evento do canal que dispara em série sozinho.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "LIVE_LOCATION" neste aviso. |
| `session` | string | O nome da sessão (o seu dispositivo). |
| `liveLocation` | object | A posição atual de quem está compartilhando. |
| `data` | object | O MESMO objeto, aninhado outra vez sob liveLocation. data.liveLocation é igual a liveLocation. |
| `number` | string \| null | Acrescentado no envio, junto de fromNumber, toNumber, lid e phoneNumberByLid. Nulo quando o evento não tem número. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "LIVE_LOCATION",
    "session": "apibrasil-financeiro",
    "liveLocation": {
      "…": "o objeto de liveLocation do WPPConnect"
    },
    "data": {
      "liveLocation": {
        "…": "o mesmo objeto, repetido"
      }
    },
    "number": null
  }
]
```

### `ADDED_TO_GROUP` — Você foi adicionado a um grupo

**Quando dispara:** Quando o seu número entra num grupo. A chave é chat, e não group — é o objeto da conversa, não o do grupo.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "ADDED_TO_GROUP" neste aviso. |
| `session` | string | O nome da sessão (o seu dispositivo). |
| `chat` | object | A conversa do grupo em que você entrou. |
| `data` | object | O MESMO objeto, aninhado outra vez sob chat. data.chat é igual a chat. |
| `number` | string \| null | Acrescentado no envio, junto de fromNumber, toNumber, lid e phoneNumberByLid. Nulo quando o evento não tem número. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "ADDED_TO_GROUP",
    "session": "apibrasil-financeiro",
    "chat": {
      "…": "o objeto de chat do WPPConnect"
    },
    "data": {
      "chat": {
        "…": "o mesmo objeto, repetido"
      }
    },
    "number": null
  }
]
```

### `PARTICIPANTS_CHANGED` — Participantes de um grupo mudaram

**Quando dispara:** Quando alguém entra, sai, é promovido a admin ou rebaixado, em qualquer grupo de que você participa.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "PARTICIPANTS_CHANGED" neste aviso. |
| `session` | string | O nome da sessão (o seu dispositivo). |
| `participantChanged` | object | Quem mudou, em qual grupo, e qual foi a ação. |
| `data` | object | O MESMO objeto, aninhado outra vez sob participantChanged. data.participantChanged é igual a participantChanged. |
| `number` | string \| null | Acrescentado no envio, junto de fromNumber, toNumber, lid e phoneNumberByLid. Nulo quando o evento não tem número. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "PARTICIPANTS_CHANGED",
    "session": "apibrasil-financeiro",
    "participantChanged": {
      "…": "o objeto de participantChanged do WPPConnect"
    },
    "data": {
      "participantChanged": {
        "…": "o mesmo objeto, repetido"
      }
    },
    "number": null
  }
]
```

### `ORDER_STATUS_UPDATE` — Status de pedido

**Quando dispara:** Quando um pedido do catálogo do WhatsApp Business muda de estado. Só faz sentido em contas com catálogo configurado.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "ORDER_STATUS_UPDATE" neste aviso. |
| `session` | string | O nome da sessão (o seu dispositivo). |
| `order` | object | O pedido e o novo estado dele. |
| `data` | object | O MESMO objeto, aninhado outra vez sob order. data.order é igual a order. |
| `number` | string \| null | Acrescentado no envio, junto de fromNumber, toNumber, lid e phoneNumberByLid. Nulo quando o evento não tem número. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "ORDER_STATUS_UPDATE",
    "session": "apibrasil-financeiro",
    "order": {
      "…": "o objeto de order do WPPConnect"
    },
    "data": {
      "order": {
        "…": "o mesmo objeto, repetido"
      }
    },
    "number": null
  }
]
```

### `NOTIFICATION_MESSAGE` — Notificação do WhatsApp

**Quando dispara:** Avisos do sistema que aparecem dentro da conversa — mudança de número, entrada em grupo, alteração de descrição. Não são mensagens de ninguém.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "NOTIFICATION_MESSAGE" neste aviso. |
| `session` | string | O nome da sessão (o seu dispositivo). |
| `notification` | object | A notificação emitida pelo WhatsApp. |
| `data` | object | O MESMO objeto, aninhado outra vez sob notification. data.notification é igual a notification. |
| `number` | string \| null | Acrescentado no envio, junto de fromNumber, toNumber, lid e phoneNumberByLid. Nulo quando o evento não tem número. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "NOTIFICATION_MESSAGE",
    "session": "apibrasil-financeiro",
    "notification": {
      "…": "o objeto de notification do WPPConnect"
    },
    "data": {
      "notification": {
        "…": "o mesmo objeto, repetido"
      }
    },
    "number": null
  }
]
```

### `INTERFACE_CHANGED` — Mudança de interface

**Quando dispara:** Quando a interface do WhatsApp Web muda de modo. Serve para diagnóstico: se ela mudou sozinha, algo aconteceu com a sessão. O envelope é diferente dos outros eventos de sala.

**Campos**

| Nome | Tipo | Significado |
| --- | --- | --- |
| `wook` | string | Sempre "INTERFACE_CHANGED". |
| `result` | number | 200. |
| `session` | string | O nome da sessão. |
| `message` | object | Uma frase montada pelo serviço descrevendo a mudança, sob message.message. |
| `body` | object | O objeto da mudança: displayInfo, mode e info. |

**Corpo de exemplo, como chega**

```json
[
  {
    "wook": "INTERFACE_CHANGED",
    "result": 200,
    "session": "apibrasil-financeiro",
    "message": {
      "message": "apibrasil-financeiro is SYNCING mode MAIN whatsapp, info CONNECTED"
    },
    "body": {
      "displayInfo": "SYNCING",
      "mode": "MAIN",
      "info": "CONNECTED"
    }
  }
]
```

## O que costuma pegar

- **São quatro URLs, e o evento errado não muda de porta** — wh_message, wh_connect, wh_qrcode e wh_status são campos separados do dispositivo. Registrar só wh_message e esperar o QR Code por ali não funciona — o QR Code sai por wh_qrcode e nada avisa que você não o registrou.
- **wook diz o evento, status diz a direção** — São dois campos e é fácil trocá-los. Numa mensagem, wook é SEND_MESSAGE ou RECEIVE_MESSAGE e status é SENT ou RECEIVED — redundantes. Já em MESSAGE_STATUS, wook é sempre MESSAGE_STATUS e status carrega o estado real (SENT, RECEIVED, READ, PLAYED, FAILED). Rotear por status quebra na primeira confirmação de leitura.
- **Tipo de mensagem fora da lista conhecida é DESCARTADO** — O montador tem caso para 14 tipos (text, image, sticker, audio, ptt, video, location, document, link, vcard, multi_vcard, order, list, list_response). Qualquer outro cai no ramo padrão, que usa o TIPO EM MAIÚSCULAS como wook — e esse nome não existe no registro de eventos, então o disparo é abortado sem erro. Se um tipo novo do WhatsApp não chegar no seu endpoint, é aqui.
- **datetime nas mensagens, dateTime na confirmação** — O aviso de mensagem traz datetime, tudo minúsculo. O de MESSAGE_STATUS traz dateTime, com T maiúsculo. Mesmo formato, nomes diferentes, no mesmo canal — ler datetime num MESSAGE_STATUS devolve undefined.
- **O aviso pode chegar duas vezes, e é por desenho** — Além da URL do seu dispositivo, existe um webhook GLOBAL do serviço. Quando os dois estão configurados, cada evento é postado nos dois destinos. Se apontarem para o mesmo lugar, você recebe o dobro — deduplique por id mais wook.
- **Confira a assinatura antes de confiar no corpo** — Quando a sessão tem chave, o POST vem com x-webhook-signature: sha256=<hmac>, calculado sobre o corpo CRU com o sessionkey do dispositivo. Comparar exige o body antes do parse de JSON — depois de JSON.parse e re-serializar, o hash não bate.

## Boas práticas

- **Confira a assinatura sobre o corpo CRU** — x-webhook-signature é sha256= mais o HMAC-SHA256 do corpo com o sessionkey do dispositivo. Calcule sobre os bytes recebidos: se você fizer JSON.parse e re-serializar, um espaço a mais já quebra o hash.
- **Você tem 10 segundos** — É o timeout do POST. Responda 200 e faça o trabalho depois. Com a fila de retentativa ligada no serviço, uma falha é repetida até 3 vezes a cada 5 segundos; sem ela, o aviso é perdido em silêncio.
- **Roteie por wook, nunca por status** — status vale SENT numa mensagem sua e também num MESSAGE_STATUS de mensagem entregue. São coisas diferentes com o mesmo valor — só wook os separa.
- **Deduplique por id + wook** — O webhook global e o do dispositivo disparam os dois. Se apontarem para a mesma URL, todo evento chega em dobro.

## Receptores de exemplo

**Node**

```node
import crypto from "node:crypto"

// O corpo CRU é necessário para conferir a assinatura.
app.post("/webhook/whatsapp", express.raw({ type: "application/json" }), (req, res) => {
  const assinatura = req.get("x-webhook-signature")
  const esperada =
    "sha256=" + crypto.createHmac("sha256", SESSION_KEY).update(req.body).digest("hex")

  if (assinatura && assinatura !== esperada) return res.sendStatus(401)

  // Responda antes de processar: você tem 10 segundos.
  res.sendStatus(200)

  const aviso = JSON.parse(req.body.toString("utf8"))

  switch (aviso.wook) {
    case "RECEIVE_MESSAGE":
      console.log(`${aviso.name || aviso.from}: ${aviso.content}`)
      break
    case "MESSAGE_STATUS":
      // Atenção: dateTime com T maiúsculo neste aviso.
      console.log(`${aviso.id} agora está ${aviso.status}`)
      break
    case "STATUS_CONNECT":
      if (aviso.state === "browserClose") alertarQueASessaoCaiu(aviso.session)
      break
  }
})
```

**Python**

```python
import hashlib
import hmac

@app.post("/webhook/whatsapp")
def webhook_whatsapp():
    corpo = request.get_data()  # bytes crus, para a assinatura bater
    assinatura = request.headers.get("x-webhook-signature")
    esperada = "sha256=" + hmac.new(SESSION_KEY.encode(), corpo, hashlib.sha256).hexdigest()

    if assinatura and not hmac.compare_digest(assinatura, esperada):
        return "", 401

    aviso = request.get_json(force=True)

    if aviso["wook"] == "RECEIVE_MESSAGE":
        print(f"{aviso.get('name') or aviso['from']}: {aviso['content']}")
    elif aviso["wook"] == "MESSAGE_STATUS":
        # Atenção: dateTime com T maiúsculo neste aviso.
        print(f"{aviso['id']} agora está {aviso['status']}")
    elif aviso["wook"] == "STATUS_CONNECT" and aviso["state"] == "browserClose":
        alertar_que_a_sessao_caiu(aviso["session"])

    return "", 200
```

**PHP**

```php
<?php

// O corpo CRU é necessário para conferir a assinatura.
$corpo = file_get_contents('php://input');
$assinatura = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? null;
$esperada = 'sha256=' . hash_hmac('sha256', $corpo, SESSION_KEY);

if ($assinatura && !hash_equals($esperada, $assinatura)) {
    http_response_code(401);
    exit;
}

http_response_code(200);

$aviso = json_decode($corpo, true);

switch ($aviso['wook']) {
    case 'RECEIVE_MESSAGE':
        error_log(($aviso['name'] ?: $aviso['from']) . ': ' . $aviso['content']);
        break;
    case 'MESSAGE_STATUS':
        // Atenção: dateTime com T maiúsculo neste aviso.
        error_log("{$aviso['id']} agora está {$aviso['status']}");
        break;
    case 'STATUS_CONNECT':
        if ($aviso['state'] === 'browserClose') {
            alertarQueASessaoCaiu($aviso['session']);
        }
        break;
}
```

## Ver também

- Webhooks: [Webhooks](https://doc.apibrasil.io/webhooks.md)
- Índice da documentação: https://doc.apibrasil.io/llms.txt
- Versão HTML desta página: https://doc.apibrasil.io/webhooks/api-whatsapp-wpp
