PEPINO ZAPdocs
Webhooks

Eventos disponíveis

Catálogo dos eventos de webhook do PEPINO ZAP, com o envelope comum e o payload de cada um, campo a campo.

Ver como Markdown

O PEPINO ZAP envia os eventos abaixo por webhook. Esta página é o catálogo de eventos: descreve o envelope comum a todos eles e, para cada evento, o objeto data exato que você recebe — campo a campo, com exemplos reais de payload.

Você cadastra os webhooks via API (POST /v1/webhooks), de forma self-service e escopada ao seu Projeto — veja Webhooks. Para validar que a entrega realmente veio do PEPINO ZAP, consulte Assinatura. Para o comportamento de timeout, reenvio e idempotência, consulte Entrega e retry. Os mesmos eventos e o mesmo envelope também chegam por WebSocket, se você preferir um transporte em tempo real sem expor um endpoint público.

Webhook ≠ histórico. Estes eventos são o push ao vivo — o que acontece no momento. Para ler o histórico persistido (listar conversas, paginar mensagens, baixar a mídia já hospedada por nós), use os endpoints do Inbox com a mesma API keyGET /v1/inbox/conversations e .../conversations/{id}/messages. O PEPINO ZAP é o system of record: você não precisa armazenar as mensagens que chegam aqui por conta própria. Ver Persistência e histórico.

EventoQuando dispara
message.receivedUma mensagem com conteúdo chegou em uma instância.
message.statusMudou o status de entrega de uma mensagem enviada pela instância (entregue/lida).
instance.statusA conexão da instância com o WhatsApp mudou de estado.
call.receivedA instância recebeu uma chamada (voz ou vídeo).
message.deletedUm contato apagou (revogou) uma mensagem em uma conversa com a instância.
group.participantsAlguém entrou, saiu, foi promovido ou rebaixado em um grupo.
group.updatedO nome ou o tópico (descrição) de um grupo mudou.
contact.updatedO nome de exibição (push name) de um contato mudou.
label.updatedUma etiqueta (WhatsApp Business) foi criada, editada ou apagada.
label.associationUma etiqueta foi aplicada ou removida de uma conversa.
campaign.statusMudou o status de um destinatário de campanha (enviado/entregue/lido/respondeu/falhou/inválido).

Um webhook só recebe os eventos que assina. Ao criar o webhook (POST /v1/webhooks), o campo events define a lista — e uma lista vazia significa "todos os eventos". Para receber só mensagens recebidas, por exemplo, crie o webhook com "events": ["message.received"]. Os nomes válidos são exatamente os da tabela acima; qualquer outro valor é recusado com 400. Ver Webhooks.

Envelope comum

Toda entrega usa o método POST e sempre o mesmo envelope. Apenas o conteúdo de data muda de um evento para outro — o que permite escrever um único handler que primeiro lê event e só então interpreta data.

{
  "event": "message.received",
  "instanceId": "cm5x8a9k20001abc123def456",
  "timestamp": "2026-05-29T22:16:30Z",
  "data": {}
}

Campos do envelope:

CampoTipoSignificado
eventstringNome do evento — um dos onze da tabela acima (message.received, message.status, instance.status, call.received, message.deleted, group.participants, group.updated, contact.updated, label.updated, label.association, campaign.status). Faça o switch por aqui — não tente adivinhar o tipo pelos campos de data.
instanceIdstringIdentificador da instância que originou o evento (formato cm...). É o mesmo id retornado ao criar a instância. Em um Projeto com vários números, use-o para saber de qual instância veio o evento.
timestampstringData e hora do evento em ISO 8601 / RFC 3339, em UTC (sufixo Z). Para message.received e message.status, é o horário do evento no WhatsApp; para instance.status, o horário da transição de conexão.
dataobjetoOs dados específicos do evento. O formato depende de event — detalhado nas seções abaixo.

Headers presentes em toda entrega:

Content-Type: application/json
User-Agent: pepino-zap-webhooks/1
X-Pepino-Event: message.received
X-Pepino-Webhook-Id: <id-do-webhook>
X-Pepino-Signature: sha256=<hmac-hex>
  • X-Pepino-Event — repete o event do corpo. Útil para rotear a entrega antes de fazer parse do JSON.
  • X-Pepino-Webhook-Id — identifica qual webhook cadastrado originou a entrega. É constante por webhook (o mesmo em todas as entregas), então não é uma chave de deduplicação; para deduplicar, use um identificador de negócio em data (como waMessageId). Ver Idempotência.
  • X-Pepino-Signature — assinatura HMAC-SHA256 do corpo bruto, no formato sha256=<hmac-hex>. Permite verificar a autenticidade do que foi recebido. O cálculo está descrito em Assinatura.

Calcule e valide a assinatura sobre os bytes crus do corpo, antes de qualquer JSON.parse. Re-serializar o objeto depois do parse muda espaçamento e ordem de chaves, e a assinatura deixa de bater. Ver Assinatura.

message.received

Disparado quando a instância recebe uma mensagem com conteúdo.

{
  "event": "message.received",
  "instanceId": "cm5x8a9k20001abc123def456",
  "timestamp": "2026-05-29T22:16:30Z",
  "data": {
    "from": "5511999999999@s.whatsapp.net",
    "type": "image",
    "text": "Mensagem de teste",
    "waMessageId": "3EB0C767D26A1D8E2B5F",
    "mediaUrl": "https://...&X-Amz-Expires=86400",
    "mimeType": "image/jpeg"
  }
}

Campos de data:

CampoTipoSempre presente?Significado
fromstringSimRemetente da mensagem (ou o grupo de origem). Pode vir em mais de um formato — ver abaixo.
typestringSimTipo do conteúdo: text, image, video, audio, document, sticker, location, contact, poll ou unknown.
textstringSim (pode ser "")Conteúdo textual. Em text, é o corpo. Em image/video/document, é a legenda (vazio se não houver). Em location/contact/poll, é o nome/título do item (nome do local, nome do contato, pergunta da enquete). Em audio/sticker, vem vazio.
waMessageIdstringSimIdentificador da mensagem no WhatsApp. Use-o para correlacionar com replies e para deduplicar mensagens repetidas.
mediaUrlstringSó em mídia baixadaURL pré-assinada para baixar o arquivo. Presente apenas quando o tipo é de mídia (image/video/audio/document/sticker) e o download foi concluído. Ver o Callout abaixo.
mimeTypestringSó junto de mediaUrlTipo MIME do arquivo (ex. image/jpeg, audio/ogg, application/pdf). Acompanha sempre o mediaUrl.

O objeto data traz apenas estes campos. Não há pushName, senderPhone, conversationId nem dados da mensagem citada no payload do webhook — não dependa de campos que não estão listados aqui.

O campo type em detalhe

O type indica como interpretar o restante do payload:

typeO que éO que esperar em textmediaUrl?
textMensagem de texto (inclui texto com link/citação)O corpo da mensagemNão
imageImagemA legenda (se houver)Sim, quando baixada
videoVídeoA legenda (se houver)Sim, quando baixada
audioÁudio ou nota de vozVazioSim, quando baixada
documentDocumento/arquivoA legenda (se houver)Sim, quando baixada
stickerFigurinhaVazioSim, quando baixada
locationLocalização (pin ou ao vivo)O nome do local (se houver)Não
contactContato compartilhado (vCard)O nome exibido do contatoNão
pollEnqueteA pergunta da enqueteNão
unknownConteúdo recebido que não se encaixa nos tipos acima, mas ainda traz textoPode ter textoNão

Trate type como uma lista aberta: faça um switch nos valores que você conhece e tenha um caso default para o resto. Assim, qualquer tipo novo (ou unknown) é tratado de forma graciosa, sem quebrar o handler.

O campo from: dois (e às vezes três) formatos

O from identifica a origem e não é sempre um telefone. Ele pode vir em três formatos, distinguíveis pelo sufixo após o @:

  • Número com sufixo @s.whatsapp.net — por exemplo 5511999999999@s.whatsapp.net. É o caso mais comum: o trecho antes do @ é o telefone em E.164 (sem o +).
  • LID com sufixo @lid — por exemplo 123456789@lid. Ocorre quando o contato usa o recurso de privacidade do WhatsApp e o número real ainda não foi resolvido. Aqui o valor antes do @lid é um identificador interno, não um telefone.
  • Grupo com sufixo @g.us — por exemplo 120363012345678901@g.us. Aparece quando a mensagem foi recebida em um grupo. O valor antes do @ identifica o grupo, não uma pessoa.

Ao processar from, não assuma que o trecho antes do @ é sempre um telefone. Use o sufixo para decidir: @s.whatsapp.net = número, @lid = identificador de privacidade, @g.us = grupo. Tentar discar/normalizar o trecho de um @lid ou @g.us como telefone leva a dados incorretos.

Mensagens recebidas em grupos geram message.received por padrão (com from terminando em @g.us). Se você só atende conversas 1:1, ligue a configuração ignoreGroups da instância — com ela ativa, mensagens de grupo não geram webhook. Ver Configurações da instância.

Mídia: como baixar o arquivo

Em mensagens de mídia, o binário não vem no payload — vem uma URL pré-assinada em mediaUrl, com validade de 24 horas.

"data": {
  "from": "5511999999999@s.whatsapp.net",
  "type": "audio",
  "text": "",
  "waMessageId": "3EB0C767D26A1D8E2B5F",
  "mediaUrl": "https://....s3.amazonaws.com/media/...&X-Amz-Expires=86400",
  "mimeType": "audio/ogg"
}

Baixe o arquivo diretamente pela URL, com um GET simples e sem credenciais adicionais (a autorização já está embutida na própria URL):

curl -L -o arquivo "$MEDIA_URL"

mediaUrl só é incluído quando o download da mídia foi concluído pela plataforma. Se o download não estava pronto no momento do evento, o payload vem sem mediaUrl/mimeType, apenas com os metadados (from, type, text, waMessageId). Trate a ausência desses campos com naturalidade — não é um erro. Como a URL expira em 24h, baixe e armazene o arquivo no seu lado se precisar dele por mais tempo; não guarde a URL para usar depois.

O que não gera message.received

Apenas mensagens com conteúdo exibível geram o evento. Não são entregues:

  • Ecos das próprias mensagens que a instância envia (mensagens "de mim").
  • Mensagens de controle do WhatsApp: cabeçalho de álbum (vem junto das fotos), reações, edições, exclusões, distribuição de chave de sessão e mensagens de protocolo.
  • Status (status@broadcast), listas de transmissão e canais (newsletters) — não são conversas e não viram evento.
  • Grupos, quando a configuração ignoreGroups da instância está ligada.

message.status

Disparado quando muda o status de entrega de uma mensagem que a instância enviou. Como o envio é assíncrono (a API responde 202 apenas enfileirando a mensagem), este evento é a sua confirmação de entrega e de leitura.

{
  "event": "message.status",
  "instanceId": "cm5x8a9k20001abc123def456",
  "timestamp": "2026-05-29T22:17:00Z",
  "data": {
    "status": "READ",
    "to": "5511999999999@s.whatsapp.net",
    "waMessageIds": ["3EB0C767D26A1D8E2B5F", "3EB1A2B3C4D5E6F70819"]
  }
}

Campos de data:

CampoTipoSignificado
statusstringDELIVERED (entregue no aparelho do destinatário) ou READ (lida — ou, no caso de áudio, ouvida).
tostringDestinatário das mensagens, no mesmo formato de JID do from (geralmente ...@s.whatsapp.net; pode ser ...@g.us para grupos).
waMessageIdsarray de stringLista dos waMessageId afetados por este status. Um único receipt pode cobrir várias mensagens de uma vez — por isso é sempre um array.

Para correlacionar com o que você enviou, guarde o waMessageId retornado no envio e cruze-o com os waMessageIds recebidos aqui.

O fluxo normal é DELIVERED seguido de READ, mas nem toda mensagem recebe os dois (e nem necessariamente nessa ordem): se o destinatário nunca abre a conversa, você verá só DELIVERED; com retries, o mesmo status pode chegar repetido. Trate cada message.status como uma atualização idempotente do estado da mensagem, e não como um passo obrigatório de uma sequência. Não há um status de "falhou" neste evento.

instance.status

Disparado quando muda o status de conexão da instância com o WhatsApp.

{
  "event": "instance.status",
  "instanceId": "cm5x8a9k20001abc123def456",
  "timestamp": "2026-05-29T22:18:00Z",
  "data": {
    "status": "DISCONNECTED",
    "jid": "",
    "reason": ""
  }
}

Campos de data:

CampoTipoSignificado
statusstringA transição de conexão. Assume um dos valores da tabela abaixo.
jidstringIdentificador WhatsApp do número conectado (ex. 5511999999999@s.whatsapp.net). Preenchido quando há um número associado à transição (notadamente em CONNECTED); vem "" caso contrário.
reasonstringInformação adicional sobre a transição, quando houver (por exemplo, o motivo de um LOGGED_OUT ou o código de um BANNED). Vem "" quando não há detalhe.

Valores possíveis de status:

statusSignificadoO que fazer
CONNECTEDA instância conectou e está operacional. O campo jid traz o identificador WhatsApp do número.Pode enviar mensagens.
DISCONNECTEDA conexão caiu.Nada — a instância reconecta automaticamente.
LOGGED_OUTA sessão foi encerrada no aparelho (o usuário removeu o aparelho conectado).É preciso parear de novo via QR Code.
STREAM_REPLACEDO número foi aberto em outra sessão; esta conexão foi substituída.Verifique se o número não está conectado em outro lugar.
BANNEDO número foi bloqueado pela plataforma do WhatsApp (temporário ou permanente).Estado terminal — não reconecta; o envio passa a responder 409. O reason traz o código do bloqueio. Ver o detalhe abaixo.

Este evento cobre apenas as transições acima. O ciclo de pareamento — os estados CONNECTING e QR_PENDING, e o sucesso de leitura do QR — não é enviado por instance.status. Acompanhe o pareamento pelo stream de QR (ou pelo polling) e consulte o estado atual a qualquer momento com GET /v1/instances/{id}.

Uma vez pareada, a instância reconecta sozinha após quedas, reinícios e deploys, sem novo QR. Por isso, um DISCONNECTED é normalmente transitório: não acione um novo pareamento por causa dele. Só LOGGED_OUT exige escanear o QR de novo. Você pode confiar no status: se a sessão morre — por exemplo, o número é desvinculado no aparelho enquanto a instância está fora do ar e o LOGGED_OUT não chega na hora — a plataforma reconcilia o estado e emite um instance.status com DISCONNECTED, em geral em um a dois minutos. Ou seja, o status não fica preso indefinidamente em um CONNECTED fantasma. Se, mesmo assim, uma instância permanecer em DISCONNECTED sem voltar para CONNECTED por alguns minutos (confirme com GET /v1/instances/{id}), trate como sessão encerrada e gere um novo QR Code. Para o ciclo de vida completo, veja Sua primeira instância.

Sobre o BANNED. Ele cobre dois casos que o WhatsApp trata como bloqueio: um bloqueio temporário (anti-spam — por exemplo, mensagens demais para quem não tem você na agenda, ou gente demais te bloqueando) e um banimento permanente da conta. Nos dois o status vai para BANNED, o envio passa a responder 409 e a instância não reconecta; o reason traz o código do bloqueio quando o WhatsApp o informa. Há ainda um caso sem evento: um número "sombreado" (soft-block) pode, em vez de emitir BANNED, simplesmente nunca concluir o pareamento — o QR fica sendo regerado sem chegar a CONNECTED. O protocolo multi-device do WhatsApp não dá um sinal determinístico de banimento aí; por isso, trate um número que não pareia depois de várias tentativas como provavelmente bloqueado, em vez de insistir no mesmo número.

call.received

Disparado quando a instância recebe uma chamada (voz ou vídeo) de um contato — no mesmo instante em que o telefone "tocaria". Útil para registrar tentativas de ligação, alertar um atendente, ou casar com a configuração de rejeitar chamadas: mesmo com a rejeição ligada, o call.received é emitido — a chamada chegou, e só depois foi rejeitada.

{
  "event": "call.received",
  "instanceId": "inst_123",
  "timestamp": "2026-06-06T12:00:00Z",
  "data": {
    "from": "5511999999999@s.whatsapp.net",
    "callId": "AB12CD34EF56"
  }
}
CampoTipoDescrição
fromstringQuem ligou, no mesmo formato JID do from de message.received (a parte antes do @ é o telefone em E.164).
callIdstringIdentificador da chamada no WhatsApp. Útil para correlacionar os eventos da mesma ligação e para deduplicar.

Este evento sinaliza apenas que chegou uma chamada — não há eventos de "atendida", "encerrada" ou duração. Se você quer que o número nunca receba chamadas, ligue rejectCalls (e opcionalmente rejectCallMessage) nas configurações da instância.

message.deleted

Disparado quando um contato apaga para todos (revoga) uma mensagem em uma conversa com a instância — o "Apagar mensagem → Apagar para todos" do WhatsApp. Serve para manter o seu CRM/registro fiel: ao receber este evento, marque como apagada (ou esconda) a mensagem correspondente no seu sistema.

{
  "event": "message.deleted",
  "instanceId": "inst_123",
  "timestamp": "2026-06-06T12:00:00Z",
  "data": {
    "from": "5511999999999@s.whatsapp.net",
    "waMessageId": "3EB0C767D26A1D8E2B5F"
  }
}
CampoTipoDescrição
fromstringA conversa onde a mensagem foi apagada (JID do contato ou do grupo), no mesmo formato do from de message.received.
waMessageIdstringO waMessageId da mensagem apagada — cruze com o waMessageId recebido em message.received para localizar qual mensagem revogar no seu lado.

Refere-se a apagar para todos (revogação), que é o que se propaga pelo WhatsApp — "Apagar para mim" é local no aparelho de quem apagou e não gera evento. Respeita o ignoreGroups: se você ignora grupos, uma revogação em grupo também não dispara.

group.participants

Disparado quando a composição de um grupo muda: alguém entrou, saiu (ou foi removido), foi promovido a admin ou rebaixado. Só vale para grupos dos quais a instância participa.

{
  "event": "group.participants",
  "instanceId": "inst_123",
  "timestamp": "2026-06-06T12:00:00Z",
  "data": {
    "groupJid": "120363012345678901@g.us",
    "by": "5511999999999@s.whatsapp.net",
    "joined": ["5511888888888@s.whatsapp.net"],
    "left": [],
    "promoted": [],
    "demoted": []
  }
}
CampoTipoDescrição
groupJidstringO grupo (...@g.us).
bystringQuem fez a mudança ("" se o WhatsApp não informou).
joined / left / promoted / demotedarray de stringOs participantes afetados (JIDs). Em geral só um desses arrays vem preenchido por evento.

group.updated

Disparado quando os metadados de um grupo mudam — o nome ou o tópico (descrição).

{
  "event": "group.updated",
  "instanceId": "inst_123",
  "timestamp": "2026-06-06T12:00:00Z",
  "data": {
    "groupJid": "120363012345678901@g.us",
    "by": "5511999999999@s.whatsapp.net",
    "name": "Novo nome do grupo"
  }
}
CampoTipoDescrição
groupJidstringO grupo.
bystringQuem fez a mudança.
namestringPresente se o nome mudou — o novo nome.
topicstringPresente se o tópico/descrição mudou — o novo texto.

contact.updated

Disparado quando o nome de exibição (push name) de um contato muda — percebido ao receber uma mensagem dele com um nome diferente do que estava em cache.

{
  "event": "contact.updated",
  "instanceId": "inst_123",
  "timestamp": "2026-06-06T12:00:00Z",
  "data": {
    "from": "5511999999999@s.whatsapp.net",
    "oldName": "João",
    "newName": "João Silva"
  }
}
CampoTipoDescrição
fromstringO contato.
oldNamestringO nome anterior (em cache).
newNamestringO novo nome de exibição.

Refere-se ao push name que o próprio contato definiu — não à foto de perfil, e não ao nome que você salvou na sua agenda.

label.updated

Disparado quando uma etiqueta (recurso do WhatsApp Business) é criada, renomeada ou apagada.

{
  "event": "label.updated",
  "instanceId": "inst_123",
  "timestamp": "2026-06-06T12:00:00Z",
  "data": {
    "labelId": "5",
    "name": "Cliente VIP",
    "color": 3,
    "deleted": false
  }
}
CampoTipoDescrição
labelIdstringIdentificador da etiqueta.
namestringNome atual da etiqueta.
colornumberÍndice de cor da etiqueta no WhatsApp.
deletedbooleantrue se a etiqueta foi apagada.

label.association

Disparado quando uma etiqueta é aplicada ou removida de uma conversa.

{
  "event": "label.association",
  "instanceId": "inst_123",
  "timestamp": "2026-06-06T12:00:00Z",
  "data": {
    "labelId": "5",
    "chat": "5511999999999@s.whatsapp.net",
    "labeled": true
  }
}
CampoTipoDescrição
labelIdstringA etiqueta.
chatstringA conversa (contato ou grupo).
labeledbooleantrue = aplicada; false = removida.

Etiquetas (label.*) são um recurso do WhatsApp Business — só fazem sentido se o número conectado é uma conta Business que usa etiquetas. Os eventos do sync inicial são ignorados; você recebe só as mudanças em tempo real.

campaign.status

Disparado a cada mudança de status de um destinatário de uma campanha de disparo em massa. É o acompanhamento em tempo real do disparo, por destinatário — complementar ao agregado de GET /v1/campaigns/{id}.

{
  "event": "campaign.status",
  "instanceId": "cm5x8a9k20001abc123def456",
  "timestamp": "2026-06-18T14:32:00Z",
  "data": {
    "campaignId": "camp_3Hk9Zq2pVxL",
    "recipient": "5511999999999",
    "name": "Maria",
    "status": "delivered",
    "error": null
  }
}

Campos de data:

CampoTipoSignificado
campaignIdstringA campanha a que o destinatário pertence.
recipientstringO número (apenas dígitos) do destinatário.
namestringNome do destinatário (variável nome), se informado. Pode vir ausente.
statusstringEstado: sent, delivered, read, responded, failed ou invalid.
errorstring | nullMotivo, quando failed ou invalid (ex.: número não está no WhatsApp).

O ciclo típico de um destinatário é sent -> delivered -> read, e responded se o contato responder. invalid sai antes do envio (número sem WhatsApp, quando validateNumbers está ligado) e não consome o teto diário; failed é uma falha real de envio. Trate cada evento como uma atualização idempotente do estado daquele destinatário — nem todos os estados ocorrem para todos, e a ordem pode variar.

Erros e situações de borda

Estes eventos são entregues ao seu endpoint; eles não retornam códigos de erro para você. O que você precisa observar do lado do receptor:

  • Responda 2xx rápido. Qualquer resposta fora de 2xx, timeout (15s) ou erro de rede faz a entrega falhar e ser reenviada com backoff, até 6 tentativas. Ver Entrega e retry.
  • Deduplique. Por causa do retry, o mesmo evento pode chegar mais de uma vez. Use um identificador de negócio em datawaMessageId em message.received, waMessageIds em message.status — como chave de idempotência. O X-Pepino-Webhook-Id é constante por webhook e não serve para isso.
  • Valide a assinatura. Trate como inválida (e descarte) qualquer entrega cujo X-Pepino-Signature não bata com o HMAC do corpo bruto. Ver Assinatura.
  • Tolere campos ausentes e tipos novos. Em message.received, mediaUrl/mimeType podem não vir; text pode ser "". Em instance.status, jid/reason podem ser "". Trate type e status como listas abertas, com um caso default.

Os erros de cadastro de webhook (criar/listar/remover via POST /v1/webhooks) — como 400 para evento inválido ou URL recusada, 401 para key ausente/inválida — estão documentados em Webhooks.

Próximos passos

  • Webhooks — como cadastrar webhooks via API e gerenciar o secret.
  • Assinatura — como validar X-Pepino-Signature (com exemplos em Node, Python e Go).
  • Entrega e retry — timeout, critério de sucesso e política de reenvio.
  • WebSocket — receber os mesmos eventos em tempo real, sem endpoint público.

On this page