Guides

Virtual account reconciliation: deposit to ledger balance

How a deposit on a named virtual account becomes a ledger entry: how each rail identifies the payer, pending versus available entries, and the daily close.

Caio Barbosa

Founder & CO-CEO

Forbes Under 30. One of the leading voices in Fintech & Crypto in Brazil. Writes weekly about stablecoins, payments, and the future of financial infrastructure in Latin America.

Cover image for Lumx blog article: Virtual account reconciliation: deposit to ledger balance
Cover image for Lumx blog article: Virtual account reconciliation: deposit to ledger balance

Virtual account reconciliation is matching each deposit on a customer's virtual account to its customer, the invoice or balance it settles, and the ledger entry that records it. On USD, EUR and GBP accounts the account identifies the payer. Pix (Brazil) and SPEI (Mexico) carry an identifier or memo.

This guide follows one deposit from the payer's bank to a ledger balance, for teams running named virtual accounts (local account numbers issued in the customer's own name) for their own customers. The accounting treatment is in a separate guide on reconciling stablecoin transactions.

Does a virtual account really remove the payment reference?

On USD, EUR and GBP it does, and on Pix and SPEI the reference moves into a code or a memo. On the account rails, the match moves from a field the payer types to an account number the payer must get right for the money to arrive at all. A payer can leave the reference blank, pay from a parent company or send an odd amount, and the deposit still lands on the right customer.

Pix (Brazil's instant payment system) carries the reference inside the payment code, and SPEI (Mexico's interbank transfer system) takes it as a memo.

What matches a deposit to its customer, by rail, from the Lumx documentation read on November 1, 2026.

Currency

Rail

What the sender uses

What matches the deposit

Does the payer type anything?

BRL

Pix

A static Pix QR code (brCode)

An identifier embedded in the code

No, if the payer scans or pastes the code

MXN

SPEI

A CLABE plus a memo

The memo picks the autoconversion rule

Yes, the memo

USD

ACH, FEDWIRE, SWIFT

Routing number or BIC, and account number

The customer's account

No

EUR

SEPA

IBAN and BIC

The customer's account

No

GBP

FPS

Sort code and account number

The customer's account

No

A Pix scan is enough, because the identifier travels inside the code. SPEI takes the memo next to the CLABE (the 18-digit Mexican bank account number), and a blank or mistyped memo is the first suspect when a deposit does not match. On a shared account, every rail would behave like SPEI, and the reference would be the only key, which is the trade-off covered in named virtual accounts vs pooled accounts.

A Lumx account is issued by a banking partner in the customer's name, holds one currency, and a customer can hold several. A customer has one active autoconversion rule per USD, EUR or GBP account, because those deposits are matched to the account and a second rule would be ambiguous. Rules on BRL and MXN accounts have no such limit, since each carries its own identifier.

A worked week: seven deposits on four accounts

The matching key gets a deposit to the right customer. Everything after that is invoice logic you write.

Illustrative example. A B2B software platform has four customers, each with one named account, and seven deposits arrive in one week. Names and amounts are invented.

#

Deposit

Rail

Customer match

Invoice result

1

BRL 10,000.00, payer scanned the Pix code

Pix

By identifier, customer A

Matches invoice A-1 in full

2

MXN 50,000.00, memo typed correctly

SPEI

By memo, customer B

Matches invoice B-1 in full

3

MXN 25,000.00, memo left blank

SPEI

Account known, no rule applied

Converted at the floating rate. Owner assigned, payer asked what it was for

4

USD 12,000.00 by ACH, sent late on a Friday

ACH

By account, customer C

No entry until the deposit is processed, then matches C-1

5

USD 8,000.00 by wire from a parent company

FEDWIRE

By account, customer C

Customer known, payer name differs. Third-party collection, rule decides

6

EUR 30,000.00

SEPA

By account, customer D

Matches invoice D-1 in full

7

USD 9,975.00 against a USD 10,000.00 invoice

SWIFT

By account, customer C

Short by 25.00, tolerance rule decides

Deposits 1, 2 and 6 post themselves. Deposit 3 is the one the account cannot fix, and only SPEI produces it. With several autoconversion rules on one MXN account, the memo or reference picks the rule. Without it the MXN still lands on the account and converts at the normal floating rate, but no rule applies. On USD and EUR, a deposit with no on-ramp and no active rule just sits as fiat on the account until you act. Deposits 4, 5 and 7 are known customers with an open invoice question, which is easier than "who paid?". Deposit 5 is a third-party collection, which Lumx allows by design. One that looks unusual can trigger an RFI (request for information) and, as a result, a return. Deposit 4 is a timing case. The Lumx timelines are processing targets, not guarantees, and the sender's bank sets its own cut-off, so a late Friday deposit can take a business day longer. Your ledger has nothing to post until the first event arrives.

Take deposit 1 through the numbers. In the Lumx documentation's sample receipt, BRL 10,000.00 converts at a base rate of 5.25 to 1,904.76 USDC. The receipt itemizes a Lumx fee of 9.52 and a partner fee of 3.81 and ends at a final 1,891.43 USDC, an effective rate of 5.29. Invoices match on the BRL that arrived. Treasury books the 1,891.43 USDC that reached the wallet, and a ledger that mixes the sides makes every invoice look short.

The same logic applies to B2B collections on named accounts, and to a company in Brazil receiving USD payments.

Post the deposit as pending, then as available

Write the ledger entry the moment the first event arrives, mark it pending, and move it to available only on the final success event. Mixing the two shows a customer a balance for money still in transit, then makes it disappear.

Three states cover it. Pending means the deposit was processed and is converting. Available means the stablecoin reached the wallet. Reversed means the transaction failed and the pending entry is taken back. A late event for an earlier step must never move an available entry back to pending: status only advances.

Key every entry on the Lumx transaction id, because an amount-and-date key merges two same-amount deposits on one day.

A deposit matched by an autoconversion rule runs as an on-ramp, and its events start at onramp.transferring_fiat, then trading, transferring_stablecoin and success. The first event is also the first moment your ledger can write the pending entry.

What to store on every deposit row

Store the identifiers that join to your records and every amount the receipt gives you, not only the amount you expected.

Fields to keep per deposit, and why, from the Lumx webhook payload and sample receipt.

Field

Where it comes from

Why you keep it

Transaction id

The event data

The deposit itself, and your key for ledger writes

Customer id

The event data

The owner of the deposit and its account

Rail and source amount

The request in the event

What arrived from the payer, which invoice matching uses

Base rate and final rate

The receipt

The two rates behind the conversion

Lumx fee and partner fee

The receipt

Where the fee landed

Final target amount

The receipt

The stablecoin delivered, which treasury books

Transaction hash and explorer link

The receipt

The proof that the stablecoin reached the wallet

Your own reference

Metadata on an on-ramp you create. Rule-driven deposits carry none

The join to your books for on-ramps you create

Event id and webhook id

The event and its headers

Deduplication

For a named-account deposit, the join to your books is the customer id plus the transaction id. The rule creates the transaction, the rule request has no metadata field, and so a rule-driven deposit carries none. The metadata field is for on-ramps your platform creates: it is set at creation and cannot be added afterwards, so rows without it are reconciled by hand.

The receipt on a successful Lumx transaction carries the amounts and rates, the fees, and the transaction hash. Read the receipt, not the request, because the request holds what was asked for and the receipt holds what happened. The webhook guide lists the payload fields.

Webhooks for speed, the API for truth

Use webhooks to move entries forward as events arrive, and the API read to settle what you are unsure about. After an outage on your side, the paginated read of transactions is the record you trust, because a ledger fed only by events will have gaps.

Expect the same event twice, so deduplicate on the webhook id. Flag any status you do not recognize instead of mapping it to the nearest known one.

Lumx retries a webhook when your endpoint does not return a 2xx response: eight attempts, from immediate to twice after 10 hours. After the last attempt the message is marked failed and can be retried from the dashboard. Each event carries the transaction id, so one deposit produces one row that moves forward. The awaiting_funds and expired statuses belong to on-ramps you create for a single expected deposit, not to rule-driven ones.

Closing the day: ledger, Lumx records and wallet

At the end of each day, check three records against each other and write down every difference with a reason. The three are your ledger, the transaction records from Lumx for that date, and the stablecoin balance in the wallet. The ledger says what you promised, the Lumx records what was processed, the wallet what arrived.

Every available entry should have a successful transaction and a hash on the receipt. Every pending entry should be inside its rail's window. Every difference should land in one of four buckets: late, failed, unmatched, or under the minimum. The payment rail timelines are Lumx processing targets, and a deposit sent late in the day, on a weekend or on a bank holiday can take a business day longer, so it is late, not broken. The US$ 50 minimum applies to every deposit, and a smaller one is not converted, and operations@lumx.io is where to ask about it. A deposit above the on-ramp limit is held as a balance until it is resolved. Unmatched means the deposit arrived but no rule applied: an MXN deposit without a memo converts at the floating rate, and a USD or EUR deposit with no active rule stays as fiat. The transaction check alone will not surface the second kind, so list the fiat balances on the accounts as part of the close.

I recommend mapping every customer id to your own customer record before the first deposit arrives, and never closing a day with an unexplained row. I would keep one suspense account for unmatched deposits, give it a named owner, and escalate any row still there after two business days.

The records for the daily check come from reading Lumx transactions with a page size of up to 100 and a cursor. The ledger and the match are yours. The docs include a prompt that builds a reconciliation ledger with late and failed as different states.

Payments that do not match the invoice need rules before launch

A virtual account tells you who paid. It does not tell you what they meant. Write the rules before the first customer pays, not per ticket.

Rules to write before launch for payments that do not match the invoice.

Case

Suggested rule

Reason

Partial payment

Apply to the oldest open invoice and keep the rest open

The customer is known, so only the invoice is in doubt

Overpayment

Hold the difference as customer credit

The next invoice usually absorbs it

Short by a small amount on SWIFT

Set a tolerance and say who absorbs it

An intermediary bank may have taken a fee in transit

SPEI deposit without a memo

Post to suspense and ask the payer what it was for

No rule applied, so the conversion used the floating rate

Deposit under US$ 50

Treat it as not converted, tell the customer and write to operations@lumx.io

The conversion never starts, so no transaction exists to post

Each matched deposit is converted and delivered with its receipt, and the invoice rules stay in your system. Lumx sees the customer and the transaction, not your invoices.

When a virtual account is the wrong tool for reconciliation

One account per invoice is the first case. Each account goes through verification, so a new account per invoice adds a review step to every sale. For a single expected deposit with an exact amount, create an on-ramp transaction for that amount and let the payment details come back with it. That transaction has a payment window, so it can expire, and a deposit that lands after expiry is not processed and is refunded to the original sender on the same rail. That is the case your ledger maps to reversed.

Collecting for your customers' own clients through one account is the second case. Pooling several parties' funds is the nested-payments pattern, which needs written approval from Lumx compliance.

Very low volume is the third case: five deposits a month fit a spreadsheet.

What a Pix deposit looks like from account to ledger on Lumx

Lumx is stablecoin payments infrastructure for businesses that move money between Latin America and the rest of the world: one API to collect, hold, convert, and pay out in BRL, MXN, COP, USD, EUR, and GBP or in USDC and USDT, over local rails such as PIX, SPEI, PSE, Bre-B, ACH, FEDWIRE, SEPA, and Faster Payments, with SWIFT and on-behalf-of payments and collections (POBO and COBO) in USD, EUR, and GBP, plus named virtual accounts, custodial wallets, and KYB/KYC built in.

Your platform creates the customer with the currencies it needs, and the account waits for verification. BRL, MXN, EUR and GBP accounts reach active right after approval, and USD takes up to one business day, so the account.active event is the moment to show payment details. Your platform then creates an autoconversion rule on the BRL account, which returns a Pix code that embeds the identifier, and the payer scans it.

From there, the events arrive in order. Your ledger writes a pending entry on onramp.transferring_fiat, keyed on the transaction id, and posts the available entry when onramp.success arrives with the receipt. The receipt shows the Lumx fee and any partner fee separately, and Pix is instant, so pending is short. If a status or event name changes, the Lumx changelog is where renames are announced, and your state map should be checked against it.

Confirm the account you need on the coverage page, and see global payments for how collection fits with payouts. See also the named virtual accounts page.

Verified on November 1, 2026. Operational context, not legal, tax, or investment advice.

Cover photo: Amadeus Moga on Unsplash.

  • What is virtual account reconciliation?

    Virtual account reconciliation is matching each deposit on a customer's virtual account to that customer, to the invoice or balance it settles, and to a ledger entry. The account number identifies the payer, so the remaining work is invoice-level rules and exceptions. On Lumx, each matched deposit runs as a transaction with its own id and receipt, and the ledger and its rules stay with the client.

  • Do virtual accounts remove manual reconciliation?

    They remove the hardest part, finding out who paid. What remains is matching a deposit to an invoice and handling partial, over and short payments by rule. On Lumx, USD, EUR and GBP deposits match by account and Pix deposits match by the identifier inside the Pix code, while SPEI still depends on a memo that the payer types.

  • How do you avoid posting the same deposit twice?

    Key every ledger write on the transaction id, and deduplicate events on the webhook id, since the same event can arrive more than once. Lumx retries a webhook that gets no 2xx response up to eight times, so duplicates are normal, and the webhook-id header is the field to deduplicate on.

  • When is a deposit final enough to post as available?

    When the transaction reaches its success status and the receipt shows the stablecoin delivered. Before that, keep the entry pending. The receipt that arrives with onramp.success carries the final amount and the transaction hash, and that event is the point to post.

Stay up to date with what Lumx is developing.

Sign up to receive them by email.

Share on socials:

virtual-account-reconciliation

A

virtual-account-reconciliation

Virtual account reconciliation: deposit to ledger balance

Copy link

Copied!

virtual-account-reconciliation

TALK TO OUR TEAM

Ready to transform your business with stablecoins?

Discover how our infrastructure can seamlessly integrate stablecoins into your financial operations quickly, securely, and efficiently.

Guides

In this page

©2026. All rights reserved.

Lumx US OP LLC ("Lumx") is a financial technology and payments infrastructure company. Lumx US OP LLC is a Money Service Business (MSB) registered with the Financial Crimes Enforcement Network (FinCEN) (MSB #31000316459619). Lumx is not a state-licensed money transmitter and does not, in its own capacity, engage in the provision of regulated money transmission services. All such regulated activities are conducted exclusively through, and under the licenses of, duly authorized financial-institution partners.


Lumx is not a bank, financial institution, payment institution, or custodian of client funds. Certain services made available through the Platform may be provided by duly authorized and regulated third-party partners, in accordance with applicable laws and regulations.

Please refer to Lumx’s Terms of Use and Privacy Notice for further information regarding the conditions governing the use of the Platform and the processing of your personal data.