WHY IDEMPOTENCY MATTERS IN PAYMENTS

AUTHOR /
IBRAHEEM UTHMAN
YEAR /
2025

RETRIES ARE NOT EDGE CASES

Payment providers retry webhooks. Mobile clients retry on timeout. Load balancers replay requests. In production, the same charge intent arrives two or three times before you notice. Without idempotency, each arrival is a new transaction.

Idempotency keys turn retries into no-ops. The first request creates the record. Every duplicate with the same key returns the original result without touching the ledger.

STATE MACHINES NEED GUARDS

A payment moves through pending, authorized, captured, failed, refunded. Each transition checks the current state before applying the next. You cannot refund a charge that never captured. Guards live in the database transaction, not in controller conditionals.

Explicit states make reconciliation possible. Finance teams can compare provider statements to internal records because every row has a clear lifecycle.

KEY DESIGN CHOICES

Keys should come from the client for user-initiated actions and from the provider for webhooks. Store them with a unique index. Expire stale pending records on a schedule so the table does not grow forever.

Return the same HTTP status and body on replay. Clients should not be able to tell whether their request created the charge or hit a cache.

TEST WITH CHAOS

Replay webhooks in staging. Kill the app mid-transaction and resend. Run concurrent requests with identical keys. If money moves twice in any scenario, the design is wrong.

These tests belong in CI, not in a manual checklist before launch. Payment bugs are expensive to unwind.