Pular para o conteúdo
Páginas da documentação

Catálogo de eventos

Todos os eventos que o hub envia ao seu webhook, com o formato de cada um.

Abrir o Markdown

O campo type do envelope assume um destes valores. Os connection.* e whatsapp.* são emitidos; os de lead ainda não.

type Quando Situação
connection.created Cliente concluiu a conexão do canal emitido
connection.updated Dados ou estado da conexão mudaram emitido
connection.disconnected Conexão encerrada ou revogada emitido
connection.token_expiring Credencial da conexão perto de expirar emitido
whatsapp.message.received Mensagem recebida emitido
whatsapp.message.status Mudança de estado de um envio no hub, ou status de entrega da Meta emitido
whatsapp.message.echo Mensagem enviada pelo app do WhatsApp Business (Coexistence) emitido
whatsapp.history.received Bloco do histórico sincronizado (Coexistence) emitido
whatsapp.contacts.synced Contatos do app WhatsApp Business sincronizados (Coexistence) emitido
whatsapp.identity.updated O contato trocou de número ou de BSUID emitido
whatsapp.media.updated Download de uma mídia recebida terminou (ou falhou) emitido
whatsapp.template.status_updated Status de aprovação de template mudou emitido
whatsapp.template.updated Categoria, qualidade ou componentes de template mudaram emitido
whatsapp.usage.free_tier O nível gratuito de mensagens de serviço do número chegou a 800 ou acabou emitido
leadgen.lead.created Novo lead de formulário do Lead Ads planejado

Trate type desconhecido como "ignorar e responder 2xx", para novos eventos não gerarem retries no seu receptor. Vale o mesmo para campo novo dentro de data e para message.type desconhecido.

Eventos de conexão

Em todos, connectionId do envelope é o id da conexão e data é a conexão no mesmo formato de GET /v1/connections/:connectionId (sem o envoltório connection), no estado logo após a mudança. Use createdAt do envelope para ordenar, já que a ordem de chegada não é garantida.

type Origem meta
connection.created Cliente concluiu o fluxo; status ainda pending null
connection.updated Ativação concluída (pending para active), falha de ativação (error), reconexão, token vencido (error com token_expired), aviso da Meta ou mudança do health (meta.health: "changed") ver abaixo
connection.disconnected DELETE /v1/connections/:id, ou a Meta removeu o acesso { "reason": ..., "accountEvent": ... }
connection.token_expiring A credencial vence em até 7 dias; um aviso por conexão null

connection.disconnected. meta.reason diz quem encerrou: api (seu DELETE), meta_uninstalled (o cliente removeu o parceiro no WhatsApp Manager) ou meta_account_deleted (a conta do WhatsApp foi apagada). Nos dois últimos, meta.accountEvent traz o nome do evento da Meta (PARTNER_APP_UNINSTALLED, ACCOUNT_DELETED); no primeiro o campo não existe.

Eventos originados por avisos da Meta. Quando um connection.updated nasce de um aviso de conta da Meta, o envelope traz meta: { "accountEvent": "<nome>" }:

meta.accountEvent O que mudou na conexão
ACCOUNT_OFFBOARDED active passa para error com errorCode: "offboarded"
ACCOUNT_RECONNECTED error com offboarded volta para active
DISABLED_UPDATE, ACCOUNT_RESTRICTION, ACCOUNT_VIOLATION Nada muda no estado: é um aviso de que a conta sofreu restrição ou violação. Sai um connection.updated para cada conexão active, pending ou error da conta (WABA), e sai de novo se a Meta reentregar o mesmo aviso: deduplique pelo seu lado se a reação não puder repetir
MM_LITE_TERMS_SIGNED Os termos do Marketing Messages API foram aceitos; emitido uma vez por conta

Os demais avisos de conta da Meta não geram evento. Se o seu receptor precisa reagir a uma restrição, trate connection.updated com meta.accountEvent em DISABLED_UPDATE, ACCOUNT_RESTRICTION ou ACCOUNT_VIOLATION.

connection.token_expiring. Chega uma vez, até 7 dias antes de tokenExpiresAt. Se nada for feito, no vencimento a conexão vai para error com token_expired (connection.updated) e o cliente precisa repetir o fluxo de conexão com uma sessão nova.

Eventos do WhatsApp

Em todos, connectionId do envelope é a conexão do número, exceto nos eventos de template, que são da conta (WABA) e vêm com connectionId: null. Datas em ISO 8601. meta, quando preenchido, traz o item original da Meta, só para diagnóstico: o contrato é o data.

O hub não entrega o que chegou antes da conexão atual. Mensagem, status ou aviso com data anterior à conexão do número ao workspace (ou à entrada da conta no workspace) é descartado: é dado do dono anterior. Isso vale também para retries da Meta depois de desconectar e reconectar em outro workspace.

whatsapp.message.received

{
  "phoneNumberId": "109876543210987",
  "message": {
    "wamid": "wamid.HBgM...",
    "type": "text",
    "timestamp": "2026-10-23T15:00:00.000Z",
    "from": { "userId": "BR.1349...", "parentUserId": null, "waId": "5543999990000", "username": null, "name": "Maria" },
    "groupId": null,
    "context": null,
    "referral": null,
    "text": { "body": "Oi" }
  },
  "window": { "expiresAt": "2026-10-24T15:00:00.000Z" }
}
  • meta: { message: <item original>, contact: <contacts[] do remetente> | null }.
  • O conteúdo fica num campo com o nome do type: text, media (para image, audio, video, document, sticker: { id, status, mimeType, sha256, caption, filename, voice, animated }, com id do hub), location, contacts, interactive (button_reply, list_reply, nfm_reply com flow.response já como objeto, product), button (resposta de botão de template), reaction (emoji: null = reação removida), order, revoke, edit ({ wamid, message }, com a mensagem nova no mesmo formato; edit e revoke só chegam de número Coexistence, porque a Meta só oferece esses webhooks para o app WhatsApp Business, e o contato só vê "Editar" e "Apagar para todos" nessas conversas) e unsupported ({ type, errors: [{ code, title }] }).
  • interactive.product.priceAmount/salePriceAmount vêm como a Meta manda: o valor multiplicado por 1 000. order.productItems[].itemPrice vem como número decimal.
  • type fora da lista conhecida mantém o nome e ganha unsupported; ignore o que não reconhecer e responda 2xx.
  • context: { wamid, from, forwarded, frequentlyForwarded, referredProduct } quando a mensagem responde ou cita outra; referral em mensagem vinda de anúncio (com ctwaClid).
  • groupId vem preenchido em mensagem de grupo; ela não abre janela (window: null).
  • Deduplicação: o hub emite uma vez por wamid, mas a entrega é "pelo menos uma vez"; deduplique pelo id do envelope.

whatsapp.message.status

Dois tipos de origem, pelo data.source:

source: "hub" (transição do envio no hub: accepted, failed, uncertain, abandoned):

{
  "messageId": "...",
  "wamid": "wamid.HBgM...",
  "source": "hub",
  "status": "accepted",
  "timestamp": "2026-10-23T15:00:00.000Z",
  "pacing": null,
  "error": null
}

error é { code, metaCode } em failed; em uncertain vem null, e o código da Meta, quando houver, aparece só em metaErrorCode de GET …/messages/:messageId. meta traz { reconciled: true } (e overridesAdminResolution: true) quando o accepted veio da conciliação, e { resolvedByAdmin: true } no abandoned. O accepted com overridesAdminResolution vence o abandoned do mesmo messageId em qualquer ordem.

source: "meta" (status de entrega do webhook da Meta: sent, delivered, read, played, failed):

{
  "messageId": "...",
  "wamid": "wamid.HBgM...",
  "source": "meta",
  "status": "delivered",
  "timestamp": "2026-10-23T15:00:03.000Z",
  "recipient": { "userId": "BR.1349...", "parentUserId": null, "waId": "5543999990000" },
  "errors": [],
  "pricing": { "billable": true, "model": "PMP", "type": "regular", "category": "utility", "subtype": null },
  "conversation": null
}
  • Correlação: use messageId (id do envio no hub). Ele vem null quando o status chegou antes da resposta da Meta ao envio, ou é de mensagem enviada fora do hub; nesse caso correlacione pelo wamid, que chega no accepted (source: "hub"). O status com messageId: null não é reemitido quando o hub liga a mensagem depois; o deliveryState do envio já reflete o status.
  • pricing vem só em alguns status (sent e um de delivered/read), com category como a Meta manda (marketing_lite, authentication-international…). conversation quase sempre null: desde a Graph v24.0 a Meta só o manda em mensagem enviada dentro de uma janela de ponto de entrada gratuito, e expiresAt só vem no sent. Reação enviada pelo hub chegou no teste manual só com sent e sem pricing. Ver Custos.
  • errors: [{ code, title, details }] em failed. 131049 (esperar), 132015 (template pausado) e 131050 (contato não quer marketing) são só repassados: a decisão é do seu app.
  • Cada status (por mensagem e por valor de status) sai uma vez. A Meta pode pular delivered quando entrega e leitura chegam juntas.

whatsapp.message.echo

Mensagem que o próprio negócio enviou pelo app WhatsApp Business (Coexistence): { phoneNumberId, message }, com message no formato do recebido e message.from sendo o negócio, mais message.to (identidade do contato). meta: { message, contact }. Eco não abre janela.

whatsapp.history.received

Histórico de até 180 dias do app WhatsApp Business (Coexistence), em blocos de até 100 mensagens de uma conversa:

{
  "phoneNumberId": "109876543210987",
  "phase": 0,
  "chunkOrder": 3,
  "progress": 40,
  "thread": { "userId": "BR.1349...", "parentUserId": null, "waId": "5543999990000", "username": null },
  "messages": []
}
  • Cada mensagem tem o formato do recebido mais direction (inbound ou outbound) e historyStatus (status da Meta em maiúsculas). Mídia antiga vem como type media_placeholder.
  • Os blocos chegam fora de ordem; ordene pelo timestamp das mensagens. progress 100 marca o fim (também visível em sync[].progress da conexão).
  • A mídia dos últimos 14 dias chega depois, num evento próprio por mensagem, com phase, chunkOrder e progress null, e a mídia baixada como em qualquer mensagem recebida.
  • Histórico não abre janela.

whatsapp.contacts.synced

Contatos do app WhatsApp Business (Coexistence): { phoneNumberId, contacts: [{ action, userId, parentUserId, username, phone, fullName, firstName, timestamp }] }, com action add ou remove. O hub não guarda contatos.

whatsapp.identity.updated

{
  "kind": "user_changed_user_id",
  "previous": { "userId": "BR.1349...", "parentUserId": null },
  "current": { "userId": "BR.8812...", "parentUserId": null, "waId": "5543999990000" },
  "timestamp": "2026-10-23T15:00:00.000Z"
}

kind é user_changed_number (trocou de telefone) ou user_changed_user_id (trocou de BSUID).

whatsapp.media.updated

{ id, status, mimeType, size, sha256, wamid, errorCode }, com status stored (pronta: GET /v1/media/:id), failed ou expired. Pode chegar antes do whatsapp.message.received da mesma mensagem.

whatsapp.template.status_updated

{ template, event, reason, otherInfo: { title, description } | null, disableDate }, com template no formato da API de templates e event como a Meta manda (APPROVED, REJECTED, PAUSED, DISABLED, PENDING_DELETION, FLAGGED, REINSTATED…). Um webhook mais velho que o status gravado não gera evento.

whatsapp.template.updated

{ template, change }, com change category_pending (a Meta avisou que vai trocar a categoria: template.correctCategory e pendingCategoryAt), category (troca concluída), quality (template.qualityScore) ou components (o template foi editado; template.components já traz a versão nova). Aviso de qualidade ou de categoria mais velho que o último aplicado (a Meta reentrega fora de ordem) não muda o template nem gera evento.

whatsapp.usage.free_tier

Nível gratuito de mensagens de serviço do número: { phoneNumberId, month, timezone, level, limit, free, charged }, com level warning (800 gratuitas no mês) ou exhausted (primeira cobrada). Um por nível, número e mês. Detalhes em Custos.