TL;DR
A crypto payment processor needs a double-entry ledger because money and orders move at different moments: one invoice can be credited three times, credited without ever closing, or left untouched while a transfer lands on its address. Paymos keeps merchant balances as separate accounts in that ledger, and the transactions posted against them are immutable and balance per token. What it buys is an answer to "which account moved" for every event, including the ones that fire no webhook.
A crypto payment processor writes one set of books and reads another. The chain's record is the one it does not control; its own is a ledger, and at Paymos that ledger is double-entry. Every movement posts against a pair of accounts, and a merchant balance is a position in that ledger rather than a figure somebody keeps up to date.
The reason to pay for the extra column shows up in the events that refuse to line up. A payer settles one invoice with three transfers. Money lands on an invoice address an hour after the window closed, or in USDC when the invoice was priced in USDT, or against an invoice that was cancelled that morning. A payout is created, its money reserved, and then cancelled before anything goes out. Each of those moves money without moving the order attached to it, and a balance held as one running total has nowhere to write the difference down.
Six of those events are worked through below, with what each one does to a merchant balance and what the invoice does at the same moment.
What does a double-entry ledger record?
Movements, and the two accounts that each one touches.
A single-entry system stores an amount and edits it. A double-entry system stores the change: the account value left, the account it reached, in one transaction that has to balance before it is allowed to commit. Balances become positions you can derive from history instead of fields someone overwrote. Payments teams well outside crypto build on the same property, and Modern Treasury's engineering journal states it flatly — a ledger sits on "an immutable, append-only log", where "all changes are preserved, and any previous state can be reconstructed".
At Paymos a merchant balance is one of those accounts, held per asset. Every transaction written against it is immutable, carries at least two sides, and balances per token; an unbalanced one is refused at write time rather than saved and fixed later.
Why does one invoice produce more than one entry?
Because the fee lands on each transfer as it confirms, sized by what that transfer brought in.
A merchant balance therefore moves several times over the life of one invoice. Three transfers means three credits and three fee postings, each priced against the amount in front of it at the moment it cleared. The rate used is the one pinned to the invoice on the day it was created, whatever the price list did afterwards.
One arrival is already three numbers: what came in, what the fee took, what the merchant kept. Rounding runs in a single direction. The fee is truncated to the smallest unit the token has, the leftover belongs to the merchant, and the real rate therefore sits at or under the published one on a transfer of any size; an amount so small the fee rounds away is charged nothing.
For a merchant reading its own statement, this is the part that surprises. Credits do not line up one-to-one with invoices. They line up with transfers, and who funded the fee on each one is a separate setting.
What happens when the amount does not match?
The credit follows the money and the invoice follows its own rule, and the two answers can differ.
Overpay, and the surplus is credited in full. Nothing is withheld and nothing goes back on its own; the invoice closes as paid, and the event reported is invoice.paid_over rather than invoice.paid, so an order system can tell the two apart without doing arithmetic on amounts.
Underpayment runs into the project's tolerance. That is a slider between 0% and 2%, moving a tenth at a time, and a new project starts it at 0.1%. Anything short by less than the tolerance still closes the invoice, and the merchant keeps whatever arrived, because no top-up is invented to cover the gap. Drag the slider to zero and the match goes exact again.
Shortfalls larger than that split by invoice type. An invoice expecting one payment is marked underpaid. An invoice expecting several can sit open while the payer sends the rest, which is precisely the position a single figure cannot express: real money credited, order unfinished.
Where does money go when it pays no invoice?
Onto the merchant's balance, with the processing fee taken and the reason kept beside it.
Transfers reach invoice addresses at inconvenient moments: an hour after the payment window shut, against an invoice paid or cancelled earlier the same day, in USDC when the invoice was priced in USDT, or onto an address carrying no invoice at all. The money is credited to the merchant in every one of those.
The invoice is the part that does not move. It is not marked paid, and no webhook fires, because a credit of this kind has no event of its own. An integration watching only the event stream never hears about it.
Where it does show up is the Balance page, in a block of its own carrying the reason, the amount, the fee, the invoice where there was one, and the sender address and transaction hash where those are known; otherwise the sender reads as unknown. Email, the dashboard bell and a linked Telegram contact carry the same notice. Returning any of it is a commercial decision that stays with the merchant, since a refund is an ordinary transfer somebody has to send.
Why does a payout sit on hold before it leaves?
Because between the request and the transaction the money is neither spendable nor gone.
Requesting a payout lifts the amount, plus the network fee for the route it will take, out of available and parks both in a hold. That is why the Balance page carries the two figures apart, and why only the available one can fund the next payout. Nothing is being kept back from settlement: there is no reserve and no rolling reserve, and what sits there is the merchant's own payouts in flight.
A payout reports seven states, three of them final, and is_final rides on every payload so an integrator never has to keep that list current by hand. One non-final state is worth recognising on sight: a payout the network has not confirmed yet reads as Unconfirmed, with its amount still held underneath.
If the payout ends without the transaction going out, failed or cancelled, everything in the hold comes back, the network fee along with the principal. Nothing is charged for a payout that never went out. Cancelling works only in the opening moments, before execution starts; after that the request is refused.
Whose account absorbs a loss the chain creates?
The platform's own.
Confirmation depth here is set by network and by payment size, a deliberate trade against waiting for absolute finality, and the trade leaves a narrow window in which a credited transfer can still be orphaned when the chain reorganises. The merchant's credit stays where it is when that happens, and the shortfall is booked against the platform's accounts.
The ledger point here is narrow. A processor holding both sides of every entry can name the account that took the hit; a processor holding one figure per merchant has exactly one place to take it from, and the merchant is standing in it. What a reorg does to a shipped order covers the mechanics, and why the wait varies by network explains the trade that opens the window.
Can you build your books out of webhooks?
Not on their own — an event stream describes transitions, and a transition is not a record of money.
Sixteen event types cover invoices, withdrawals and payment-channel deposits, and each announces that a resource changed state. That makes the stream a dependable way to hear promptly that something happened; the delivery contract sets out how hard it tries.
Completeness is a different question. A credit that pays no invoice produces no event at all, and a payout that fails fires a webhook while sending no email and no in-app notice. Add webhooks up into a balance and both of those cases go quietly wrong.
The payment-channel deposit feed assumes the opposite. It publishes in order behind a cursor that always advances, so a reader holding its position cannot skip a settled payment, and a page with nothing on it still returns a cursor rather than an ending. A stored position lasts 24 hours from issue. Past that the feed answers pagination_cursor_invalid, and the reader starts again from confirmed_from. Treat an event as a prompt to go and read, and let the feed and the balance be what your books trust.
What should your own accounting record?
Transfers, with the invoice as an attribute rather than the key.
That one change absorbs most of what is above. Three credits against a single invoice stop being an anomaly, and a credit with no invoice on it has somewhere to live instead of sitting in a support thread. A reversed hold reads as one entry undoing another, which you can look at, rather than a subtraction you have to reconstruct.
Two smaller habits follow. Branch on is_final wherever a payout's state matters. And key balances by asset, because that is how they are held here: one USDT balance covers every network the USDT arrived on, and which network it leaves by is decided later, when the payout is created.
None of this requires a ledger of your own, and most merchants should not build one. What it requires is that your record of a payment survives the day the payment and the order disagree.
| Event | What the merchant balance does | What the invoice does | |
|---|---|---|---|
| A transfer confirms against an invoice | Credited the amount that arrived, less the processing fee on it | Advances toward paid as each transfer lands | |
| The payer sends more than the invoice asked for | Credited in full, surplus included | Closes as paid, reported as invoice.paid_over | |
| A transfer lands after the payment window closed | Credited less the ordinary processing fee | Does not move, and no webhook fires | |
| A payout is created | The amount plus its network fee move into a hold | Not involved | |
| That payout fails or is cancelled | The whole hold returns to available, network fee included | Not involved | |
| A reorg discards a confirmed transfer | Unchanged, the credit stays | Unchanged |
Frequently asked questions
What is a double-entry ledger in a payment processor?
A record in which every movement of value posts against a pair of accounts instead of adjusting a stored total. Merchant balances are accounts in one, held per asset, and a transaction that does not balance is refused instead of written and corrected later.
Why is one invoice credited more than once?
Each transfer is priced on its own, by what it brought in, and the credit lands as that transfer confirms instead of at the end of the invoice's life. An invoice settled in three transfers therefore shows up as three credits.
What happens to money sent to an invoice address after the invoice closed?
The merchant's balance takes it, with the ordinary processing fee deducted, and the invoice is left where it was. No webhook fires for it, so the Balance page and the email notice are where a merchant sees it.
Does a cancelled payout cost me the network fee?
No. A payout that never goes out, whether it failed or was cancelled, releases its hold in full — the network fee comes back with the principal.
Can I reconcile a crypto payment balance from webhooks alone?
No. The event stream reaches invoices, withdrawals and payment-channel deposits — sixteen types in all — but a credit that pays no invoice has no event at all, so a balance summed from webhooks drifts away from the real one.
Is part of a settlement held back?
No. There is no reserve and no rolling reserve. The "on hold" figure on the Balance page is your own payouts in flight, and it returns to available if one of them never goes out.
When NOT to use a ledger of your own
- If you take a few dozen orders a month, your order table and the Paymos Balance page reconcile by eye. A second ledger is infrastructure you will maintain and never open.
- If your accounting package is already the system of record for revenue, leave it there. Mirroring the same movements into a home-built ledger gives you two numbers to defend instead of one.
- If what you need is a per-customer statement, reading a deposit feed into your own transfer table does that job without any of the accounting machinery.
- If you sell something instant and cheap to replace, the awkward events above are cheaper to handle by hand than to model.
Sources
- 1. Paymos — Webhooks (accessed 2026-09-15)
- 2. Paymos — Payment flow (accessed 2026-09-15)
- 3. Modern Treasury — How to Scale a Ledger, Part V: Immutability and Double-Entry (accessed 2026-09-15)
Last reviewed Sep 15, 2026


