Read the refund reason code your server already gets, and it tells you whether to fix your app or fight the customer
Every refund Apple and Google send your server carries a reason code. Google Play stamps one of nine reasons and a source on each void, Apple flags whether the refund blames your app. Here is what each code means, how to sort them into fix, fight, or accept, and what they are worth in money.

Key takeaways
- Every refund Apple or Google sends your server carries a refund reason code, and it is the one piece of a refund you can read after the money has already moved. It tells you why the refund happened, which tells you what to do next.
- Google Play's Voided Purchases API stamps two numbers on every void: a voidedReason from 0 to 8 (other, remorse, not received, defective, accidental purchase, fraud, friendly fraud, chargeback, unacknowledged purchase) and a voidedSource of 0 user, 1 developer, or 2 Google.
- Apple gives you a narrower but sharp signal. On a refunded transaction the revocationReason is 1 when the App Store refunded due to an actual or perceived issue within your app, and 0 when it refunded for another reason such as an accidental purchase.
- The codes sort into three piles. Defective, not received, unacknowledged, and Apple's issue-in-app code point at your product, so you fix them. Fraud, friendly fraud, and chargeback are disputes you fight or prevent. Remorse and accidental purchase were never yours to stop.
- voidedReason 8, unacknowledged purchase, is a refund you billed yourself. Google auto-refunds and revokes any purchase your app fails to acknowledge within three days, and this code is how you find that bug in your own integration.
- voidedReason 7, chargeback, is the expensive one. For Google Play orders placed on or after August 3, 2026, a lost chargeback costs the developer the purchase price less Play's service fee plus the bank's chargeback fee, so counting your chargeback-coded voids is counting real added cost.
- The Voided Purchases API only looks back 30 days, and it filters by when Google sees the void, not when the purchase happened, so a reason code you do not capture inside that window is a reason code you lose for good.
When Apple or Google refunds one of your customers, the money is usually gone before you get a vote. What lands on your server afterward looks like a receipt, and most teams treat it like one. It is more than that. Every refund carries a refund reason code, and it is the single part of a refund you still get to read once the decision is made. Google Play tells you the refund was a chargeback, or a remorse request, or a purchase your own app never acknowledged. Apple tells you whether the refund blamed something inside your app. Read that code and a refund stops being a line in a report and becomes an instruction: fix this, fight this, or let this one go. Here is what every code means, how to triage them, and what each one costs.
What a refund reason code actually is
A refund reason code is the store's own label for why a purchase was reversed. You do not set it and you cannot argue with it. It arrives attached to the refund after the fact, and the two stores expose it in different shapes and with very different resolution.
Google Play stamps a reason and a source on every void
Google Play's Voided Purchases API returns one record per reversed purchase, and each record carries two integers that matter. The voidedReason says why the purchase was voided. The voidedSource says who set it in motion. Together they turn a bare refund into a sentence: this order was voided because of a chargeback, initiated by Google, or voided as remorse, initiated by the user. You read these by polling the API or by subscribing to the real-time developer notification that fires when a void lands. Either way, the two numbers are the payload worth keeping.
Apple gives you a narrower signal, but a sharp one
Apple does not hand you a nine-way reason. On a refunded transaction, Apple sets revocationReason to one of two values. A 1 means the App Store refunded the transaction because of an actual or perceived issue within your app. A 0 means it refunded for another reason, for example an accidental purchase. The field appears only on transactions that were refunded or revoked, alongside a revocationDate, inside the signed transaction info of the REFUND App Store Server Notification. Two values is not much, but the one that matters, a 1, is Apple telling you the refund was about your product, not the customer's second thoughts.
The nine reasons Google Play gives you
Google's voidedReason is the richer of the two, and every value is worth knowing on sight because each one points somewhere different. Here is the full set, straight from the VoidedPurchase resource, with what each code is actually telling you to do.
| voidedReason | Google's label | What the code is telling you |
|---|---|---|
| 0 | Other | No specific reason recorded. Bucket it and watch the volume, not the single case. |
| 1 | Remorse | The customer changed their mind. Nothing was wrong with your app. |
| 2 | Not received | The customer says they never got what they paid for. A delivery problem to check. |
| 3 | Defective | The purchase did not work. A product bug, and the most actionable code on this list. |
| 4 | Accidental purchase | A misclick or an unintended buy. Consider a clearer confirmation step. |
| 5 | Fraud | Google flagged the transaction as fraudulent. Not your customer, and not your revenue to keep. |
| 6 | Friendly fraud | The buyer disputed a charge they made and received. Evidence can still touch this one. |
| 7 | Chargeback | The bank reversed the charge. The most expensive path, now with a fee attached. |
| 8 | Unacknowledged purchase | Your app never acknowledged the purchase, so Google auto-refunded it. A bug in your code. |
voidedSource tells you who pulled the trigger
Next to the reason sits voidedSource, and it answers a different question: who reversed this. A 0 means the user did it, through self-service or a bank. A 1 means the developer did it, which is you or your own tooling issuing a refund. A 2 means Google did it, on its own judgment, including the auto-refund for an unacknowledged purchase. When you see a spike of voids, source is the first cut. A wall of source 2 is Google acting on your account, and that is usually a signal pointing back at your integration rather than at your customers.
Sort every refund into fix, fight, or accept
The reason a code is useful is that it tells you which of three responses a refund deserves. Most teams treat all refunds the same and burn effort on the ones they can never win. The codes split cleanly.
Fix: the refunds your product caused
Some codes are bug reports wearing a refund's clothes. Defective (3) and not received (2) on Google, and a revocationReason of 1 on Apple, all say the same thing: the customer paid and your app did not deliver. Unacknowledged purchase (8) is the sharpest of these because the fault is entirely in your billing code. These are the cheapest refunds to eliminate, because you eliminate them by fixing something you own, not by persuading anyone. A rising count in this pile is a product defect with a dollar figure attached.
Fight: the refunds someone is working
Fraud (5), friendly fraud (6), and chargeback (7) are the disputes. Pure fraud (5) is not your customer and not revenue you were ever going to keep. Friendly fraud (6), where the buyer got exactly what they paid for and then disputed it, is the one dispute your evidence can still move, and chargeback (7) is where that evidence gets submitted. When one of these lands you revoke access if you have not already, and where a review window is open you answer it with what you know about the account.
Accept: the refunds that were never yours to stop
Remorse (1) and accidental purchase (4) are the customer's own change of heart. Apple's revocationReason of 0 sits here too. No feature failed and no fraud occurred. You can soften the accidental bucket with a clearer purchase confirmation, but you cannot argue a remorse refund away, and time spent trying is time taken from the fix pile where the money actually is.
| Bucket | Google codes | Apple signal | Your move |
|---|---|---|---|
| Fix | 2 not received, 3 defective, 8 unacknowledged | revocationReason 1 | Root-cause the product or billing bug behind it |
| Fight | 5 fraud, 6 friendly fraud, 7 chargeback | (surfaces via REFUND, not the reason) | Revoke access, answer the review window with evidence |
| Accept | 1 remorse, 4 accidental purchase | revocationReason 0 | Log it, tune the buy flow, move on |

The 30-day catch that makes reason codes easy to lose
There is a hard limit on Google's side that turns this from a reporting feature into a deadline. If you are not capturing the codes continuously, you are losing them.
The Voided Purchases API only looks back 30 days
Google is explicit that the API can only show voided purchases from the past 30 days. Older voids are not returned no matter what startTime you pass, and the startTime value itself cannot be set earlier than 30 days ago. Worse for a naive integration, the 30-day window is measured by when Google's systems see a purchase as voided, not by when the purchase was made or even by the voidedTimeMillis in the record. So a refund reason code you do not pull inside that window is gone, and a monthly export job with any gap will silently drop voids it was too slow to catch.
What the reason codes are worth in money
Two codes carry a specific price, and reading them is how you put a number on problems that otherwise hide inside an aggregate refund rate.
One code is a bill you wrote yourself
voidedReason 8, unacknowledged purchase, is the cleanest example of a refund you caused. Google Play requires your app to acknowledge a purchase within three days of granting entitlement, and if you do not, Google automatically refunds the order and revokes the item. Every void stamped 8 is a real sale, from a customer who wanted the product, handed back because a call to acknowledge the purchase never fired. The lost amount is the full sale price plus the compute, API calls, and storage you already spent delivering it. This is not a refund you negotiate. It is a bug you close, and the code is how you find it.
The chargeback code now carries a fee
voidedReason 7, chargeback, changed cost on August 3, 2026. For Google Play orders placed on or after that date, a lost chargeback costs the developer the purchase price less Play's service fee, plus the bank's chargeback fee, while Google covers only its own service fee. Because chargeback fees are flat and product prices are not, on a cheap in-app purchase the fee alone can exceed what the customer paid. Counting your code 7 voids is now counting a line item, not just a lost sale, which is exactly why the chargeback bucket deserves its own row in any refund report you build.
| Reason code | What it costs you | Why the code matters |
|---|---|---|
| 8 Unacknowledged purchase | Full sale price plus delivery cost, on a sale the customer wanted | It is self-inflicted, so the code is a bug tracker |
| 7 Chargeback (order on or after Aug 3, 2026) | Sale price less Play's service fee, plus the bank's chargeback fee | The only code that adds a fee on top of the lost sale |
| 3 Defective | Sale price plus the delivery cost, repeated for every customer hitting the bug | Volume in this code sizes a product defect in dollars |
| 1 Remorse | Sale price, and the delivery cost you already spent | Real cost, but not one a code change can recover |
How Apple and Google line up
The two stores answer the same question at different resolutions, so a cross-store refund report has to normalize them rather than expect them to match.
| Question | App Store | Google Play |
|---|---|---|
| Where the code lives | revocationReason in the REFUND notification's signed transaction | voidedReason in the Voided Purchases API and its notification |
| How many reasons | Two: 1 issue in your app, 0 other | Nine, from 0 other through 8 unacknowledged purchase |
| Who did it | Not broken out | voidedSource: 0 user, 1 developer, 2 Google |
| How far back you can read | Available on the transaction whenever you query it | Only the past 30 days of voids |
| The sharpest signal | A 1 means the refund is about your product | Codes 3, 8, and 7 each point at a distinct, fixable cost |
The stores will never give you the same code for the same refund, and that is fine. What matters is that both hand you a machine-readable reason, and both reward a team that reads it. Apple's single bit tells you when a refund is your product's fault. Google's nine reasons and its source flag tell you which product bug, which dispute, and which self-inflicted billing gap you are looking at. Neither code stops a refund. Both tell you what to do so the next one does not happen.
RefundHalt captures the reason code on every refund the moment it arrives, on both stores, and holds it well inside Google's 30-day window so nothing slips away. It sorts each void into fix, fight, or accept, so a spike in code 3 defective reaches you as a product alert and a spike in code 8 unacknowledged reaches you as an integration bug, not as a vague dip in revenue. It answers Apple's CONSUMPTION_REQUEST inside 12 hours and Google Play's chargeback review inside 24, and it revokes access the moment a refund or chargeback lands. You cannot change the code a store stamps on a refund. You can make sure you read every one, and act on the ones that are actually yours to fix.
Frequently asked questions
- What is a refund reason code on the App Store and Google Play?
- It is the store's own label for why a purchase was reversed, delivered to your server with the refund. Google Play's Voided Purchases API returns a voidedReason from 0 to 8 and a voidedSource of 0 user, 1 developer, or 2 Google. Apple sets revocationReason to 1 when the refund was due to an issue within your app or 0 for another reason such as an accidental purchase. You do not set the code and cannot change it, but reading it tells you whether the refund points at your product, a dispute, or a customer's change of mind.
- What are the Google Play voidedReason values?
- There are nine: 0 other, 1 remorse, 2 not received, 3 defective, 4 accidental purchase, 5 fraud, 6 friendly fraud, 7 chargeback, and 8 unacknowledged purchase. Each one is returned per voided purchase by the Voided Purchases API alongside a voidedSource that says who initiated the void. Codes 2, 3, and 8 point at problems in your own app, codes 5, 6, and 7 are disputes, and codes 1 and 4 are the customer's own decision.
- What does an Apple revocationReason of 1 mean?
- It means the App Store refunded the transaction because of an actual or perceived issue within your app, as opposed to a value of 0, which means the refund happened for another reason such as an accidental purchase. The field appears only on refunded or revoked transactions, together with a revocationDate, inside the signed transaction info of the REFUND App Store Server Notification. A 1 is Apple telling you the refund was about your product.
- Why did Google refund a purchase with the unacknowledged reason code?
- Because your app did not acknowledge the purchase in time. Google Play requires you to acknowledge a purchase within three days of granting entitlement, and if you do not, Google automatically refunds the order and revokes the item, stamping the void with voidedReason 8. It is a refund you caused with a billing bug, not a customer request, so the fix is in your purchase-processing code rather than in any negotiation.
- How far back can I read refund reason codes?
- On Google Play, only 30 days. The Voided Purchases API returns voids from the past 30 days and ignores any startTime older than that, and it measures the window by when Google sees the void, not when the purchase was made. A code you do not capture inside 30 days is lost, so you should subscribe to the real-time voided-purchase notification or poll on a schedule well inside the window. Apple's revocationReason stays on the transaction whenever you query it.
Sources and further reading
- Google Play Developer API: REST Resource purchases.voidedpurchases (voidedReason and voidedSource values)
- Google Play Developer API: Method purchases.voidedpurchases.list (30-day lookback window)
- Google Play: Voided Purchases API overview
- Apple Developer: revocationReason (App Store Server Notifications)
- Apple Developer: Transaction.RevocationReason (StoreKit)
- Apple Developer: Handling refund notifications (revocationDate and revocationReason on REFUND)
- Google Play Console Help: Updates to refund protection and chargeback cost responsibility (August 3, 2026)
- Google Play Billing: Integrate the Google Play Billing Library (acknowledge within three days or auto-refund)
RefundHalt
The refund autopilot for the App Store and Google Play
Keep reading
Unauthorized in-app purchases by children get refunded to the parent almost every time, and you carry the cost
When a child buys a coin pack on a parent's phone, both Apple and Google refund it and neither one asks you first. Regulators built it that way. Here is how these unauthorized in-app purchase refunds work on each store, the 15-minute window where the money goes, and what one actually costs you.
You never owned the app refund tax, so a refund costs you your share, not the total on the receipt
Refund an in-app purchase and the receipt shows the price plus tax going back. The tax was never your money. Apple and Google collect and remit it as the merchant of record, then reverse it on a refund without touching your share. Here is what a refund actually costs, and the one setup where the tax becomes yours.