Como validar a assinatura de um webhook da Meta?
Ao expor uma rota pública para receber eventos da WhatsApp Cloud API, você abre uma porta por onde entram dados de fora. Isso é necessário para atender em tempo real, mas tem um custo se a porta não tiver tranca.
Sem validação, qualquer pessoa que descubra a URL do seu webhook pode enviar requisições forjadas: simular mensagens de clientes que nunca existiram, mudar o status de uma conversa ou alimentar a sua automação com dados falsos. O trabalho de defesa cabe em umas quinze linhas de código.
O que a validação garante: duas coisas ao mesmo tempo. Que o payload saiu mesmo dos servidores da Meta e que o corpo da requisição não foi alterado no caminho até você.
O que é o X-Hub-Signature-256
Toda vez que a Meta dispara um evento por POST, ela inclui um cabeçalho HTTP chamado X-Hub-Signature-256. Dentro dele vai uma assinatura criptográfica gerada com HMAC-SHA256, no formato sha256= seguido do hash.
Essa assinatura combina duas informações.
- O corpo bruto da requisição O JSON exatamente como foi enviado, byte a byte, sem passar por leitura, reformatação ou reordenação de campos.
- O App Secret O segredo do aplicativo configurado no Meta for Developers, conhecido apenas pela sua aplicação e pela própria Meta.
Como o App Secret é privado, quem não o tem não consegue produzir uma assinatura válida para um payload inventado. A pessoa até consegue enviar JSON para o seu endpoint. O que ela não consegue é provar que aquele JSON foi assinado com a chave certa.
Imagine que a Meta lacra cada envelope com um carimbo que só ela e você sabem fazer. Você não precisa confiar no carteiro: basta conferir o carimbo.
O App Secret não pode vazar em lugar nenhum. Nada de front-end, log, repositório ou mensagem de erro. Ele vive em variável de ambiente ou em cofre de segredos, e só isso. Se ele vazar, a assinatura deixa de provar qualquer coisa.
O fluxo, em três passos
A lógica é curta: o servidor recebe a requisição, calcula a assinatura por conta própria e compara com o que veio no cabeçalho.
- Capture Leia o payload bruto da requisição e o valor do cabeçalho X-Hub-Signature-256. Nesta etapa nada ainda foi interpretado como JSON.
- Calcule localmente Gere um HMAC-SHA256 desse corpo bruto usando o App Secret como chave. O resultado é uma sequência hexadecimal.
- Compare com segurança Se o hash calculado for idêntico ao recebido, aceite o evento e coloque na fila. Se não for, responda 403 e pare por ali, sem gravar nem processar nada.
Implementando em PHP
O exemplo abaixo usa PHP puro. Em Laravel, Symfony ou qualquer outro framework a ideia não muda: capture o corpo bruto antes de qualquer transformação e compare com uma função segura.
<?php
$appSecret = getenv('META_APP_SECRET');
$signatureHeader = $_SERVER['HTTP_X_HUB_SIGNATURE_256'] ?? '';
if (!$appSecret || $signatureHeader === '') {
http_response_code(401);
exit('Assinatura ausente.');
}
if (!str_starts_with($signatureHeader, 'sha256=')) {
http_response_code(403);
exit('Formato de assinatura invalido.');
}
$receivedHash = substr($signatureHeader, strlen('sha256='));
$rawPayload = file_get_contents('php://input');
$calculatedHash = hash_hmac('sha256', $rawPayload, $appSecret);
if (!hash_equals($calculatedHash, $receivedHash)) {
http_response_code(403);
exit('Assinatura invalida.');
}
// A partir daqui o evento e confiavel.
// Enfileire o processamento e responda imediatamente.
http_response_code(200);
echo 'EVENT_RECEIVED';
Dois detalhes desse código merecem atenção. A comparação usa hash_equals(), que gasta o mesmo tempo independentemente de onde as strings divergem e assim não dá pistas a quem tenta adivinhar o hash. E o cálculo usa php://input, que é o corpo bruto, e não o array já interpretado.
A ordem importa: valide a assinatura antes de gravar, interpretar ou acionar qualquer automação. O endpoint precisa recusar tráfego inválido o mais cedo possível, de preferência antes de tocar no banco de dados.
O que bloquear em produção
Nem toda falha tem a mesma causa, mas todas devem interromper o processamento. Assinatura ausente, malformada ou incompatível significa a mesma coisa: aquela requisição não é confiável.
| Cenário | Ação | Status | Por quê |
|---|---|---|---|
| Cabeçalho ausente | Bloquear | 401 | Não existe prova criptográfica da origem |
| Formato diferente de sha256= | Bloquear | 403 | Cabeçalho inesperado ou manipulado |
| Hash incompatível | Bloquear | 403 | O payload pode ter sido forjado ou alterado |
| Hash válido | Aceitar e enfileirar | 200 | O evento passou pela validação |
Além do status HTTP, registre um log estruturado de cada bloqueio, com horário, IP de origem, motivo e identificador do endpoint. Evite gravar o payload inteiro quando ele carregar dados de clientes: o log também é um lugar onde dado pessoal vaza. Esse cuidado conversa com o tema das permissões de usuário e dados sensíveis no CRM.
Três erros que quebram a validação
Curiosamente, o bug quase nunca está na função de hash. Ele aparece na forma como o framework entrega a requisição para o seu código.
Calcular o hash sobre o JSON já interpretado
Se você gerar o hash a partir de um objeto que o framework leu e reserializou, qualquer diferença de espaçamento, ordem de chave ou codificação muda o resultado. A assinatura precisa ser calculada sobre os bytes originais, exatamente como chegaram.
Comparar com igualdade comum
Evite == e === para comparar assinaturas. Eles param na primeira diferença, e essa diferença de tempo pode ser medida por quem está tentando descobrir o hash correto. Use hash_equals() no PHP ou crypto.timingSafeEqual() no Node.js.
Processar tudo antes de responder
Depois de aprovar o hash, jogue o evento em uma fila e responda 200 na hora. Se você rodar regra de negócio dentro do endpoint, a Meta interpreta a demora como falha e reentrega o mesmo evento, o que pode duplicar o efeito no seu sistema.
Checklist antes de subir para produção
Vale passar por esta lista uma última vez antes de liberar o endpoint.
- App Secret fora do código Em variável de ambiente ou cofre de segredos, nunca escrito direto no arquivo nem comitado por engano.
- HTTPS obrigatório Webhook em endpoint sem TLS não é aceito pela Meta e, mesmo que fosse, exporia o conteúdo das conversas.
- Validação antes de qualquer processamento Nada de gravar, interpretar ou disparar automação antes de a assinatura ser aceita.
- Log sem dado sensível Registre o suficiente para auditar e investigar, sem despejar o conteúdo das mensagens dos clientes no arquivo de log.
- Fila para o processamento O endpoint responde rápido e deixa a regra de negócio para os workers, que podem falhar e tentar de novo sem perder evento.
- Alerta para picos de 403 Um aumento repentino de bloqueios pode significar tentativa de ataque, configuração errada ou App Secret desatualizado depois de uma rotação.
Como a Atys trata isso por baixo do pano
O problema deste artigo é concreto: quem recebe webhook da Meta precisa manter uma rota pública segura, com segredo guardado, assinatura conferida a cada requisição, log auditado e fila para não perder evento. Isso não é difícil de escrever, mas é fácil de esquecer de manter.
Quem usa a Atys não expõe endpoint nenhum. O webhook fica do lado da plataforma, e a validação de assinatura, a rotação de segredo, o tratamento de reentrega e o monitoramento de falhas já vêm prontos e são mantidos por quem cuida disso o dia inteiro.
Nenhum evento chega à sua operação sem passar pela verificação do HMAC contra o App Secret.
Você não guarda App Secret nem token permanente, então eles não podem vazar por um log ou por um repositório da sua empresa.
Evento duplicado é identificado e descartado, o que evita o cliente receber a mesma resposta automática duas vezes.
Se você precisa ligar o WhatsApp ao seu sistema, consome a API da Atys em vez de manter a superfície pública exposta à internet.
Para o contexto mais amplo de como a Meta trata segurança e limites de volume na API, vale ler o guia sobre segurança e escalabilidade na API oficial do WhatsApp.
Resumindo
Validar a assinatura é a camada mais barata de segurança que existe em uma integração com a Cloud API. Ela custa poucas linhas e protege tudo o que vem depois: chatbot, fila, CRM e relatório.
Se você prefere não manter essa superfície exposta e nem os segredos que ela exige, fale com o time da Atys e conecte a sua operação sem abrir uma porta pública no seu servidor.