Anúncios incomodam? Ir Sem anúncios Hoje

Assinaturas de Webhook Verifique payloads e pare requisições falsas

Atualizado em

Aprenda como funcionam as assinaturas de webhook HMAC-SHA256, como implementar a verificação em tempo constante em Python, Node.js e PHP, e os erros comuns que causam falhas silenciosas em produção.

Assinaturas de Webhook: Verifique os payloads e pare requisições falsificadas 1
ANUNCIADO Remover?

Qualquer pessoa pode enviar uma solicitação POST para o seu ponto final de webhook. Sem verificação de assinatura, seu servidor não tem forma de saber se a solicitação realmente veio de Stripe, GitHub ou da sua própria infraestrutura — ou de um atacante que está reproduzindo um evento legítimo.

A verificação de assinatura de webhook resolve isso. É como os processadores de pagamentos, plataformas de controle de versão e sistemas de comércio eletrônico permitem que você prove a autenticidade antes de agir sobre os dados recebidos. Este artigo explica como as assinaturas HMAC-SHA256 funcionam, o padrão passo a passo de verificação e os erros sutis que causam falhas em produção.

Por que webhooks não autenticados representam um risco de segurança

Um ponto final de webhook é simplesmente um manipulador HTTP exposto à internet. Sem verificação, qualquer solicitação que atingir esse ponto final será processada. Dois tipos de ataque se destacam:

  • Falsificação — um atacante cria um payload falso que parece um evento legítimo (um pagamento concluído, uma assinatura renovada) e desencadeia ações em sua aplicação sem que haja transação real ocorrendo.
  • Ataques de replay — um pedido legítimo é capturado durante a transmissão e reenviado posteriormente. Se o ponto final for idempotente mas não protegido, o mesmo evento será acionado várias vezes.

Ambos os ataques são previstos por um segredo compartilhado combinado com uma assinatura criptográfica. O remetente assina o payload; você verifica a assinatura antes de processar qualquer coisa.

Como as assinaturas HMAC-SHA256 funcionam

O HMAC (Código de Autenticação Baseado em Hash) recebe duas entradas: uma chave secreta e um mensagem. Ele as processa através de uma função de hash — SHA-256 nas maioria das implementações de webhook — e produz uma assinatura de comprimento fixo.

A propriedade chave: o mesmo segredo + mensagem sempre produz a mesma assinatura, e alterar até um único byte na mensagem produz uma saída completamente diferente. Qualquer pessoa sem a chave secreta não consegue produzir uma assinatura válida, mesmo que consiga ver o payload inteiro.

Na prática, o fluxo é assim:

  1. Você registra seu webhook com um serviço (por exemplo, Stripe). O serviço fornece a você uma segredo de assinatura.
  2. Quando o serviço envia um evento, ele calcula HMAC-SHA256(secret, payload) e inclui o resultado em uma cabecalho de requisição.
  3. Seu servidor recebe a requisição, calcula o mesmo HMAC usando a chave armazenada e o corpo bruto da requisição, depois compara as duas assinaturas.
  4. Se elas coincidem, a requisição é autêntica. Se não, rejeita-a.

O padrão de verificação, passo a passo

Aqui está uma implementação em Python que reflete o que cada serviço principal espera:

import hmac
import hashlib

def verify_signature(payload: bytes, secret: str, received_sig: str) -> bool:
    expected = hmac.new(
        key=secret.encode("utf-8"),
        msg=payload,
        digestmod=hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, received_sig)

Duas coisas são importantes aqui além da chamada HMAC. Primeiro: payload é bytes, não uma string decodificada — você deve passar o corpo bruto da requisição exatamente como recebido da rede. Segundo: a comparação usa hmac.compare_digest em vez de ==. Isso é intencional.

Por que a comparação em tempo constante não é opcional

A comparação de strings em muitas linguagens curta-circuita: ela retorna false no momento em que encontra um caractere diferente. Um atacante que pode medir o tempo de resposta pode explorar isso — enviando milhares de requisições com assinaturas variadas e usando diferenças de tempo para adivinhar o valor correto caractere por caractere. Isso é um ataque de tempo.

hmac.compare_digest em Python, hash_equals em PHP e crypto.timingSafeEqual em Node.js todos levam o mesmo tempo, independentemente de onde as strings se diferem. Use-os sempre que comparar uma assinatura — sem exceções.

Formatos de cabeçalho no mundo real: Stripe, GitHub, Shopify

Cada serviço tem um nome de cabeçalho e uma codificação de assinatura ligeiramente diferente. Aqui está o que deve ser analisado para as três integrações mais comuns.

Stripe

Stripe envia um Stripe-Signature cabeçalho com este formato:

Stripe-Signature: t=1492774577,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a05bd539e6d5b9d2a2d2fbe

O t campo é o timestamp Unix do momento em que o evento foi enviado. O v1 valor é o HMAC-SHA256 de timestamp + "." + raw_body. Ao implementar manualmente:

  1. Analise o cabeçalho para extrair t e v1.
  2. Calcule HMAC-SHA256(secret, t + "." + raw_body).
  3. Compare com v1 usando uma comparação em tempo constante.
  4. Rejeite se t estiver mais de 300 segundos (5 minutos) do tempo atual.

GitHub

GitHub usa o X-Hub-Signature-256 cabecalho:

X-Hub-Signature-256: sha256=6ffbb59b2300aae63f272406069a9788598b770f698a48021f99b32f8de06bb3

Remova o prefixo sha256= e compare o resto com o HMAC do corpo bruto. GitHub não inclui um timestamp no conteúdo assinado, portanto, implemente deduplação de eventos separadamente se a proteção contra replay for importante para seu uso.

Shopify

Shopify usa X-Shopify-Hmac-Sha256 com o digest codificado em Base64 em vez de hexa:

X-Shopify-Hmac-Sha256: b6LPiZidmXnJQf0Ff/p7MZQPIBPN9TqAqLMgAfn2YLQ=

Decodifique tanto o HMAC calculado quanto o valor do cabeçalho de Base64 para bytes brutos, depois compare com uma função de comparação em tempo constante. Comparar strings em Base64 diretamente pode introduzir bugs sutis de normalização de codificação.

O erro mais comum: corpo bruto versus JSON decodificado

É aqui que a maioria das falhas na verificação de assinatura de webhook ocorre em produção. Quando seu framework automatiza a decodificação do corpo da requisição em um dicionário ou objeto antes do seu manipulador ser executado, os bytes brutos são perdidos. O HMAC foi calculado com esses bytes originais — não com o que sua biblioteca JSON produz quando re-encodeia a estrutura decodificada.

Mesmo quando os dados são semanticamente idênticos, {"amount": 100} e {"amount":100} (sem espaço após a vírgula) produzem valores HMAC diferentes. Diferenças na ordem dos campos, normalização de Unicode e precisão de floats podem quebrar a verificação silenciosamente sem nenhum erro aparente.

A solução: buffer o corpo bruto da requisição antes de qualquer decodificação e passe esses bytes para a função de verificação. Em Express.js, registre a rota com express.raw() middleware em vez de express.json():

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const rawBody = req.body; // Buffer — not a parsed object
  const sig = req.headers['stripe-signature'];
  // Verify rawBody against sig before JSON.parse()
});

No Django, use request.body (bytes). Em Laravel, use $request->getContent(). O padrão é consistente: acesse os bytes brutos diretamente do pedido, nunca re-serialize uma representação decodificada.

Proteção contra replay com timestamps

Uma assinatura válida prova apenas que o payload não foi alterado durante a transmissão. Ela não previne um atacante de capturar uma requisição legítima e reenviá-la horas depois — a assinatura ainda será verificada corretamente porque nada mudou.

Stripe resolve isso incluindo um timestamp Unix no conteúdo assinado e recomendando uma janela de tolerância de 5 minutos. Após extrair t do cabeçalho Stripe-Signature , rejeite requisições onde o timestamp está fora dessa janela:

import time

def is_within_tolerance(timestamp: int, tolerance_seconds: int = 300) -> bool:
    return abs(time.time() - timestamp) <= tolerance_seconds

# In your handler:
if not is_within_tolerance(stripe_timestamp):
    return HttpResponse(status=403)  # Reject stale request

Para serviços como GitHub e Shopify que não incluem timestamps no esquema de assinatura, implemente sua própria proteção contra replay armazenando IDs de eventos processados (disponíveis no payload) e rejeitando qualquer ID que já tenha sido tratado. Um cache curto ou um conjunto Redis com TTL correspondente à sua janela de processamento funciona bem.

Verifique assinaturas sem escrever código

Ao depurar uma integração de webhook — ou auditar um payload que já foi recebido — o Validador de Assinatura de Webhook permite que você cole um payload, uma chave secreta e uma assinatura recebida para verificar imediatamente, sem precisar executar um ambiente local.

Para gerar assinaturas HMAC para testes com payloads criados, use o Gerador HMAC. Ele produz saídas em hexa e Base64 para SHA-256, SHA-512 e vários outros algoritmos de digest — úteis para construir casos de teste que cobrem os formatos usados por cada serviço.

Referência Rápida

  • O esquema de assinatura HMAC-SHA256 com chave secreta é o padrão usado por Stripe, GitHub, Shopify e a maioria dos outros serviços.
  • Sempre use comparação em tempo constante (hmac.compare_digest, hash_equals, crypto.timingSafeEqual) — não ==.
  • Passe os bytes brutos do corpo da requisição para a função HMAC, nunca um objeto re-serializado em JSON.
  • Verifique o timestamp nos serviços que o incluem; a janela de tolerância do Stripe é de 5 minutos.
  • Deduplique por ID do evento para serviços sem proteção contra replay baseada em timestamp.
  • O nome do cabeçalho e a codificação (hexa vs Base64) variam por serviço — sempre verifique as documentações de integração.
Quer eliminar anúncios? Fique sem anúncios hoje mesmo

Instale nossas extensões

Adicione ferramentas de IO ao seu navegador favorito para acesso instantâneo e pesquisa mais rápida

Ao Extensão do Chrome Ao Extensão de Borda Ao Extensão Firefox Ao Extensão Opera

O placar chegou!

Placar é uma forma divertida de acompanhar seus jogos, todos os dados são armazenados em seu navegador. Mais recursos serão lançados em breve!

ANUNCIADO Remover?
ANUNCIADO Remover?
ANUNCIADO Remover?

Notícias com destaques técnicos

Envolver-se

Ajude-nos a continuar fornecendo ferramentas gratuitas valiosas

Compre-me um café
ANUNCIADO Remover?