The App Store & Google Play subscription rejection checklist
Subscription rejections don't come out of nowhere. Store review teams at Apple and Google reject subscription apps for a small, stable set of reasons — and if you build with a coding agent, several of these are exactly the failure modes agents bake in silently: nothing crashes, nothing logs, everything type-checks. The rejection arrives later, in review.
This checklist maps each risk to the guideline that cites it, so you can fix them before submission instead of after a bounce. It works whatever your stack — RevenueCat, StoreKit directly, react-native-iap — and however the code was written. If you want the automated version, iap-preflight checks items 1–6 mechanically.
1. A restore affordance exists and is reachable
Apple guideline 3.1.1 requires a restore mechanism for restorable purchases. Auto-renewing subscriptions restore on a fresh install via the user's Apple/Google account — but only if your app exposes a way to trigger it. Agents routinely build a beautiful paywall with buy buttons and no "Restore Purchases" link, because no type checker complains about a missing button.
Check: open your paywall on a clean install. Is there a restore action? Does calling it actually re-sync the entitlement? On iOS, also confirm it works signed into a different device with the same Apple ID.
2. No placeholder content in the binary
Apple 2.1(a) requires placeholder content scrubbed before submission — and reviewers do read config-looking strings. An agent that scaffolds your billing config with {{REVENUECAT_PUBLIC_API_KEY}}, YOUR_KEY_HERE, or CHANGEME and ships it will bounce. This is the most preventable rejection on the list.
Check: grep your repo for {{, YOUR_, CHANGEME, REPLACE_WITH before every release build. All of them — in config, constants, and strings files.
3. Product identifiers match everywhere and follow the charset rules
Store product IDs allow letters, digits, _, and . — anything else gets rejected at configuration time or, worse, behaves inconsistently between consoles and client code. Monthly and yearly must be two distinct products; pointing both at the same ID is a fail. And the same IDs must appear in App Store Connect, Play Console, your RevenueCat catalog, and your client config — one casing drift and the purchase silently finds nothing.
Check: diff the four lists against each other. Not eyeball them — diff them.
4. The API key is the right kind of key
RevenueCat public SDK keys start with appl_ (iOS) or goog_ (Android). A secret key, a project ID, or a random token pasted in the wrong field won't crash anything — the SDK just fails to reach the backend, and the paywall spins or errors at runtime.
Check: the prefix. Public SDK keys are safe to embed; anything else shouldn't be in client code at all.
5. Native billing SDKs don't run in Expo Go
If your project depends on react-native-purchases, react-native-iap, or any native IAP library, it cannot load inside Expo Go — the runtime doesn't include native modules. An agent that "verified" the paywall in Expo Go has verified nothing; the module errors the moment a dev build touches it. You need a development build: npx expo prebuild, then an EAS or local dev client. (An Expo project with a native IAP dependency and no expo-dev-client is itself a red flag — that's exactly what this check looks for.)
Check: confirm your build path is a dev build, not Expo Go. If your repo has a native IAP dependency but no expo-dev-client, add one or prebuild.
6. Disclosures — on the paywall and in the listing
Apple 3.1.2(c) and Google Play's subscriptions policy both require: the price and billing period clearly shown before purchase, what the subscription unlocks, auto-renewal terms, how to cancel, and — if you offer a trial — trial length and post-trial price. Your store listing additionally needs the subscription disclosed (both stores) plus working links to Terms/EULA and a privacy policy, and accurate data-safety/privacy-label answers: purchase history and entitlement validation transit the store and (if you use one) your billing SDK vendor — say so on the forms.
Check: read the paywall as a reviewer would. Price + period visible? Trial terms stated? Then open the store listing and repeat.
7. Console state is production-ready
Client code is half of it. Before submission: both subscription products Active in both consoles; the RevenueCat offering you read from still marked Current (a stock paywall reads only the Current offering); the Google merchant profile created (subscription products cannot even be created without it); license testers and sandbox testers in place.
8. Google personal accounts: closed testing first
Play developer accounts created after 2023-11-13 (personal accounts) must complete closed testing before production access: keep at least 12 testers opted in for the last 14 days. Organization accounts are exempt. Plan for this — it's a calendar item, not a code item.
Automate items 1–6
iap-preflight runs this checklist's mechanical parts against your repo — read-only, zero dependencies — and prints one PASS / WARN / FAIL line per finding with the guideline references inline. Run it before every submission build; wire it into CI and let the exit code fail the pipeline.
For the full journey from zero (store setup → RevenueCat → paywall code → sandbox test → production), see the React Native paywall guide. If you'd rather install paywall code that ships with these checks already passing, there's a module for that.
FAQ
Does this apply if I use RevenueCat (or another billing SDK)?
Yes — the guidelines bind your app, not your SDK vendor. SDKs provide the mechanism (e.g. restorePurchases()), but surfacing restore in your UI, disclosing terms, and scrubbing placeholders is your responsibility. The audit tool checks the app repo, whatever SDK it uses.
Which rejection is most common?
We can't rank them from our own data (we don't have a rejection corpus), but the restore-mechanism and subscription-disclosure family (Apple 3.1.x) is among the most frequently reported indie billing pains — which is why it leads this list.
Are Apple's and Google's rules the same?
They overlap heavily but aren't identical. Both require disclosure, accurate listings, and working subscriptions; Apple's 3.1.1/3.1.2(c) and Google's subscriptions policy (linked in the tool's output) are the operative texts. Google adds the closed-testing requirement for newer personal accounts.
What does WARN vs FAIL mean in the audit tool?
A FAIL (missing restore, invalid product IDs, Expo Go setup) should be fixed before submission. A WARN (e.g. unfilled config placeholders) is a "check this on purpose" — placeholders during development are normal; placeholders in a release build are a 2.1(a) problem.