DEV Community

Cover image for 3 Statement Descriptor Mistakes That Quietly Turn Real Purchases Into Chargebacks
Divy Yadav
Divy Yadav

Posted on

3 Statement Descriptor Mistakes That Quietly Turn Real Purchases Into Chargebacks

I spent a week chasing what looked like a fraud spike. It wasn't fraud. It was 22 characters of bad UX that I'd shipped myself.

A support ticket queue that's usually quiet started filling up with the same complaint: "I didn't make this charge." Different customers, different amounts, same pattern.

My first instinct was fraud

Rotate keys, check for a leaked webhook secret, the usual on-call checklist. None of it panned out. Every single "disputed" charge turned out to be a real purchase, made by the actual customer, on the actual date.

The problem wasn't the payment

It was what the payment looked like on a bank statement.


The 22-character problem nobody warns you about

Card networks give you roughly 22 characters to explain a charge to your customer. Not 22 words. Twenty-two characters, including spaces.

If you're on Stripe, this isn't a soft guideline, it's enforced. Statement descriptors are capped at 22 characters, and you can't use <, >, ', ", or *, and the descriptor can't be only numbers.

That limit is tight enough that your actual product name often doesn't fit. "RunClub Premium Monthly" doesn't fit in 22 characters. Neither does most of what a real business is called.

So it gets cut. And a cut-off name looks nothing like the thing your customer remembers buying.

Where dynamic descriptors make it worse

If you're setting a dynamic suffix so each transaction shows different context, the math gets tighter, not looser. Here's a basic example using Stripe's Payment Intents API:

curl https://api.stripe.com/v1/payment_intents \
  -u "sk_test_yourkey:" \
  -d amount=1099 \
  -d currency=usd \
  -d "statement_descriptor_suffix=RUN CLUB APR"
Enter fullscreen mode Exit fullscreen mode

Your account-level prefix, an asterisk, a space, and your suffix all share that same 22-character budget. Stripe allots roughly 10 characters for the dynamic part by default. Go over, and Stripe truncates it, sometimes into something that reads as gibberish or, worse, as a duplicate of your own business name stacked twice.

I've seen this exact failure mode described in payment platform support docs: a merchant's static prefix and their dynamic suffix both containing a version of the brand name, so the customer sees the company name twice in a row and assumes something's broken. Nothing was broken. It was just two systems both trying to be helpful in the same 22 characters.

Testing this before it reaches a real customer

Here's the part most teams skip: nobody on the engineering side actually checks what the final descriptor renders as on a real bank statement, because you can't easily see that in a sandbox environment.

What I started doing instead: after setting a new descriptor pattern, I paste the exact string I'm about to ship into UnknownCharges' descriptor decoder, the same tool a confused customer would use if they searched their statement. If a decoder built for confused consumers can't make sense of what I just shipped, neither will they.

It's a five-second sanity check, and it catches the "wait, this reads as nonsense" problem before a support ticket does.

Understanding processor prefixes as the person who set them

If your payment stack routes through a processor like Square, Stripe Connect, or Toast instead of settling directly, your customers see the processor's name before they see yours. That's not a bug in your integration. It's how card networks handle sub-merchants.

SQ *YOURBUSINESS is a completely normal, expected pattern for Square-routed charges. Customers who don't know that read it as a stranger's name attached to their statement. UnknownCharges keeps a running reference page for the SQ* prefix specifically because this exact confusion generates enough support volume across enough businesses to be worth documenting on its own.

If you're building on top of a processor, it's worth reading how your specific prefix looks to an end user who has zero context on your backend architecture, because that's the only context your customer actually has.

What happens after the customer still disputes it

Even with a clean descriptor, some percentage of customers will still file a dispute instead of contacting you first. That's a habit, not a fixable bug, and it's worth understanding what happens on their end so your support responses actually match reality.

Credit card disputes in the US run under the Fair Credit Billing Act, with a 60-day window from the statement date. Debit card disputes run under Regulation E instead, with a much tighter two-business-day window before liability caps start rising. If your support team is telling customers "just call your bank" without knowing which rule applies to which card type, you're giving advice that's sometimes wrong.

I've pointed support-facing teammates at UnknownCharges' dispute playbook when writing our own canned responses, mainly because it lays out the credit-versus-debit distinction more clearly than the two paragraphs buried in most payment processor docs.


A quick checklist before you ship a descriptor change

Situation What to check
Setting a static prefix Does it fit in 22 characters, no truncation guesswork?
Adding a dynamic suffix Does prefix + * + space + suffix stay under 22 total?
Routing through Square, Stripe Connect, or similar Does the resulting PROCESSOR *NAME pattern make sense out of context?
Multi-line-item or subscription billing Is the descriptor different enough per product to avoid support confusion?
Before shipping any descriptor change Paste the exact rendered string into a decoder and read it like a confused customer would

None of this is complicated engineering. It's just the one part of the payment flow that has zero automated tests, because the failure mode isn't a 500 error. It's a confused human, three weeks later, staring at their banking app.

Statement descriptors are the last UI your product shows a customer, and it's the one team most engineers never design on purpose.

Top comments (0)