Start with the retry path you want to preserve
The goal is simple: if the same action is retried, the customer should get the same billing result, not a second invoice or another payment attempt.
How a safe billing write behaves under retries
A durable idempotency record is created before any money-moving side effect. From that point on, every retry should resolve to the same committed result.
The caller sends a tenant-scoped idempotency key with the billing action it wants to perform.
Store the key and reserve the result slot before creating invoices, charges, or ledger entries.
Jobs and payment-provider calls inherit the same deterministic key so every retry maps back to the first committed outcome.
Require a stable request key for money-moving endpoints
Any endpoint that can create financial state should require an idempotency_key. Scope it by tenant so one customer’s request identity can never collide with another’s.
Persist the first committed outcome
Store (tenant_id, key) → result_id. When the same request returns, the system should replay the original answer instead of creating a new one.
Carry the same identity into jobs and providers
If you enqueue work or call a PSP, propagate the same deterministic identity there too. The protection is only real when every downstream boundary respects it.
Keep reading on the site, or start the guided email sequence if you want the same lessons delivered in order.