Quando seu servidor está fora do ar e a Stripe dispara um evento payment_intent.succeeded, o que acontece com o evento? A Stripe tenta de novo para sempre? Por quanto tempo? E como você concilia os eventos que perdeu?
Essa é a pergunta que eu mais ouço de quem está integrando a Stripe, e ela importa porque um webhook que você não consegue processar é receita sobre a qual seu produto não consegue agir. Um cliente acabou de pagar. Sua rotina de liberação do pedido nunca dispara. O painel dele nunca atualiza.
TL;DR
- A Stripe tenta de novo por 3 dias, com backoff exponencial, e depois para de vez
- Responda 200 rápido, faça o trabalho de forma assíncrona, registre cada evento antes de processar, seja idempotente
- Se você não tem tempo de construir tudo isso, um relay de webhooks (um serviço que recebe, guarda e encaminha seus webhooks) dá a você novas tentativas (retries), log e replay (um novo envio de um evento armazenado, feito por você) com a troca de uma única URL
Como a Stripe tenta de novo
A Stripe espera que seu endpoint de webhook responda rápido com um código de status 2xx. Ela não publica um número exato, e a documentação só diz para responder 2xx "antes de qualquer lógica complexa que possa causar um timeout". O número com que a comunidade trabalha é 20 segundos. Uma entrega que deu timeout conta como falha, assim como respostas 5xx e falhas de rede.
Quando a entrega falha, a Stripe tenta enviar o evento de novo, com backoff exponencial, por até 3 dias. As novas tentativas ficam cada vez mais espaçadas, e a última janela fica por volta de 72 horas depois do evento original.
Depois de 3 dias, a Stripe marca o evento como falha e para de tentar de novo. Ela não acorda você. Não envia nenhum e-mail de contingência. O evento simplesmente some da sua fila de entrega, embora você ainda possa vê-lo no Dashboard da Stripe, em Developers → Events.
A curva da Stripe também está entre as mais suaves. Plataformas merchant of record podem tentar de novo com muito mais força: a Paddle faz até 60 tentativas em cerca de dois dias, e um handler que dá conta do cronograma da Stripe ainda pode se afogar no da Paddle.
Se as falhas continuam depois disso, a Stripe acaba desativando o endpoint por completo e manda um e-mail com a data do desligamento. Recuperar-se disso é outro trabalho, e os eventos continuam disponíveis para consulta pela Events API por 30 dias: veja A Stripe desativou seu endpoint de webhook para os passos de preencher a lacuna (backfill).
Os três modos de falha que pegam você
Seu endpoint é lento. A Stripe conta como falha uma resposta que passa do timeout não publicado dela. Se seu handler de webhook grava de forma síncrona num banco lento, sincroniza com o Salesforce e manda um e-mail ao cliente, você está flertando com o timeout em todo evento.
Sua janela de deploy. Seu servidor reinicia durante um deploy, perde cerca de 10 eventos e volta. A Stripe tenta de novo esses eventos, mas só por 3 dias.
Um bug que gera erros 500. Você faz um deploy que lança uma exceção num tipo de evento específico. A Stripe tenta de novo, mas continua esbarrando no mesmo erro. Depois de 3 dias, a Stripe desiste e você fica com uma lacuna silenciosa nos dados.
Nos três casos, a Stripe está fazendo a coisa certa. A falha está do seu lado da conexão.
Quatro coisas que integrações prontas para produção fazem
1. Responda 200 rápido e faça o trabalho de forma assíncrona
Não faça trabalho pesado no handler do webhook. Aceite o evento, grave-o numa fila ou num log durável, responda 200 e processe de forma assíncrona. Isso mantém você longe do timeout e desacopla a entrega da lógica de negócio.
export async function POST(req: Request) {
const event = await verifyStripeSignature(req);
await queue.publish(event); // durable write
return new Response(null, { status: 200 });
}
2. Registre cada evento antes de processar
Grave o payload bruto e os cabeçalhos antes de tocar na lógica de negócio. Se sua lógica lançar uma exceção, você pode fazer o replay a partir do log. A Stripe para de tentar de novo depois de 3 dias e guarda os eventos por 30, então, passado esse prazo, seu log é a única coisa que permite recuperar.
3. Idempotência do seu lado
A Stripe envia o mesmo campo event.id nas novas tentativas, então seu handler precisa ser idempotente: se você já processou o evento evt_1H..., não processe de novo. Um padrão simples:
INSERT INTO processed_events (stripe_event_id, processed_at)
VALUES ($1, NOW())
ON CONFLICT (stripe_event_id) DO NOTHING;
Se o insert retornar 0 linhas afetadas, você já viu esse evento. Pule.
A idempotência é mais profunda do que parece quando as novas tentativas começam a chegar em paralelo ou fora de ordem. Você recebeu o mesmo webhook duas vezes trata da fronteira da transação, do padrão claim-then-commit para efeitos colaterais que você não consegue desfazer e de por que são as guardas de versão, e não a deduplicação, que resolvem a entrega fora de ordem.
4. Alertas de falha
Se 5 eventos seguidos falham, você quer saber em minutos, não quando um cliente escrever para o suporte. Acompanhe as falhas consecutivas e acione seu próprio plantão.
Onde o AnyHook entra
O AnyHook fica na frente do seu servidor e resolve isso na camada de transporte. Você aponta a Stripe para in.anyhook.net/you/stripe em vez do seu servidor e, a partir daí:
- Respondemos 200 à Stripe em cerca de um segundo, então a janela de resposta nunca é um problema
- Registramos cada evento com os cabeçalhos e o corpo completos, com criptografia em repouso
- Tentamos de novo a entrega ao seu servidor em 5 minutos, 30 minutos, 3 horas, 18 horas e depois uma vez por dia: 3 tentativas no Free, 5 no Pro, 10 no Scale. O cronograma da própria Stripe vai até três dias, então, no Free e no Pro, a rede de segurança longa é o log armazenado e o replay, não o número de novas tentativas
- Damos a você replay com um clique. Se seu servidor ficou fora do ar por uma hora, a página do app (uma URL de entrada para uma origem) faz o replay dos eventos com falha daquela hora num só clique (até 100 por vez), e replays de eventos com falha não contam na sua cota mensal, embora tenham um limite diário e usem sua franquia de computação
- Alertamos sobre sequências de falhas. Mandamos um e-mail com 1 e com 5 falhas consecutivas. Com 20, pausamos a entrega para esse destino (10 minutos na primeira vez, mais tempo a cada nova pausa) e mandamos outro e-mail; os eventos que chegam durante uma pausa ficam registrados para replay, em vez de entregues
Sem SDK, só uma troca de URL. A primeira entrega continua passando na sua verificação atual da Stripe. As novas tentativas e os replays levam o timestamp original da Stripe, que o SDK da Stripe recusa depois de cinco minutos; então verifique o cabeçalho AnyHook-Signature para aceitá-los.
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 você está perdendo eventos da Stripe hoje, a solução normalmente não é ter mais novas tentativas. É gravar antes de processar, para sempre ter de onde fazer o replay.