A gateway does not promise one delivery. Stripe, PayPal, Telebirr and QisstPay all retry when the response is slow or the connection drops. If the handler creates a payment on every call, the ledger drifts.
I store the event id before the business write. A repeat with the same id returns the first result and does nothing else. The handler stays small: verify the signature, record the event, then queue the refund, dispute or order update.
Card numbers never land in that table. The gateway keeps the card. We keep the event, the status and an audit row so finance can reconcile. On the payments platform this stayed above 99% reconciliation accuracy.
The same rule covers crypto checkout. A wallet webhook is just another event id on the same trail, so card and crypto orders share one history.