Building a secure fintech backend: Idempotency & Concurrency
"Deposits confirmed through two racing paths, a double-credit bug that only shows up under real concurrency, and the atomic claim that closed it — plus the ledger, idempotency, and withdrawal-threshold design that came after."
1. The Anatomy of the Race Condition
In custodial settlement systems, payment webhooks from gateways like NOWPayments and internal polling daemons frequently process payment confirmations concurrently. Without strict locking, two simultaneous handler calls can read an unfulfilled deposit status and issue double credits to a user balance.
// Atomic Deposit Claim Pattern in MongoDB
const claimDeposit = async (txHash, userId, amount) => {
const result = await Deposit.findOneAndUpdate(
{ txHash, status: 'PENDING' },
{ $set: { status: 'CLAIMED', claimedAt: new Date() } },
{ new: true }
);
if (!result) return false; // Claim failed or already processed
await Ledger.creditUserBalance(userId, amount);
return true;
};2. Immutable Double-Entry Ledger
Financial state should never rely on raw mutative balance increments. Every transfer or credit generates a pair of debits and credits in an immutable ledger append stream.
// Double-Entry Ledger Entry
const ledgerRecord = {
debitAccount: 'SYS_RESERVE_CUSTODY',
creditAccount: `USER_${userId}`,
amount: amountInCents,
timestamp: new Date().toISOString(),
signature: crypto.hmacSign(payload, SECRET_KEY)
};3. Threshold-Gated Multi-Sig Withdrawals
Withdrawals above a dynamic threshold trigger secondary validation hooks and time-locked approval gates, shielding custodial vaults from single-point compromise.
- Never mutate financial balances directly without atomic database conditional filters.
- Double-entry ledgers ensure full auditability even during partial system failures.
- Webhook delivery MUST be treated as at-least-once, requiring idempotent handlers.
Explore verified case study write-up
Read full system architecture breakdown and telemetry.