Someone refunding for the fourth time is not an unhappy customer. They are running a routine, and it costs you the returned revenue plus everything you burned fulfilling the order. Apple and Google each expose an identity field, appAccountToken on one side and the obfuscated account ID on the other, that turns those separate incidents into a visible pattern.
Key takeaways
Evidence from you reaches a store in exactly two situations: Apple's CONSUMPTION_REQUEST, answerable inside 12 hours, and Google Play's chargeback review via the Review Refund API, answerable inside 24. Everything else gets decided in your absence.
On Apple, a UUID you generate travels with the purchase as appAccountToken. StoreKit fastens it to the transaction, which means any later refund resolves to one identifiable account in your own records.
Google's counterpart is a hashed string no longer than 64 characters. Google itself reads it, using the field to spot irregular behaviour such as a single account buying across an implausible number of devices in a short span.
Cleartext personal data in that Google field will get purchases blocked outright. An encrypted value or one-way hash is required. A raw email address is not.
Play's voidedReason distinguishes friendly_fraud and fraud from an ordinary change of mind refund, which gives you grounds to send repeat offenders into a review queue rather than the default refund and move on path.
Google reports having stopped more than $2 billion in fraudulent and abusive transactions during 2022. Those defences run on identity signals that only exist if your purchases carry them.
Access that survives a refund keeps consuming storage, compute, and API budget you are no longer being paid for, and somebody refunding deliberately is engineering exactly that outcome repeatedly.
Introduction
Refunds happen, and one of them is simply overhead. What changes the picture is the same account coming back a fourth time, having consumed the goods on each previous occasion. That behaviour is only legible to you if every transaction arrives carrying an identifier you assigned. Both platforms provide one. Both also open a narrow response window before the outcome is settled. Skip the identifier, or miss the window, and somebody working the system is indistinguishable from a first-timer with a genuine complaint. What separates the two, and what that separation is worth in money, is what follows.
Refund abuse is really an identity problem wearing a refund costume
Refund decisions belong to the store. Customers will ask, and across most flows the store will agree, with no input available from you. Your influence sits elsewhere: establishing whether this particular account has a history of doing this, and whether what they bought was actually delivered and used. Both questions are identity questions. Which user sits behind this transaction, and what have they done before. Purchases that carry no stable link into your user records leave both unanswerable, so every refund lands stripped of context.
That is the gap worth closing. Policy fixes the mechanics and you cannot move them. The evidence, though, is yours to assemble, and assembly begins at checkout rather than at the refund.
Two windows where what you send actually matters
Only two flows treat your input as input. A customer requesting a refund on a consumable or auto-renewable subscription triggers Apple's CONSUMPTION_REQUEST, and your answer is due within 12 hours. A customer disputing a charge with their bank triggers Google Play's chargeback review, answerable through the Review Refund API within 24. Outside those two, the decision happens without you: Play's 48-hour self-service refund, a refund granted by support, an auto-refund for a purchase you never acknowledged.
Both channels get considerably sharper when the transaction already carries your identifier. Consumption information sent to Apple can include appAccountToken next to that account's spending and history, while Google's review draws on an obfuscated account ID it has been observing since the purchase completed. Where there is no identifier, there is no history to submit.
The two identity fields, compared
Both companies arrived at nearly identical solutions under different names and different constraints. Each lets you brand a purchase with a value resolving back to your own user record. Neither is enabled for you. Both are useless unless set while the purchase is happening.
Apple: appAccountToken
Generate a UUID, pass it as the purchase begins, and StoreKit ties it to the transaction that results. It then surfaces in the transaction data your server verifies, and again in the consumption information you return during a CONSUMPTION_REQUEST. Since the value originated with you, it resolves a store transaction onto a database row without any name or email being involved. Real UUIDs only; anything else gets refused. Apple has since shipped a Set App Account Token endpoint capable of writing or updating the token on transactions that already happened, offer-code redemptions and similar out-of-app purchases included.
Google Play: the obfuscated account ID
setObfuscatedAccountId is Google's version, an optional string bound to the purchasing account. Google's documentation states its purpose without hedging: Play uses the value to detect irregular activity, many devices transacting on one account inside a brief window being the given example, and to stop some fraudulent purchases before they finish. The ceiling is 64 characters, and cleartext personal data is forbidden. Put a raw email in there and the purchase gets blocked, because what the field expects is an encrypted value or a one-way hash. Apps where a single account contains multiple profiles get a companion field, setObfuscatedProfileId.
appAccountToken is Apple's field. It has to be a UUID and nothing else, your code sets it during checkout, and it is absent unless you add it. Apple uses it to anchor the transaction to your user, and it reappears in the transaction and consumption payloads.
obfuscatedAccountId is Google Play's field. It takes a hashed string of at most 64 characters, your code sets it during checkout, and it too is absent by default. Google feeds it into irregular-activity detection and uses it to stop some fraudulent purchases before they complete.
One asymmetry matters enormously. Google offers no backfill whatsoever. A Play purchase completed without the identifier is permanently anonymous, and tagging future purchases is the only remedy available. Apple's Set App Account Token endpoint can repair history, which makes it the exception rather than the pattern.
Costing out a refund from someone doing it repeatedly
Returned revenue is the visible portion and also the smaller portion. Granting a refund puts the sale price back, which your ledger records. Absent from that line is everything spent producing what the customer now holds for free.
Consider a consumable that sets real work in motion: images generated, a run of calls to a model provider, a video exported, a payout released to a creator. That compute was purchased at the moment of use. Refunding retrieves the sale price and leaves the provider invoice exactly where it was. Somebody who has worked out that the goods outlive the refund simply repeats the sequence, buying, consuming, refunding, and each iteration charges you the delivery cost again.
There is also the platform shifting costs toward you. As of August 3, 2026, Google Play assigns both the purchase price and the bank's chargeback fees to developers for orders placed from that date. Disputes that were largely Google's expense become your lost revenue plus a fee, and somebody charging back habitually escalates that figure with every attempt. Identity fields are what make it possible to hand the Review Refund API something worth reading within the 24-hour window.
Google puts a figure on the scale of the problem: more than $2 billion in fraudulent and abusive transactions blocked during 2022. That figure exists because the underlying signals, obfuscated account ID among them, were actually populated. Shipping purchases with those fields empty is a decision to sit outside that protection.
Catching repeat refunders before the store rules
Stamp every purchase with an identity you control
Neither platform should see a purchase without one. StoreKit purchases carry appAccountToken, Play Billing purchases carry setObfuscatedAccountId, and both values originate in your own user record. Apple expects a genuine UUID mapped to that account, Google expects the account key hashed so nothing personal travels in the clear. Checkout is the moment for both, and on Google that timing cannot be renegotiated afterwards.
Maintain a ledger keyed on that identity
Log the deliveries, the consumption events, the purchases, and the refunds, all filed under that identifier. A CONSUMPTION_REQUEST or chargeback review then puts that account's entire past, earlier refunds included, a single query away. Apple's consumption information provides dedicated fields for precisely this material: consumptionStatus, deliveryStatus, playTime, accountTenure, lifetimeDollarsPurchased, and your own refundPreference.
Treat voidedReason as a routing decision
Voided Play purchases carry a voidedReason explaining the cause, and friendly_fraud and fraud are recorded separately from routine remorse. Those deserve a review queue and a firmer policy rather than the automatic handling a first-time refund receives. Google's guidance describes escalating responses: a warning initially, something firmer on repetition, and purchases disabled for accounts that persist.
Never miss the window
Evidence submitted late might as well not exist. Automate the CONSUMPTION_REQUEST reply so it dispatches comfortably within 12 hours, and the chargeback review within 24. A flawless account history filed after the deadline is a history nobody at the store will open.
To spot one account transacting across implausibly many devices at once, lean on Google Play's irregular-activity detection, which reads the obfuscated account ID.
To see a prior refund history on this account, lean on your own records, meaning the ledger you keyed on either platform's identifier.
To confirm the goods actually reached the buyer and were used, lean on Apple, where consumptionStatus and deliveryStatus live inside the consumption info.
To catch a dispute that is really the buyer defrauding you, lean on Google Play, where voidedReason comes back as friendly_fraud.
Where the platforms help, and where they stop
Being precise about the division of responsibility is worthwhile, because assuming the stores handle abuse is easy and they rarely announce the point at which they stop. Google genuinely does read the obfuscated account ID to block some fraud pre-purchase, and platform-level systems intercept a substantial volume. Apple reserves every refund decision for itself and treats your consumption information as evidence rather than a vote. What neither store does is keep an abuser list on your behalf, credit a refunded consumable back to your balance, or refuse a repeat refunder for you. At the margins the identity is the store's to act on. Everywhere else it is yours.
Refund Sensor is architected around that identity for the same reason. A refund, a chargeback review, and a consumption request belonging to one account collapse into a single history, so a repeat pattern becomes visible on the second occurrence rather than after the fourth invoice has already been paid.
Where these rules are documented
Frequently asked questions
It describes a customer who requests refunds repeatedly after taking delivery of and using what they purchased, retaining the benefit while recovering the payment. In an app store context this typically appears as one account refunding consumables or subscriptions over and over, or as friendly fraud, where a valid charge gets disputed through the bank instead.
On Apple you mint a UUID, hand it over as appAccountToken while the purchase begins, and StoreKit attaches it to the resulting transaction, from where it resurfaces in both transaction data and consumption data. On Google you call setObfuscatedAccountId using a hashed value derived from that account. Both approaches resolve a store transaction onto your own user record without exposing anything personal.
Neither will, at least not directly. Google reads the obfuscated account ID to identify irregular activity and stop some fraud before purchase, and Apple accepts account history submitted during a consumption request, but no roster of repeat offenders comes back from either company. Assembling that roster falls to you, built out of the records you filed under those identifiers.
Cleartext personal data in the obfuscated account ID is the usual reason. Placing something like a raw email address there causes Play to block the purchase outright. Supply an encrypted value or a one-way hash instead, staying within the 64-character ceiling.
It reduces it rather than stopping it. CONSUMPTION_REQUEST is one of only two paths where your evidence arrives before the decision, and account history combined with delivery and consumption data assists Apple in recognising return abuse and friendly fraud. Apple still decides, though, and abuse arriving through flows you cannot contest remains untouched.






