Webhooks Explicados: Payloads Assinados e Retentativas
Você acabou de integrar um gateway de pagamento e agora precisa atualizar seu banco de dados quando um pagamento for bem-sucedido. Fazer polling na API a cada poucos segundos é desperdício e lento. Webhooks resolvem isso enviando eventos para o seu servidor conforme eles acontecem. Mas sem segurança e confiabilidade adequadas, webhooks podem se tornar uma fonte de bugs e vulnerabilidades. Este artigo explica como implementar webhooks corretamente, com foco em payloads assinados e retentativas.
O Que São Webhooks?
Um webhook é um callback HTTP: um evento ocorre em um serviço, e esse serviço envia uma requisição HTTP POST para uma URL que você fornece. O payload normalmente contém dados JSON sobre o evento. Por exemplo, quando um cliente conclui um pagamento, o Stripe envia um evento charge.succeeded para o seu endpoint.
Webhooks são baseados em push, então reduzem a latência e a carga do servidor em comparação com polling. No entanto, introduzem desafios: como você sabe que a requisição é genuína? E se o seu servidor estiver fora do ar quando o evento disparar?
Por Que Payloads Assinados Importam
Qualquer pessoa pode enviar um HTTP POST para o seu endpoint de webhook. Sem verificação, um atacante poderia forjar eventos, levando a ações não autorizadas, como marcar um pedido como pago. Payloads assinados resolvem isso incluindo uma assinatura criptográfica que apenas o remetente e você podem gerar.
O método mais comum é HMAC (Hash-based Message Authentication Code) com um segredo compartilhado. O remetente calcula um hash do payload usando o segredo e o inclui em um cabeçalho. Você recalcula o hash e compara. Se corresponderem, a requisição é autêntica.
Como Verificar um Payload Assinado
Aqui está uma abordagem passo a passo usando Node.js e Express:
- Recupere o corpo bruto da requisição como string. Não o analise como JSON antes da verificação, pois a análise pode alterar a sequência de bytes.
- Extraia a assinatura do cabeçalho (por exemplo,
X-Signature). - Calcule o HMAC usando seu segredo e o corpo bruto.
- Compare a assinatura calculada com a recebida usando uma comparação de tempo constante para evitar ataques de temporização.
const crypto = require('crypto');
function verifySignature(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-signature'];
if (!verifySignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
// Process event
res.status(200).send('OK');
});
Sempre use um segredo forte e rotacione-o periodicamente. Nunca o exponha em código do lado do cliente.
Implementando Retentativas com Backoff
Webhooks são entregues pela internet, então falhas acontecem. Seu servidor pode estar fora do ar, ou a rede pode falhar. Um sistema de webhook robusto tenta novamente entregas falhas com backoff exponencial.
Normalmente, o remetente tenta novamente se não receber uma resposta 2xx dentro de um timeout (por exemplo, 5 segundos). O cronograma de retentativas pode ser: imediato, depois após 1 minuto, 5 minutos, 30 minutos, 2 horas, etc., até um número máximo de tentativas (por exemplo, 5).
Como receptor, você deve responder rapidamente para evitar timeouts. Confirme o recebimento com um 200 OK e processe o evento de forma assíncrona.
Lidando com Idempotência
Como as retentativas podem causar entregas duplicadas, o processamento do seu evento deve ser idempotente. Inclua um ID de evento único no payload e armazene os IDs processados. Antes de processar, verifique se o ID já foi tratado.
async function processEvent(event) {
const { id, type, data } = event;
if (await db.processedEvents.findOne({ id })) {
return; // Already processed
}
await db.processedEvents.insertOne({ id });
// Handle event based on type
}
Use uma transação de banco de dados para garantir que o evento seja marcado como processado somente após o tratamento bem-sucedido.
Melhores Práticas para Segurança de Webhooks
- Sempre verifique as assinaturas antes de processar.
- Use HTTPS para evitar escutas.
- Valide o timestamp se incluído, para prevenir ataques de replay.
- Limite o tamanho do payload para evitar negação de serviço.
- Registre todas as tentativas de webhook para depuração e auditoria.
Comparação: Polling vs Webhooks
| Aspecto | Polling | Webhooks |
|---|---|---|
| Latência | Alta (baseada em intervalo) | Baixa (quase em tempo real) |
| Carga do Servidor | Alta (requisições constantes) | Baixa (apenas em eventos) |
| Complexidade | Simples de implementar | Requer segurança e retentativas |
| Confiabilidade | Depende da frequência de polling | Depende da lógica de retentativa |
FAQ
Como testar webhooks localmente?
Use uma ferramenta como ngrok para expor seu servidor local à internet. Configure o provedor de webhook para enviar eventos para sua URL do ngrok.
E se meu servidor estiver fora do ar quando um webhook for enviado?
O remetente deve tentar novamente com backoff. Garanta que seu servidor seja altamente disponível e responda rapidamente para evitar timeouts.
Posso usar assinaturas assimétricas em vez de HMAC?
Sim, alguns provedores usam assinaturas RSA ou ECDSA. Você verifica com a chave pública deles. HMAC é mais simples, mas requer um segredo compartilhado.
Ao depurar payloads de webhook, é útil formatar o JSON para facilitar a leitura. Experimente nosso JSON Formatter para formatar e validar payloads de webhook instantaneamente.