Implementando retries de webhooks

Entregar um webhook é um ato otimista. Você está enviando uma requisição HTTP para um servidor que não controla, por uma rede que não consegue prever, torcendo para que ela chegue intacta. Na maior parte do tempo funciona. Mas quando não funciona (e uma hora não vai funcionar), a sua estratégia de retry determina se aquela entrega falha vira um contratempo pequeno ou um evento perdido que quebra silenciosamente a integração do seu cliente.

Um sistema de retry bem projetado transforma um mecanismo de transporte inerentemente não confiável em algo com que seus usuários podem contar. Um sistema mal projetado agrava a falha original com processamento duplicado, servidores atropelados e desenvolvedores frustrados vasculhando logs. Este guia cobre o que é preciso para acertar nos retries, das bases algorítmicas aos detalhes operacionais que separam uma infraestrutura de webhooks de nível produção de um simples fire-and-forget de melhor esforço.

Nunca perca um webhook.

A Hookdeck cuida dos retries, do rastreamento de erros e da recuperação — para que entregas com falha não virem dados perdidos.

Por que os retries existem

HTTP não é um protocolo de entrega garantida. Requisições falham por dezenas de motivos: o servidor do consumidor está no meio de um deploy, um load balancer está rotacionando, uma região de cloud está com a rede degradada ou a aplicação receptora simplesmente caiu. São condições transitórias. O evento em si é perfeitamente válido, só chegou em um momento ruim.

Sem retries, toda falha transitória vira perda permanente de dados. Seu cliente perde uma confirmação de pagamento, uma atualização de envio ou um evento de provisionamento de usuário. Ele só descobre quando algo quebra mais adiante, e a essa altura o dano à confiança já está feito.

Os retries existem para preencher a lacuna entre garantias de entrega at-least-once e a realidade bagunçada dos sistemas distribuídos. Eles dão ao sistema receptor tempo para se recuperar e dão ao evento mais uma chance de chegar. Mas a forma como você faz o retry importa enormemente, tanto para a confiabilidade da entrega quanto para a saúde dos sistemas nas duas pontas da conexão.

Backoff exponencial: dando espaço para o endpoint respirar

Quando uma tentativa de entrega falha, o impulso natural é reenviar na hora. Para o primeiro retry, isso faz sentido: uma interrupção breve de rede pode já ter passado. Mas, se a segunda e a terceira tentativas também forem rejeitadas, disparar requisições no mesmo ritmo começa a fazer mais mal do que bem. Um endpoint sob dificuldade não se beneficia de uma enxurrada de tráfego duplicado.

O backoff exponencial resolve isso espaçando cada tentativa cada vez mais conforme as falhas se acumulam. Um cronograma representativo pode ser assim:

TentativaEspera após a falha
1Imediata
25 segundos
35 minutos
430 minutos
52 horas
65 horas
710 horas
810 horas

As tentativas iniciais capturam rapidamente falhas passageiras. As posteriores acomodam indisponibilidades mais longas (como uma migração de servidor, um certificado TLS expirado ou um incidente do provedor de cloud) sem queimar todas as tentativas nos primeiros minutos. A janela total deste exemplo cobre cerca de 27 horas, dando ao consumidor um dia útil inteiro para perceber e corrigir o que deu errado.

A principal decisão de projeto é onde colocar o teto do intervalo de backoff. Baixo demais e você esgota as tentativas antes que o consumidor tenha tempo de reagir. Alto demais e eventos urgentes ficam no limbo por um período irrazoável. A maioria dos sistemas de webhooks em produção limita cada intervalo de retry entre 6 e 12 horas, com uma janela total de retry de 1 a 3 dias.

Adicionando jitter para evitar colisões de retry

No entanto, o backoff determinístico tem um ponto cego. Imagine o servidor de um consumidor saindo do ar às 14h com 10.000 eventos de webhook na fila para entrega. Todos falham na primeira tentativa simultaneamente e, como a fórmula de backoff é a mesma para todos, todos vão repetir exatamente nos mesmos instantes futuros: 14:00:05, 14:05:00, 14:30:00. O servidor volta e imediatamente absorve uma parede sincronizada de requisições, que pode derrubá-lo de novo.

Esse efeito manada é o motivo pelo qual sistemas de retry em produção introduzem jitter: uma dose controlada de aleatoriedade aplicada a cada intervalo calculado. Se o algoritmo de backoff diz "repita em 5 minutos", o jitter pode ajustar isso para qualquer ponto entre 3:30 e 6:30 para um dado evento. O resultado é que os retries chegam pingando ao longo de uma janela, em vez de virem como uma rajada coordenada.

As três abordagens mais comuns de jitter diferem no quanto aleatorizam:

  • Full jitter escolhe um valor aleatório entre zero e o intervalo calculado. Isso maximiza a dispersão, mas faz com que alguns retries aconteçam antes do esperado.
  • Equal jitter usa o intervalo calculado como âncora, aleatorizando apenas na metade superior da faixa. Você garante um espaçamento mínimo confiável com variação suficiente para evitar colisões.
  • Decorrelated jitter deriva cada intervalo do anterior, em vez do número da tentativa, o que naturalmente dessincroniza eventos que começaram a falhar em momentos diferentes.

Na prática, qualquer abordagem de jitter supera de longe uma curva de backoff pura. A estratégia específica importa menos do que o princípio: dois eventos nunca devem repetir no mesmo relógio.

Usando códigos de resposta para decidir sobre o retry

O código de status HTTP que o consumidor retorna é o seu melhor sinal para decidir o próximo passo. Tratar todas as respostas não-2xx da mesma forma cria problemas evitáveis.

A distinção central é entre falhas que provavelmente se resolvem sozinhas e falhas que não vão mudar por mais que você reenvie. Um 503 Service Unavailable quase sempre se resolve em minutos; o servidor está vivo, mas temporariamente sobrecarregado ou em manutenção. Já um 401 Unauthorized significa que as credenciais do consumidor estão erradas ou foram revogadas. Enviar a mesma requisição uma hora depois não resolve isso.

Na prática, códigos 5xx e falhas de conexão (timeouts, conexões recusadas, erros de DNS) justificam retry. A maioria dos códigos 4xx não, com duas exceções importantes. Um 408 Request Timeout sugere que o servidor foi apenas lento demais e pode ter sucesso em uma tentativa seguinte. Um 429 Too Many Requests é o consumidor dizendo ativamente para você reduzir o ritmo; você deve repetir, mas apenas depois de respeitar o intervalo que ele indicar.

Quando um consumidor retorna 429 com um header Retry-After, esse valor deve sobrepor totalmente o seu algoritmo de backoff. Se o header diz para esperar 120 segundos, espere pelo menos 120 segundos, mesmo que o seu cronograma exponencial fosse repetir antes. Ignorar sinais explícitos de backpressure corre o risco de ter seu tráfego bloqueado na infraestrutura do consumidor.

Respostas de redirecionamento (3xx) são um caso à parte. Seguir redirecionamentos na entrega de webhooks abre riscos de segurança e complica a verificação de assinatura. O padrão mais seguro é tratá-las como falhas não repetíveis e mostrar a URL de redirecionamento ao consumidor, para que ele reconfigure o endpoint.

Lidando com timeouts

Antes mesmo de qualquer código de resposta entrar em cena, você precisa decidir quanto tempo esperar por uma resposta. Se o servidor do consumidor aceita a conexão mas leva 60 segundos processando o webhook de forma síncrona antes de responder, você fica com recursos presos do seu lado durante todo esse tempo.

A maioria dos provedores de webhooks define janelas de timeout agressivas, normalmente entre 5 e 15 segundos. Isso é deliberado. Incentiva os consumidores a adotar padrões de processamento assíncrono: aceitar o webhook, persistir em uma queue, retornar 200 imediatamente e tratar a lógica de negócio em um worker em segundo plano.

De todo modo, documente seu timeout com clareza. Se seus consumidores não souberem que têm 5 segundos para responder, vão construir handlers síncronos que estouram o tempo de forma intermitente sob carga, disparando retries de eventos que eles de fato receberam.

Dead-letter queues: uma rede de segurança

Todo cronograma de retry tem uma última tentativa. Quando ela falha, o evento precisa ir para algum lugar. Descartá-lo silenciosamente é o pior desfecho: seu cliente perde dados e não tem como saber até que algo mais adiante quebre de forma confusa.

Uma dead-letter queue armazena eventos que não puderam ser entregues após todas as tentativas, preservando o payload original junto com metadados sobre cada tentativa que falhou (timestamps, códigos de resposta, mensagens de erro). O valor de uma dead-letter queue depende inteiramente do que você constrói em volta dela. Uma fila em que os eventos entram mas nunca saem é funcionalmente equivalente a descartá-los.

Três capacidades transformam uma dead-letter queue de armazenamento passivo em uma ferramenta real de recuperação. Primeiro, um mecanismo de replay que permita aos consumidores redisparar a entrega de eventos individuais ou fazer replay em massa de tudo a partir de uma data, assim que corrigirem o problema de origem. Segundo, uma política de retenção que mantenha os eventos em dead letter por tempo suficiente para serem úteis. Trinta dias é um piso razoável, embora integrações financeiras ou sensíveis a compliance possam exigir mais. Terceiro, notificações proativas que alertem o consumidor conforme os eventos se acumulam na fila, em vez de esperar que ele perceba a falta de dados por conta própria.

Desativação de endpoints: protegendo os dois lados

Se o endpoint de um consumidor falha de forma consistente por um período prolongado (digamos, todas as tentativas de entrega ao longo de vários dias), continuar enviando tráfego para ele é desperdício para você e potencialmente prejudicial para ele. Alguns modos de falha, como um firewall mal configurado, não se resolvem sem intervenção humana.

Sistemas de webhooks em produção costumam implementar desativação automática de endpoints. Após um período sustentado de falhas (frequentemente de 3 a 5 dias), o endpoint é marcado como inativo e nenhuma nova entrega é tentada até que o consumidor tome uma ação.

A chave para fazer isso bem é a comunicação. Ao desativar um endpoint, dispare uma notificação operacional (um e-mail, um alerta no dashboard, um webhook operacional separado, alguma coisa!) para que o consumidor saiba o que aconteceu e possa reativar o endpoint depois de corrigir o problema. Desativar em silêncio é quase tão ruim quanto perder dados em silêncio.

Idempotência: contando com as duplicatas

Retries produzem, por natureza, risco de entrega duplicada. O cenário mais comum: seu sistema envia um webhook, o consumidor o processa com sucesso, mas a resposta dele se perde por um problema de rede. Do seu ponto de vista, a entrega falhou, então você repete. Do ponto de vista do consumidor, ele recebeu o mesmo evento duas vezes.

É por isso que o processamento idempotente é um complemento essencial de qualquer estratégia de retry. Como provedor de webhooks, você pode dar suporte à idempotência incluindo um identificador único de evento em cada entrega (normalmente em um header). Os consumidores então deduplicam do lado deles, controlando quais IDs de evento já processaram.

Faça esse identificador ser estável entre retries: o mesmo evento deve carregar o mesmo ID, seja na primeira ou na quinta tentativa. Isso dá aos consumidores uma chave confiável para detectar e descartar duplicatas.

Deixando o consumidor definir a própria política de retry

Nenhum cronograma único de retry serve para todos os casos de uso. Uma integração de processamento de pagamentos pode precisar de retries agressivos em uma janela curta, porque dados de transação desatualizados não valem nada. Uma sincronização de CRM pode preferir retries mais suaves distribuídos em um período mais longo, porque os dados continuam válidos por dias.

Sempre que possível, exponha a configuração de retry aos seus consumidores. Isso pode incluir o número de tentativas, o multiplicador de backoff, o intervalo máximo de retry ou a janela total. Mesmo oferecer a escolha entre duas ou três políticas predefinidas (por exemplo, "agressiva", "padrão", "relaxada") é mais útil do que um cronograma único e fixo.

Essa flexibilidade sinaliza aos seus consumidores que você pensou a sério nas diversas formas como webhooks são usados em produção, e reduz a carga de suporte de atender pedidos de usuários cujas necessidades não batem com os seus padrões.

Circuit breakers: estancando a sangria

Os retries por evento tratam cada entrega de webhook como um problema independente. Isso funciona bem para falhas isoladas, mas desmorona quando um endpoint está genuinamente fora do ar. Se o servidor de um consumidor está inacessível há uma hora, enfileirar centenas de cadeias individuais de retry para cada evento desse período desperdiça workers de entrega e memória sem aproximar nada do sucesso.

Circuit breakers operam no nível do endpoint, não do evento. Eles acompanham a taxa recente de falhas de um destino e, quando essa taxa cruza um limite configurável, interrompem imediatamente todas as entregas pendentes e novas para aquele endpoint. Em vez de tentar entregar e esperar por um timeout, o sistema desvia os eventos direto para recuperação de erros e os enfileira para replay posterior.

Após uma janela de resfriamento, tipicamente de 30 a 120 segundos, o circuit breaker deixa passar uma única requisição de sondagem para testar se o endpoint se recuperou. Uma sondagem bem-sucedida reabre o fluxo normal de entrega. Uma sondagem que falha reinicia o resfriamento.

A decisão de projeto mais crítica é o escopo. Circuit breakers devem ser implementados por endpoint de consumidor. Um circuit breaker global, que dispara quando o endpoint de um único cliente degrada, interromperia a entrega para todos os clientes saudáveis da sua plataforma — o oposto de isolamento de falhas. Breakers por endpoint contêm o raio de impacto, para que uma integração quebrada não consuma a capacidade de entrega de todo mundo.

Documentando o seu comportamento de retry

Políticas de retry só são úteis se os seus consumidores souberem delas. Desenvolvedores que integram seus webhooks precisam de respostas claras para perguntas específicas:

  • Qual é o cronograma exato de retry? Não "repetimos algumas vezes" — os intervalos reais.
  • Quais códigos de resposta são tratados como falha? Redirecionamentos são seguidos ou tratados como erro?
  • Qual é a janela total de retry antes de o evento ir para dead letter?
  • O que acontece com o endpoint após falhas prolongadas?
  • Existe uma forma de fazer replay manual de eventos que falharam?
  • Qual é a janela de timeout que o consumidor tem para responder?

Ambiguidade aqui obriga os consumidores a fazer engenharia reversa do seu comportamento por tentativa e erro. Documentação detalhada, por outro lado, permite que eles construam os handlers corretamente desde o início e reduz tickets de suporte dos dois lados.

Monitoramento e observabilidade

Um sistema de retry que você não consegue observar é um sistema de retry em que você não pode confiar. Instrumente sua infraestrutura de retry para acompanhar, no mínimo:

  • Taxa de sucesso na primeira tentativa. Que percentual das entregas tem sucesso sem nenhum retry? Uma taxa em queda pode indicar um problema sistêmico no endpoint de um consumidor ou na sua própria infraestrutura de entrega.
  • Distribuição de retries. Quantos eventos exigem 1, 2, 3 ou mais retries? Um pico de eventos que precisam de muitas tentativas sugere um consumidor com problemas persistentes.
  • Taxa de dead letter. Quantos eventos esgotam todas as tentativas? Esse é o seu sinal de confiabilidade mais importante.
  • Tempo até a entrega bem-sucedida. Para eventos que acabam tendo sucesso após retries, quanto tempo leva o ciclo completo? Isso mostra quanta latência o seu sistema de retry introduz.

Exponha essas métricas tanto internamente (para o time de operações) quanto externamente (para os consumidores, via dashboard ou API). Consumidores que conseguem ver as próprias taxas de sucesso de entrega estão muito mais preparados para diagnosticar e corrigir problemas de integração.

Juntando tudo

Cada um dos padrões descritos aqui resolve um modo de falha específico. Mas a força real deles vem de como se encaixam. Backoff sem jitter ainda cria picos de tráfego. Jitter sem atenção aos códigos de resposta desperdiça retries em falhas permanentes.

Construir um sistema resiliente de retry significa pensar nesses padrões como um mecanismo único e interligado, não como uma lista de recursos independentes. Quando uma camada falha em capturar um problema, a seguinte deve capturar. Quando os sistemas automatizados finalmente se esgotam, as ferramentas operacionais devem entregar o problema a uma pessoa em um estado sobre o qual ela consiga agir.

O objetivo não é a perfeição. Sistemas distribuídos sempre vão encontrar novas formas de falhar. O objetivo é tornar as falhas recuperáveis, visíveis e limitadas, para que um problema momentâneo de rede continue sendo um problema momentâneo de rede em vez de virar um incidente que atinge o cliente.

Como a Hookdeck ajuda

Implementar boas práticas de retry corretamente é mais do que escolher uma curva de backoff exponencial. É acompanhar o que já foi repetido, expor padrões de falha, mandar para dead letter os eventos que estouram o orçamento de tentativas e dar aos operadores uma forma de fazer replay manual depois que o problema de origem for resolvido. Construir isso do zero significa implementar backoff exponencial, circuit breakers, tratamento de dead letter e ferramentas de replay manual, e manter tudo isso junto com o código do seu produto.

O Event Gateway da Hookdeck é infraestrutura gerenciada para receber e entregar webhooks de forma confiável. Ele faz retry automático das entregas que falharam com estratégias de backoff configuráveis, agrupa falhas relacionadas em Issues para que você veja as causas raiz e faça retry em massa dos eventos afetados com um clique, e oferece deduplicação nativa para que eventos reenviados nunca produzam efeitos colaterais duplicados. Cada entrega é rastreada de ponta a ponta, com payload, headers e dados de resposta completos.