Webhooks são chamadas HTTP automáticas que um sistema dispara para outro quando um evento acontece (por exemplo: pagamento confirmado, pedido criado, assinatura cancelada). Eles são simples e poderosos porque eliminam a necessidade de “ficar consultando” uma API o tempo todo: o sistema emissor te avisa assim que algo muda.
O problema é que um endpoint de webhook exposto na internet vira um alvo óbvio para falsificações. Qualquer pessoa pode tentar enviar um POST “imitando” o provedor, e se o seu sistema confiar no conteúdo sem validar a origem, você abre brechas graves (marcar pedido como pago, liberar acesso, registrar ações indevidas, etc.).
A forma mais comum (e eficiente) de proteger webhooks é com HMAC (Hash-based Message Authentication Code): o emissor assina o corpo da requisição com um segredo compartilhado e o receptor recalcula essa assinatura. Se bater, você tem uma forte garantia de integridade (o corpo não foi alterado) e autenticidade (quem assinou conhece o segredo).
Como funciona a verificação HMAC em webhooks
O fluxo é este:
- O emissor monta um JSON com o evento e envia via
POSTpara a sua URL de webhook. - Antes de enviar, ele gera uma assinatura HMAC do corpo bruto (
raw body) e inclui em um header (ex.:X-Signature). - Seu receptor lê o
raw body, recalcula o HMAC usando o mesmo segredo e compara com a assinatura recebida. - Se não bater: responde
401/403. Se bater: processa o evento e responde200.
Boas práticas (que evitam dor de cabeça)
- Nunca use
$_POSTpara validar assinatura: HMAC deve ser calculado em cima do corpo bruto (php://input). - Use
hash_equals()para comparar assinaturas (evita ataques por timing). - Inclua timestamp e valide janela de tempo (anti-replay).
- Inclua um event id e faça deduplicação (webhooks podem ser reenviados).
- Logue
request-id, latência e resultados (sem vazar segredos).
1) Definindo o padrão de headers
Neste guia vamos usar estes headers (você pode adaptar):
X-Webhook-Id: ID único do evento (UUID, por exemplo).X-Webhook-Timestamp: timestamp Unix (segundos).X-Webhook-Signature: assinatura HMAC (hex ou base64).
A assinatura será calculada em cima do texto: {timestamp}.{rawBody}. Isso impede replay fácil com corpo reaproveitado em outro momento.
2) Exemplo do emissor: gerando e enviando um webhook assinado (PHP puro + cURL)
%%OZI_ILB_PROTECT_e8e00f928b0d6a6497d3aae0748b9d53%%
3) Exemplo do receptor: validando HMAC com segurança (PHP puro)
Agora o principal: o endpoint que recebe o POST. Ele precisa (1) ler o raw body, (2) extrair headers, (3) validar timestamp, (4) recalcular HMAC e comparar com hash_equals e (5) só então processar.
%%OZI_ILB_PROTECT_f011537b2096ae2eb1b16ca38b3cd789%%
4) Versão com assinatura em Base64 (opcional)
Alguns provedores enviam assinatura em Base64. É a mesma ideia, só muda o formato:
Emissor:
%%OZI_ILB_PROTECT_5e7d0827796a18eaf420fac542d763a3%%
Receptor:
%%OZI_ILB_PROTECT_da05fc7337bdd1aa4d2889eeee9dd5d2%%
5) Erros comuns que fazem a assinatura “não bater”
- Recalcular HMAC a partir de JSON reformatado: se você faz
json_decodee depoisjson_encode, muda espaços/ordem e a assinatura quebra. Assine sempre oraw body. - Encoding: garanta que o corpo é tratado como UTF-8 e enviado como foi recebido.
- Proxy/CDN: headers podem ter nomes diferentes. Por isso é bom padronizar e logar o que chega.
- Comparação insegura: usar
==em vez dehash_equalsé má prática e pode vazar informação por tempo de execução. - Sem anti-replay: se você não valida timestamp e id do evento, alguém pode reenviar um payload válido e repetir ações.
Se precisar de apoio prático para implementar este padrão de webhooks com HMAC (incluindo armazenamento seguro de segredo, deduplicação em banco/Redis, filas e reprocessamento), os serviços da Saldaris Consultoria estão disponíveis e você pode entrar em contato pelo formulário abaixo.
Erro: Formulário de contato não encontrado.


