Um cliente manda um e-mail: "meu webhook da Stripe está quebrado". Você confere o Dashboard da Stripe e os eventos estão disparando. Você confere os logs do servidor e não há nada. No Sentry, nada. Os eventos estão indo para algum lugar, só não para onde você espera.
Este texto é o roteiro passo a passo para depurar problemas de entrega de webhooks em produção, com base nos modos de falha que mais aparecem.
TL;DR
- Comece pelo painel do provedor: o evento está disparando, e qual status ele está recebendo de volta?
4xxsignifica que você está recusando o evento (assinatura, middleware, URL errada)5xxsignifica que seu handler quebrou. Confira o monitoramento de erros naquele timestamp exato- Um timeout significa que seu handler está lento demais. Passe o trabalho pesado para assíncrono e responda 200 rápido
- A solução para "levou 2 horas para depurar" é um lugar só que mostre juntos a entrada, a saída, as novas tentativas (retries) e o replay (um novo envio de um evento armazenado, feito por você). Na prática, isso costuma significar um relay de webhooks (um serviço que recebe, guarda e encaminha seus webhooks)
A árvore de decisão
Comece por aqui. Isso poupa 30 minutos de chute.
P1: O provedor está mesmo enviando o evento?
Confira o painel do provedor: na Stripe, Developers → Events; no GitHub, Settings → Webhooks → Recent Deliveries (entregas recentes); na Shopify, Dev Dashboard → Apps → seu app → Logs.
Se o evento não está lá, o gatilho não está disparando; então vá depurar, mais acima na cadeia, o estado que deveria gerar o evento. Se está lá, vá para a P2.
P2: O provedor está recebendo um 2xx de volta?
No mesmo painel, veja o status da resposta do seu endpoint.
2xx: o provedor acha que deu certo. Se sua aplicação nunca viu o evento, ele chegou e se perdeu depois da resposta. Confira sua fila assíncrona, a escrita no banco e a lógica do handler.4xx: você está recusando o evento. Na maioria das vezes é uma falha na verificação de assinatura, um cabeçalho ausente ou um middleware (WAF, rate limiter, camada de autenticação) bloqueando a requisição.5xx: seu endpoint quebrou. Confira o monitoramento de erros, e não só "a última hora", mas o timestamp exato dessa entrega.- Timeout: seu endpoint está lento demais. Veja a P4.
- Conexão recusada ou DNS: seu endpoint está inacessível, o que é outro problema, que mora em algum lugar da rede, do DNS ou do firewall.
P3: Se for 4xx, qual?
400, requisição inválida. Normalmente é um cabeçalho que não bate, ou sua verificação de assinatura lançou uma exceção antes de conseguir devolver um erro claro. Confira o que seu handler responde quando o corpo está malformado ou a assinatura está ausente.401/403, você está recusando a requisição como não autorizada. Falha na verificação de assinatura? Um middleware exigindo sessão de usuário num endpoint que não deveria precisar de uma?404, a URL está errada. Você fez deploy de uma mudança de rota que moveu o endpoint do webhook?413, payload grande demais. Aumente o limite do seu body parser.429, o rate limiter disparou. O que quer que esteja aplicando rate limit na sua rota de webhooks não deveria estar. A maioria dos provedores de webhooks envia a partir de um conjunto finito de IPs, e seu limiter pode estar tratando todos eles como um único atacante.
P4: Se for timeout, o que está lento?
O GitHub dá 10 segundos. A Shopify dá 5. O Slack e o Discord dão 3. A Stripe nunca publicou um número; a documentação dela só diz para responder 2xx antes de qualquer lógica lenta. A tabela completa, com uma fonte por linha, está na referência de provedores de webhook.
A maioria dos timeouts vem de trabalho demais dentro do handler. Os culpados de sempre são escritas síncronas no banco de dados com um pool saturado, uma chamada para outro serviço (Stripe, Salesforce, Notion, sua própria API) de dentro do handler, o envio síncrono de um e-mail ou de uma mensagem no Slack e o log do corpo inteiro no stdout com um coletor de logs aplicando backpressure.
O padrão da correção: verifique a assinatura, coloque o evento na fila, responda 200. Toda a lógica de negócio acontece depois, de forma assíncrona.
O que normalmente está quebrado de verdade
Depois de alguns anos vendo isso, este é o 80/20:
- A verificação de assinatura está serializando o corpo de novo. Veja o guia de verificação de assinatura
- Um proxy ou WAF está engolindo o webhook antes que ele chegue ao seu handler. Confira os logs de requisições da Cloudflare, da Vercel ou do que sua plataforma usa
- O rate limit está disparando. O Bot Management da Cloudflare pode dar falso positivo em webhooks legítimos
- A URL do endpoint mudou num deploy e ninguém atualizou o provedor
- Falta uma variável de ambiente em produção (segredo de assinatura, URL do banco) e o handler responde 500 em silêncio
- Uma tarefa em segundo plano está quebrando depois que o webhook responde 200. O provedor acha que está tudo bem, mas nada está sendo processado
O número 6 é endêmico em serverless, onde o trabalho agendado para depois da resposta não tem garantia nenhuma de durabilidade: Como receber webhooks na Vercel sem perder nenhum. E, se o sintoma é uma duplicata e não uma perda, normalmente é o sistema funcionando como projetado: Você recebeu o mesmo webhook duas vezes.
Ferramentas que ajudam de verdade
Os painéis dos provedores são bons para um evento de cada vez e ruins para padrões. Se você quer "todos os eventos com falha nas últimas 24 horas", vai ter que rolar a tela manualmente.
O ngrok e o webhook.site servem para desenvolvimento local. Eles não ajudam você em produção.
Seus próprios logs ajudam se você registrou a coisa certa. Se você registrou "webhook received for event evt_xxx" antes da verificação de assinatura, pelo menos sabe que o evento chegou. A maioria das pessoas registra depois, o que deixa invisíveis os eventos com falha de assinatura.
Um relay de webhooks como o AnyHook fica na frente do seu handler e registra cabeçalhos, corpo, tempos, status da resposta e número de tentativas. O ciclo de depuração vira: olhe o fluxo de eventos, clique no que falhou, inspecione o payload bruto, faça o replay.
O ciclo de depuração do AnyHook
Quando alguém escreve para o suporte do AnyHook dizendo "meu webhook não está funcionando", o painel normalmente deixa a causa óbvia em menos de um minuto:
- Abra o fluxo de eventos e filtre por
status=failed - Clique no evento com falha
- Olhe os cabeçalhos de entrada (a assinatura estava lá?), o corpo de entrada (tinha o formato que você esperava?), a resposta de saída (o que seu servidor respondeu?) e o detalhamento dos tempos (edge → fila → entrega)
- Se foi um caso isolado, um deploy ruim ou uma rede instável, clique em Replay e veja o evento passar
- Se todos os eventos estão falhando, agora você sabe exatamente o que corrigir, e seus eventos continuam armazenados e disponíveis para replay durante a janela de replay do seu plano (3 dias no Free, 30 no Pro, 90 no Scale) enquanto você corrige
O que transforma uma sessão de depuração de 2 horas numa de 2 minutos é ter num lugar só a requisição que seu provedor de fato enviou, a resposta que seu servidor de fato devolveu e os timestamps entre elas. Sem isso, você fica reconstruindo tudo a partir de três painéis e dos seus próprios logs.
Para ver isso antes de se cadastrar em qualquer coisa, um único comando retorna uma URL de entrada ativa, sem conta:
curl -X POST https://anyhook.net/api/v1/quickstart
A resposta traz o campo inbound_url, para apontar um provedor, o campo api_key, para ler o log de eventos pela API, e o campo claim_url. O endpoint é temporário: o AnyHook exclui o endpoint e os eventos dele depois de 7 dias, a menos que você abra a URL de vinculação e entre na sua conta, o que mantém a mesma URL.
Conclusão
Se essa é a sua tarde de domingo com mais frequência do que você gostaria, o AnyHook transforma a maior parte disso num clique no painel.