All articles
Deep dive9 min read

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.

A rubber-stamped code mark on a paper label beside a magnifying glass, standing in for the refund reason code your server receives on every refund

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.

voidedReasonGoogle's labelWhat the code is telling you
0OtherNo specific reason recorded. Bucket it and watch the volume, not the single case.
1RemorseThe customer changed their mind. Nothing was wrong with your app.
2Not receivedThe customer says they never got what they paid for. A delivery problem to check.
3DefectiveThe purchase did not work. A product bug, and the most actionable code on this list.
4Accidental purchaseA misclick or an unintended buy. Consider a clearer confirmation step.
5FraudGoogle flagged the transaction as fraudulent. Not your customer, and not your revenue to keep.
6Friendly fraudThe buyer disputed a charge they made and received. Evidence can still touch this one.
7ChargebackThe bank reversed the charge. The most expensive path, now with a fee attached.
8Unacknowledged purchaseYour 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.

BucketGoogle codesApple signalYour move
Fix2 not received, 3 defective, 8 unacknowledgedrevocationReason 1Root-cause the product or billing bug behind it
Fight5 fraud, 6 friendly fraud, 7 chargeback(surfaces via REFUND, not the reason)Revoke access, answer the review window with evidence
Accept1 remorse, 4 accidental purchaserevocationReason 0Log it, tune the buy flow, move on
Three labeled sorting trays on a workbench catching sorted metal tokens, one tray lit brighter, standing in for triaging refunds by reason code into fix, fight, and accept

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 codeWhat it costs youWhy the code matters
8 Unacknowledged purchaseFull sale price plus delivery cost, on a sale the customer wantedIt 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 feeThe only code that adds a fee on top of the lost sale
3 DefectiveSale price plus the delivery cost, repeated for every customer hitting the bugVolume in this code sizes a product defect in dollars
1 RemorseSale price, and the delivery cost you already spentReal 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.

QuestionApp StoreGoogle Play
Where the code livesrevocationReason in the REFUND notification's signed transactionvoidedReason in the Voided Purchases API and its notification
How many reasonsTwo: 1 issue in your app, 0 otherNine, from 0 other through 8 unacknowledged purchase
Who did itNot broken outvoidedSource: 0 user, 1 developer, 2 Google
How far back you can readAvailable on the transaction whenever you query itOnly the past 30 days of voids
The sharpest signalA 1 means the refund is about your productCodes 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

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.