Refunds get the attention. Disputes are worse, and the reason is structural rather than a matter of degree.
On a refund you at least have a parameter. reverse_transfer: true exists, you can pass it, and if you forget there is a boolean to point at afterwards. On a dispute there is no such parameter, because there is no such option.
What Stripe does when you lose
Stripe debits the platform for the disputed amount plus the dispute fee. That is it. That is the whole automatic behaviour.
The connected account that received the original transfer is not touched. Not reduced, not notified, not flagged. From that seller's perspective nothing happened at all — the money arrived weeks ago and is still theirs.
So the platform's position after losing a dispute on a destination charge is:
- disputed amount (debited from platform balance)
- dispute fee (debited from platform balance, typically ~15 USD)
+ nothing (the transfer is untouched)
The seller keeps the funds. You paid the customer back and paid a fee for the privilege.
Why this is not symmetrical with refunds
A refund is initiated by you, so Stripe can offer a parameter at the moment of initiation. A dispute is initiated by the cardholder's bank, days or weeks after the charge, through a process you are not in. There is no call of yours to attach a flag to.
The reversal, if you want one, has to be a separate action you take afterwards:
// on charge.dispute.closed with status 'lost'
await stripe.transfers.createReversal(transferId, {
amount: disputedAmount, // not necessarily the full transfer
description: `dispute ${disputeId}`,
});
Nothing calls that for you. If your webhook handler for charge.dispute.closed only writes a row and updates a status, the clawback never happens.
Three things that make this accumulate quietly
The timing hides it. A dispute closes 30 to 90 days after the charge. By then the transfer is in a different payout, a different month, and usually a different reconciliation report. The debit and the un-reversed transfer are never adjacent in any view you look at.
The connected balance may not cover it. Even when you do reverse, the seller may have already paid out. The reversal leaves the account negative, which surfaces later as a failed payout — a third event, in a fourth place, with no obvious link back to a dispute.
Winning still costs. The dispute fee is generally not returned on a win in most regions. A platform with a good win rate still bleeds fees, and those fees are charged to the platform rather than to the seller whose transaction caused them.
The reconciliation query
The question is the same shape as the refund one, and it is worth running over full history rather than a recent window, because the whole failure mode is that it is old by the time it matters:
SELECT d.id AS dispute_id,
d.charge_id,
d.amount AS debited,
d.fee AS dispute_fee,
COALESCE(r.amount, 0) AS reversed,
d.amount - COALESCE(r.amount, 0) AS unrecovered
FROM disputes d
LEFT JOIN transfer_reversals r ON r.charge_id = d.charge_id
WHERE d.status = 'lost'
AND d.amount > COALESCE(r.amount, 0)
ORDER BY unrecovered DESC;
The policy question underneath the technical one
Whether to claw back is genuinely a business decision, and reasonable platforms answer it differently. A marketplace with sellers who have no control over fraud may absorb disputes deliberately. A platform whose sellers do control fraud usually should not.
What is not a decision is doing it by accident. If you have never reversed a transfer on a lost dispute, you have chosen to absorb every one of them — you just did not know that was the choice you were making. Run the query and find out which platform you are.
Top comments (0)