Webhook é o mecanismo mais comum de integração entre sistemas: quando algo acontece no sistema A (um pagamento, um pedido, uma mensagem), ele faz uma chamada HTTP para o sistema B avisando. É simples de implementar no caminho feliz, e é por isso que quase toda integração por webhook em produção tem o mesmo trio de defeitos latentes: perde evento quando B está fora do ar, duplica efeito quando A reenvia e falha em silêncio quando algo no meio muda.
O padrão que descrevemos aqui cabe em qualquer stack e elimina os três. Nada dele é novidade; a diferença entre as integrações que operam anos sem incidente e as que perdem pedido de madrugada é aplicá-lo por inteiro.
Assuma a entrega "pelo menos uma vez"
Todo emissor sério de webhooks reenvia quando não recebe HTTP 2xx, seja por timeout, por erro 500 ou por queda de rede. Consequência direta: o mesmo evento vai chegar mais de uma vez, e o seu lado precisa ser idempotente (processar a duplicata sem repetir o efeito). A implementação mais barata e mais confiável é uma restrição de unicidade no banco, usando o identificador que o emissor manda:
CREATE TABLE eventos_recebidos (
origem text NOT NULL,
evento_id text NOT NULL,
payload jsonb NOT NULL,
status text NOT NULL DEFAULT 'pendente',
recebido_em timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (origem, evento_id)
);
O INSERT que conflita nessa chave é uma duplicata: responda 200 e descarte. Checagem de duplicata em memória ou em cache deixa de proteger no primeiro reinício do processo ou na segunda réplica do serviço.
Grave primeiro, processe depois
O erro estrutural mais comum é processar o evento dentro do request do webhook: chamar a API do ERP, enviar o e-mail, baixar o estoque, e só então responder. Se qualquer passo demorar, o emissor estoura o timeout (tipicamente 5 a 10 segundos), marca falha e reenvia, agora com o efeito parcialmente aplicado.
Inverta a ordem. O handler do webhook faz três coisas: valida a assinatura, insere a linha em eventos_recebidos e responde 200. Um worker separado consome os pendentes e aplica os efeitos. O request passa a durar milissegundos, o reenvio vira duplicata inofensiva, e um pico de mil eventos vira fila em vez de avalanche.
A validação de assinatura é obrigatória nesse desenho: o endpoint é público, e sem verificar o HMAC (o hash que o emissor calcula com um segredo compartilhado) qualquer um que descubra a URL fabrica eventos.
Reprocesso com desistência explícita
O worker vai falhar em algum momento: a API de destino cai, o dado vem malformado. Retentativa imediata em loop só transforma falha passageira em martelo. O espaçamento exponencial dá tempo do mundo se recuperar:
tentativa 1: imediata tentativa 4: +30 min
tentativa 2: +1 min tentativa 5: +4 h
tentativa 3: +5 min depois: status 'morto' + alerta
O estado final importa tanto quanto as tentativas. Evento que esgotou as tentativas muda para um status terminal e dispara alerta para alguém olhar; nenhum evento é descartado sem registro. No nosso histórico de sustentação, o defeito mais recorrente em integração alheia é exatamente este: o caminho de erro existe no código, mas nunca foi exercitado, e o dia em que roda pela primeira vez é o dia do incidente. Teste o caminho de falha como se testa o de sucesso: derrube o destino de propósito em homologação e assista ao reprocesso funcionar.
Reconciliação para o que ninguém previu
Idempotência e reprocesso cobrem as falhas conhecidas. Sobra a categoria que nenhum código prevê: o emissor que parou de enviar, o segredo rotacionado, a URL alterada por engano. Para essa categoria o mecanismo é contábil. Uma rotina diária compara os totais dos dois lados:
eventos emitidos por A (via API de consulta, últimas 24h): 1.412
eventos registrados em B: 1.409
diferença: 3 → alerta, com os IDs faltantes no corpo
O mecanismo é deselegante e cumpre o papel: as três linhas de diferença de hoje são os pedidos perdidos que, sem a reconciliação, alguém descobriria no fechamento do mês.
Se existe uma integração na sua operação em que ninguém saberia dizer quantos eventos se perderam no último trimestre, é por ela que vale começar: contato@castelapi.com.br.