Designing Idempotent APIs for Payments

How idempotency keys, deduplication, and clear status models prevent double charges and support safe retries.

2 min readFintechAPI DesignNode.jsPayments

Payment APIs fail in the real world: timeouts, client retries, and mobile apps that submit twice. Idempotency is how you make “pay once” a guarantee, not a hope.

What idempotency means

The same request, sent multiple times with the same idempotency key, produces one financial effect. The client can safely retry on network errors.

Implementation pattern

  1. Client sends Idempotency-Key: <uuid> header (or body field) on POST /payments.
  2. Server checks a store (Redis or DB table): key + route + tenant.
  3. First time: process payment, persist result, store key → response 201.
  4. Duplicate: return the same stored response without re-charging.

Store at least: key, status, response body hash, created_at, expiry (e.g. 24h).

Status model

Use explicit states: pending, succeeded, failed, refunded. Never infer from HTTP code alone. Webhooks and polling should read the same canonical record.

Edge cases

  • Concurrent duplicates — Use DB unique constraint on idempotency key or distributed lock during processing.
  • Partial failure — If charge succeeded but response failed, retry must return success with same transaction id.
  • Refunds — New idempotency key per refund attempt.

What to log

Log idempotency key, payment id, and outcome—never full PAN. Makes dispute resolution faster.

Takeaway

Idempotent payment APIs are non-negotiable for fintech. Design them on day one; retrofitting is painful and expensive. Pair with webhook signatures and reconciliation jobs for a complete story.