Você configura um webhook em vinte minutos. Registra uma URL, escolhe os eventos que importam, vê o primeiro payload chegar nos logs. Parece uma daquelas raras peças de infraestrutura que simplesmente funcionam. Três semanas depois, um provedor de pagamentos dispara o mesmo evento de "pagamento aprovado" quatro vezes seguidas, seu handler processa quatro vezes, e um cliente acaba cobrado duas vezes pelo mesmo pedido.
Essa parte nunca aparece no guia de início rápido. Webhooks são uma das ideias mais simples em sistemas distribuídos: quando algo acontece, chame esta URL. Mas a distância entre "chamar esta URL quando algo acontece" e "construir algo que sobreviva à internet real" é enorme, e quase ninguém fecha essa distância até que algo quebre em produção.
A garantia de entrega que ninguém te prometeu
Isto é o que a maioria dos times erra no primeiro dia: assumem entrega "pelo menos uma vez" quando na verdade estão construindo para "exatamente uma vez", e só percebem o descompasso quando isso custa dinheiro. Qualquer provedor de webhooks que se preze, Stripe, GitHub, Shopify, qualquer um, documenta que os eventos podem chegar mais de uma vez e de fato vão chegar mais de uma vez. Falhas de rede, timeouts do seu lado, retries do provedor depois de um erro 5xx, um load balancer que mata a conexão no meio da resposta. Tudo isso parece igual do lado de quem envia: não chegou confirmação, então reenvia.
Se o seu handler trata cada webhook recebido como um evento novo que nunca viu antes, você não tem uma integração de webhooks. Você tem um sistema que em algum momento vai processar duas vezes algo importante, e geralmente é dinheiro, estoque, ou uma notificação que sai duplicada e faz o seu produto parecer quebrado.
A solução não é complicada na teoria. Cada evento tem um ID. Você guarda quais IDs já processou. Você checa antes de agir. Na prática isso significa uma tabela extra, uma restrição de unicidade, e uma decisão sobre quanto tempo guardar esse histórico, porque "para sempre" não é de graça e "24 horas" pode não bastar se o provedor decidir reenviar uma entrega falha uma semana depois. A Stripe, como referência, reenvia certos webhooks falhos por até três dias. Desenhe sua janela de deduplicação para essa realidade, não para o caminho feliz que você testou em desenvolvimento.
A ordem é uma promessa que quase nenhum provedor faz
A segunda suposição que quebra as coisas em silêncio é a ordem. Você construiu seu handler assumindo que o evento de "pedido criado" chega antes do de "pedido enviado", porque foi assim que os fatos aconteceram. O problema é que as requisições HTTP não entram na fila educadamente. Dois eventos disparados com sessenta milissegundos de diferença podem chegar ao seu servidor em qualquer ordem, principalmente quando você roda mais de uma instância atrás de um load balancer, ou quando um retry fica na fila atrás de um evento mais recente.
A maioria dos provedores é explícita sobre isso na documentação, e a maioria das integrações ignora o aviso do mesmo jeito, porque construir um handler que não depende da ordem dá mais trabalho do que construir um que assume uma linha do tempo. As opções honestas são: desenhar sua máquina de estados para que eventos fora de ordem sejam seguros (um pedido que já foi marcado como enviado simplesmente ignora um evento tardio de "pedido criado"), ou consultar de novo o estado atual na API de origem em vez de confiar que o payload conta a história completa. As duas custam mais no início do que "processar o evento como ele chega". As duas evitam o chamado de suporte que diz que o sistema mostra um pedido criado depois de enviado, algo genuinamente difícil de explicar para alguém que não é técnico.
Verificar a assinatura não é opcional, e pular essa etapa é mais comum do que parece
Seu endpoint de webhook é uma URL pública que dispara ações reais no seu sistema quando recebe um POST. Se você não verifica que a requisição realmente veio do provedor, você construiu uma API que qualquer pessoa na internet pode chamar para fazer seu sistema acreditar que um pagamento foi aprovado, que uma conta foi criada, ou que um envio saiu.
Todo provedor que leva isso a sério assina seus payloads com uma assinatura HMAC em um header, e a documentação de cada provedor explica como verificar. O motivo desse gap continuar aparecendo em sistemas de produção não é falta de conhecimento. É que verificar a assinatura parece uma formalidade quando você está testando localmente com curl e um payload fixo, então isso fica comentado "por enquanto", e o card para adicionar depois nunca é priorizado quando a feature já saiu e está funcionando.
Os engenheiros que fazem isso corretamente tratam a verificação de assinatura como parte do endpoint, não como uma melhoria sobre ele. Não existe uma versão "pronta" de um handler de webhooks sem isso, do mesmo jeito que não existe uma versão "pronta" de um login sem verificar a senha.
Os retries vão durar mais que suas suposições sobre tempo
Os provedores não desistem depois de uma tentativa falha. Eles reenviam, geralmente com backoff exponencial, às vezes por dias. Isso significa que seu handler precisa sobreviver a ser chamado por um evento que aconteceu na terça passada, com um estado que já mudou por baixo dos panos. Um pedido que foi cancelado depois que o webhook disparou mas antes de o retry chegar. Um usuário que foi excluído entre a primeira tentativa e a quinta.
É aqui que idempotência e ordem deixam de ser preocupações separadas e começam a se somar. Um handler idempotente que assume que o estado atual bate com o timestamp do payload ainda vai errar. Os incidentes reais quase nunca são causados pela primeira entrega. São causados pelo terceiro retry, quatro dias depois, atingindo um sistema que já seguiu em frente sem avisar ninguém.
Timeouts são um contrato que você não leu
Os provedores esperam que seu endpoint responda rápido, geralmente dentro de poucos segundos, e contam um timeout como uma falha que vale a pena reenviar. Se seu handler faz trabalho real de forma síncrona (atualizar um banco, chamar uma API downstream, enviar uma notificação) antes de devolver um 200, você está apostando que tudo isso termina dentro de uma janela que você não controla.
O padrão que funciona: confirmar o recebimento imediatamente, e fazer o trabalho real de forma assíncrona em uma fila. Isso adiciona uma peça a mais no sistema, e é exatamente por isso que muitos times pulam essa etapa no início. Também é por isso que esses mesmos times acabam debugando processamento duplicado misterioso mais tarde, porque um handler síncrono lento que dá timeout parece, do lado do provedor, exatamente igual a um handler que nunca recebeu a mensagem. Então ele reenvia. E você volta para o primeiro problema, só que desta vez você mesmo causou.
Testar isso direito significa simular falha, não sucesso
A maioria dos times testa uma integração de webhook clicando em "enviar evento de teste" no painel do provedor uma vez e considerando pronto. Esse teste envia um payload limpo, bem formado, no horário certo, exatamente o único cenário que nunca ia quebrar seu handler.
Testar de verdade significa simular entrega duplicada, chegada fora de ordem, assinaturas inválidas e respostas lentas, nada disso o botão de teste do painel reproduz bem. Alguns times constroem um pequeno ambiente local que reproduz payloads capturados em produção com duplicatas, atraso e reordenação propositais. É trabalhoso montar isso, e é a única forma de descobrir como o seu handler se comporta exatamente nas condições que o quebram, antes que um cliente descubra isso por você.
O custo de fazer isso errado se acumula em silêncio
O perigo de um handler de webhooks malfeito é que ele pode rodar durante meses sem que ninguém note. O processamento duplicado não derruba o servidor. Ele cobra duas vezes um cliente em cada dez mil, ou dispara uma notificação duplicada uma vez em cada cem mil, e a menos que alguém esteja procurando duplicatas especificamente, o primeiro sinal de problema é um chamado de suporte que parece um caso isolado em vez de um padrão.
Quando alguém finalmente conecta os pontos, geralmente existe um acúmulo de registros afetados sem uma forma clara de distinguir quais foram realmente duplicados e quais foram processados duas vezes por outro motivo legítimo. Desembaraçar isso depois dos fatos toma muito mais tempo do que teria tomado construir a validação desde o início. Essa é a parte que transforma a confiabilidade de webhooks em uma questão genuinamente senior e não em um checkbox: a falha não se anuncia, ela se acumula.
Nada disso torna webhooks a escolha errada
Nada do que foi dito acima é um argumento contra webhooks. Polling é pior: mais lento, mais caro em escala, e não elimina nenhum desses problemas, só os esconde detrás de outro conjunto de suposições. Webhooks continuam sendo a ferramenta certa para integração baseada em eventos entre sistemas que não compartilham infraestrutura, e não vão sumir.
O ponto é que "adicionar um webhook" não é uma tarefa pequena, mesmo quando o código para receber um é dez linhas. Receber um payload é a tarefa pequena. Construir algo que sobreviva a entregas duplicadas, chegadas fora de ordem, requisições falsificadas, retries atrasados, e ao próprio tempo de resposta, tudo ao mesmo tempo, sem que ninguém note quando funciona bem, é a tarefa real. Isso não é uma feature de fim de semana. É infraestrutura, e merece orçamento, revisão e dono como qualquer outra peça de infraestrutura, muito antes de chegar o primeiro chamado perguntando por que um cliente foi cobrado duas vezes.




