01
Reentrega não é falha do provedor, é o contrato dele
A primeira coisa a aceitar é que o provedor de pagamento não promete entregar cada evento uma vez. Ele promete entregar pelo menos uma vez, e essa diferença é o artigo inteiro. Se o seu endpoint demora mais que o timeout dele, se responde 500 por causa de um deploy, se a conexão cai depois de o seu servidor ter processado mas antes de a resposta chegar, o provedor reenfileira e tenta de novo. Do lado dele, a tentativa anterior não teve confirmação e portanto não conta. Do lado do seu banco, ela contou perfeitamente.
O caso mais traiçoeiro é justamente esse último: o processamento deu certo, você gravou a transação, disparou o e-mail, liberou o acesso, e só então o processo caiu ou a rede engasgou antes do 200 sair. O provedor vê um timeout, marca a entrega como falha e tenta de novo em trinta segundos. Não existe nada que o seu handler possa responder para desfazer isso, porque a resposta nunca chegou. A única defesa possível está na segunda execução, não na primeira: ela precisa reconhecer que aquele trabalho já foi feito e não refazer.
- Timeout do endpoint: o provedor corta em poucos segundos e reenfileira, mesmo que o seu handler ainda esteja rodando e vá terminar com sucesso.
- Resposta perdida: o trabalho foi concluído e persistido, mas o 200 não chegou por queda de processo, deploy ou reset de conexão.
- Retentativa por erro real: uma exceção no meio do handler devolve 500, o provedor tenta de novo e a parte que já tinha sido gravada roda pela segunda vez.
- Reenvio manual: alguém clica em reenviar no painel do provedor durante uma investigação, e o evento antigo chega de novo semanas depois.
- Entrega duplicada sem motivo aparente: sistemas de fila com garantia de pelo menos uma vez duplicam por conta própria, sem que nada tenha falhado do seu lado.
A conclusão prática é que o handler de webhook não é um endpoint comum. Ele é um consumidor de fila que precisa ser seguro para reexecução por construção, e todo desenho que assume "isso aqui roda uma vez" está errado desde o primeiro dia, mesmo que só quebre no dia do primeiro incidente.