How to turn a Stripe refund request into a bug report
You can't catch it from the refund itself. A refund is the customer's way of ending a conversation, and Stripe's own data model treats it that way. When you issue one, Stripe asks for a reason pulled from a short internal list built for reporting and fraud review, not for your product team, and even that field is optional. Whatever the customer actually said, "the export button hasn't worked since Tuesday" or "we just switched tools," lives in the conversation that led to the refund, not in the refund object. If nobody captures it there, it's gone the moment the money moves.
Here's what Stripe's refund actually records, why almost every one of them ends up logged the same generic way, and the habit that catches the real reason before it disappears into a closed ticket.
What the refund object actually records
Stripe's Refund object has a reason attribute, and it takes exactly four values: duplicate, fraudulent, and requested_by_customer if a person sets it, or expired_uncaptured_charge if Stripe generates it automatically. That's the whole list. It's designed to help you categorize refunds for your own books and for fraud patterns. It says nothing about product quality on purpose; that was never the field's job.
Refunding through the Dashboard does prompt for a reason at the point of action, and if you pick "Other" you have to type a short note explaining why. That note has nowhere to go, though. It isn't visible on the customer's receipt, isn't searchable across accounts, and nothing pushes it into your support tool or your tracker. Refund via the API and the field defaults to null unless your code sets it, which most refund-processing code doesn't bother to. Stripe will email the customer confirming the refund if you've turned on "Email customers for refunds" in your account settings, but there's no equivalent nudge on your side reminding anyone to write down what actually happened.
The gap that swallows the signal
Almost every support-issued refund gets logged as requested_by_customer, and that's technically true of all of them; a customer asked, so a person clicked refund. It doesn't distinguish a billing mistake from a competitor switch from an actual product failure, and once it's logged, there's no forcing function pulling anyone back to check. Compare that to a Stripe dispute, which arrives with a bank-imposed response window and real financial risk attached, covered in our guide on connecting a Stripe dispute to the bug that caused it. A refund has none of that pressure. It's a two-click action with a default reason attached, and the whole exchange can close out without anyone reading past the first sentence of what the customer wrote.
Larchmont's double-billed month
Three people rotate the shared inbox at Bramante, and by default a refund request gets the same short handling every time: apologize, refund, close. The product is scheduling and invoicing software, billed monthly per location through Stripe subscriptions to tutoring centers, and Anwei Tavares is one of the three who work the queue.
Owner, Larchmont Tutoring: Can you refund this month? Two of our three locations got charged twice, and honestly we're behind on other stuff so I don't have time to sort out which charge was right.
Anwei's first instinct was to run the usual handling. Instead, because "two of our three locations" sounded specific enough to be worth ten seconds, she asked which locations and pulled up the account before refunding anything.
Anwei Tavares, Bramante: Looked at the invoices for your Eastside and Downtown locations and you're right, both show two charges eleven minutes apart on the 3rd. Refunding the duplicates now. Can I ask what you were doing right before that happened, if anything?
Owner, Larchmont Tutoring: I added a fourth teacher slot to Eastside that morning and then went to do the same thing at Downtown. Maybe it re-billed the whole location instead of just the seat?
That matched a support ticket from four days earlier, a different tutoring franchise reporting almost the same thing after adding a seat mid-cycle, which Anwei only remembered because she'd been the one to close it. Both cases traced to the same bug. Bramante's new "add-on seat" billing path, shipped two weeks prior, was re-triggering the full location's monthly charge instead of prorating just the added seat. Anwei filed BRA-118 with both accounts and the reproduction steps, and engineering shipped a fix within two days, before a third location hit it.
Anwei Tavares, Bramante: If I'd done the usual apologize-and-refund, Larchmont's happy and the bug ships to the next customer who adds a seat. It only got caught because their message was specific enough that stopping to ask felt worth it.
Three habits that would have caught it without relying on Anwei's memory
What Anwei did generalizes into three habits, each cheap enough to fit inside the two minutes a refund request usually gets:
- Require a one-line reason on the ticket, not just on the refund. Whoever handles the request writes the customer's actual words into the support tool's internal note before processing anything, even if it's just a copy-paste of the original message.
- Ask one clarifying question when the stated reason is vague. "Not working for us" or "didn't need it" gets a reply asking what specifically broke or changed, before the refund goes through, not after.
- File it immediately once the answer names a product problem. Don't wait for a second complaint to decide a bug is real. The first report is the earliest possible warning, and it's usually the only one that arrives before the account has already churned. Larchmont's answer, a seat added right before the double charge, was already specific enough to file on the spot.
Where this depends on one person's memory
Anwei's catch worked, and it also depended on things that don't scale past one careful person on one shift:
- The refund flow has no built-in reason to slow down. Unlike a dispute's response deadline, nothing about processing a refund forces anyone to read past "please refund me." The default handling exists precisely because refunds are supposed to be fast.
- Pattern recognition still runs on memory. Anwei connected Larchmont's report to the one from four days earlier because she personally handled both. A teammate on shift that day, with no shared tag between the two tickets, has no way to know the second report is the same bug as the first.
- Nothing links a refund back to the ticket that explains it, once you have more than a handful. The reason lives in a support conversation; the refund lives in Stripe; nothing connects the two records unless a person writes the connection down, every time.
Past a few refunds a week, or once "requested_by_customer" starts hiding more than one real cause, this is the point where a team wants the support conversation and the billing account sitting on one record automatically, not reconstructed from memory each time.
Where Modem picks this up
This isn't a problem Modem solves by reading the Stripe refund object more closely, because the object doesn't carry the answer. Modem's Stripe integration matches Stripe customers to the people and companies already in your feedback, so plan and billing context sits next to whatever that account says elsewhere. The actual signal, the sentence explaining what broke, arrives through the support tool, Slack, or email conversation that Modem is already watching alongside Stripe. When a second tutoring center's ticket describes the same "charged twice after adding a seat" problem, Modem holds it on the same topic as the first one instead of leaving that connection to whoever happens to remember the earlier ticket.
Modem is the product we're building, worth saying plainly since it colors the comparison above. Whether it's worth adding comes down to volume more than anything else: at Larchmont's scale, one attentive support lead already covers this ground. The case changes once refund-adjacent tickets stop being rare enough for anyone to hold the pattern in their head; a shared topic that survives shift changes becomes the difference between catching a billing bug on report two and shipping it to report five. The related side of this same problem, cancellations that carry no usable reason either, is covered in capturing Stripe cancellation feedback when cancellation_details is null.
