Blog

Quatro regras para um webhook que não pode falhar

14 de julho de 2026 · Time Orion

Webhook parece a parte fácil da integração: alguém faz um POST no seu endpoint e você grava. Na prática, é onde mora a maior parte dos bugs difíceis de reproduzir — porque o provedor reenvia, reordena, duplica e desiste de você sem avisar.

Quatro regras dão conta de quase tudo.

1. Valide a assinatura sobre o corpo cru

Provedores sérios assinam o payload com HMAC. A verificação parece trivial e tem uma armadilha: você precisa calcular o hash sobre exatamente os mesmos bytes que o provedor assinou.

Se o seu framework fizer o parse do JSON antes e você reserializar o objeto para verificar, o hash não bate — não porque o conteúdo mudou, mas porque a serialização é outra: ordem de chaves, espaçamento, escape de unicode. Nada disso é preservado num round-trip.

A regra prática: leia o corpo como texto antes de qualquer parse, verifique a assinatura sobre esse texto, e só então converta para objeto.

O corolário incômodo é que middlewares que fazem parse automático precisam ser desligados nessa rota. Vale a chateação.

2. Depois de autenticado, responda 200

Esta é contraintuitiva para quem vem de API REST: um erro no seu processamento não deve virar erro na resposta do webhook.

O motivo é operacional. Provedores monitoram a saúde do seu endpoint e desabilitam a assinatura depois de uma sequência de falhas. Um bug no seu processamento vira, em poucos minutos, uma integração desligada — e aí você não perde uma mensagem, perde todas as próximas.

O desenho certo separa dois momentos: autenticar (assinatura inválida → recuse, e recuse com erro mesmo) e processar (falhou → responda 200, registre e trate do seu lado, com fila ou alerta).

Você troca "perder um evento" por "não perder o canal". É a troca certa.

3. Deduplique por item, não pelo envelope

Reentrega é normal. Todo provedor reenvia quando não tem certeza de que você recebeu — e você vai receber o mesmo evento duas vezes.

A tentação é deduplicar pelo identificador da requisição. Não funciona: um único envelope costuma trazer vários itens — mensagens novas, mudanças de status, confirmações — e a reentrega pode vir com um recorte diferente do mesmo conjunto. Descartar o envelope inteiro porque parte dele já foi vista significa perder o que era novo.

A chave de deduplicação tem que ser do item, e precisa incluir o tipo do evento. No nosso caso, é a diferença entre mensagem:<id> e status:<id>:<estado> — a mesma mensagem legitimamente produz um evento de entrega e outro de leitura, e os dois precisam passar.

4. Retry só em requisição idempotente

Reenviar automaticamente o que falhou é bom senso — até você reenviar um POST que já tinha funcionado.

Um GET repetido não muda o mundo. Um POST repetido pode entregar a mesma mensagem duas vezes ao cliente final — e a duplicata chega no aparelho de alguém, não num log.

Nosso cliente HTTP faz retry apenas em métodos de leitura, e apenas para falhas de rede e erros 5xx. Escrita que falha sobe para quem chamou decidir — porque só quem chamou sabe se repetir é seguro.

Um extra que economiza banco

Nem todo evento merece uma linha.

Estados voláteis — "digitando", presença, conexão, batimento — chegam aos milhares por dia e não têm valor histórico nenhum. Guardá-los como registro infla a tabela sem responder a nenhuma pergunta futura. Nós escrevemos esses direto no estado atual do canal e nunca como evento.

A pergunta que resolve a dúvida: daqui a seis meses, alguém vai querer saber que isso aconteceu naquele segundo? Se não, não é linha.

O resumo

Corpo cru para a assinatura, 200 depois de autenticado, dedupe por item com tipo, retry só em leitura. Quatro regras que parecem detalhe e que decidem se a integração sobrevive ao primeiro mês de produção.

Todas as quatro têm a mesma raiz: o provedor não é seu; ele vai fazer coisas que você não controla, e o seu lado precisa continuar correto quando isso acontecer. É o mesmo espírito de tratar erro transitório como transitório.