Skip to content
WhatIsUp.dev

Payloads de eventos

Todo corpo de webhook tem o mesmo envelope:

{
  "api_version": "v1",
  "event_id": "9bc38acc-2edc-482e-93d4-0482b432ffa6",
  "event": "message.received",
  "customer_id": "2696bc9b-2e37-43d9-a33b-5a79f81daeba",
  "channel_id": "8d653c66-e4ff-43ee-97da-3de5ad5680d4",
  "created_at": "2026-05-01T12:34:56.000Z",
  "data": { /* formato específico do evento, veja abaixo */ }
}

Os campos do envelope:

CampoTipoNotas
api_versionstringHoje é sempre "v1". Compare exatamente com isso se você validar a versão.
event_idUUID v4Estável entre retentativas da mesma entrega. Use como sua chave de deduplicação.
eventstringO nome do evento. Veja a lista abaixo.
customer_idUUIDO id da sua conta. Sempre presente.
channel_idUUIDO canal que produziu o evento. Sempre presente.
created_atISO-8601 UTCHorário do relógio no gateway quando o evento foi emitido.
dataobjetoPayload específico do evento, schemas abaixo.

Os cinco campos escalares são obrigatórios — nenhum é nulo. data é obrigatório e sempre um objeto; o formato dele é decidido por event.

Não existe campo occurred_at, e os ids são UUID, não ULID — revisões anteriores desta página documentavam os dois. Um receptor escrito a partir daquilo lê undefined no timestamp.

Tipos de evento

Todos os 25 eventos. Assinar um nome fora desta lista é rejeitado na criação do endpoint com 400 invalid_request.

Mensagens

  • message.received — Mensagem recebida de um contato. Carrega from, to, chat_id, timestamp, is_group, body, e opcionalmente push_name. Veja a nota sobre resolução de LID abaixo.
  • message.sent — Sua mensagem de saída chegou ao WhatsApp. Carrega message_id, to, timestamp, body, e o seu client_ref.
  • message.status — Um tique de entrega para uma mensagem que você enviou: sentdeliveredread/played, ou failed. Veja abaixo.
  • message.reaction — Alguém reagiu numa conversa em que seu canal está. Carrega message_key, reactor, emoji, timestamp. Um emoji vazio significa reação removida.

Ciclo de vida do canal

  • channel.statusToda transição de ciclo de vida, num evento só. Carrega status, previous_status, changed_at, simple (qr / connected / logged_out / stopped), live e can_send. Se você conduz o pareamento sem interface, assine este e ignore os três abaixo. simple é sinal de pareamento, não de saúde — um canal disconnected reporta simple: "connected" de propósito, porque as credenciais seguem boas e o socket volta sozinho. Leia live para "o socket está de pé agora" e can_send para "um envio vai ser aceito".
  • channel.connected — O canal terminou o pareamento. Carrega phone_number, connected_at.
  • channel.disconnected — A sessão terminou. Carrega reason, will_reconnect, disconnected_at.
  • qr.updated — Um novo QR está disponível. Carrega qr_png_base64 (base64 pura) e expires_at (epoch ms).

Grupos

  • group.created — Carrega group_id, by, timestamp, subject, participants.
  • group.updated — Metadados do grupo mudaram.
  • group.participant_added / group.participant_removed — Composição mudou.
  • group.admin_promoted / group.admin_demoted — Direitos de admin mudaram.

Os seis carregam group_id, o ator by (pode ser nulo), e timestamp.

Chats

  • chat.updated — Estado do chat mudou (mute, não-lidas, …).
  • chat.archived — Chat arquivado ou desarquivado.
  • chat.pinned — Chat fixado ou desafixado.

Comércio (apenas contas WhatsApp Business)

  • order.placed / order.cancelled — Um pedido de entrada mudou de estado.
  • cart.updated — O carrinho de um cliente mudou.

Outros

  • story.viewed — Alguém viu um story que você publicou.
  • call.offered / call.terminated — Ciclo de vida de chamada recebida.
  • presence.updated — A presença de um contato mudou. Carrega chat_id, contact_id, presence (available / unavailable / composing / recording / paused) e last_seen (segundos Unix, ou null quando a privacidade esconde).
  • 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.

Não existe evento message.delivered nem message.failed. Os dois são frames message.status discriminados pelo campo status. Revisões anteriores desta documentação listavam os dois como eventos separados — assinar aqueles nomes falha na validação.

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": "3EB0C9A17F2B4D8E1A05F3D77C10B4E2A991",
  "to": "558585218491@s.whatsapp.net",
  "status": "read",
  "is_group": false,
  "timestamp": 1779425257573
}
CampoNotas
statusUm de sent (chegou ao WhatsApp), delivered (chegou ao dispositivo, ✓✓), read (conversa aberta, ✓✓ azul), played (áudio/vídeo reproduzido), failed (não entregue).
participantEm 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:

CampoNotas
fromMelhor JID conhecido do remetente. <phone>@s.whatsapp.net quando resolvido, caso contrário o <digits>@lid bruto.
from_resolvedtrue quando from é um JID de telefone entregável. false quando só o LID é conhecido até agora.
from_lidO 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_phoneO 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 apenas JIDs de telefone. Qualquer to terminado em @lid é rejeitado de cara com 400 undeliverable_recipient — conhecido ou não, em cache ou não. O WhatsApp descarta em silêncio mensagens endereçadas a um LID, então recusamos em vez de reportar uma entrega que nunca acontece.

Ou seja: quando from_resolved é false você não consegue responder ainda. Guarde a mensagem sob from_lid, espere o contact.resolved, e então envie para o JID de telefone.

Payload de contact.resolved

{
  "channel_id": "8d653c66-e4ff-43ee-97da-3de5ad5680d4",
  "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/lids
 { "data": [{ "lid": "...@lid", "phone_jid": "...@s.whatsapp.net" }] }

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: 9bc38acc-2edc-482e-93d4-0482b432ffa6
X-WhatIsUp-Correlation-Id: req-3f7a1c02

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/channels/{channel_id}/messages (o mesmo valor devolvido no header x-request-id). Para um message.received, é um ID gerado estável entre retentativas. Útil em logs.

Compatibilidade

Versionamos os schemas via api_version, hoje v1. Dentro de uma versão, 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.)

O stream SSE em /v1/events carrega os mesmos payloads data, mas não é o mesmo envelope e não é o mesmo vocabulário de eventos — frames SSE não são assinados, não vêm embrulhados e usam alguns nomes de evento diferentes. Leia aquela página antes de escrever um handler só para os dois.