Referência de eventos de webhook
Webhooks entregam eventos em tempo real para a URL do seu sistema. Esta página documenta cada evento, seu payload e como correlacionar eventos com suas chamadas de API. Para criar e gerenciar webhooks, consulte Configurar webhooks.
Todo evento chega como JSON:
{
"event": "message.sent",
"data": {}
}
Esquema v3 (camelCase). Todos os campos de data são camelCase. Números de telefone são sempre E.164 com + (sem sufixo de dispositivo, sem @s.whatsapp.net / @lid). Timestamps são sempre ISO 8601 no campo único createdAt (o horário em que o evento ocorreu).
Eventos disponíveis
Saída (entrega/status): message.sent, message.delivered, message.read, message.failed
Entrada (mensagens recebidas):
message.reply — resposta correlacionada
message.received — mensagem recebida no número conectado
message.group — mensagem recebida em um grupo
message.newsletter — mensagem recebida em um canal (@newsletter)
Ciclo de vida do número:
number.created — instância criada no Pilot Status
number.connected — conectado ao WhatsApp (estado OPEN)
number.disconnected — desconexão confirmada após verificação periódica de saúde (não reflete cada breve flutuação de conectividade)
number.removed — instância removida
number.recovered — número recuperado de um estado degradado/bloqueado
Eventos de saúde (somente assinatura "*"): os eventos de transição de saúde — number.health_blocked, number.health_degraded, number.health_shadowban — e number.recovered não são assináveis individualmente e não aparecem no seletor de eventos. Eles são entregues somente a webhooks assinados com o curinga "*".
Chamadas de voz (WhatsApp Business Calling, consulte Chamadas de Voz):
Os eventos de chamada normalizados — call.ringing, call.connected, call.ended, call.missed — estão disponíveis em qualquer número com suporte a chamadas (web nativo (não oficial) — e Meta Cloud API). call.permission_updated e o envelope nativo calls são somente da Meta Cloud API.
call.ringing — chamada tocando (UIC de entrada ou transição BIC de saída)
call.connected — chamada atendida/conectada
call.ended — chamada finalizada (status: COMPLETED | FAILED | REJECTED; duration em segundos quando atendida)
call.missed — chamada de entrada encerrada sem ser atendida
call.permission_updated — (somente Meta Cloud API) resposta a uma solicitação de permissão de chamada (status: NO_PERMISSION | TEMPORARY | PERMANENT)
calls — (somente Meta Cloud API) envelope nativo calls da Meta (bruto entry[].changes[].value, carregando SDPs) para sinalização WebRTC personalizada
Eventos da camada de migração
Se você se conectou pela camada de compatibilidade Evolution GO ou Evolution v2, os eventos também são encaminhados ao seu webhook no formato de evento nativo desse provedor (não no esquema normalizado acima). Consulte as listas de eventos nas páginas de camada Evolution GO e Evolution V2.
Identificadores e correlação
Todo evento de mensagem carrega dois IDs distintos:
messageId — ID da mensagem do WhatsApp/provedor (ex.: key.id). Pode ser null para falhas que ocorrem antes de o provedor retornar um ID.
id — ID interno da mensagem no Pilot Status. Mesmo valor do id no HTTP 202 de POST /v1/messages/send.
numberId — o ID público do número (instância) que tratou o evento — o mesmo id exposto por GET /v1/numbers.
correlationId — presente quando o evento se correlaciona a um envio anterior (mesmo valor do correlationId do 202).
quotedMessageId — em message.reply, o messageId da mensagem original citada (igual ao messageId do message.sent original).
Correlação com POST /v1/messages/send
Após um envio aceito, a API retorna HTTP 202 com id e correlationId:
Campo do 202 | Equivalente no webhook |
|---|
id | id em message.sent / message.delivered / message.read / message.failed (mesmo valor). |
correlationId | Pode se repetir em eventos de status de saída e em message.reply / message.received quando correlacionado ao envio. |
(não presente no 202) | messageId do WhatsApp — só aparece a partir de message.sent. |
Em message.reply: quotedMessageId = o messageId do message.sent original; o próprio messageId da resposta é a nova mensagem de entrada. Use quotedMessageId (e correlationId quando presente) para associar a resposta ao seu envio anterior.
Payload de message.*
Campos comuns
| Campo | Presença | Descrição |
|---|
event | sempre | nome do evento |
from | sempre | remetente em E.164 |
to | sempre | destinatário em E.164 |
numberId | sempre | ID público do número que tratou a mensagem |
messageId | sempre | ID da mensagem do WhatsApp/provedor (null antes de o provedor retornar um) |
id | sempre | ID interno do Pilot Status (= id do HTTP 202) |
type | sempre | text, image, audio, video, document, location, contacts, sticker ou reaction |
fromMe | sempre | booleano — true em eventos de saída, false em eventos de entrada |
createdAt | sempre | ISO 8601 — horário do evento |
content | condicional | texto da mensagem (ou legenda) |
participantName | condicional | nome do remetente no WhatsApp (recebida / grupo / canal) |
correlationId | condicional | quando correlacionado a um envio anterior |
contentReplied | condicional | texto da mensagem citada (somente message.reply) |
quotedMessageId | condicional | messageId da mensagem citada (somente message.reply) |
mediaLink | condicional | URL da mídia, quando o provedor a expõe |
mediaType | condicional | tipo da mídia |
mediaCaption | condicional | legenda da mídia |
mediaFilename | condicional | nome do arquivo da mídia |
groupName | condicional | nome do grupo (somente message.group) |
groupId | condicional | JID do grupo (somente message.group) |
newsletterName | condicional | nome do canal (somente message.newsletter) |
newsletterId | condicional | JID do canal (somente message.newsletter) |
error | condicional | mensagem de erro (somente message.failed) |
errorCode | condicional | código de erro estável (somente message.failed) |
Semântica de direção de from/to:
- Saída (
sent/delivered/read/failed): to = número de destino (E.164); from = número próprio (presente quando resolvível de forma barata, caso contrário omitido); fromMe = true.
- Entrada (
received/reply): from = contato/remetente (E.164); to = número próprio (E.164); fromMe = false.
- Grupo / canal (
group/newsletter): from = participante (E.164); to = número próprio; mais groupId/groupName ou newsletterId/newsletterName.
message.sent
{
"event": "message.sent",
"data": {
"to": "+5511999999999",
"from": "+5511888888888",
"numberId": "cmm0abc123",
"messageId": "A52298BB1619CB5EC464BEFB8A3ACB94",
"id": "cmm04obm46zz0qv4ycjp8x6r2",
"type": "text",
"fromMe": true,
"content": "sent text",
"correlationId": "corr_123",
"createdAt": "2026-02-24T15:00:05.000Z"
}
}
message.delivered
{
"event": "message.delivered",
"data": {
"to": "+5511999999999",
"from": "+5511888888888",
"numberId": "cmm0abc123",
"messageId": "A52298BB1619CB5EC464BEFB8A3ACB94",
"id": "cmm04obm46zz0qv4ycjp8x6r2",
"type": "text",
"fromMe": true,
"content": "sent text",
"createdAt": "2026-02-24T15:00:06.000Z"
}
}
message.read
message.read só dispara quando o destinatário tem os recibos de leitura do WhatsApp ativados.
{
"event": "message.read",
"data": {
"to": "+5511999999999",
"from": "+5511888888888",
"numberId": "cmm0abc123",
"messageId": "A52298BB1619CB5EC464BEFB8A3ACB94",
"id": "cmm04obm46zz0qv4ycjp8x6r2",
"type": "text",
"fromMe": true,
"content": "sent text",
"createdAt": "2026-02-24T15:00:10.000Z"
}
}
message.failed
Inclui error e, quando disponível, um errorCode estável (ex.: DELIVER_NOT_CONFIRMED).
{
"event": "message.failed",
"data": {
"to": "+5511999999999",
"from": "+5511888888888",
"numberId": "cmm0abc123",
"messageId": null,
"id": "cmm04obm46zz0qv4ycjp8x6r2",
"type": "text",
"fromMe": true,
"content": "sent text",
"error": "Failed to send message via Pilot Status.",
"errorCode": "DELIVER_NOT_CONFIRMED",
"createdAt": "2026-02-24T15:00:05.000Z"
}
}
message.received
{
"event": "message.received",
"data": {
"from": "+5511999999999",
"to": "+5511888888888",
"numberId": "cmm0abc123",
"messageId": "msg_in_id",
"id": "cmm04obm46zz0qv4ycjp8x6r2",
"type": "text",
"fromMe": false,
"content": "Hi",
"participantName": "WhatsApp name",
"createdAt": "2026-02-24T10:30:00.000Z"
}
}
Exemplo de mídia (quando o provedor expõe a URL — ex.: Meta Cloud API). Para provedores sem uma URL de mídia pública, mediaLink/mediaType/mediaCaption/mediaFilename podem ser omitidos.
{
"event": "message.received",
"data": {
"from": "+5511999999999",
"to": "+5511888888888",
"numberId": "cmm0abc123",
"messageId": "msg_in_id",
"id": "cmm04obm46zz0qv4ycjp8x6r2",
"type": "image",
"fromMe": false,
"content": "Check this photo",
"mediaLink": "https://...",
"mediaType": "image",
"mediaCaption": "Check this photo",
"mediaFilename": "photo.jpg",
"createdAt": "2026-02-24T10:30:00.000Z"
}
}
message.reply
O novo texto do contato está em content; o texto da mensagem original citada está em contentReplied. Use quotedMessageId para corresponder ao messageId do seu message.sent de saída original.
{
"event": "message.reply",
"data": {
"from": "+5511999999999",
"to": "+5511888888888",
"numberId": "cmm0abc123",
"messageId": "msg_12345",
"id": "cmm04obm46zz0qv4ycjp8x6r2",
"type": "text",
"fromMe": false,
"content": "Yes, I confirm",
"contentReplied": "Hi! Do you confirm your appointment?",
"quotedMessageId": "msg_original_123",
"correlationId": "corr_123",
"createdAt": "2026-02-24T10:30:00.000Z"
}
}
message.group
{
"event": "message.group",
"data": {
"from": "+5511999999999",
"to": "+5511888888888",
"numberId": "cmm0abc123",
"messageId": "msg_in_id",
"id": "cmm04obm46zz0qv4ycjp8x6r2",
"type": "text",
"fromMe": false,
"content": "Message in the group",
"participantName": "WhatsApp name",
"groupId": "120363123456789012@g.us",
"groupName": "My Group",
"createdAt": "2026-02-24T10:30:00.000Z"
}
}
message.newsletter
{
"event": "message.newsletter",
"data": {
"from": "+5511999999999",
"to": "+5511888888888",
"numberId": "cmm0abc123",
"messageId": "msg_in_id",
"id": "cmm04obm46zz0qv4ycjp8x6r2",
"type": "text",
"fromMe": false,
"content": "Text in the channel",
"participantName": "WhatsApp name",
"newsletterId": "120363123456789012@newsletter",
"newsletterName": "My Channel",
"createdAt": "2026-02-24T10:30:00.000Z"
}
}
Payload de number.*
Campos para number.created / number.connected / number.disconnected / number.removed / number.recovered (e os eventos number.health_blocked / number.health_degraded / number.health_shadowban):
| Campo | Presença | Descrição |
|---|
event | sempre | nome do evento |
numberId | sempre | ID público do número (mesmo id de GET /v1/numbers) |
phone | sempre | número em E.164 com + |
displayName | sempre | nome de exibição do número |
createdAt | sempre | ISO 8601 — horário do evento |
profileName | condicional | nome do perfil do WhatsApp, quando disponível |
error | condicional | mensagem de erro (eventos de saúde / falha) |
errorCode | condicional | código de erro estável, quando presente |
number.created
{
"event": "number.created",
"data": {
"numberId": "cmm0abc123",
"phone": "+5511999999999",
"displayName": "Main store",
"createdAt": "2026-02-24T15:00:05.000Z"
}
}
number.connected
{
"event": "number.connected",
"data": {
"numberId": "cmm0abc123",
"phone": "+5511999999999",
"displayName": "Main store",
"profileName": "Main Store Official",
"createdAt": "2026-02-24T15:00:05.000Z"
}
}
number.disconnected
{
"event": "number.disconnected",
"data": {
"numberId": "cmm0abc123",
"phone": "+5511999999999",
"displayName": "Main store",
"createdAt": "2026-02-24T15:10:00.000Z"
}
}
number.removed
{
"event": "number.removed",
"data": {
"numberId": "cmm0abc123",
"phone": "+5511999999999",
"displayName": "Main store",
"createdAt": "2026-02-24T15:10:00.000Z"
}
}
Payload de call.*
Diferente de message.* / number.*, os eventos normalizados call.* são planos (sem o wrapper data):
{
"event": "call.ended",
"callId": "call_01HZX...",
"externalCallId": "wacid.ABGG...",
"direction": "OUTBOUND",
"status": "COMPLETED",
"from": "5511888888888",
"to": "5511999999999",
"timestamp": "2026-07-03T15:02:05.000Z",
"duration": 120
}
| Campo | Presença | Descrição |
|---|
event | sempre | call.ringing / call.connected / call.ended / call.missed / call.permission_updated |
callId | sempre (null em call.permission_updated) | id da chamada no Pilot Status (use com GET /v1/calls/{callId}) |
externalCallId | condicional | id da chamada na Meta (wacid...) — correlaciona com o envelope nativo calls |
direction | sempre (null em call.permission_updated) | INBOUND (iniciada pelo usuário) ou OUTBOUND (iniciada pela empresa) |
status | sempre | status da chamada (RINGING/ACCEPTED/COMPLETED/FAILED/MISSED/REJECTED) ou status de permissão (NO_PERMISSION/TEMPORARY/PERMANENT) |
from / to | sempre (anulável) | as duas partes (dígitos) |
timestamp | sempre | ISO 8601 — horário do evento |
duration | condicional | duração da chamada em segundos (call.ended, apenas quando atendida) |
Consulte o fluxo completo de chamadas.
Notas importantes
message.newsletter: newsletterName é o nome de exibição do canal quando disponível; caso contrário, pode ser omitido. O identificador do canal é newsletterId (JID completo ...@newsletter). participantName é o autor da mensagem no canal.
- Mídia:
mediaLink/mediaType/mediaCaption/mediaFilename aparecem somente quando o provedor expõe esses dados (ex.: Meta Cloud API). Para provedores sem uma URL de mídia pública, são omitidos.
- Eventos
number.* não são correlacionados a POST /v1/messages/send; use-os para provisionamento/monitoramento. number.disconnected é emitido após a verificação de saúde confirmar a desconexão, não a cada oscilação de conexão.
- Payloads de webhook do cliente não incluem campos internos como
lastMessageId.
- A entrega de campos sensíveis pode depender da configuração de retenção. Com a retenção desligada, campos condicionais como
content podem ficar vazios; IDs e timestamps continuam existindo.
- O evento
message.read (e o status Read na API/Logs) só ocorre quando o destinatário tem os recibos de leitura do WhatsApp ativados.