All posts22 September 2026

Stripe Connect marketplace billing: four hard-to-reverse decisions

Charge type, fee ownership, payout policy and ledger strategy are cheap to choose and expensive to undo. What we picked on a coaching platform, and why.

W. Akram12 min readengineering · product

Four decisions in a marketplace billing design are cheap to make and expensive to undo: which charge type you use, who carries the processing cost and where your platform fee comes out, when sellers get their money, and whether your database or Stripe's is the record of what happened. Nearly everything else can be changed on a Tuesday afternoon.

Marketplace billing is the set of rules deciding who the customer pays, who carries the processing cost, when the seller is paid, and which system holds the record of it.

What follows is how we approached each one on a coaching platform where practitioners are paid per session, what it cost, and what we would have had to unpick to change our minds later.

Why these four resist reversal

Ordinary software decisions are reversible because the only thing at stake is code. These four are different because each one has already produced artefacts you cannot rewrite: money that moved, accounts that were onboarded under a particular arrangement, statements customers have already read, and a year of records an accountant will want to reproduce.

Charge type is the worst of them. Connected accounts are onboarded against a set of capabilities, historical charges live in whichever account processed them, and a customer's bank statement carries whoever was the settlement merchant at the time. Changing it does not migrate any of that. You run both models side by side, with two code paths and two reporting shapes, until the old charges age out of anything anyone asks about.

Fee ownership is reversible in code and not in commerce. The number a seller nets is the number they budget against, and moving it is a conversation with every seller you have.

Payout policy is the one sellers notice fastest. Lengthening a hold is felt as a cash flow cut and generates support tickets within a day.

Ledger strategy is technically the easiest to change and practically the hardest, because you cannot backfill intent. If your rows never recorded which delivered thing a transfer paid for, no migration invents that later.

That is not an argument for agonising over them. It is an argument for making them deliberately, writing down why, and knowing which ones you have signed up to for the life of the product.

Decision one: who owns the customer relationship

Stripe offers three shapes, and its guide to charge types is the place to compare them properly. Direct charges put the payment on the connected account, which suits software where sellers transact with their own customers and your platform is mostly invisible. Destination charges put the charge on your platform and immediately move funds to the connected account. Separate charges and transfers put the charge on your platform and decouple the transfers entirely, which is what you need when one payment splits across several sellers, or when you cannot yet say who should be paid.

The deciding question is not technical. It is who the customer thinks they bought from. If they would email your support address about a bad experience, the charge belongs on your platform. If they would email the seller, and the seller has their own brand and their own dashboard, direct charges are honest about that. Stripe's on_behalf_of parameter sits in between: on a destination charge it sets the settlement merchant, which changes whose statement descriptor and address the customer sees, and the settlement currency. The full comparison, including chargeback control, is in the piece on destination charges versus separate charges and transfers.

On the coaching platform we chose separate charges and transfers, and the reason was not flexibility. It was that money arrives weeks before anybody has earned it. A destination charge moves the seller's share at checkout, which is precisely what we did not want, and every mechanism further down this page exists to undo that timing rather than to buy us options.

Reversing that decision later would mean re-onboarding connected accounts against different capabilities, rewriting every refund path, and living with the fact that a year of charges sit in one account and the new ones sit somewhere else. We would only do it for a change in the business model, not for engineering taste.

Decision two: who pays Stripe's fee, and where yours comes out

Two separate questions get muddled here. The first is who carries Stripe's processing cost. With direct charges the payment lands on the connected account and the fee comes off there. With destination charges and with separate charges and transfers, Stripe debits the platform account for fees, refunds and chargebacks, which the destination charges documentation states in as many words. Choosing an indirect charge type means choosing to be the fee payer, whatever your commercial arrangement with sellers says.

The second question is where your own cut comes out. With destination and direct charges you can set an application fee at charge time and Stripe splits it for you. With separate charges and transfers there is no application fee at all: you decide the transfer amount, and your cut is simply the part you did not transfer.

We took the second route deliberately. On the coaching platform there is no application fee at charge time. The platform's share is computed per payable unit and folded into each transfer, with every split checked against what came in before a row is written.

That arrangement earns its keep on refunds. Because the fee is expressed per unit rather than taken once at checkout, a partial refund can return the platform's own share of the refunded units, which is the policy the product wanted: the platform eats its cut when a client cancels. Doing that with an application fee taken at charge time is possible but noticeably more fiddly.

One small detail. Support staff can override the fee for an individual account, and the override is clamped in one direction only, downwards, so a mistyped value quietly becomes the standard rate rather than a multiple of what somebody agreed to.

Decision three: when the seller gets the money

The default is that you do not decide much. Funds land in a connected account's balance and Stripe pays them out on a schedule, which you can configure per account: the interval, the days, and how long charges take to become available. Stripe's guide to managing the payout schedule covers the settings, including a settlement delay that can be extended for accounts where your platform owns dispute liability. You can also switch an account to manual payouts and trigger them yourself, which is the lightest version of holding funds back.

The alternative is to hold the money on the platform and release it against delivery, which is what we built. Every payable unit gets a pending transfer row with a release date that starts empty. It is filled in on delivery: a one-off deliverable's transfer becomes due when the document reaches the client, and a session's transfer becomes due 24 hours after that session finishes. A background process picks up whatever has come due since it last looked.

The argument for it is unglamorous. Refunding money you still hold is a database update and a Stripe refund. Refunding money you already paid out is a negotiation. The seller has been paid, has possibly spent it, and now either gives it back or you absorb it, and Stripe's mechanics for connected account balances going negative are the least pleasant corner of Connect. We wrote up what happens in refunds, chargebacks and negative balances in a marketplace, and the summary is that every option is worse than not being in that position.

The cost is real and worth stating. Sellers wait longer for money than they would on a plain destination charge, and any hold you introduce is felt immediately. Lengthening one later is the change most likely to produce an angry email, which is why the policy belongs in the seller terms from the first week rather than being tightened after somebody gets burned.

Decision four: whose records are the record

Every marketplace starts by treating the Stripe dashboard as the source of truth, because for the first year it more or less is. Charges are visible, payouts are listed, and anything a founder needs is two clicks away. The month it stops working is the month somebody has to produce accounts.

The dashboard records what happened to money. It does not record what the money was for. It cannot tell you which delivered unit a transfer paid for, which sessions a client has left, what you owe sellers who have not been paid yet, or why a particular payout is smaller than the invoices it covers. Those answers only exist if you wrote your own rows at the moment each movement was decided, which is the argument set out in reconciling Stripe payouts with your own ledger. It is also the answer to a diligence question that turns up later, since revenue recognition is one of the things buyers test, and it sits alongside the other technical due diligence red flags worth clearing early.

The version of this decision we would defend hardest is the unglamorous one: one row per movement of money, append-only, written when the movement is decided rather than when it settles, with Stripe's own identifiers on every row. Refunds on the coaching platform are computed from unconsumed transfer rows, and where anything else in the system says something different, the transfer rows are the ones we believe. They are the only rows in the system written because money moved.

The same reasoning led to a decision that is harder to defend and worth admitting. We did not use Stripe's billing product for subscriptions, because the unit of value is a session credit consumed one at a time rather than continuous access to something. So renewals are off-session payments against a saved card, on top of an in-house credit ledger of typed events. What that cost: no proration, no dunning ladder, no smart retries, no tax automation. A failed payment sets a status and appears on a practitioner's list of things needing attention, which a human then chases. Every one of those would have arrived free with the hosted product, and we knew it when we chose.

The four decisions in one table

DecisionThe realistic optionsWhat we choseWhat reversing it costs
Charge typeDirect, destination, or separate charges and transfersSeparate charges and transfers, because the platform holds money it has not yet earnedRe-onboarding connected accounts, rewriting refund paths, and two reporting shapes until old charges age out
Fee ownershipConnected account pays (direct), or platform pays (destination and separate)Platform pays Stripe; the platform's own share is folded into each transfer rather than taken at checkoutCheap in code, expensive in commerce: the seller's net is the number they budget against
Payout policyStripe's automatic schedule, adjusted settlement delay, manual payouts, or release against deliveryRelease against delivery, with a transfer row per payable unit and a scheduler pushing due rowsSellers feel any lengthened hold within a day, so it belongs in the terms from week one
Ledger strategyStripe's records, or your own row per money movementOwn ledger, append-only, written when the movement is decidedYou cannot backfill intent, so the gap is permanent for every month you did not write rows

Read down the third column and the pattern is that we consistently chose the option that keeps money and information on the platform for longer, and paid for it in machinery. That is the right trade when the platform holds funds it has not yet earned. It is the wrong trade for a marketplace where the seller is owed the money the moment the customer pays, and we would not build any of this for that.

The gap we would close first

Writing the failure paths down is how you find the holes, and this build has one worth naming. A refund call that fails is logged, but the cancellation and the voiding of the pending transfers still go ahead. So a client can end up cancelled without their money back, with nothing but a log line saying so. It is a small window, it has an obvious fix in making the whole thing a single retriable unit of work, and it stays invisible until you write out what happens when each external call fails.

What connects all four decisions is a preference for keeping the record fine-grained: assertions that throw instead of logging, a fee expressed per unit rather than netted once at checkout, a row for every obligation written before any money moves. Nothing there is clever. It is most of the difference between a billing system you can explain to an accountant and one you have to reconstruct in front of them. We build platforms where money moves between parties, and that is the part we would not compromise on.

Common questions

Should a marketplace use destination charges or separate charges and transfers?

Destination charges when each payment goes to one seller and you want Stripe to move the funds at checkout. Separate charges and transfers when a payment splits across several sellers, when you do not yet know who to pay, or when you are holding money until a service is delivered.

Who pays the Stripe fee in a marketplace?

It follows the charge type. With direct charges the payment lands on the connected account and the fee comes off there. With destination charges and separate charges and transfers, Stripe debits the platform account for fees, refunds and chargebacks, regardless of what your seller agreement says about who bears it commercially.

Can we change our Connect charge type later?

Technically yes, practically it is a migration. Connected accounts are onboarded against particular capabilities, historical charges stay in whichever account processed them, and refunds and disputes on old payments still follow the old model. Expect to run both paths side by side rather than switching over.

Should we hold funds until the service is delivered?

Hold them if a refund before delivery is a normal event rather than an exception. Holding costs sellers cash flow and costs you a release mechanism to build. It buys you the ability to refund without asking anybody to return money they have already received and possibly spent.

Do we need our own ledger if Stripe already records everything?

Yes. Stripe records what happened to money. Your ledger records what it was for, which delivered thing each transfer covers, and what you owe sellers who have not been paid yet. Those columns cannot be reconstructed later from payment records alone.

Let's talk

Ready to build the thing?

Book a free 30-minute call. We'll dig into your idea, your stack and your timeline, and give you an honest read on what it will take to build and launch. You'll leave with a clearer plan whether or not you hire us.

Free 30-min call. No pitch.