All articles
Playbook8 min read

Your app's ROAS still counts the purchases Apple and Google refunded, so bid on net ROAS instead

Refunds rarely flow back to ad platforms, so ROAS keeps crediting purchases Apple and Google already paid back. Here's where Firebase, Google Ads and AdAttributionKit lose refunds, what that costs, and how to build net ROAS for an app.

A marketing analyst's desk with ad campaign charts on a laptop, an iPhone, an Android phone and printed rows crossed out, standing in for net ROAS after app refunds

Key takeaways

  • Firebase's automatically collected app_store_refund event fires only when Google Play refunds an in-app purchase. Google marks it Android only, so App Store refunds never reduce the in_app_purchase revenue Firebase logged.
  • Google Ads tracks Google Play in-app purchases automatically for Android apps and reports the purchase value as the conversion value. For subscriptions, it records only the first purchase and the first payment value.
  • Google Ads conversion adjustments can restate a conversion's value or retract it to zero, and Google says online conversions can be adjusted only if a transaction ID was sent with them.
  • Apple's AdAttributionKit has three conversion windows after first launch, days 0 to 2, 3 to 7 and 8 to 35, and a fine-grained conversion value appears only in the first postback.
  • Net ROAS is ad-attributed revenue minus refunds on those purchases, divided by ad spend. A campaign with the higher gross ROAS can rank lower on net ROAS when its buyers refund more.
  • Refund signals reach the developer's server, not the ad network. Apple sends a REFUND App Store Server Notification, and Google Play reports voids through real-time developer notifications and the Voided Purchases API, which looks back 30 days.
  • Only two refund flows let a developer submit evidence: Apple's CONSUMPTION_REQUEST, answered within 12 hours, and Google Play's chargeback review through orders.reviewrefund, answered within 24 hours.

Ad dashboards book an in-app purchase the moment it happens, and most of them never take it back. When Apple or Google later refunds that purchase, the money leaves your payout but stays in the campaign's revenue, so ROAS keeps crediting the ad that sold it. Net ROAS, revenue after refunds divided by ad spend, is the number that tells you which campaigns actually paid. You have to build it yourself, because the refund signal lands on your server, not in the ad network.

Why ROAS overstates revenue when refunds are left out

ROAS divides attributed revenue by ad spend. The revenue side is usually written once, at purchase, by an SDK on the device or a conversion import from the store. A refund is a later event that happens off the device, between the customer and Apple or Google. Unless something carries it back to the same campaign, the purchase keeps counting.

That would be harmless if refunds were rare and evenly spread. They usually aren't. A creative that promises more than the app delivers, an offer shown to the wrong audience, or a traffic source full of stolen cards will pull refunds toward specific campaigns. Gross ROAS hides exactly that difference.

What is net ROAS?

Net ROAS is the same ratio with refunds taken out of the top line: revenue attributed to a campaign, minus the refunds on those same purchases, divided by what the campaign cost. Some teams also take out store commission and tax. Whatever else you subtract, refunds are the part that changes which campaign wins.

Where refunds go missing between the store and your ad dashboard

Firebase logs Google Play refunds, not App Store refunds

Google's list of automatically collected events includes in_app_purchase, which fires when a user completes an in-app purchase, including an initial subscription, processed by the Apple App Store or Google Play. Its refund counterpart, app_store_refund, fires when an in-app purchase is refunded by Google Play, and Google marks it Android only. Neither app_store_refund nor the automatic subscription events are exported to BigQuery.

The same page says Analytics doesn't automatically measure paid-app purchase revenue and refunds, and that reported revenue may differ from the Google Play Developer Console. On iOS, nothing automatic subtracts an App Store refund from a purchase Firebase already logged.

Google Ads books the purchase value as the conversion value

For Android apps that use in-app billing, Google Ads can track in-app purchases by people who clicked your ad, with no code in the app. Google says the value of the purchase is automatically reported as the conversion value. For subscriptions, only the first purchase is recorded as a conversion, and only the first payment value is recorded. The help article doesn't mention refunds.

Google Ads does have a correction tool. Conversion adjustments let you restate a conversion's value without changing the count, or retract it, which sets its value to 0.00 and feeds both CPA and ROAS bid strategies. Google says online conversions can be adjusted only if you used a transaction ID, and a retracted conversion can't be adjusted again. That page doesn't mention app conversions, so confirm your conversion setup supports adjustments before you build on them.

Apple's ad postbacks close before many refunds arrive

AdAttributionKit has three conversion windows that start when the user first launches the app: days 0 to 2, days 3 to 7, and days 8 to 35. The app can update its conversion value until a window ends. Apple then sends the postback after a random delay, 24 to 48 hours for the first and 24 to 144 hours for the second and third. If the app locks a value early, the system ignores further updates in that window.

The fine-grained conversion value only appears in the first postback. Later postbacks carry a coarse value at best. A purchase on day 1 that Apple refunds on day 10 was reported in a postback that can't change anymore.

Measurement surfaceWhat it records at purchaseWhat happens when the purchase is refunded
Firebase in_app_purchase, Android and iOSProduct ID, price, value, currency, quantityAndroid: app_store_refund fires. iOS: no automatic refund event
Google Ads, Google Play conversions, AndroidThe purchase value as the conversion value, first payment only for subscriptionsNot described. Adjustments can restate or retract online conversions sent with a transaction ID
AdAttributionKit postbacks, iOSA conversion value per window, fine-grained only in the firstFixed once that window's postback is prepared
Your own serverEvery transaction, tied to your user IDApple REFUND notification, Google Play voided purchase signals

When app refunds arrive, and who decides them

Refunds don't land on one schedule. Some arrive within hours of the purchase and some months later, which is why a cohort's ROAS keeps drifting down after the campaign report looked final.

Refund pathTiming in the store's own wordsWho decidesSignal on your server
Google Play refund requestWithin 48 hours the customer asks Google. After that, Google sends them to the developerGoogle, then youVoided purchase via RTDN and the Voided Purchases API
Google Play developer refundWhenever you issue itYouVoided purchase, if you revoke
Google Play unauthorized charge reportWithin 120 days of the transactionGoogleVoided purchase
Apple refundRequested at reportaproblem.apple.com, update in 24 to 48 hoursAppleREFUND notification with revocationReason
Apple consumption requestYou answer within 12 hoursApple, using your dataCONSUMPTION_REQUEST
Google Play chargeback reviewYou answer within 24 hours through orders.reviewrefundThe bank, after your evidenceVoided purchase if the chargeback stands

What a refund-blind ROAS costs in money

Take two campaigns with the same budget. The numbers below are an illustration, not a benchmark.

LineCampaign ACampaign B
Ad spend$10,000$10,000
Attributed purchase revenue$14,000$12,500
Gross ROAS1.401.25
Refunded share of that revenue18%4%
Revenue after refunds$11,480$12,000
Net ROAS1.151.20

Gross ROAS tells you to move money into A. Net ROAS says B kept more of every dollar. If your bidding runs on gross value, the algorithm keeps finding more of A's buyers, because nothing ever tells it they asked for their money back.

Ad spend is only the first bill. A refunded customer also leaves these behind:

  • Payouts. Affiliate, creator and influencer deals paid per purchase usually pay on the sale, not on the sale minus refunds, unless the contract says otherwise.
  • Compute, API calls and storage. A refunded user ran your servers, your paid third-party APIs and your storage until access was revoked. Those invoices don't get refunded.
  • Chargeback costs. Since August 3 2026, Google shifts the chargeback purchase price plus bank fees onto the developer, so a dispute-heavy Android traffic source costs more than the revenue it loses.
  • Worse targeting. Value-based bidding trained on refunded purchases goes looking for more people like the ones who were paid back.
Two stacks of coins on a desk, one shorter after coins were taken away, next to a phone and a calculator, showing how refunds shrink the revenue behind net ROAS

Is a high refund rate on one campaign fraud or a broken promise?

Often it's the promise. Apple's revocationReason tells you which side a refund fell on. A 0 means the App Store refunded the transaction for other reasons, for example an accidental purchase. A 1 means it refunded due to an actual or perceived issue within your app. A cluster of 1s from one campaign usually points at the ad creative or the onboarding it leads into. A cluster of unauthorized-charge reports on Google Play points at the traffic source.

How to build net ROAS for an app

You don't need a new tool to start. You need a few joins your data probably doesn't make yet.

  • Keep the campaign on the user. Store the attribution result next to your own user ID at install, so any later transaction can be traced to a campaign.
  • Keep the transaction on the user. Set an appAccountToken on iOS purchases and an obfuscated account ID on Google Play purchases, so every store event maps to your user without guessing.
  • Listen for refunds on the server. Handle Apple's REFUND notification from App Store Server Notifications V2 and Google Play's voided purchase notifications. Poll the Voided Purchases API as a backstop, since it only returns voids from the past 30 days.
  • Report gross and net side by side, by purchase cohort. Count each refund against the campaign and purchase date that earned it, not the day the refund showed up.
  • Send corrections where a platform accepts them. If your Google Ads conversions carry a transaction ID, restate or retract the refunded ones. Where a platform can't take corrections, move its budget on net numbers instead.
  • Answer the two evidence requests on time. RefundHalt answers Apple's CONSUMPTION_REQUEST inside the 12-hour window and Google Play's chargeback review inside 24 hours, so the refunds you can contest get your side of the story.

Should refunds count on the purchase date or the refund date?

For campaign decisions, the purchase date. A refund is a fact about a purchase, and the purchase belongs to a campaign. Finance books it on the refund date, which is why your ROAS report and your payout won't agree on any given day. Keep both views and label them.

When is a cohort's net ROAS final?

It settles in steps. Google's self-service refund window closes after 48 hours, Apple tells customers to expect an update on a refund request in 24 to 48 hours, and a Google Play unauthorized-charge report can come within 120 days of the transaction. Treat early numbers as provisional and recheck the same cohort later.

Frequently asked questions

Does Firebase subtract refunds from in-app purchase revenue?
Only for Google Play. Firebase's automatically collected app_store_refund event fires when Google Play refunds an in-app purchase, and Google marks it Android only. There's no automatic equivalent for App Store refunds, and Google says Analytics doesn't automatically measure paid-app purchase revenue and refunds.
Does Google Ads remove refunded in-app purchases from conversions?
Google's own article on Google Play conversions doesn't say it does. It says the purchase value is reported as the conversion value and doesn't mention refunds. Google Ads conversion adjustments can restate or retract a conversion, and Google says online conversions can be adjusted only if a transaction ID was sent with them.
Can AdAttributionKit postbacks reflect a refund?
Only if the refund happens before that conversion window's postback is prepared. The windows run days 0 to 2, 3 to 7 and 8 to 35 after first launch, and once a window ends or the value is locked, Apple ignores further updates in that window.
How do I calculate net ROAS for an app?
Take the revenue attributed to a campaign, subtract the refunds on those same purchases, and divide by the campaign's ad spend. Count each refund against the purchase cohort that earned it, using Apple's REFUND notification and Google Play's voided purchase signals as the refund record.
Can I contest the refunds that lower my ROAS?
Only two kinds. Apple's CONSUMPTION_REQUEST lets you send consumption data within 12 hours, and Google Play's chargeback review through orders.reviewrefund gives you 24 hours. Google Play's 48-hour refunds, support refunds and voided purchases are decided without you, and bank chargebacks are final.

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.