Chapter 3 · Reverse transfers
The Platform Refund Ledger · 6 min read
This chapter answers one question: how does money actually come back from a connected account, what happens by default versus on request, and what happens when the account cannot cover it?
A transfer reversal is the instrument that undoes a transfer — wholly or partially — by moving funds from the connected account's balance back to yours. It is also the single mechanism most often assumed to be automatic that is not.
The call
Reversals are created against an existing transfer (POST /v1/transfers/{id}/reversals, docs.stripe.com/api/transfer_reversals/create). The parameters that matter:
| Parameter | Behavior |
|---|---|
amount | Optional. "Can only reverse up to the unreversed amount remaining of the transfer. Defaults to the entire transfer amount." Partial reversals are allowed repeatedly until nothing remains; once fully reversed, a transfer cannot be reversed again. |
refund_application_fee | Optional boolean. A full reversal refunds the full application fee; otherwise the fee is refunded proportionally to the amount reversed. Default off — the same keep-by-default rule as chapter 2. |
The returned TransferReversal carries balance_transaction — the ledger line — plus source_refund or destination_payment_refund, which tie the reversal to the refund that motivated it when one exists. transfer.amount_reversed on the parent transfer is the running total returned so far; transfer.amount − amount_reversed is the ceiling on anything you attempt next.
When it happens by default: never
Stripe creates reversals in two situations only:
- You pass
reverse_transfer: trueon a refund (chapters 2 and 6). Even then it is opt-in per call — the parameter defaults to false on every charge pattern. - You create the reversal yourself, standalone. This is how you recover from refunds already issued without the flag, and it is the documented remediation path for disputes (chapter 5).
One legitimate alternative exists in the separate pattern, straight from Stripe's documentation: instead of reversing, "reduc[e] subsequent transfer amounts" — netting what you are owed against the next payout-bound transfer you create (docs.stripe.com/connect/separate-charges-and-transfers). Chapter 10 returns to when netting is the better instrument.
The race against payout
The reversal reaches only what is still in the connected account's Stripe balance. Everything already paid out to their bank is gone from reach. Worked timeline on the book's $120 destination sale ($12 fee withheld, so a $108 transfer):
| Moment | Event | Connected balance | Reversible now |
|---|---|---|---|
| Day 0, 09:14 | Charge succeeds; transfer of $108 lands in pending | +$0 available (pending) | effectively $0 |
| Day 2 | Funds become available | +$108.00 | $108.00 |
| Day 3 | Seller's daily payout runs | +$0 available | $0 |
| Day 5 | Buyer requests refund; reverse_transfer: true | −$108.00 after reversal | — |
On day 2 the flagged refund would have been clean: $120.00 out to the buyer, $108.00 back from the seller, and the $12.00 fee never having left the platform — everyone at zero. On day 5 the call still executes — but the account has no funds, and that changes the outcome from retrieval to receivable.
When the balance cannot cover it
What happens next depends on configuration, and this is the part to get exactly right because folklore contradicts documentation here.
Stripe's separate-charges page states a hard rule: "It's only possible to reverse a transfer if the connected account's available balance is greater than the reversal amount or has connected reserves enabled" (docs.stripe.com/connect/separate-charges-and-transfers). Meanwhile the account-balances documentation describes connected accounts going negative from "refunds and chargebacks" as an ordinary condition that Stripe manages rather than prevents (docs.stripe.com/connect/account-balances). Both statements are accurate; which one governs your reversal depends on your platform's loss-responsibility configuration — read it from controller.losses.payments on Accounts v1 (stripe or application) or defaults.responsibilities.losses_collector on v2. Where your platform is ultimately responsible for connected-account shortfalls, reversals are permitted to drive balances below zero; where Stripe bears losses, expect the stricter limit.
When a connected account is negative, the documented sequence is mechanical (docs.stripe.com/connect/account-balances):
- Future volume offsets it first. Subsequent payments on that account are applied against the deficit before anything pays out.
- Payouts stop. While the balance is negative, payouts to the seller's bank are suspended; they resume when the balance turns positive.
- Optional external debit. With the balance setting
debit_negative_balances: true, Stripe attempts to debit the connected account's external bank account for the deficit (never their debit card; supported regions include the US, Canada, Europe/SEPA including the UK, Australia, and New Zealand). - Your platform may carry a reserve. If your platform is responsible for the shortfall, Stripe holds a reserve on your balance via
reserve_transactionentries while the account is negative. - The 180-day endgame. An account still negative after 180 days triggers a
connect_collection_transfer: Stripe moves funds from your balance to zero the account out, and Stripe's own guidance is to consider rejecting the account at that point.
Worked continuation of the day-5 case: the reversal leaves the seller at −$108.00. They keep selling — $80 of new volume next week applies against the deficit first, leaving −$28, released to normal operation once crossed. They churn instead, and 180 days later the connect collection transfer makes your platform whole at the cost of holding a reserve for six months and eating the relationship. Same API success code; two completely different financial events.
[DIAGRAM: Life of a transfer, and the three exits]
Timeline axis left to right: charge -> transfer created -> pending ->
available -> payout(s) -> time.
Above the axis, exit A: reversal while available (clean recovery;
balance returns to platform).
Below the axis, exit B: reversal after payout (call succeeds; account
goes negative; arrows showing offset-by-future-volume loop, suspended
payouts badge, optional external-debit arrow to bank icon).
Exit C drawn as a fork after exit B: 180 days without recovery -->
connect_collection_transfer arrow from PLATFORM balance zeroing the
negative, labeled "platform made whole by Stripe, at its own expense".
Caption: the earlier the exit, the less the outcome depends on anyone's
future behavior.
Getting the amount wrong
Two classic mistakes, both fatal to the call:
- Computing from the charge instead of the transfer. On a $120 charge where you transferred $108 (separate pattern), requesting a $120 reversal exceeds the transfer and fails. The ceiling is always
transfer.amount − amount_reversed. Destination charges are no safer: the fee is withheld from the transfer there too, so a $120 charge carrying a $12 fee leaves $108 to reverse and the same request fails for the same reason. This is why chapter 1 insists you readtransfer.amountrather than reconstruct it. - Summing partials carelessly. Three partial refunds each reversing proportionally can leave
amount_reversedwithin a cent or two of your own arithmetic (chapter 6 works the rounding). Pre-flight check: fetch the transfer live, readamount_reversed, request the difference.
What to check on your own platform
- For ten recent transfers, compare
amountagainstamount_reversedand list every nonzero gap with its cause. - Record your platform's negative-balance responsibility field value (
controller.losses.paymentsor the v2 equivalent) and date-stamp it. - Check whether
debit_negative_balancesis enabled on your connected accounts' balance settings, and decide deliberately. - List connected accounts with negative available balances today; note their last transaction date — that list is your real receivables aging report.
- Find any
reserve_transactionorconnect_collection_transferlines in your balance history and reconcile them to specific accounts.
Sources: docs.stripe.com/api/transfer_reversals/create · docs.stripe.com/connect/separate-charges-and-transfers · docs.stripe.com/connect/account-balances