All articles
Playbook8 min read

Serial refund abuse costs you twice, here's how the stores let you fight back

The customer who refunds again and again is not an accident. Refund abuse costs you the money back plus the compute you already spent, and both stores hand you an identity signal, Apple's appAccountToken and Google's obfuscated account ID, to tie the pattern together.

A turnstile in a dim transit hall with one figure passing through repeatedly, illustrating serial refund abuse

Key takeaways

  • Only two refund flows ever let a developer submit evidence against refund abuse: Apple's CONSUMPTION_REQUEST, with a 12-hour window, and Google Play's chargeback review through the Review Refund API, with a 24-hour window.
  • Apple's appAccountToken is a UUID you generate and attach to a purchase, and StoreKit binds it to the resulting transaction, so your server can tie every refund back to the exact account that made it.
  • Google Play's obfuscated account ID is a hashed string, capped at 64 characters, that Google itself uses to detect irregular activity such as many devices buying on one account in a short window.
  • Storing cleartext personal data in Google's obfuscated account ID gets your purchases blocked, so the field must be an encrypted value or a one-way hash, never a raw email.
  • Google Play's voidedReason field marks friendly_fraud and fraud as reasons distinct from an ordinary remorse refund, which lets you route repeat abusers to a review queue instead of an automatic refund-and-forget path.
  • Google says it blocked over $2 billion in fraudulent and abusive transactions in 2022, and the identity fields that feed that system only work if you populate them on every purchase.
  • Every refunded purchase that keeps its access is compute, storage, and API spend you keep paying on money you already gave back, and a serial refunder multiplies that bill on purpose.

A single refund is a cost of doing business. The same account refunding a fourth time, after using what it bought each time, is refund abuse, and it is a pattern you can only see if every purchase carries an identity you control. Apple and Google both give you that identity, and both give you a narrow window to put it in front of the store before a refund or chargeback is decided. Miss the window, or never attach the identity, and a serial refunder looks exactly like a first-time unhappy customer. Here is how to tell them apart, and what the difference is worth in money.

Why refund abuse is an identity problem, not a refund problem

The store decides the refund. You can't stop a customer from asking, and on most flows you can't stop the store from granting. What you can do is decide whether a given account has done this before, and whether the content it bought was actually delivered and used. Both of those are questions about identity: which of your users is behind this transaction, and what is their history. If your purchases don't carry a stable identifier back to your own user records, you can't answer either question, and every refund arrives context-free.

That is the real gap. The refund mechanics are fixed by policy. The evidence you bring to them is entirely yours to build, and it starts at the moment of purchase, not the moment of the refund.

The two windows where evidence changes the outcome

There are exactly two flows where anything you send can move a decision. Apple's CONSUMPTION_REQUEST fires when a customer asks for a refund on a consumable or an auto-renewable subscription, and you have 12 hours to answer. Google Play's chargeback review fires when a user disputes a charge with their bank, and you have 24 hours to respond through the Review Refund API. Every other refund, the 48-hour Play self-service refund, a support-granted refund, an unacknowledged-purchase auto-refund, is decided without you.

Both of those evidence channels are far stronger when the transaction already carries your identifier. Apple's consumption information can include the appAccountToken alongside the account's history and spend, and Google's review can lean on the obfuscated account ID it has been watching since the purchase. An account with no identifier is an account with no history to submit.

The two identity fields, side by side

Apple and Google solved the same problem in nearly the same way, with different names and different rules. Both let you stamp a purchase with a value that points back to your own user record. Neither is on by default. Both are worthless if you don't set them at purchase time.

Apple: appAccountToken

appAccountToken is a UUID you generate and pass when a purchase begins. StoreKit associates it with the resulting transaction, and it appears in the transaction data your server verifies and in the consumption information you send back during a CONSUMPTION_REQUEST. Because it is your value, it maps a store transaction directly onto the account in your database, with no email or name involved. It has to be a real UUID, and anything else is rejected. Apple later added a Set App Account Token endpoint that can attach or update the token on past transactions, including purchases made outside your app such as offer-code redemptions.

Google Play: the obfuscated account ID

Google's equivalent is setObfuscatedAccountId, an optional string tied to the purchasing account. Google's own documentation is blunt about its purpose: Google Play uses it to detect irregular activity, such as many devices making purchases on the same account within a brief timeframe, and to block some fraudulent transactions before they complete. It is limited to 64 characters, and it must not carry cleartext personal data. Store a raw email there and Google blocks the purchase outright, because the field expects an encrypted value or a one-way hash. A sibling field, setObfuscatedProfileId, exists for apps where one account holds several profiles.

FieldStoreType and limitSet byDefaultWhat the store does with it
appAccountTokenAppleUUIDYou, at purchaseNot setBinds the transaction to your user, returned in transaction and consumption data
obfuscatedAccountIdGoogle PlayHashed string, 64 char maxYou, at purchaseNot setDetects irregular activity and blocks some fraud before completion

What a refund from a serial abuser actually costs

The refund itself is the smallest part of the bill. When a store grants a refund, you return the revenue, and that much is visible on the ledger. What is not on the refund line is everything you spent delivering the product that is now free.

Picture a consumable that triggers real work: an image generation, a batch of API calls to a model provider, a video export, a payout to a creator. You paid for that compute the moment the customer used it. The refund claws back the price, but it does not claw back the provider invoice. A serial refunder is someone who has learned that the content survives the refund, so they run the loop again: buy, consume, refund, repeat. Each cycle, you eat the delivery cost a second time.

Then there is the store's own cost shift. From August 3, 2026, Google Play moves the purchase price and the bank's chargeback fees onto the developer for orders placed after that date. A chargeback that used to be mostly Google's problem becomes your revenue plus a fee, and a repeat charger raises that number every time. The identity fields are what let you feed the Review Refund API the evidence to contest an illegitimate dispute inside the 24-hour window.

Google frames the scale plainly: it says it blocked over $2 billion in fraudulent and abusive transactions in 2022. That number exists because the signals, the obfuscated account ID among them, were populated. An app that ships purchases with empty identity fields opts out of that protection.

A fingerprint read on a lit glass sensor, illustrating how store identity fields tie a purchase back to the account behind refund abuse

How to catch repeat refunders before the store decides

Tag every purchase with an identity you own

Set appAccountToken on every StoreKit purchase and setObfuscatedAccountId on every Play Billing purchase, both generated from your own user record. On Apple, use a real UUID mapped to the account. On Google, hash the account key so no personal data travels in cleartext. Do this at purchase time, because on Google it is the one step you cannot recover later.

Keep a ledger keyed on that identity

Store every purchase, delivery, consumption event, and refund against the identifier. When a CONSUMPTION_REQUEST or a chargeback review arrives, the account's full history, prior refunds included, is one lookup away. Apple's consumption information has explicit fields for exactly this: consumptionStatus, deliveryStatus, playTime, accountTenure, lifetimeDollarsPurchased, and your refundPreference.

Read voidedReason and route abusers differently

When a Google purchase is voided, the voidedReason field tells you why, and friendly_fraud and fraud are separate values from an ordinary remorse refund. Route those to a review queue and a stricter policy, not the same automatic path as a first-time refund. Google's own guidance is a multiple-strikes model: warn a first offender, take firmer action on repeat ones, and disable purchases for an account that keeps at it.

Answer the window every time

The evidence only counts inside the two windows. Automate the CONSUMPTION_REQUEST response so it fires well inside 12 hours, and the chargeback review so it fires inside 24. A perfect history you submit late is a history the store never reads.

Signal you want to catchWhere it livesThe field or flow that surfaces it
Same account, many devices, short windowGoogle PlayObfuscated account ID irregular-activity detection
This account has refunded beforeYour ledgerHistory keyed on appAccountToken or obfuscated account ID
Content was delivered and consumedAppledeliveryStatus and consumptionStatus in consumption info
A dispute is really first-party fraudGoogle PlayvoidedReason value friendly_fraud

What the stores will and won't do for you

It is worth being precise about the division of labor, because it is easy to assume the stores handle abuse, and they mostly don't tell you when they don't. Google actively uses the obfuscated account ID to block some fraud before a purchase completes, and its systems catch a large share at the platform level. Apple decides every refund itself and treats your consumption information as an input, never a vote. Neither store maintains your abuser list for you, deducts a refunded consumable from your balance, or tells a repeat refunder no on your behalf. The identity is the store's to act on at the margins and yours to act on everywhere else.

This is the same reason RefundHalt keys everything on the purchase identity: a refund, a chargeback review, and a consumption request for the same account resolve to one history, so a serial refunder shows up as a pattern the first time they repeat, not after the fourth invoice you already paid.

Frequently asked questions

What is refund abuse in an app?
Refund abuse is a customer repeatedly requesting refunds after receiving and using what they bought, so they keep the content or benefit while getting the money back. On app stores it usually shows up as the same account refunding consumables or subscriptions again and again, or as friendly fraud, where a legitimate charge is disputed with the bank.
How do I link an in-app purchase to a specific user?
On Apple, generate a UUID and pass it as appAccountToken when the purchase starts, and StoreKit binds it to the transaction and returns it in transaction and consumption data. On Google Play, call setObfuscatedAccountId with a hashed value tied to the account. Both map a store transaction to your own user record without exposing personal data.
Can Apple or Google tell me an account is a repeat refunder?
Not directly. Google uses the obfuscated account ID to detect irregular activity and block some fraud before purchase, and Apple lets you submit account history during a consumption request, but neither store hands you a list of repeat refunders. You build that yourself from your own ledger keyed on the identity fields.
Why does Google block my purchase when I set the account ID?
Because the obfuscated account ID must not contain cleartext personal data. Storing something like a raw email address in it causes Google Play to block the purchase. Use an encrypted value or a one-way hash, and keep it within the 64-character limit.
Does contesting refunds through the consumption request stop abuse?
It can reduce it. The CONSUMPTION_REQUEST is one of only two flows where your evidence reaches the store before it decides, and account history plus delivery and consumption data help Apple identify return abuse and friendly fraud. It is not a block, though. Apple still decides, and abuse that arrives through non-contestable flows is unaffected.

Sources and further reading

RefundHalt

The refund autopilot for the App Store and Google Play

Keep reading

The next refund request is already on its way.

Set up RefundHalt in the time it takes to read another support email about a refund you didn't get to contest.