Webhooks são uma das formas mais poderosas de receber atualizações em tempo real de serviços de terceiros. Uma única requisição HTTP POST de Stripe, GitHub, Shopify ou Twilio pode acionar lógica de negócios crítica em sua aplicação — cobrando um cliente, atualizando um repositório, enviando um pedido ou mandando um SMS de confirmação.
Mas cada requisição de webhook chega pela internet pública. E isso significa que qualquer um que adivinhe ou descubra sua URL de webhook pode enviar payloads maliciosos que parecem completamente legítimos. Sem a autenticação adequada, sua aplicação não tem como diferenciar um evento real de um forjado.
É aí que entra a verificação de assinatura de webhook. É um mecanismo simples e padronizado que garante que cada requisição de webhook recebida seja genuinamente do serviço que você espera e que não tenha sido alterada durante o trânsito.
Neste guia abrangente, você aprenderá exatamente como funciona a verificação de assinatura de webhook e como implementá-la corretamente em linguagens populares. Você também verá erros comuns a serem evitados e como testar tudo de ponta a ponta — de forma rápida e confiável.
O Que É Verificação de Assinatura de Webhook?
A verificação de assinatura de webhook é o processo de confirmar que uma requisição de webhook recebida realmente vem do serviço que você espera e que não foi adulterada.
A maioria dos provedores usa HMAC (Hash-based Message Authentication Code) com SHA-256 ou SHA-512. O serviço calcula:
signature = HMAC-SHA256(secret_key, payload)
Em seguida, eles enviam a assinatura em um cabeçalho (geralmente X-Signature, Signature ou X-Hub-Signature-256).
Seu servidor:
- Recebe o payload como bytes brutos (importante!)
- Recalcula o HMAC usando seu segredo armazenado
- Compara a assinatura calculada com a recebida
Se elas corresponderem exatamente, você processa o webhook. Caso contrário, você retorna HTTP 401 ou 403.
Por Que HMAC-SHA256 é o Padrão da Indústria
Os provedores escolhem HMAC-SHA256 por boas razões:
- Rápido: Mesmo em hardware modesto, é incrivelmente rápido.
- Seguro: SHA-256 permanece inquebrável em 2025.
- Simples: Uma chave secreta, sem pares de chaves públicas/privadas para gerenciar.
- Padronizado: Bibliotecas em todas as linguagens o implementam corretamente.
GitHub, Stripe, Shopify, Slack e dezenas de outros usam HMAC-SHA256.
Como Implementar Verificação de Assinatura de Webhook em Node.js
Vamos começar com um exemplo real em Node.js.
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const computedSignature = hmac.update(payload).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(computedSignature),
Buffer.from(signature)
);
}
Pontos-chave a serem observados:
- Sempre use
crypto.timingSafeEqualpara prevenir ataques de tempo. - Nunca use
===em strings — é vulnerável. - Use
Buffer.from()para garantir comparação em tempo constante.
Exemplo de middleware Express:
app.post('/webhooks/stripe', (req, res, next) => {
const signature = req.headers['stripe-signature'];
const secret = process.env.STRIPE_WEBHOOK_SECRET;
// Obter corpo bruto (Express precisa de middleware para preservar o corpo bruto)
const rawBody = req.rawBody || req.body; // usar body-parser com a opção verify
if (!verifyWebhookSignature(rawBody, signature, secret)) {
return res.status(401).send('Invalid signature');
}
// A assinatura é válida → processar o evento
next();
});
Implementação em Python (FastAPI + Pydantic)
from fastapi import FastAPI, Request, HTTPException
import hmac
import hashlib
app = FastAPI()
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
computed = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed, signature)
@app.post("/webhooks/github")
async def github_webhook(request: Request):
signature = request.headers.get("X-Hub-Signature-256")
if not signature:
raise HTTPException(status_code=401, detail="Missing signature")
payload = await request.body()
if not verify_signature(payload, signature.split('=')[1], SECRET):
raise HTTPException(status_code=401, detail="Invalid signature")
# Processar webhook
return {"status": "ok"}
Armadilhas Comuns Que Desenvolvedores Cometem (e Como Evitá-las)
1. Usando JSON.stringify() ou Corpo Parsed
Muitos frameworks analisam JSON automaticamente. Isso quebra a verificação porque espaços em branco, ordem das chaves e formatação diferem.
Solução: Sempre capture o corpo bruto antes de analisar.
No Express: Use body-parser com { verify: true }
No FastAPI: Use await request.body()
2. Comparando Strings com ===
Ataques de tempo podem vazar informações. Use crypto.timingSafeEqual ou hmac.compare_digest.
3. Armazenando Segredos no Código
Use variáveis de ambiente ou gerenciadores de segredos (AWS Secrets Manager, HashiCorp Vault, etc.).
4. Esquecer de Lidar com Ataques de Replay
A maioria dos provedores inclui um carimbo de data/hora. Verifique se o evento é recente (por exemplo, dentro de 5 minutos).
const timestamp = req.headers['X-Signature-Timestamp'];
if (Date.now() - timestamp > 5 * 60 * 1000) {
return res.status(401).send('Timestamp too old');
}
5. Usando SHA-1 (Ainda Acontece!)
O GitHub descontinuou o SHA-1 em 2022. Sempre use SHA-256.
Testando a Verificação de Assinatura de Webhook com Apidog
Testar webhooks manualmente é doloroso. Você envia uma requisição, verifica os logs, corrige, repete.
O Apidog torna isso trivial:
No seu projeto Apidog, clique no ícone + na barra lateral esquerda e escolha "New Other Protocol APls" > "Webhook".
Após criar o Webhook, preencha os seguintes campos no editor:Método de Requisição: Tipicamente POST.Nome do Webhook: Isso aparecerá na documentação da API e na exportação OpenAPI, por exemplo, pedido.URL de Depuração (opcional): A URL real usada para enviar requisições de teste. *Nota: Isso é apenas para fins de teste e não será incluído na documentação*.Outras Informações: Como o corpo da requisição.
Clique em Salvar assim que tiver preenchido todos os campos obrigatórios.
Basta inserir sua URL de Webhook no campo URL de Depuração e, em seguida, clicar em Enviar para simular uma chamada de Webhook.
Eu economizei horas de depuração com o simulador de webhook do Apidog. Ele até suporta o formato exato stripe-signature do Stripe e o prefixo sha256=... do GitHub.
Exemplo do Mundo Real: Verificando Webhooks do Stripe
O Stripe usa um formato de cabeçalho especial:
stripe-signature: t=1681234567,v1=abc123...,v0=def456...
Você deve:
- Extrair o carimbo de data/hora
t= - Extrair a assinatura
v1=(preferencial) - Recalcular usando
payload + timestamp - Comparar apenas a parte
v1
O Stripe fornece bibliotecas oficiais para lidar com essa complexidade:
const stripe = require('stripe')('sk_...');
stripe.webhooks.constructEvent(payload, sigHeader, endpointSecret);
Mas entender o HMAC subjacente é crucial quando você precisa implementá-lo por conta própria.
Tópicos Avançados: Tolerando Múltiplas Assinaturas
Alguns provedores (como o Stripe) enviam múltiplas assinaturas para compatibilidade retroativa. Seu código deve:
- Dividir o cabeçalho por
, - Tentar cada um
- Aceitar se algum corresponder
Melhores Práticas de Segurança em 2025
- Gire os segredos de webhook a cada 90 dias.
- Use segredos de curta duração quando possível.
- Registre as verificações falhas, mas nunca registre o segredo.
- Limite a taxa dos endpoints de webhook para prevenir ataques de força bruta.
- Sempre use HTTPS.
Conclusão: Pequeno Passo de Verificação, Grande Ganho de Segurança
A verificação de assinatura de webhook parece um pequeno detalhe. Mas é a diferença entre uma aplicação segura e uma que os atacantes podem explorar trivialmente.
Implemente-a corretamente, teste-a minuciosamente com ferramentas como o Apidog e durma melhor sabendo que suas integrações estão protegidas.
Baixe o Apidog gratuitamente hoje e verifique seu primeiro webhook em menos de 5 minutos. É a maneira mais rápida de provar que seu código realmente funciona.
