In-App Purchases in React Native: What Apple and Google Actually Reject in 2026
A practical breakdown of the IAP rejection patterns we keep seeing in React Native and Expo apps in 2026 — from receipt validation to subscription restore flows — with code you can lift straight into your project.

Every quarter we get the same call: an app that has been live for a year suddenly hits a wall on an IAP update, or a fresh submission gets bounced three times in a row over subscription wording. The rules haven't really changed in 2026, but reviewers are stricter, and React Native apps in particular keep tripping on the same handful of issues. Here is what we keep seeing, and how we fix it.
The rejection patterns that dominate 2026
Apple's App Review and Google's Play policy team flag IAP issues for fairly predictable reasons. After shipping or auditing maybe two dozen subscription apps in the last eighteen months, the rejections cluster into five buckets:
- Missing or broken restore purchases flow (iOS, Guideline 3.1.1)
- Subscription terms not visible before purchase (both stores)
- Receipt validation done only client-side — usually surfaces as a fraud or refund complaint later, not a review rejection, but it bites
- Mismatched product metadata between the store and what the app displays
- Using a non-IAP payment method for digital goods, often accidentally via a webview
The React Native angle is that the JavaScript layer makes some of these easier to get wrong. A webview that links to a Stripe checkout for a digital subscription is a one-line mistake that costs you a week. Let's go through the ones we see most.
Restore purchases: the rejection that keeps coming back
If your app has any account-bound entitlement, Apple expects a visible Restore Purchases control. Not buried in settings, not gated behind a login wall that the reviewer can't get past. We have seen apps rejected even when the restore flow exists, because the reviewer couldn't find it in under thirty seconds.
With react-native-iap (still the most common choice in 2026, alongside Expo's expo-in-app-purchases successor and RevenueCat's SDK), the restore call itself is simple. The mistake is usually what you do with the result.
import {
getAvailablePurchases,
finishTransaction,
} from 'react-native-iap';
export async function restorePurchases() {
try {
const purchases = await getAvailablePurchases();
const active = purchases.filter(isStillEntitled);
for (const p of active) {
await syncEntitlementWithBackend(p);
// iOS: only finish after your server confirms
if (p.transactionId) {
await finishTransaction({ purchase: p, isConsumable: false });
}
}
return active.length > 0;
} catch (err) {
reportError(err);
throw err;
}
}
Three things matter here:
- Always hit your backend to revalidate the receipt before you grant access. Restore is a common fraud vector.
- Don't finish the transaction until the server confirms. Otherwise a failed sync leaves the user paid-up on Apple's side but with no access in your app.
- Show a result toast or screen. Reviewers tap restore on a fresh install with no purchases — if nothing happens visually, they assume it's broken.
Where to put the button
For iOS submissions in 2026 we put a Restore Purchases button in two places: on the paywall itself, and in account settings. Both. Yes, it feels redundant. No, you will not get rejected for redundancy. You will get rejected for the reviewer not finding it.
Subscription disclosure: the wall of text Apple wants
Apple's subscription disclosure requirements are old, and yet at least a third of the apps we audit get this wrong. Before the purchase action, the user must see:
- Subscription title and length
- Price per period
- That it auto-renews
- How to cancel (or a link to Apple's cancellation docs)
- Links to your Terms of Use (EULA) and Privacy Policy
This text has to be visible on the same screen as the buy button. Not behind a tooltip, not on a previous screen. We have had submissions rejected because the disclosure was a one-tap accordion that started collapsed.
The React Native pitfall: paywalls built with a remote config or A/B testing tool sometimes ship variants that drop the disclosure block. If you A/B test paywalls, the disclosure text should be a non-removable component, not a configurable field.
Server-side receipt validation is not optional anymore
Client-side validateReceiptIos or validateReceiptAndroid calls are fine for a hackathon. In production they are a liability. Both stores have moved most of their subscription state to server-to-server notifications:
- Apple's App Store Server Notifications V2
- Google Play's Real-time Developer Notifications via Pub/Sub
If you are not consuming these, you will not know when a user's subscription is refunded, paused, or moved to a different family-sharing setup. The user calls support angry that they cancelled a month ago and you are still serving them paywalled content — or worse, you cut off someone who is still paying because you trusted a stale client receipt.
Our usual server architecture:
Client purchase
-> POST /iap/verify { platform, receipt }
-> Server validates with Apple/Google
-> Server writes entitlement row { userId, productId, expiresAt, source }
-> Server returns entitlement to client
Apple/Google webhook
-> POST /iap/webhook
-> Server updates entitlement row
-> Server pushes silent notification to client to refresh
The entitlement row is the source of truth, not the receipt. The client asks your server, not the store. This also makes cross-platform restore (paid on iOS, opened on Android) trivial — assuming you have user accounts, which is its own debate.
When RevenueCat earns its keep
We do not recommend tools by default, but RevenueCat genuinely removes a class of work here. The webhook handling, the entitlement model, the cross-platform reconciliation — it is all done. For a team without backend bandwidth to babysit Apple's notification format changes, the cost is usually justified. For a team that already runs a billing-adjacent backend, rolling your own with react-native-iap and a clean entitlement service is fine.
The webview trap
If your app is even partially a webview wrapper, audit every link. Apple's Guideline 3.1.1 prohibits using non-IAP payment methods for digital goods inside the app. A login screen that opens a webview to your site, where the user can then navigate to an upgrade page with a Stripe button, is a rejection.
The Reader app exception exists, but it is narrower than people think and requires an entitlement. Do not assume you qualify.
For React Native apps with embedded WebView components, the safe pattern is:
- Strip or intercept any URL that leads to a payment page
- For physical goods or services consumed outside the app (e-commerce, ride hailing, food delivery), external payment is fine — and in the EU and a growing list of jurisdictions, external payment links for digital goods are now also permitted under specific conditions
- For digital subscriptions consumed in the app, use IAP unless you have explicitly applied for and received the external link entitlement
The 2026 reality is that external payment rules vary wildly by region. We have stopped trying to be clever about it on global apps — IAP everywhere is boring, but it ships.
Google Play's quieter but stricter side
Google's billing library (Play Billing 7+ in 2026) is technically less fussy than Apple's review process, but the policy team is catching up. Common Play rejections we have hit:
- Subscription details not matching the Play Console product. If your Play Console says "Monthly Pro" at $9.99 and your app shows "Pro Plan" at "$9.99/mo", that string mismatch has been flagged.
- Acknowledging purchases late or never. Google gives you three days to acknowledge a purchase. Miss it and the purchase is auto-refunded. With
react-native-iapthis means callingfinishTransaction(which acknowledges under the hood) reliably, including after app restarts where a purchase completed but your handler crashed. - Free trials without a clear price-after-trial label. This one is now enforced almost as strictly as on iOS.
A simple pattern: on every app launch, fetch outstanding purchases and acknowledge any that your server has already granted entitlement for. It is idempotent and saves the occasional dropped acknowledgement.
A submission checklist that has passed first time
We keep this taped to the wall, metaphorically:
- Restore Purchases button on paywall and in settings
- Disclosure block on paywall: title, length, price, auto-renew, cancel info, Terms, Privacy
- Terms of Use (EULA) URL set in App Store Connect — Apple's default EULA is fine if you do not have a custom one, but the link must work
- Server-side receipt validation live, with webhooks consumed
- No webview routes to external digital-goods payment
- Test account credentials provided in App Review notes, including how to reach the paywall
- Sandbox tested for: new purchase, restore, cancellation, refund, upgrade between tiers
- Android: acknowledgement happens within app session, with a safety net at launch
The single biggest accelerator is the App Review notes field. Tell the reviewer exactly where the paywall is, what test card or sandbox account to use, and what to expect. Reviewers process hundreds of apps a day — make their job easy and you stop being a rejection statistic.
Where we'd start
If you are about to ship IAP in a React Native app for the first time, do these three things before you write any UI code: stand up the entitlement table and webhook endpoint on your backend, decide whether RevenueCat is worth it for your team's bandwidth, and write the App Review notes draft. The paywall screen is the easy part. The infrastructure around it is what gets you approved — and what keeps your refund rate from quietly eating your margin six months later. If you want a second pair of eyes on an IAP build before you submit, our mobile team does pre-submission audits.
Want a team like ours?
72Technologies builds production software for the kind of teams who actually read this blog.
Start a projectKeep reading

Handling Android 15's Edge-to-Edge Mandate in React Native and Expo
Android 15 forces edge-to-edge by default and it's quietly breaking React Native UIs. Here's what changed, how to fix it in Expo, and where react-native-safe-area-context still gets it wrong.

App Size Bloat in React Native: How We Got an Expo App Back Under 40 MB
An Expo app crept past 120 MB on Android. Here's the audit we ran, the libraries we ripped out, and the build flags that actually moved the needle.
Deep Linking in React Native 2026: Universal Links, App Links, and the Edge Cases That Bite
Deep linking looks trivial until a marketing campaign goes live and half your users land on the App Store instead of the screen you promised. Here's what actually works in 2026.
