Nordlet

Blog

Marketplace Seller Subledgers: Revenue vs Payables

A practitioner's guide to separating customer cash, platform commission, seller payables, reserves, and payouts with balanced journal entries and a working order-to-payout data model.

Nordlet Team · · 12 min read

The most common mistake in marketplace accounting is treating the buyer's gross payment as platform revenue. A customer pays €100, that €100 lands in the processor account, and someone posts the whole thing to sales. The books balance for a while, revenue looks great, and then an auditor asks where the seller liability is. There isn't one, because the seller's €90 was never recorded as money owed.

The fix is structural, not cosmetic. A marketplace needs a ledger model that separates five distinct pools from the first entry: customer cash held in transit, platform commission revenue, seller payables, fees, and reserves. Payouts are a sixth event that settles the payable, not a revenue or expense event on their own. This guide shows the balanced entries and the data model that keep those pools distinct.

Start with the principal-versus-agent conclusion

Before any account structure matters, the platform has to decide what it is selling. Under ASC 606, this is assessed per specified good or service, and the answer determines whether revenue is gross or net. A platform can be principal for one flow and agent for another.

For a typical marketplace sale of a seller's goods, the platform is usually an agent. It arranges for the seller's product to reach the buyer, it does not take inventory risk, and its consideration is a commission. Deloitte's principal-versus-agent guidance under ASC 606 frames the test around control: responsibility for fulfilling the promise, inventory risk, and pricing discretion are indicators, not a checklist that overrides the underlying control assessment.

The reason this belongs in the ledger design, not just in a memo, is that the conclusion drives every downstream entry. If the platform is an agent, the seller's share is a liability the moment it is earned. If it is principal for a fulfillment or warranty service, that piece is platform revenue. Store the conclusion as data:

  • principal_agent_role
  • specified_good_or_service
  • commission_rule_id
  • control_assessment_version
  • effective_from / effective_to

A reviewer looking at a net commission line two years later should be able to see why gross was not used. As BillingPlatform notes on ASC 606 principal vs agent, the label "marketplace" does not settle the question. The control analysis does.

Define seller balance before you write a single journal rule

"Seller balance" is not one number. A seller can have a healthy lifetime balance and nothing available to pay out because the money is still pending or held in reserve. Collapse these states and you will misreport what the seller can withdraw and what the platform still owes.

At minimum, track:

  • Pending: earned or collected, not yet eligible for payout
  • Available: eligible for payout under platform rules
  • Reserved: withheld against expected refunds, disputes, or chargebacks
  • Paid out: transferred or submitted for settlement
  • Negative: amount the seller owes, recoverable from future proceeds
  • Suspense: received, but seller or treatment not yet established

Payment providers already model this distinction. Stripe separates each connected account into pending and available balances and keeps a platform-level reserved balance for certain negative seller balances. If the processor separates these, the ledger has to as well, or reconciliation will never tie.

The chart of accounts and the dimensions that matter

A workable starting structure keeps clearing accounts, liabilities, and revenue apart, and carries a seller dimension on every material posting.

Purpose Account or dimension Treatment
Processor funds awaiting settlement Payment processor clearing Clearing asset
Banked processor payout Bank Asset
Seller amount earned but unpaid Seller payables Liability
Seller-level detail seller_id dimension Liability detail, not revenue
Platform commission Commission revenue Revenue
VAT or sales tax Tax payable Liability
Processor fees Payment-processing expense Expense
Seller reserves Reserve liability Contract-dependent
Refunds Contra-revenue or refunds Reverses original treatment
Chargebacks Loss, receivable, or seller recovery Depends who bears loss
Payouts awaiting bank confirmation Seller payout clearing Clearing account
Unallocated amounts Suspense Temporary exception

Every material entry should carry seller, order, payout batch, processor, currency, legal entity, tax treatment, event type, external reference, and any reversal link. Without these, the seller subledger cannot prove an individual balance and the general ledger cannot prove the total.

You can implement the subledger as one liability account per seller, one aggregate payables account with seller_id as a dimension, or a split across payable, reserve, and payout clearing. The aggregate-plus-dimension approach scales better once you pass a few dozen sellers. Pick the model with your accountant, because safeguarding rules and local payment regulation can affect how the funds must be presented. For the mechanics behind the postings, our write-up on double-entry ledger APIs for marketplace accounting covers the ledger primitives this model sits on.

Post the sale with separate seller and platform lines

Take an agent sale: customer pays €100, platform commission is €10, seller earns €90. At the point commission and seller entitlement are earned:

Account Debit Credit
Payment processor clearing €100
Seller payable — Seller A €90
Commission revenue €10

The control that matters: €90 credits a liability, not revenue. The platform recognizes €10. This is the outcome ASC 606's marketplace example reaches for an agent, where the platform recognizes its commission rather than the seller's full sales amount.

If tax is collected separately, add the tax-payable line and reduce the seller or platform figure according to the tax conclusion. Do not fold tax into commission or seller payable as a convenience.

Keep processor fees out of the revenue and payable math

Three things get confused constantly: platform commission, processor fee, and payout amount. They are different, and they hit different accounts.

Customer pays €100, commission is €10, processor retains €3.

  • Commission revenue stays €10 if that is the contract.
  • The €3 processor fee is an expense.
  • Seller payable is €90 unless the seller contract says the seller bears the processing fee. Only then does it drop to €87 with a matching €3 charged to the seller.

Posting only the net €97 that lands in the bank erases the gross sale, the commission, and the fee in one move. Our payment reconciliation reference makes the same point: decompose the settlement, treat processor fees as expenses, and never let the net deposit stand in for revenue.

Model availability, reserves, and payouts as separate events

Money changes status several times between capture and settled cash. Each transition deserves its own entry, and a reserve is never an unexplained cut to revenue.

When available funds move into reserve:

Account Debit Credit
Seller payable — Seller A €15
Seller reserve liability €15

When the reserve releases, reverse it. The seller's total obligation has not changed; its status has. Stripe documents reserve transactions that increase and release reserves, plus collection transfers for long-standing negative balances, which is why these need identifiable accounts rather than a single netting entry.

Payouts get their own two-step treatment. At approval:

Account Debit Credit
Seller payable — Seller A €90
Seller payout clearing €90

At confirmed bank settlement:

Account Debit Credit
Seller payout clearing €90
Bank €90

If the payout fails, reverse the clearing back into seller payable. A payout is a liability settlement, not an expense. Treating it as an expense double-counts against the sale that already recorded the payable. Stripe separates payout status (pending, in_transit, paid, failed, canceled) and expected arrival date from initiation, and those dates should not collapse into one timestamp.

The order-to-payout data model

The subledger runs on events, not on the application's current order status. Every seller-affecting event carries the same envelope:

  • seller_id, event_id, order_id
  • event_type (sale, refund, chargeback, reserve_hold, reserve_release, payout, adjustment)
  • effective_date, posting_date
  • currency, direction, gross, commission, fees, reserve
  • refund_ref / dispute_ref
  • payout_batch_id
  • reversal_of
  • source_status, idempotency_key

From those events you calculate the closing balance rather than storing a mutable field:

Seller closing balance
= opening balance
+ seller credits
− seller debits
− payouts
± reserve movements
± corrections

Each external event needs a durable idempotency key so retries and duplicate webhooks do not double-credit a seller or double-pay a payout. Patterns that work:

  • Seller journal event: seller_id:order_id:event_type:version
  • Payout batch: processor:payout_id
  • Processor event: processor:event_type:processor_event_id

Stripe's own webhook guidance warns that duplicate events happen, so duplicate detection is not optional. Posted entries stay immutable. Corrections go through a reversing entry plus a corrected entry, and it is your own event envelope that carries the reversal_of link and the reason. The ledger holds the postings, not that link: Nordlet exposes create, get and list for journal transactions in its API reference and no update or delete, so a correction is always a new transaction rather than an edit.

Refunds and chargebacks link back, they never float

A refund that shows up in next week's payout should never become a fresh negative sale attached to that payout. Link it to the original transaction and preserve the original seller allocation. Depending on the contract:

  • Reverse commission if the contract makes it refundable
  • Debit seller payable if the seller bears the refund
  • Debit a platform loss account if the platform bears it
  • Increase a reserve while a dispute is open, release or consume it on resolution

Chargebacks after payout are where negative balances appear. The seller's money is already gone, so the platform either recovers it from future proceeds or absorbs the loss. The model has to permit negative seller balances and define the recovery rule. Adyen's marketplace reporting shows refunds and chargebacks producing separate debit rows per account holder, which is the granularity to aim for.

Reconcile per payout, not per month

Decompose every processor settlement before posting, and reconcile each payout rather than aggregating a month:

Gross charges
− refunds
− processor fees
− chargebacks
± reserves and adjustments
= net processor payout

Then prove four balances agree for each seller and currency: the seller subledger, the processor's seller balance, the payout report, and the bank clearing. When they differ, classify the difference (timing, missing event, duplicate, wrong attribution, wrong currency, failed payout, chargeback posted to the wrong party). Netting differences away is how errors offset each other and survive to the next close.

Aggregating a full month lets timing differences cancel out and hides real breaks. Per-payout matching, keyed on payout ID and transaction reference rather than amount and date, catches them.

Common failure modes

Failure mode Why it hurts Control
Gross customer payment posted as revenue Inflates revenue, hides seller liability Split payable and commission at the earning event
Only net deposits posted Loses gross, fees, refunds, reserves Decompose every settlement
Payout booked as expense Misstates a liability settlement Debit seller payable on payout
One seller balance field Mixes pending, available, reserved, paid Track balance states explicitly
Commission calculated at payout time Revenue follows payout scheduling Recognize commission when earned
Reserve treated as revenue cut Obscures timing and risk Track hold, release, consume separately
Refund applied to latest payout Breaks order attribution Link refund to original transaction
Editing posted entries Destroys audit trail Reverse and repost
Replayed webhooks Duplicate credits and payouts Idempotency keys, duplicate detection
Matching on amount and date only Different items collide Match on payout ID, transaction ID, seller, currency

What I would do first

Map the money flow before touching the chart of accounts. Write down every event from payment capture to reserve release, and mark which ones create revenue, which create a liability, and which only move status.

Then build one end-to-end flow and prove it balances: a normal sale, a partial refund, a chargeback after payout, a reserve held and released, and a failed payout. Run those with known expected balances. If the seller payable control account equals the sum of the subledger, the processor clearing ties to the settlement report, and the bank deposit ties to the payout ID, you have a working spine.

Turn on period locking only after the first month closes cleanly with an empty suspense balance. Locking too early just freezes mistakes.

Nordlet posts settlements along these lines out of the box: the platform's commission goes to commission revenue, the seller's share to a seller payable, processor fees to a payment-processing expense, and anything not yet attributed to a suspense account. Ledger periods stay open until you lock them explicitly, so the first month can be closed once suspense is empty. The features page lists what is included.

FAQ

Is the buyer's payment ever platform revenue?

Only for the specific goods or services where the platform is the principal. For an agent marketplace sale of a seller's goods, revenue is the commission and the seller's share is a liability. A platform can be principal for a fulfillment service and agent for the underlying goods in the same order, so the assessment is per specified good or service, not per company.

Should reserves reduce revenue?

No. A reserve is a timing and risk allocation, not a revenue adjustment. Move funds from seller payable into a reserve liability when you hold them, and reverse when you release. Revenue and commission are unaffected by whether the seller's money is currently reserved.

How do I stop duplicate payouts from webhook retries?

Attach an idempotency key to every external event, such as processor:payout_id for payout batches, and detect duplicate events on write. Providers explicitly warn that duplicate webhook deliveries occur, so the ledger has to reject the second attempt rather than post it again.

Can a seller balance go negative?

Yes, and the model must allow it. A chargeback that lands after the seller has been paid leaves the platform with either a receivable from the seller or a loss it absorbs. Suppressing negative balances just hides an unrecorded obligation.

Why reconcile each payout instead of the monthly total?

Because timing differences and errors cancel out when you aggregate a month, so the totals can tie while individual transactions are wrong. Per-payout reconciliation, matched on payout and transaction IDs, surfaces missing events, duplicates, and misattributed chargebacks before close.

Further reading