Payloads de eventos
Todo corpo de webhook tem o mesmo envelope:
{
"event": "message.received",
"event_id": "evt_01J...",
"api_version": "2026-04",
"channel_id": "inst_01J...",
"occurred_at": "2026-05-01T12:34:56.000Z",
"data": { /* event-specific shape, see below */ }
}Os campos do envelope:
| Campo | Notas |
|---|---|
event | O nome do evento. Veja a lista abaixo. |
event_id | ULID, estável entre retentativas. Use como sua chave de deduplicação. |
api_version | Versão datada do schema do evento. Não quebramos o formato dentro de uma versão. |
channel_id | O canal que produziu o evento. Sempre presente. |
occurred_at | Horário do relógio no gateway quando o evento foi emitido. |
data | Payload específico do evento, schemas abaixo. |
Tipos de evento
A lista completa de eventos:
qr.updated— Um novo PNG de QR está disponível para um canal. O dashboard usa isso; assinantes de webhook geralmente filtram fora.channel.connected— O canal terminou o pareamento. Incluiphone_number.channel.disconnected— A sessão terminou. Incluireason(network,logout,session_invalidated,kicked_other_device).message.received— Mensagem recebida de um contato. Incluifrom,body(texto ou referência de mídia) ereceived_at. Veja a nota sobre resolução de LID abaixo.message.sent— Sua mensagem enviada chegou ao WhatsApp. Incluimessage_id.message.status— Um tique de entrega para uma mensagem que você enviou:sent→delivered→read/played, oufailed. Veja abaixo.contact.resolved— Dispara na primeira vez que descobrimos o JID de telefone por trás de um contato antes conhecido apenas por LID. Veja abaixo.
Payload de message.status
Acompanha os tiques de uma mensagem que você enviou — use para montar um indicador de entregue/lido ou uma visão de "aguardando resposta".
{
"message_id": "wamid.HBgM...",
"to": "558585218491@s.whatsapp.net",
"status": "read",
"is_group": false,
"timestamp": 1748550000
}| Campo | Notas |
|---|---|
status | Um de sent (chegou ao WhatsApp), delivered (chegou ao dispositivo, ✓✓), read (conversa aberta, ✓✓ azul), played (áudio/vídeo reproduzido), failed (não entregue). |
participant | Em grupos, o membro a quem este ack pertence — cada membro confirma de forma independente. Omitido em conversas 1:1. |
O mesmo message_id dispara várias vezes conforme o status avança. read só chega quando o destinatário tem as confirmações de leitura ativadas — sua ausência não prova que a mensagem está não lida.
Resolução de LID (Linked-Device ID)
O protocolo multi-device do WhatsApp expõe contatos a sessões linkadas como Linked-Device IDs (<digits>@lid) em vez de JIDs de telefone (<digits>@s.whatsapp.net). Respostas endereçadas a um JID @lid são silenciosamente descartadas no servidor do WhatsApp, então as resolvemos no lado do gateway antes de emitir o webhook.
Cada payload de message.received carrega três campos de rastreabilidade:
| Campo | Notas |
|---|---|
from | Melhor JID conhecido do remetente. <phone>@s.whatsapp.net quando resolvido, caso contrário o <digits>@lid bruto. |
from_resolved | true quando from é um JID de telefone entregável. false quando só o LID é conhecido até agora. |
from_lid | O JID @lid original, presente sempre que o WhatsApp entregou a mensagem como um LID — mesmo após a resolução. Permite correlacionar registros anteriores indexados por LID com o telefone resolvido. |
from_phone | O JID <phone>@s.whatsapp.net resolvido, presente quando from_resolved é true. Sempre igual a from nesse caso — exposto separadamente para você pegá-lo incondicionalmente sem checar o sufixo de from. |
Chave de persistência recomendada: from_phone quando presente, com fallback para from_lid. Quando from_resolved muda para true para um contato que você antes via apenas como LID, também emitimos um evento contact.resolved para você reescrever a chave de forma determinística.
Endereçamento de resposta: POST /v1/channels/:id/messages aceita qualquer uma das formas no campo to. JIDs de telefone (<digits>@s.whatsapp.net) são preferidos. LIDs (<digits>@lid) são roteados via o cache por canal LID↔telefone preenchido conforme o tráfego de entrada flui; um LID desconhecido retorna 400 undeliverable_recipient.
Payload de contact.resolved
{
"channel_id": "inst_01J...",
"lid": "47064251658474@lid",
"phone_jid": "558585218491@s.whatsapp.net",
"first_seen_at": "2026-05-22T21:17:11.576Z"
}Você também pode listar todos os pares (LID → JID de telefone) que observamos desde a abertura da sessão:
GET /v1/channels/{id}/contacts
→ { "data": [{ "lid": "...@lid", "phone_jid": "...@s.whatsapp.net" }], "count": 1 }Headers que seu endpoint vai ver
POST /your/endpoint HTTP/1.1
Host: api.acme.dev
Content-Type: application/json
User-Agent: whatisup-webhooks/0.0.1
X-WhatIsUp-Signature: t=1700000000,v1=...
X-WhatIsUp-Event: message.received
X-WhatIsUp-Event-Id: evt_01J...
X-WhatIsUp-Correlation-Id: req_01J...
X-WhatIsUp-Event-Id e X-WhatIsUp-Event são conveniências — espelham o que está no corpo, mas deixam você rotear ou curto-circuitar antes de fazer o parse do JSON.
X-WhatIsUp-Correlation-Id rastreia o evento de volta ao que quer que o tenha disparado. Para um message.sent de saída, é o ID da requisição da chamada POST /v1/messages. Para um message.received, é um ID gerado estável entre retentativas. Útil em logs.
Compatibilidade
Versionamos os schemas via api_version. Dentro de 2026-04, vamos apenas adicionar campos opcionais — nunca renomear, nunca remover. Novos formatos incompatíveis ganham uma nova string de versão; você opta por eles atualizando seu endpoint no seu ritmo. (Ainda não temos múltiplas versões no ar — só uma.)
Os payloads de rede aqui são os mesmos formatos que descem por /v1/events. O stream SSE é só uma visão em tempo real, não assinada e sem torradeira, do mesmo barramento.