Eventos webhook
Hub emite 10 eventos webhook. Esta pagina documenta cada um. Pra contrato geral (HMAC, retry), ver Webhook contract.
Eventos de mensagem
message.received
Disparado pra cada mensagem nova recebida pelo chip (ou enviada manualmente do celular pareado, se receive_self_chat=true).
{ "event": "message.received", "timestamp": "2026-04-30T12:34:56.789Z", "data": { "message_id": "01HZTQMSG...", "chip_id": "01HZTQCHIP...", "wa_message_id": "BAE5...", "from": "5511988887777@s.whatsapp.net", "to": "5511999998888:30@s.whatsapp.net", "direction": "in", "type": "text", "content": { "text": "ola" }, "timestamp": "2026-04-30T12:34:55.000Z", "sender": { "jid": "5511988887777@s.whatsapp.net", "name": "Maria", "phone_number": "5511988887777", "lid": "12345678901234@lid", "is_business": false, "verified_business_name": null }, "chat": { "jid": "5511988887777@s.whatsapp.net", "is_group": false, "name": null, "participants": null, "participants_count": null } }}Campos chave
| Campo | Notas |
|---|---|
direction | 'in' recebida; 'out' enviada do celular pareado (precisa opt-in) |
sender.jid | JID do humano que enviou (em grupo == participant, em 1-1 == from) |
sender.name | pushName (editavel pelo proprio remetente) |
sender.phone_number | telefone “puro” (so digitos). Vazio quando WA nao expoe |
sender.lid | LID alternate quando WA expoe — cross-match LID↔telefone |
sender.is_business | true se conta WhatsApp Business verificada |
sender.verified_business_name | nome verificado pelo WA (null em conta pessoal) |
chat.is_group | derivado de chat.jid terminar em @g.us |
chat.participants | so em grupo: [{jid, admin: 'admin'|'superadmin'|null}] |
Tipos de content por type
// type: text{ "text": "..." }
// type: image / audio / document / video{ "mimetype": "...", "media_id": "01HZTQ...", "caption": "...", ... }
// type: location{ "lat": -23.5505, "lng": -46.6333, "name": "...", "address": "..." }
// type: reaction{ "emoji": "❤️", "to_wa_message_id": "BAE5..." }
// type: unknown (protocolo Baileys que nao sabemos parsear){ "kind": "..." }message.status
Mudanca de status de mensagem outbound (sent → delivered → read → failed).
{ "event": "message.status", "timestamp": "...", "data": { "message_id": "01HZTQMSG...", "chip_id": "01HZTQCHIP...", "wa_message_id": "BAE5...", "status": "delivered" }}message.transcribed
Audio transcrito (so pra apps com transcribe_audio_mode != 'off').
{ "event": "message.transcribed", "timestamp": "...", "data": { "message_id": "01HZTQMSG...", "chip_id": "01HZTQCHIP...", "status": "success", "transcription": { "text": "ola, queria confirmar meu pedido", "language": "pt", "duration_seconds": 8.5, "cost_cents": 0.085, "provider": "openai", "model": "whisper-1" }, "error": null }}Em falha: status: "failed", transcription: null, error: "...".
Eventos de chip
chip.connected
Chip pareou ou reconectou e o socket Baileys esta open.
{ "event": "chip.connected", "timestamp": "...", "data": { "chip_id": "01HZTQCHIP...", "phone_number": "5511999998888" }}chip.disconnected
⚠️ So em close TERMINAL (apos badSession streak ou loggedOut/multidevice/replaced). Closes transient (badSession recuperavel, connectionLost, etc) nao disparam webhook — recuperam em segundos via reconnect automatico.
{ "event": "chip.disconnected", "timestamp": "...", "data": { "chip_id": "01HZTQCHIP...", "reason": "loggedOut", "will_reconnect": false, "is_terminal": true, "status_code": 401, "consecutive_bad_session": 0 }}chip.logged_out
Chip foi marcado como logged_out (sessao Baileys destruida, precisa re-parear). Disparado junto com chip.disconnected mas separado pra app reagir especificamente.
{ "event": "chip.logged_out", "timestamp": "...", "data": { "chip_id": "01HZTQCHIP..." }}chip.banned
Detector automatico de ban marcou chip como banned. Critico — fila pausada, requer intervencao manual.
{ "event": "chip.banned", "timestamp": "...", "data": { "chip_id": "01HZTQCHIP...", "reason": "401 persistent + 0 inbound 1h" }}chip.qr_loop
Chip ficou em pairing por mais que 5min — sintoma de QR esquecido. Re-notifica a cada 30min ate 24h, depois envia 1x final e para.
{ "event": "chip.qr_loop", "timestamp": "...", "data": { "chip_id": "01HZTQCHIP...", "pairing_for_minutes": 7 }}chip.bad_session_streak
Preventivo. Disparado quando consecutiveBadSession==2 (penultimo close antes de virar terminal real). Avisa antes de cair.
{ "event": "chip.bad_session_streak", "timestamp": "...", "data": { "chip_id": "01HZTQCHIP...", "streak": 2 }}chip.silent
Chip esta connected mas sem atividade ha mais que 2h — sintoma de socket fantasma (WA parou de rotar eventos). Cobre o caso kill -9 em sessao anterior + outros sintomas silenciosos.
{ "event": "chip.silent", "timestamp": "...", "data": { "chip_id": "01HZTQCHIP...", "silent_for_minutes": 145 }}Resumo — quando reagir
| Evento | Acao recomendada |
|---|---|
message.received | processa mensagem; se chatbot, responda |
message.status | atualiza status na sua UI |
message.transcribed | salva texto da transcricao |
chip.connected | atualiza UI (“chip ativo”) |
chip.disconnected | alerta operador: chip caiu terminal |
chip.logged_out | alerta CRITICO: precisa re-parear |
chip.banned | alerta CRITICO: troca de chip imediato |
chip.qr_loop | lembrete: alguem esqueceu QR |
chip.bad_session_streak | atencao: chip pode cair em breve |
chip.silent | investigar: chip parece fantasma |
Como sua app deve listar eventos que escuta
Hoje hub manda todos os eventos pra app que tem can_receive=true no chip. Filtro por tipo de evento por app esta em backlog (P3). Ate la, ignore os que nao usa.
Versionamento de eventos
Adicao de campo nao-breaking entra direto. Renomear/remover campo virara versao nova do evento (message.received.v2) ou novo evento. Hoje todos os eventos sao v1 implicito.