Webhook
Receba confirmações de entrega e mensagens dos contatos no seu sistema.
Cadastre a URL do seu endpoint no painel, em Integrações, e escolha os eventos. Fazemos um POST assinado para cada evento selecionado.
Eventos disponíveis (link para esta seção)
| Evento | Dispara quando | Serve para |
|---|---|---|
| contact.marketing_opt_out | O contato pede para parar promoções. | Registrar a saída sem depender do rótulo do botão. |
| template.status | A Meta comunica aprovação, rejeição, pausa ou outro estado. | Atualizar o catálogo e suspender modelos indisponíveis. |
| template.quality | A Meta informa mudança de qualidade do modelo. | Acompanhar a nota, incluindo a primeira leitura. |
message.status | Uma mensagem enviada pela plataforma muda de status. | Confirmar entrega, leitura ou falha. |
message.received | Uma mensagem do contato é recebida. | Processar a resposta e atualizar a janela de atendimento. |
message.received; ele contém dados da conversa.Os novos eventos são opcionais. Habilite-os em Integrações quando seu receptor estiver preparado; não alteramos as assinaturas já existentes. Uma entrega HTTP 2xx confirma que o receptor aceitou o evento, não que concluiu a operação. Use o catálogo com status=ALL para reconciliar o estado atual dos modelos.
Testar pelo painel (link para esta seção)
Em Integrações → Webhook → Testar um evento, escolha o evento e clique em Enviar teste. Os cinco tipos estão disponíveis conforme as assinaturas salvas. O webhook precisa estar ativo, sem alterações por salvar.
data.test: true, inclusive contact.marketing_opt_out. Valide primeiro a assinatura; depois, se o marcador for o booleano true, responda 2xx sem gravar o evento nem alterar cadastros. Faça isso antes das validações de telefone e das operações de negócio.Os dados são fictícios: telefone composto por zeros, identificadores de exemplo e nenhum cadastro real consultado. Cada clique gera um delivery_id diferente, com prefixo test_; trate o identificador como texto. A assinatura e o cabeçalho adicional seguem a configuração salva.
{
"event": "contact.marketing_opt_out",
"occurred_at": "2026-11-06T16:00:00Z",
"delivery_id": "test_0123456789abcdef0123456789abcdef",
"data": {
"message_id": "wamid.TESTE",
"to": "000000000000",
"phone_number_id": "0",
"occurred_at": "2026-11-06T16:00:00Z",
"source": "whatsapp",O teste faz uma única tentativa, mostra o HTTP retornado e registra a ação na auditoria. Não entra na fila de entregas, não tem repetição automática e não altera contatos, consentimentos ou templates. Um 2xx confirma a recepção HTTP; não comprova o processamento de eventos reais, que não recebem esse marcador.
Mudanças de templates (link para esta seção)
{
"event": "template.status",
"occurred_at": "2026-11-06T16:00:00Z",
"delivery_id": "2001",
"data": {
"waba_id": "123456789012345",
"template_id": "234567890123456",
"name": "pedido_recebido",
"language": "pt_BR",
"status": "APPROVED",
"reason": "NONE"
}
}O evento template.quality mantém a identificação do modelo e traz previous_quality e quality, no lugar de status/reason. Campos indisponíveis podem ser nulos; aceite códigos futuros sem quebrar o receptor. A WABA identifica a conta, pois um template pode servir a vários números. O horário do envelope é o registro do evento na plataforma; não prova o instante exato da decisão da Meta.
Saída de promoções (link para esta seção)
message.received preserva texto e acrescenta interaction para botões/listas e consent quando uma saída é reconhecida. Use consent.action = marketing_opt_out, em vez de comparar o texto “Parar promoções”. O evento opcional contact.marketing_opt_out comunica essa mesma ação com identificador da mensagem e contato. Não soma uma segunda recusa; deduplique a sua operação pelo contato/mensagem.
A recusa de marketing vale para os números da empresa. Não representa cancelamento dos avisos legítimos de pedidos. A estrutura e as regras de aceite estão em Consentimento.
message.status (link para esta seção)
{
"event": "message.status",
"occurred_at": "2026-07-30T18:00:00Z",
"delivery_id": "1841",
"data": {
"message_id": "wamid.HBgNNTUyMT...",
"status": "delivered",
"status_rank": 3,
"to": "5521999999999",
"error": null,message.received (link para esta seção)
O evento informa o conteúdo recebido, o contato e o número da empresa. Use esses dados para atualizar sua aplicação ou preparar uma resposta.
{
"event": "message.received",
"occurred_at": "2026-08-02T11:14:22Z",
"delivery_id": "1842",
"data": {
"message_id": "wamid.HBgNNTUyMT...",
"from": "5521999999999",
"phone_number_id": "1234567890",
"type": "text",
"text": "Recebi, obrigado!",| Campo | O que é |
|---|---|
| from | Telefone do contato que enviou a mensagem. Em message.status, o telefone do destinatário aparece em to. |
| type | text, image, audio, video, document, sticker, location, contacts, button, interactive, unsupported. |
| interaction | Objeto com type, id e title para botão ou lista recebida; nulo nos demais casos. id preserva o payload do botão ou o identificador da opção. |
| consent | Quando reconhecemos uma saída de promoções: action, marketing_allowed, scope e changed. Nulo quando não houve essa ação. Não deduza consentimento pelo texto da mensagem. |
| text | O texto, a legenda de uma mídia ou o rótulo de um botão/opção recebida. Nulo quando não há texto. O identificador da interação fica no campo interaction. |
| media_id | Presente quando veio arquivo. Consulte a conversa no painel para acessar a mídia. O identificador não é uma URL pública nem garante disponibilidade permanente. |
| reply_to_message_id | ID da mensagem citada pelo contato. Use-o para localizar o envio e a operação correspondente. |
| window_expires_at | Até quando a mensagem recebida permite resposta com texto livre. A janela não garante gratuidade. |
Headers (link para esta seção)
| Header | O que é |
|---|---|
| X-PT-Timestamp | Timestamp Unix em segundos. Faz parte da assinatura e muda a cada tentativa. |
| X-PT-Signature | Prefixo sha256= mais o HMAC em hexadecimal minúsculo. |
| X-PT-Delivery-Id | Id desta entrega. Repete nas retentativas do mesmo evento. |
| X-PT-Attempt | Número da tentativa, começando em 1. |
| X-PT-Event | Um dos cinco eventos disponíveis acima. Confira também event no corpo assinado. |
Conferir a assinatura (link para esta seção)
A assinatura é o HMAC-SHA256 de "timestamp" + "." + corpo cru, em hexadecimal minúsculo, com o prefixo sha256=. O segredo aparece no painel, em Integrações.
const crypto = require("crypto");
// Guarde o corpo CRU. Sem isto o express.json() já consumiu o stream.
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf; } }));
function assinaturaValida(req, segredo) {
const ts = req.get("X-PT-Timestamp");
const assinada = req.get("X-PT-Signature") || "";
if (!segredo || !Buffer.isBuffer(req.rawBody)) return false;
if (!/^\d{1,12}$/.test(ts || "") || !/^sha256=[a-f0-9]{64}$/.test(assinada)) return false;$corpoCru = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_PT_TIMESTAMP'] ?? '';
$assinada = $_SERVER['HTTP_X_PT_SIGNATURE'] ?? '';
$formatoValido = preg_match('/^[0-9]{1,12}$/D', $ts)
&& preg_match('/^sha256=[a-f0-9]{64}$/D', $assinada);
if (!$segredo || !$formatoValido || abs(time() - (int) $ts) > 300) {
http_response_code(401);
exit;
}Campos de message.status (link para esta seção)
| Campo | O que é |
|---|---|
| message_id | O mesmo id devolvido no envio. |
| status | sent, delivered, read ou failed. |
| status_rank | sent=2, delivered=3, read=4. Ignore regressões entre esses estados. failed=1 é um desfecho separado: pode substituir sent, mas não delivered ou read; não aplique uma comparação numérica simples para falhas. |
| to | Número do destinatário. Mesmo nome do campo no envio. |
| error | Objeto {code, message, reason} só quando o status é failed; nulo nos outros. reason é o nosso código estável, code é o número da Meta. |
| client_ref | Referência informada no envio. Pode ser nula; combine-a com message_id para localizar a operação. |
| campaign_id | Preenchido quando a mensagem saiu de uma campanha do painel. |
| delivery_id | Id desta entrega. Repete nas retentativas, então serve de chave de idempotência do seu lado. |
| occurred_at | No envelope: quando o evento entrou na fila. Permanece fixo nas tentativas. Em message.received, data.occurred_at informa o horário da mensagem recebido da Meta. |
Confirmar o recebimento do evento (link para esta seção)
Para eventos reais, qualquer coisa fora de 2xx entra na fila de retentativa. Se o seu processamento é demorado, confirme primeiro a gravação numa fila durável, responda e processe depois. Há limite de 10 segundos por tentativa e redirecionamentos HTTP não são seguidos.
2xx confirma que seu endpoint aceitou o evento. A plataforma não interpreta o corpo para decidir se deve repetir. Para eventos reais, não responda sucesso antes de persistir: se o processo cair, o evento ainda precisa estar disponível para seu worker.O mesmo delivery_id pode chegar mais de uma vez. Use uma chave única por integração e entrega para não repetir efeitos já aplicados.
Se o seu endpoint cair (link para esta seção)
Eventos habilitados são persistidos numa fila. Após a tentativa inicial, repetimos em 30s, 2min, 10min, 1h, 6h, 24h, com variação de até 20% para distribuir a carga: sete tentativas automáticas e cerca de 31 horas de espera acumulada. O que esgota as tentativas fica guardado esperando reenvio, que você dispara pelo painel ou pela API de entregas.