Refunds, chargebacks and negative balances in a marketplace
Stripe debits refunds and chargebacks from the platform balance first. How to get that money back from a seller, and what to build before you need to.
On destination charges and separate transfers, Stripe takes refunds and chargebacks out of your platform balance first. Getting that money back from the seller is a second call that can fail. Every hard case in a marketplace comes out of that gap, and the thing that makes it survivable is a ledger that knows exactly what each payout was for.
A transfer reversal is Stripe pulling money back out of a connected account's balance and returning it to the platform.
There is nothing exotic in any of it. This is the ordinary second half of taking payments on behalf of other people, and in our experience it gets scoped after launch about as often as before. Which version you face depends on the charge type you chose, which sets who is debited by default and how much work recovery is.
Refunding a customer after the seller has been paid
Refund a destination charge with no flags set and the default may surprise you. Per Stripe's documentation, "the destination account keeps the funds that were transferred to it, leaving the platform account to cover the negative balance from the refund". You have refunded a customer with your own money. Setting reverse_transfer=true on the refund pulls the seller's share back. Setting refund_application_fee=true also returns your fee to the seller, and if you do that you have to reverse the transfer as well.
Then there is the case everyone hits eventually, where the seller has already been paid out to their bank. A reversal is only possible if the connected account's available balance is greater than the reversal amount or connected reserves are enabled. The API is not being awkward. The money is genuinely somewhere else.
| Flow | Money moved | Who carries it | What to build |
|---|---|---|---|
| Refund with the transfer reversed | Customer paid from the platform balance, seller's share pulled back | Nobody, if the balance is there | One refund call with the flag set |
| Refund when the seller's balance is short | Customer paid from the platform balance, reversal rejected | The platform | A retry path and a record of what the seller now owes you |
| Refund with no reversal | Customer paid from the platform balance, seller keeps their share | The platform | A written policy, or you have a bug |
The bottom row is worth arguing about in a planning meeting. Plenty of marketplaces deliberately absorb small refunds rather than claw back from sellers, because the goodwill outweighs the money and the support conversation costs more than both. That is a reasonable policy and a terrible accident.
Chargebacks: who is debited, and who has the evidence
For destination charges and for separate charges and transfers, Stripe debits dispute amounts and fees from your platform account. For direct charges Stripe debits the connected account, and if that balance falls short the platform is next in line when the platform carries loss liability.
The awkward part is that the account being debited is not the account holding the facts. You are out of pocket; the seller knows what was delivered and to whom. There are two honest ways to bridge that. Stripe's Connect embedded components let connected accounts respond to disputes directly, which is less work and less control. Or you collect the evidence from the seller and submit it yourself, which means a form, a deadline and a rule for silence.
| Charge type | Stripe debits | Who has the evidence | What to build |
|---|---|---|---|
| Direct | The connected account, then the platform if the balance is short | The seller | A route for the seller to respond, plus visibility of their dispute rate |
Destination, with or without on_behalf_of | The platform, amount and fee | The seller | Dispute webhooks, evidence collection, a transfer reversal |
| Separate charges and transfers | The platform, amount and fee | The seller | The same, plus your own decision about which transfers to reverse |
Recovery is a listen-and-reverse loop: take charge.dispute.created, reverse the transfer, and if you win later, transfer the money back. One trap before you write that code. Where the platform and the connected account sit in different regions, retransferring a previous reversal is subject to cross-border restrictions, so you can end up unable to repay a seller you clawed back from. Stripe's advice for cross-border destination charges with on_behalf_of is to wait until the dispute is lost before recovering.
Negative balances on connected accounts
A connected account goes negative when refunds, reversals or disputes land on a balance that cannot absorb them. While it is negative, Stripe cannot send payouts to that account's bank, and payouts resume once the balance turns positive. So the seller with the problem is also the seller whose income has stopped, which is usually how your support inbox finds out.
The balance recovers when new charges offset it, when the platform transfers funds in, or when Stripe debits the account's external bank account where debit_negative_balances is turned on. That last one works with bank accounts and not debit cards, and only in supported regions. Past 180 days negative, Stripe zeroes the account out of the platform's reserves.
Behind all of it sits a choice you make once, at integration design time: whether the platform or Stripe carries loss liability for connected accounts. Stripe advises that new platforms let Stripe take responsibility, reserving the platform-liable model for teams confident about managing merchant risk, since that model brings onboarding review, monitoring and a remediation process with it. Taking liability on to look grown up, without the operations behind it, buys you someone else's fraud.
The ledger is the reason any of this is survivable
On a coaching platform we built, the payment lands whole on the platform account and every payable unit gets its own pending transfer row, with a release date that stays empty until that unit has been delivered. We chose that shape for reasons to do with when coaches get paid. Refunds are where it turned out to earn its keep.
The structure does more work than it looks like. Because there is a row per unit, "refund the sessions this client has not had yet" is a query rather than an estimate. Refunds are computed from unconsumed transfer rows and not from the customer's credit balance, and when the two disagree the transfer ledger wins and the mismatch is logged. One of them describes money that moved. The other is a convenience.
The arithmetic underneath is guarded rather than trusted. Every split is checked against what came in before a single row is persisted, and the check throws. Not warns. Throws. A payout that does not add up never reaches Stripe, so the class of bug where a whole cohort of sellers is quietly a penny light cannot get started.
Money code should refuse to be approximately right. A reconciliation check that logs a warning is a check nobody reads. One that throws gets dealt with while somebody still remembers writing the code.
Refund policy is business logic, and it has a timezone
"Cancel any time, but not right before the session" is one sentence on a marketing page and a fortnight of decisions underneath it, starting with whose clock the cutoff is measured against.
On that platform it resolves the next occurrence in the class's own timezone, not the caller's and not UTC. Inside the cutoff the client keeps their imminent session and everything after it is refunded. A cancellation initiated by the coach skips the cutoff and refunds in full, because the rule exists to protect the coach from late client cancellations rather than the other way round. The platform's own cut comes back too. And if the cutoff calculation throws, it is caught and treated as outside the cutoff, which fails towards the fuller refund. When your own arithmetic is uncertain, be generous.
Dates are where this turns nasty. One bug we fixed came down to a helper deciding which subscription to take a credit from without knowing what date the session was on, so a client on one weekly pattern lost a credit when a session ran on a different day. Another was the backend trusting a date sent by the client app, so a coach in a different timezone from the class filed a session under the wrong day and made it invisible to everyone including its own participants. Both are the same bug: whose midnight? The fixes were boring. Thread the date through every call site, evaluate the recurrence rule against it, and derive the date server side rather than believing what arrives in the request.
Holding a payout you are not sure about
Automatic release needs an automatic hold, or the first disputed delivery teaches you why. On that platform a completed session gets checked for signs it did not really happen: too short to be real, nobody present, nobody who stayed. A session that trips the check puts every associated pending transfer on hold, records that a rule flagged it rather than a person, and sends one consolidated notification instead of one per transfer.
The detail that took a second attempt was what the check keys on. It reads the type stamped on the session record rather than looking up the parent class, because the parent can be deleted, and because in-person sessions have no participants and no attendance timeline by construction. A check that resolved the parent would have held every in-person payout in the system.
A hold is only half a mechanism, though. Somebody has to be able to look at a flagged session and release it, and the seller needs telling why their money paused rather than inferring it from a smaller payout.
The operational questions nobody scopes
Refund mechanics are a week of engineering. The operations around them run for the life of the product, and they get designed by whoever is on support that day unless somebody decides sooner.
Who can issue a refund, and up to what amount without a second pair of eyes? Does the seller hear from you before or after they notice their balance moved? Seller communication templates look like a marketing chore and behave like a product surface, because the message explaining a reversal decides whether the seller opens a ticket.
Then the trap that catches careful teams. A cancellation does several things at once: void the pending transfers, mark the subscription cancelled, call Stripe for the refund. If the refund is the step that fails after the others have succeeded, you have a customer who is cancelled and not refunded, and nothing in the logs looks like an error to anybody except them. Order those steps deliberately and make the refund the one everything else waits on.
Dispute webhooks are the handler teams defer, us included. That is defensible on day one. What is not defensible is deferring without writing down that every dispute has become a manual operation, because the person who discovers it otherwise is a support agent holding an email from a card network and a deadline.
Common questions
Who pays for a refund in a Stripe Connect marketplace?
Your platform, first. On destination charges and separate transfers the refund comes out of the platform balance, and recovering the seller's share is a separate step you have to ask for. Reverse the transfer on the refund and the seller carries their share, provided their balance can cover it.
What happens if the seller has already withdrawn the money?
The reversal fails, because Stripe can only reverse against an available balance or connected reserves. Your platform is out of pocket until the seller's future volume covers it or you collect it another way. If you want a real answer rather than a hopeful one, hold funds longer or keep reserves.
Can a connected account go negative and just stay there?
For a while. Payouts to that account's bank stop while the balance is negative, and it recovers through new charges, a transfer from the platform, or a bank debit where that setting is enabled. Past 180 days, Stripe clears the balance out of the platform's reserves.
How much of this can wait until after launch?
Refund with reversal and a dispute webhook that alerts a human are day-one work. Automated evidence collection, negative-balance chasing and seller-facing dispute screens can wait. What cannot wait is the ledger, because retrofitting one onto a year of payouts is a data migration nobody enjoys.
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.