DEV Community

Cover image for Available Balance Is Eventually Consistent and Nobody Tells the User
SparqCard
SparqCard

Posted on

Available Balance Is Eventually Consistent and Nobody Tells the User

Disclosure: I work on Sparq, a crypto card issuer. The mechanics apply to any card; product note is one paragraph near the end.

There's a failure mode in card payments that is structurally a distributed-systems problem, and it produces support tickets that are almost impossible to diagnose from the user side.

Two events, one window

A card transaction is not atomic. It's two events separated by time:

t=0s AUTHORIZE
→ issuer reserves amount
→ available_balance -= amount
→ nothing moves

t=24-72h SETTLE
→ merchant batches for clearing
→ funds actually transfer
→ excess authorization reversed/adjusted

Visa's timeline puts that gap at roughly 24 to 72 hours.

So account_balance and available_balance diverge for one to three days after every transaction. Most interfaces surface the first and the user reasons about the second without having it.

Classic eventually-consistent read. The user is querying a replica that hasn't caught up, except the replica is the UI and nobody labelled it.

The amplifier: estimated authorizations

Some merchant categories authorize an estimate rather than a final amount:

Hotels — stay plus potential incidentals
Car rentals — damage/fuel/late-return buffer
US restaurants — tip added post-authorization

Network rules require merchants to reverse or adjust the excess during clearing. That reversal is real but runs on the merchant's clearing schedule, not the cardholder's.

Net effect: available_balance gets decremented by a number the user never agreed to, for a duration they don't control, and corrected without notification.

Why crypto cards surface this more

The mechanism is identical across all cards. The difference is in balance-sizing behaviour, which is a UX consequence of friction.

A bank account carries an ambient balance — slack exists by default. A crypto card top-up is a deliberate act: choose amount, choose network, pay fee, wait for confirmations. Friction produces precision, so users load close to intended spend.

Which removes the slack that would otherwise absorb an estimated hold:

balance: $280
hotel auth: $250 (estimate, will settle ~$190)
available: $30
subscription: $20 → clears
second charge: $15 → DECLINES

The decline is generic, the cause is 72 hours old, and there's no correlation path exposed to the user. Reasonable share of "crypto card randomly declined" reports are this, misattributed.

Conversion model matters for one part of it

Two designs, rarely stated prominently:

convert-at-load — crypto → spending balance at top-up. No crypto movement between auth and settle, so no rate exposure across the window.

convert-at-transaction — conversion at point of sale, value exposed across the 24–72h gap.

Neither dominates, but if you're holding an authorization for three days it's worth knowing whether the underlying asset is exposed for those three days. Fair question to put to any provider.

Orthogonal but related: on foreign-currency spend, the rate applied at authorization and at settlement can differ regardless of model.

Mitigations are structural

Headroom. Don't top up to exact intended spend. Free, solves most of it. Size the buffer to the largest plausible hold in your pattern — for travel that's higher than intuition suggests.

Partition by hold behaviour. Hold-heavy merchants (hotels, rentals) on a dedicated card; recurring charges on a separate one. The estimated hold then constrains only the card provisioned for that purpose, and renewals are structurally insulated.

Same blast-radius reasoning as scoped credentials — and notably, the same partition that contains fraud exposure also contains hold exposure. Two unrelated failure modes solved by one boundary is usually a signal the boundary is correctly placed.

Precondition: issuance has to be cheap enough that maintaining the split isn't a decision. If each card costs money or takes a day, teams consolidate and inherit both problems.

Product note: Sparq issues multiple virtual cards from one crypto balance with independent spending limits, instantly and with no KYC. Debit BINs across multiple countries, 3DS and recurring billing supported, funded from BTC/ETH/USDT over TRC20/BEP20/ERC20. Makes the partition practical. Pattern works with any low-friction issuer — the provider isn't the interesting part.

The observability complaint

This is the part that bothers me as an engineering problem.

available_balance exists. It's computed. Three parties know the state — merchant who authorized the estimate, issuer holding the reservation, network defining the window. The cardholder, who is the only party experiencing the consequence, gets a generic decline on an unrelated transaction days later with no correlation ID, no causal link, no way to query it.

It's a solvable surfacing problem that nobody is incentivised to solve, which is roughly the same diagnosis as most persistent friction in payments.

Anyone built reconciliation against pending-vs-posted on card APIs? Curious how well the pending-transaction feeds actually expose hold amounts and expected reversal timing, or whether that's as opaque programmatically as it is in consumer UIs.

Top comments (0)