Webhooks — eventos

Webhooks — eventos

Catálogo de eventos, envelope de entrega, verificação de assinatura HMAC e política de retry.

Envelope de entrega

Toda entrega é um POST application/json com este envelope:

{
  "id": "evt_...",
  "type": "message.delivered",
  "api_version": "2026-06-01",
  "created_at": "2026-06-22T14:05:00.000Z",
  "account_id": "1029384756",
  "data": { }
}
CampoTipoDescrição
idstringID único do evento (evt_…)
typestringTipo do evento (catálogo abaixo)
api_versionstringFixo: "2026-06-01"
created_atISO 8601Timestamp do evento
account_idstring | nullWABA ID que originou o evento
dataobjectPayload específico do tipo

Catálogo de eventos + payloads

Mensagens

EventoGatilho
message.sentMensagem outbound postada na Meta
message.deliveredEntregue no WhatsApp do destinatário
message.readDestinatário leu a mensagem
message.failedFalha no envio outbound
message.receivedMensagem inbound de um contato
message.echoedMensagem enviada pelo app WhatsApp Business (pass-through)
// message.sent | message.delivered | message.read
{ "message_id": "...", "to": "...", "status": "sent|delivered|read", "pricing": { } }

// message.failed
{ "message_id": "...", "to": "...", "status": "failed", "errors": [ ] }

// message.received
{ "message_id": "...", "from": "...", "contact_name": "...", "type": "text",
  "text": "...", "media": { "id": "...", "mime_type": "...", "caption": "..." } }

// message.echoed
{ "message_id": "...", "from": "...", "to": "...", "type": "text",
  "text": "...", "media": { "id": "...", "mime_type": "...", "caption": "..." } }

Templates

EventoGatilho
template.status_updatedAprovação/rejeição mudou
template.quality_updatedQuality score mudou
// template.status_updated
{ "template_name": "...", "language": "...", "event": "APPROVED", "reason": null }

// template.quality_updated
{ "template_name": "...", "language": "...", "previous_quality_score": "...", "new_quality_score": "..." }

Número de telefone

EventoGatilho
phone_number.quality_updatedQuality/limite do número mudou
phone_number.name_updatedDecisão sobre nome verificado
// phone_number.quality_updated
{ "display_phone_number": "...", "event": "...", "current_limit": "..." }

// phone_number.name_updated
{ "display_phone_number": "...", "decision": "...", "requested_verified_name": "...", "rejection_reason": "..." }

Conta / capacidade

EventoGatilho
account.updatedReview status / verificação / restrições mudaram
account.alertAlerta emitido na conta
business_capability.updatedLimites de capacidade mudaram
// account.updated
{ "event": "...", "account_review_status": "...", "business_verification_status": "...", "restriction_info": [ ] }

// account.alert
{ "entity_type": "...", "entity_id": "...", "alert_severity": "...", "alert_status": "...", "alert_type": "...", "alert_description": "..." }

// business_capability.updated
{ "max_daily_conversation_per_phone": "...", "max_phone_numbers_per_business": "...", "max_phone_numbers_per_waba": "..." }

Contato / usuário

EventoGatilho
contact.syncedContato adicionado/editado/removido
user.preferences_updatedPreferências do usuário (ex.: consentimento de marketing)
// contact.synced
{ "action": "add|remove", "name": "...", "phone": "..." }

// user.preferences_updated
{ "wa_id": "...", "category": "...", "value": "stop|resume", "detail": "..." }

O evento especial endpoint.test é enviado apenas por POST /v1/webhooks/\{id\}/test. Use-o para validar sua implementação de verificação de assinatura.

Assinatura (verificação)

Segue o padrão Standard Webhooks. Cada entrega traz 3 headers:

HeaderConteúdo
webhook-idID do evento (ex.: evt_abc123)
webhook-timestampUnix em segundos (anti-replay)
webhook-signaturev1,<assinatura_base64>

Segredo: whsec_ + 24 bytes aleatórios em base64.

Algoritmo de assinatura

  1. secretBytes = base64decode(secret.slice(7)) (remove o prefixo whsec_).
  2. signedContent = "{webhook-id}.{webhook-timestamp}.{raw_body}".
  3. signature = base64(HMAC_SHA256(secretBytes, signedContent)).
  4. Header = v1,{signature}.

Para verificar (no seu servidor)

  1. Extraia os 3 headers.
  2. Confirme que webhook-timestamp está dentro de ~5 min do agora (anti-replay).
  3. Recomponha signedContent com o corpo bruto recebido (bytes exatos, antes de qualquer JSON.parse).
  4. Recalcule o HMAC e compare (constante) com a parte após v1,.
  5. Aceite se bater; rejeite se não.

Use sempre o body bruto (string/bytes recebidos pelo HTTP server) na recomposição. Aplicar JSON.stringify no body parseado muda espaços/chaves e quebra a assinatura.

Exemplo (Node.js)

# Não há exemplo cURL para verificação — rode dentro do seu app.

Política de retry / entrega

ItemValor
MétodoPOST application/json
Timeout10s por tentativa
Redirectsmanual — 3xx conta como falha
SucessoHTTP 2xx (>=200 && <300)
Máx. tentativas8 (1 inicial + 7 retries)

Cronograma de retry após falha: 5s → 5min → 30min → 2h → 5h → 10h → 14h → DEAD (≈ 36h no total).

Estados de delivery: PENDINGDELIVERINGSUCCESS | FAILED (re-tentará) | DEAD (terminal).

Auto-disable: após 15 falhas consecutivas o endpoint vai para DISABLED (notificação no painel, link /developers/keys). PAUSED adia entregas com back-off de 60s; reativar zera o contador.

Campos de uma delivery (via API): id, event_type, status, attempts (0–8), last_response_code, last_error, delivered_at, created_at.

Pular para o conteúdo