WhatsMax

Webhooks

O gateway avisa o seu sistema do que acontece: mensagem recebida, entrega, falha, conexão do número. Configure o endereço em Conta → Configurações → Avisos para o seu sistema.

Eventos

Evento Quando
message.received Um contato escreveu para o número
message.queued A mensagem entrou na fila
message.sent Saiu pelo WhatsApp
message.delivered Chegou ao aparelho
message.read Foi lida
message.failed Não chegou. Traz error (texto) e reason (código: failed, blocked, invalid_number, assinatura_suspensa ou resposta_vencida, quando a resposta do agente ou do painel esperou mais de 30 min para sair e foi descartada; no número oficial também fora_da_janela, template_invalido, pagamento_meta, limite_da_meta, meta_nao_entregou, midia_invalida, conta_bloqueada e credencial_meta)
message.echo Alguém da empresa respondeu pelo celular ou pelo WhatsApp Web (número QR, ou oficial em coexistência). Traz to, body, id e sent_at; se foi arquivo, media como no message.received, com a url de GET /v1/media/{instance}/{id}: baixe assim que o evento chegar
template.status Número oficial: um template foi aprovado, rejeitado, pausado… Traz name, language, status e reason
conversation.history Número oficial em coexistência: as conversas antigas do WhatsApp Business do celular, por contato (from), em lotes de até 100 messages com id, direction (in/out), body e sent_at. Grave sem abrir a janela de 24 h, sem marcar como não lida e sem acionar o agente; ignore id repetido
instance.connected O número conectou
instance.disconnected O número caiu, com o motivo
webhook.test Só quando você clica em Testar webhook no painel

Cabeçalhos

Cabeçalho O que é
X-Signature sha256= e o HMAC-SHA256 do corpo, com o token do projeto como segredo
Idempotency-Key Identifica a entrega. Ignore repetições
X-Event O nome do evento

Conferindo a assinatura

Calcule o HMAC sobre os bytes exatos do corpo, como chegaram. Reserializar o JSON muda espaços e ordem e a conta não bate.

$corpo = file_get_contents('php://input');
$esperada = 'sha256='.hash_hmac('sha256', $corpo, $tokenDoProjeto);

if (! hash_equals($esperada, $_SERVER['HTTP_X_SIGNATURE'] ?? '')) {
    http_response_code(401);
    exit;
}
import { createHmac, timingSafeEqual } from 'node:crypto';

function confere(corpoBruto, assinatura, token) {
  const esperada = 'sha256=' + createHmac('sha256', token).update(corpoBruto).digest('hex');
  const a = Buffer.from(esperada);
  const b = Buffer.from(assinatura ?? '');
  return a.length === b.length && timingSafeEqual(a, b);
}

Entrega e reenvio

Responda 2xx para confirmar. Qualquer outra resposta, ou falta de resposta em 15 segundos, é tentada de novo depois de 1 minuto, 5 minutos, 15 minutos, 1 hora e 6 horas. Cada tentativa fica registrada em Integração → Webhooks, onde também dá para reenviar uma entrega que desistiu. O evento webhook.test não é repetido.

Como a mesma entrega pode chegar mais de uma vez, use o Idempotency-Key para ignorar repetições.