Engenharia

Por que entregar um webhook de forma confiável é mais difícil do que parece

O que parece um POST trivial esconde um problema clássico de sistemas distribuídos: entrega confiável entre dois sistemas que falham em momentos diferentes.

Leitura de 6 minutos · Por HookSafe

Todo mundo que já integrou um gateway de pagamento (Kiwify, Hotmart, Stripe, Asaas) conhece o roteiro: a plataforma manda um POST pro seu endpoint avisando "pedido aprovado", e o seu sistema libera o acesso do cliente. Simples. Até o dia em que um cliente paga e não recebe nada, e você descobre que aquele webhook se perdeu no meio do caminho.

Este artigo destrincha os pontos que tornam isso difícil e como se resolve cada um.

O ingênuo: receber e processar

A primeira versão que todo mundo escreve é assim:

POST /webhook -> valida -> libera acesso -> envia e-mail -> responde 200

O problema aparece rápido. Se "libera acesso" e "envia e-mail" demoram, ou dependem de uma API externa lenta, a resposta 200 demora junto. E o remetente (o gateway) não espera pra sempre: ele tem um timeout. Se você não confirma a tempo, ele considera falha, mesmo que o seu processamento termine depois. Ambiguidade: do lado dele "falhou"; do seu lado, pode ter processado, ou não.

Ponto 1: responder rápido, processar depois

A primeira correção é desacoplar recepção de processamento:

POST /webhook -> grava o evento cru -> responde 200 imediatamente
(em segundo plano) worker -> lê o evento -> processa

O endpoint agora faz só duas coisas: persistir o payload e responder. Milissegundos. O trabalho pesado sai da frente e roda num worker separado. Isso introduz a necessidade de uma fila (ou de uma tabela de eventos funcionando como fila). E fila traz os próximos problemas.

Ponto 2: idempotência

Gateways reenviam webhooks quando acham que falharam. Isso é bom (não perde evento), mas significa que você vai receber o mesmo evento mais de uma vez. Se o worker processa cada entrega cegamente, o cliente recebe o produto duas vezes, ou você conta a venda em dobro.

A solução é idempotência: cada evento precisa de uma chave estável, e você processa cada chave uma única vez. Um detalhe que pega muita gente: use um identificador do recurso (o id do pedido/venda), não um id da tentativa de entrega. Na Kiwify, por exemplo, o id do "envelope" muda a cada reenvio, então não serve pra deduplicar. No banco, um UNIQUE na chave resolve limpo: a segunda inserção do mesmo evento não entra.

Ponto 3: retry com backoff

Até aqui protegemos contra o nosso endpoint ser lento. Mas o worker, ao entregar o evento pro destino final (o sistema do cliente), enfrenta o mesmo problema ao contrário: e se o destino estiver fora do ar?

Reentregar na hora, em loop, é a pior ideia: você martela um servidor que já está sofrendo. A resposta é backoff exponencial: falhou, espera um pouco e tenta de novo, aumentando o intervalo (1min, 5min, 15min, 1h). Depois de N tentativas sem sucesso, o evento vai pra uma Dead Letter Queue pra inspeção manual, em vez de tentar pra sempre.

Ponto 4: visibilidade e replay

Quando algo dá errado (e vai), você precisa enxergar. Sem log do payload que entrou, do que saiu e da resposta exata do destino, você fica no escuro. Guardar isso permite duas coisas valiosas: diagnosticar ("o servidor devolveu 500 nesse header") e reprocessar em massa (o destino ficou 2h fora, você reenvia todos os eventos daquele período de uma vez).

Ponto 5: aguentar o pico

Tudo isso é testado no pior momento: a Black Friday ou o lançamento, quando chegam milhares de eventos em minutos. O endpoint de ingestão precisa ser leve o suficiente pra absorver o pico sem cair (por isso ele só persiste e responde), e a fila precisa segurar o acúmulo enquanto o worker drena no seu ritmo. Desacoplar recepção de processamento não é elegância acadêmica: é o que mantém tudo de pé quando a carga sobe.

Juntando as peças

Uma arquitetura confiável de webhook, no mínimo, tem: um endpoint de ingestão que persiste e responde 2xx na hora; uma fila entre recepção e processamento; idempotência por chave estável; retry com backoff exponencial e uma DLQ; e logs que permitam diagnóstico e replay. Nenhuma peça é exótica sozinha. A dificuldade é precisar de todas juntas, corretas, pra não perder um evento, e mantê-las é trabalho contínuo.

É disso que o HookSafe cuida por você

Uma camada entre o gateway e o seu servidor que responde 2xx na hora e resolve fila, retry, idempotência e replay, sem reescrever seu sistema. Estamos em early access.

Entrar na waitlist

Se o tema te interessa, o problema é mais fundo do que parece, e é um puta exercício de engenharia. Como você resolve entrega confiável de webhook nos seus projetos?