INTRO
Payment incidents damage trust quickly: duplicate charges, missed confirmations, or pending states that never resolve. The core fix is deterministic backend design. Every critical write must be safe to repeat, and webhook processing must tolerate delay, retries, and reordering.
Use idempotency keys on charge creation
Require an idempotency key from client or gateway for create-charge endpoints. Store response by key with a TTL and return the original result on retry.
Hash request payload with the key; if same key arrives with different payload, reject it as misuse to avoid silent data corruption.
Webhook deduplication and ordering
Persist provider event IDs in a processed-events table. If an event is seen again, acknowledge and skip side effects.
Design state transitions to be monotonic where possible (e.g., `pending -> paid -> refunded`) so older events cannot roll back newer truth.
code-snippet.typescript
const alreadyProcessed = await ProcessedEvent.findOne({ eventId })
if (alreadyProcessed) return res.status(200).send('ok')
await ProcessedEvent.create({ eventId, receivedAt: new Date() })
await applyPaymentEvent(event)
return res.status(200).send('ok')Reconciliation and operational safeguards
Run a scheduled reconciliation job comparing provider records with internal orders to catch missed webhooks.
Log every payment state change with actor/source metadata so support can resolve disputes with an audit trail.
KEY TAKEAWAYS
“Idempotency keys are mandatory for create-payment endpoints.”
- 02
Deduplicate webhooks and guard state transitions against out-of-order events.
- 03
Add reconciliation jobs and audit logs to reduce financial support risk.
CONCLUSION
Closing thought
Reliable payment systems assume retries and race conditions from day one. Idempotency plus reconciliation turns unpredictable network behavior into predictable business outcomes. Ready to talk scope? Use Contact — or browse Services for fit.
CONTINUE
Related pages
UP NEXT
Turning On TypeScript Strictness in Existing Node Codebases
A migration path that does not freeze the repo for a month: enable compiler flags gradually, type boundaries first, and defer legacy internals.
Read next article