Skip to content

Why wallets debit before they settle

Debiting a wallet before the upstream provider confirms looks backwards until you think about what happens when two purchases arrive at once.

When a purchase comes in, there is an obvious ordering: call the provider, see whether it worked, then take the money. It reads correctly and it is wrong, and the reason is worth understanding because it shapes how you should reason about your balance.

The problem with settling first

Provider calls take time. Hundreds of milliseconds on a good day, several seconds on a bad one. During that window the wallet still shows the full balance, so a second purchase arriving concurrently sees funds that are already committed. Send four requests against a balance that covers three, and all four pass the check before any of them completes. You are now overdrawn, and the platform has to choose which customer to disappoint.

No amount of careful application code fixes this, because the gap between "check the balance" and "reduce the balance" is exactly where the race lives.

Debit first, refund on failure

Ureh reserves funds before contacting the provider. The debit and the transaction row are written in one database transaction, with the wallet row locked for the duration, so a concurrent purchase against the same wallet waits rather than reading a stale balance. When the lock releases, the second request sees the real remaining figure and is declined cleanly if it no longer fits.

If the provider then fails, the transaction moves to a failed state and the amount is credited back. Every movement is written to the ledger, so a refund appears as its own entry rather than as an edit to the original.

What this means for your integration

Three consequences follow, and they are the practical part.

  • Your balance can dip and recover within seconds. A balance read taken between a debit and a refund is accurate, not a glitch. Do not build alerts that treat a momentary dip as a discrepancy.
  • A failed purchase is not a missing purchase. It has a reference, a state and a ledger trail. If you are reconciling, match on references rather than on net balance movement, which will not tell you the difference between a purchase that never happened and one that failed and was refunded.
  • Insufficient funds is a fast, deterministic decline. It happens before the provider is contacted, so it costs nothing and returns immediately. Treat it as a business outcome to surface to your user, not an error to retry.

The ledger is the record

The balance is a number. The ledger is the explanation. Every credit and debit is an append only entry with its own reference and reason, which is what makes a disputed balance answerable rather than a matter of opinion.

When you build reconciliation, read the ledger. It is the only view that distinguishes a refund from a top up, and it is the one that will still make sense six months later.

Building against the Ureh API?

The reference documents every endpoint, request and response shape.

Get API keys API reference

Related Articles