Pular para o conteúdo

Garantias de entrega

Duas garantias mudam como se escreve o receptor de um webhook, e elas estão declaradas aqui de propósito. Nenhuma das duas é limitação a esconder: são decisões, e a alternativa a cada uma custaria mais do que vale.

Todo webhook é entregue pelo menos uma vez. Na esmagadora maioria dos casos, exatamente uma. Mas duplicata é possível, é esperada, e quem recebe precisa tratá-la.

Entre “entregamos e o receptor pode receber duas vezes” e “podemos deixar de entregar”, a escolha não é difícil: um pagamento processado duas vezes é um bug que se conserta com três linhas de código; um pagamento que nunca chegou é dinheiro perdido e um cliente irritado que não sabe por quê.

Entrega exatamente uma vez não existe sobre HTTP. Quando o receptor recebe a requisição, processa e a resposta se perde na volta, nós não temos como distinguir isso de “a requisição nunca chegou”. As duas situações são o mesmo timeout. Alguém tem que decidir, e a decisão que não perde evento é tentar de novo.

Os casos concretos em que a duplicata acontece:

  • A resposta se perdeu. O receptor processou, o 200 não chegou até nós, e a tentativa seguinte do cronograma repete a entrega.
  • O receptor demorou mais que o timeout. Processou até o fim, mas nós já tínhamos desistido daquela tentativa.
  • Um processo nosso morreu entre enviar e registrar o resultado. A entrega volta a ser processável e sai de novo.

A receita para deduplicar, com tabela e código, está em deduplicação e ordem.

Os webhooks não chegam necessariamente na ordem em que os eventos aconteceram.

Não é um detalhe de implementação que um dia melhora: é consequência direta do retry, que é a funcionalidade principal do produto.

Você publica pedido.criado às 10h00 e pedido.pago às 10h01. O endpoint está instável e recusa o primeiro; o segundo pega uma janela boa e passa:

Instante O que acontece
10h00 pedido.criado → o endpoint responde 500
10h01 pedido.pagoentregue
10h00 + 30s… pedido.criado reagendado, com espera crescente
10h10 pedido.criadoentregue, nove minutos depois do que veio depois dele

O retry com espera crescente reordena por construção. Preservar ordem exigiria uma fila serial por destino, em que um evento parado bloqueia todos os seguintes daquele receptor. Seria trocar “chegou fora de ordem” por “não chegou nada enquanto um evento estava travado”. A entrega mais importante do dia ficaria refém do retry de uma que não importa.

Além do retry, o fan-out para vários endpoints e a concorrência entre entregas também não têm ordem definida entre si.

Para não parecer que a lista é só de ressalvas, o que fica de pé:

  • Nenhum evento aceito se perde. A intenção de entregar é gravada na mesma transação do evento. Se respondemos 202, a entrega vai ser tentada.
  • O cronograma de retry é escrito e previsível: uma tentativa imediata e mais oito, a última cerca de 45 horas depois da primeira. Veja o ciclo de vida.
  • Toda tentativa fica registrada, com o status e o começo da resposta do receptor.
  • Toda entrega é assinada, e a rotação de secret tem uma janela em que as duas assinaturas viajam juntas. Veja verificando a assinatura.
  • Esgotado o cronograma, a entrega aparece como esgotada no painel, você recebe e-mail, e o replay fica disponível.