Webhooks3 de agosto de 20269 min de leitura

A Stripe desativou seu endpoint de webhook. Este é o roteiro de recuperação.

A Stripe desativou seu endpoint de webhook, e a janela de recuperação de 30 dias é menor do que parece. A sequência: reativar, puxar da Events API.

O QUE AINDA DÁ PARA RECUPERAR, E POR QUANTO TEMPO3 dias de novas tentativasavisodesativadoa Events API ainda guarda tudo · 30 diasprimeira falha+30 dias, excluídoreativar retoma só os eventos novos. não faz replay de nada.a recuperação é preencher a lacuna: GET /v1/events?delivery_success=falsepasse tudo por um consumidor idempotente, ou haverá processamento duplicado
Reativar é a parte fácil, os 10%. A janela de 30 dias da Events API é a parte que custa dinheiro de verdade se você a perder.

O assunto do e-mail é alguma variação de "Your webhook endpoint is failing" e, se você ignorou o primeiro, o seguinte diz que o endpoint foi desativado. Agora nada chega. Assinaturas não são provisionadas, faturas não são conciliadas, e você não sabe até onde vai o estrago.

A boa notícia: a Stripe guarda uma cópia de cada evento por 30 dias, tenha a entrega dado certo ou não. Quase tudo é recuperável se você agir dentro dessa janela. A má notícia: a maioria das pessoas reativa o endpoint, vê os checks verdes no tráfego novo e nunca preenche a lacuna (backfill). A lacuna é a parte que custa dinheiro.

TL;DR

  • Reative o endpoint primeiro, mas isso só retoma os eventos novos. Nada é reenviado
  • Preencha a lacuna com GET /v1/events?delivery_success=false, que alcança até 30 dias atrás
  • Garanta a idempotência antes de preencher a lacuna, ou você vai liberar o mesmo pedido duas vezes
  • A causa raiz quase sempre é um handler lento, não um handler quebrado
  • Grave o payload antes de processá-lo, e isso deixa de ser uma emergência

Por que a Stripe desativa endpoints

No modo de produção, a Stripe tenta de novo uma entrega com falha por até três dias, com backoff exponencial. É uma janela generosa, e ela existe para que um deploy ou uma queda breve não custe nada. Retry de webhooks da Stripe: um guia para produção percorre esse cronograma e mostra como projetar para ele antes de chegar à situação de que este post trata.

Mas três dias não é um padrão, e supor que é vai custar caro na próxima integração. A Paddle envia o mesmo evento com falha até sessenta vezes em cerca de dois dias. A Paddle tenta 60 vezes em dois dias. A Stripe espaça as tentativas. põe os dois cronogramas lado a lado, o que importa mais se você cobra pelas duas.

Um endpoint que falha continuamente é outro sinal. O endereço não funciona mais, e continuar martelando nele desperdiça a capacidade de entrega da Stripe e enterra seu log de eventos em ruído. Então a Stripe avisa você por e-mail, informa a data em que o endpoint será desligado se nada mudar e, no fim, desliga.

Vale internalizar: a desativação é o segundo aviso, não o primeiro. Quando ela acontece, as falhas já estão visíveis no Dashboard há dias. Se o e-mail de aviso foi para uma caixa de entrada de fundador que ninguém lê, ou para um alias que devolveu a mensagem, esse é o bug de verdade.

Passo 1: descubra o que quebrou antes de reativar

Resista à vontade de clicar em Enable (ativar) imediatamente. Se a causa de fundo continuar lá, você vai queimar a janela de novas tentativas (retries) uma segunda vez.

Encontrar essa causa é um exercício à parte, e fica mais difícil quanto mais você demora, porque as entregas com falha vão saindo do Dashboard enquanto você ainda está olhando para elas. Como depurar webhooks com falha em produção sem perder o fim de semana traz a ordem que vale seguir.

No Dashboard, vá em Developers → Webhooks (ou Workbench → Webhooks), abra o endpoint e veja as entregas recentes. A Stripe registra a resposta que recebeu em cada tentativa. Você está procurando qual destes é seu caso:

O que você vêO que significa
Timed outSeu handler está lento demais. De longe, o mais comum
500 / 502Seu código lançou um erro, ou a plataforma na frente dele lançou
503Sua aplicação estava fora do ar ou escalada para zero e nunca acordou
401 / 403Algo na frente da sua aplicação está bloqueando a Stripe. WAF, regra da Cloudflare, middleware de autenticação
404A rota mudou durante uma refatoração e ninguém atualizou a URL do endpoint

O caso 401/403 pega as pessoas de surpresa depois que adicionam proteção contra bots ou uma nova camada de autenticação, porque requisições de webhook não levam cookie de sessão e são tratadas como tráfego anônimo. O caso 404 costuma aparecer depois de uma migração de framework.

Corrija isso primeiro. Depois, ative.

Passo 2: reative e entenda o que isso não faz

Reativar retoma a entrega dos eventos criados daquele momento em diante. Eventos dos quais a Stripe já desistiu não são reenviados automaticamente. Há uma exceção estreita, documentada pela Stripe: se você desativa e reativa antes da próxima tentativa da Stripe, as novas tentativas em andamento ainda chegam. Se o endpoint ficou desativado por uma semana, nada vai voltar sozinho.

Então, a situação agora é esta: eventos novos chegando, lacuna histórica intacta.

Passo 3: preencha a lacuna pela Events API

O QUE AINDA DÁ PARA RECUPERAR, E POR QUANTO TEMPO3 dias de novas tentativasavisodesativadoa Events API ainda guarda tudo · 30 diasprimeira falha+30 dias, excluídoreativar retoma só os eventos novos. não faz replay de nada.a recuperação é preencher a lacuna: GET /v1/events?delivery_success=falsepasse tudo por um consumidor idempotente, ou haverá processamento duplicado

Esta é a parte que as pessoas pulam. A List Events API da Stripe expõe todos os eventos dos últimos 30 dias e aceita um filtro delivery_success:

curl -G https://api.stripe.com/v1/events \
  -u "$STRIPE_SECRET_KEY:" \
  -d delivery_success=false \
  -d "types[]=payment_intent.succeeded" \
  -d "types[]=customer.subscription.created" \
  -d "types[]=invoice.paid" \
  -d limit=100

Algumas coisas importam aqui:

Filtre por tipo em vez de puxar tudo. Uma conta movimentada gera muitos tipos de evento para os quais você não tem handler. Puxe os que você de fato processa.

Use ending_before com paginação automática. Passe o ID de um evento de pouco antes de a queda começar, e a paginação automática devolve os resultados em ordem cronológica. Isso importa: customer.subscription.created deve ser processado antes de customer.subscription.updated, e a ordem padrão é do mais novo para o mais antigo.

const missed = stripe.events.list({
  delivery_success: false,
  ending_before: "evt_lastKnownGood",
  types: ["payment_intent.succeeded", "invoice.paid"],
  limit: 100,
});

for await (const event of missed) {
  await handleEvent(event);   // must be idempotent (see below)
}

O limite de 30 dias é rígido. Eventos mais antigos que isso somem da API. Se seu endpoint foi desativado há cinco semanas, a primeira semana não tem recuperação pela Stripe, e você vai ter que conciliar a partir dos objetos charges e subscriptions, o que rende uma tarde muito pior.

Passo 4: garanta a idempotência ou não rode o backfill

Seu backfill pode incluir eventos que seu handler terminou depois que a Stripe já tinha registrado um timeout, e a Stripe pode ainda estar tentando de novo parte do que você agora está processando à mão. Se handleEvent não for idempotente, você vai enviar dois recibos, gerar duas chaves de licença ou despachar dois pedidos.

A versão barata, e ela basta de verdade:

INSERT INTO processed_events (stripe_event_id, processed_at)
VALUES ($1, NOW())
ON CONFLICT (stripe_event_id) DO NOTHING;

Zero linhas afetadas significa que você já viu esse evento e deve parar. Sempre que puder, faça esse insert na mesma transação que o efeito colateral. Se o efeito colateral for uma chamada a uma API externa que não pode entrar na sua transação, marque a linha como processing antes e como processed depois, para que um crash no meio do caminho fique visível, em vez de o evento ser tentado de novo em silêncio para sempre.

Escrevemos sobre a versão mais ampla desse problema, incluindo entrega fora de ordem e por que a entrega pelo menos uma vez (at-least-once) é uma escolha de design deliberada, em Você recebeu o mesmo webhook duas vezes.

Passo 5: corrija o que de fato causou isso

Pela nossa experiência, a desativação quase nunca é causada por um handler errado. Ela é causada por um handler lento. O padrão é este:

// The shape that eventually gets your endpoint disabled
export async function POST(req: Request) {
  const event = await verifyStripeSignature(req);
  await db.insertOrder(event);        // 40ms
  await salesforce.sync(event);       // 900ms, sometimes 8s
  await resend.sendReceipt(event);    // 300ms, sometimes times out
  await slack.notify(event);          // 200ms
  return Response.json({ ok: true });
}

Numa terça-feira tranquila, isso dá 1,5 segundo e ninguém percebe. Quando o Salesforce tem uma tarde ruim, ele trava, todas as entregas dão timeout ao mesmo tempo, e você fica a poucos dias de um endpoint desativado. A dependência que falhou nem é sua.

A correção é estrutural, não um timeout maior:

export async function POST(req: Request) {
  const event = await verifyStripeSignature(req);
  await queue.publish(event);   // durable write, ~10ms
  return new Response(null, { status: 200 });
}

Aceite, grave, confirme. Faça a sincronização com o Salesforce num worker que pode tentar de novo no próprio ritmo, sem a Stripe olhando o relógio. Agora um terceiro lento é um job lento, não uma falha de entrega.

A outra metade é alerta. Você quer saber na quinta falha consecutiva, não dias depois, quando a Stripe mandar por e-mail uma data de desativação. Se o único monitoramento que você tem é o próprio e-mail de aviso da Stripe, garanta mesmo que ele chegue a algum lugar que um humano lê.

Onde o AnyHook entra

O AnyHook fica entre a Stripe e seu servidor, para que um servidor lento não vire um endpoint desativado. A Stripe fala com in.anyhook.net/you/stripe em vez da sua aplicação, e respondemos 200 em cerca de um segundo, não importa o que seu servidor esteja fazendo.

  • A Stripe nunca vê um timeout, então o caminho da desativação nunca começa
  • O AnyHook armazena cada evento com os cabeçalhos e o corpo completos, com criptografia em repouso, antes de qualquer tentativa de entrega
  • O replay (um novo envio de um evento armazenado, feito por você) é um botão. Se seu servidor ficou fora do ar das 14:00 às 16:00, faça o replay dessa janela. Os replays saem do seu log, não do log da Stripe, e um evento fica disponível para replay por 3 dias no Free, 30 no Pro, 90 no Scale. Replays de eventos com falha não consomem sua cota mensal, embora tenham um limite diário e usem sua franquia de computação
  • Alertas de sequência de falhas disparam com 1, 5 e 20 falhas consecutivas, com pausa automática em 20, para que um deploy quebrado não vire uma tempestade de novas tentativas

A diferença na prática: com um relay de webhooks (um serviço que recebe, guarda e encaminha seus webhooks) na frente, o roteiro de recuperação acima se resume a selecionar um intervalo de tempo e clicar em Replay. Veja Como fazer o replay de um webhook quando seu servidor caiu.

Conclusão

Reativar o endpoint é 10% da recuperação. Os outros 90% são preencher a lacuna antes que a janela de 30 dias se feche e garantir que o handler seja rápido o bastante para que a Stripe nunca mais tenha motivo para desativá-lo. Se seu handler de webhook chama uma API de terceiros de forma síncrona, você já está nesse caminho. Só ainda não teve a tarde ruim.

Todos os posts3 de agosto de 2026 · 9 min

Pare de perder webhooks.

Troque uma URL. Tenha novas tentativas, um log de eventos e replay com um clique. Ou veja antes: crie uma conta, rode a simulação de queda de 30 segundos e veja um evento sobreviver ao seu servidor fora do ar. Não precisa de servidor.