You spent weeks building your subscription app. Sandbox testing passed. Your TestFlight testers loved it. You hit Submit for Review — and two days later, Apple's review team sends it back with a cryptic message about a broken purchase flow.

This is one of the most demoralizing moments for an indie developer. The good news: the vast majority of IAP rejections are preventable, and most of them cluster around the same handful of fixable issues. This guide covers the real rejection vectors — the ones RevenueCat's own support team sees most often — and shows exactly how RevenueCat's SDK and dashboard features let you address them before review.


The Rejection Landscape

According to Apple's own App Review Center, over 40% of all rejections fall under Guideline 2.1 — App Completeness: something didn't work during the review itself. Source

For subscription apps, the three most common purchase-related failure points are:

  1. Products fail to load during the reviewer's session
  2. An error occurs during the purchase (often STORE_PROBLEM)
  3. Content is not unlocked after a successful purchase

Both Apple and Google also scrutinize paywalls for disclosure and UI compliance. Let's go through each vector and the corresponding fix.


Rejection Vector 1: Products Fail to Fetch

The problem

App Review runs in an environment separate from your Sandbox. If your in-app purchases are not in "Ready to Submit" status in App Store Connect — or if they were never explicitly submitted with your first binary — the reviewer will see an empty paywall and reject your app immediately.

Source

The fix

Before submitting any build that includes IAP for the first time, verify:

  1. Every product you intend to use shows "Ready to Submit" status in App Store Connect.
  2. On the app version submission page, look for the option to include in-app purchases with the binary. This option only appears the first time you ship IAP. Don't skip it.

App Store Connect shows "prepare for submission" for products

For existing apps adding new products: new products must reach "Approved" status via an independent product review before they will be purchasable on the live App Store. Source

RevenueCat dashboard check

The RevenueCat dashboard will show you if a product is missing or misconfigured in your Offering. Navigate to your Product Catalog → Offerings, verify all packages resolve to real products, and confirm that empty offerings are not being served to users.

Important: Empty offerings are almost always a configuration issue in App Store Connect or the Google Play Console, not a RevenueCat bug. Source


Rejection Vector 2: Missing Restore Purchases Button

The problem

Apple's App Review Guidelines require that all apps selling non-consumable or auto-renewable subscription products provide a visible Restore Purchases mechanism. Reviewers will test this. If it's missing or broken, your app gets rejected.

The fix with RevenueCat

The restorePurchases() method re-syncs all transactions from the user's Apple or Google account with their current RevenueCat App User ID:

// Swift — iOS
// Wire this to a "Restore Purchases" button in your UI
Button("Restore Purchases") {
    Purchases.shared.restorePurchases { customerInfo, error in
        if let error = error {
            // Show error to user
            print("Restore failed: \(error.localizedDescription)")
            return
        }
        // Check if the expected entitlement is now active
        if customerInfo?.entitlements["premium"]?.isActive == true {
            // Unlock content
        }
    }
}
// Kotlin — Android
// Wire this to a "Restore Purchases" button in your UI
Purchases.sharedInstance.restorePurchases(
    onSuccess = { customerInfo ->
        if (customerInfo.entitlements["premium"]?.isActive == true) {
            // Unlock content
        }
    },
    onError = { error ->
        // Show error to user
    }
)

⚠️ Do not call restorePurchases() programmatically on app launch. It can trigger OS-level sign-in prompts, which is itself a rejection reason. Only call it from an explicit user action (e.g., tapping a "Restore Purchases" button). If you need to programmatically sync purchases without user interaction, use syncPurchases() instead. Source

Special case: Consumables and non-renewing subscriptions

Consumables and non-renewing subscriptions do not appear on the underlying store receipt after the transaction is consumed. RevenueCat can only restore them if you use a custom App User ID system (i.e., your users log in). Anonymous users cannot recover consumed purchases through receipt inspection alone. Source

If your app has lifetime or one-time unlocks, model them as non-consumable IAPs in App Store Connect and attach them to an entitlement in RevenueCat. This ensures they are always present on the receipt and can be restored reliably. Source


The problem

Apple requires apps with auto-renewable subscriptions to provide a way for users to manage (and cancel) their subscription from within the app. Google Play has a similar requirement. Reviewers will check this during the review process.

The fix: RevenueCat Customer Center

RevenueCat's Customer Center is a pre-built, configurable UI that handles subscription management in a few lines of code. It includes:

  • Cancel subscription
  • Request a refund (iOS)
  • Restore missing purchases
  • Change plans (iOS)

Source

iOS — present Customer Center modally:

import RevenueCatUI

// In your settings or account screen
struct AccountView: View {
    @State private var showCustomerCenter = false

    var body: some View {
        Button("Manage Subscription") {
            showCustomerCenter = true
        }
        .presentCustomerCenter(isPresented: $showCustomerCenter)
    }
}

Android — launch Customer Center:

Add the RevenueCatUI dependency to your build.gradle, then:

// In your settings or account Activity/Fragment
CustomerCenter.show(activity = this)

The Customer Center is configured entirely from the RevenueCat dashboard — no app update needed to adjust paths, feedback surveys, or promotional retention offers. Source


Rejection Vector 4: Paywall Metadata and Disclosure Issues

Paywalls are one of the most scrutinized areas of your app during review on both platforms. Source

Here are the four most common paywall rejection reasons and how to fix them:

4a. Full billed amount not clearly shown

If you normalize an annual price to a monthly equivalent (e.g., "$4.16/mo"), you must also display the full annual charge ($49.99/yr). Only showing the per-month breakdown can result in rejection.

Use RevenueCat's paywall variables: product.price_per_period or product.price_per_period_abbreviated to ensure the correct full price is always surfaced. Using product.price_per_month alone will not satisfy this requirement. Source

4b. Introductory offer not clearly disclosed

If you offer a free trial or discounted first period, the nature of that offer must be unambiguously clear on your paywall — because that's what the customer is actually signing up for.

Use product.offer_price and product.offer_period variables in your CTA or package description. India has particularly strict rules on offer disclosure and is a common source of region-specific rejections. Source

4c. No cancellation language

Both stores may flag paywalls that don't clearly tell users they can cancel. Add explicit copy such as: "Try free for 7 days, then $9.99/mo. Cancel anytime." Source

4d. Missing Terms & Privacy Policy

While Apple no longer requires T&C links directly on the paywall, they must be accessible somewhere in your app. Reviewers who can't easily find them may reject. The safest approach is to include both links directly on your paywall.

RevenueCat Paywalls automatically include Restore Purchases, Terms of Service, and Privacy Policy components, and these will be automatically localized. Source


Rejection Vector 5: Sandbox Testing Gaps

The problem

Purchases that work perfectly in RevenueCat's Test Store or even Apple Sandbox can still fail during App Review if your integration isn't complete. The most common traps:

  • Using a Test Store API key in your release build. This will crash your app in production. Source
  • Only testing with Test Store, never with real platform sandbox. Test Store doesn't exercise StoreKit, billing grace periods, or platform-specific edge cases.
  • Not signing tax forms and banking agreements in App Store Connect / Google Play Console. Products will silently fail to fetch if these are missing. Source

RevenueCat recommends a three-phase approach before submitting: Source

Phase Environment Goal
Development RevenueCat Test Store Fast iteration, verify entitlement logic
Pre-launch Apple Sandbox / Google Play testing End-to-end with real store APIs
Submission Platform-specific API key only Never submit with Test Store key

Create an Apple Sandbox tester account in App Store Connect → Users and Access → Sandbox Testers, then add it to your device under: - iOS 18+: Settings > Developer > Sandbox Apple Account - iOS 13–17: Settings > App Store > Sandbox Account

Source

⚠️ When testing in platform sandboxes, focus on validating the flow of purchases — not prices or metadata, which are often inaccurate in sandbox environments. That inconsistency is expected and not a sign of a broken integration. Source

Content must actually unlock

After a sandbox purchase completes, verify that the entitlement is active by checking CustomerInfo:

// Swift — check entitlement after purchase
Purchases.shared.getCustomerInfo { customerInfo, error in
    if customerInfo?.entitlements["premium"]?.isActive == true {
        // Show premium content
        showPremiumContent()
    }
}
// Kotlin — check entitlement after purchase
Purchases.sharedInstance.getCustomerInfo(
    onSuccess = { customerInfo ->
        if (customerInfo.entitlements["premium"]?.isActive == true) {
            showPremiumContent()
        }
    },
    onError = { error -> /* handle */ }
)

If your products aren't attached to entitlements in the RevenueCat dashboard, isActive will never return true, and your app will be rejected for failing to unlock content. Source


Rejection Vector 6: The STORE_PROBLEM Error

The problem

This is the most common RevenueCat error that causes unexplained rejections. It occurs when Apple's sandbox environment is down and cannot verify a purchase. Apple uses this error internally for multiple failure modes, so the message "There was a problem with the App Store" may appear to you or a reviewer with no other explanation. Source

What to do

  1. Check status.revenuecat.com for known Apple sandbox outages (these can last hours to days).
  2. If you can reproduce the purchase successfully on your own device, the issue is likely transient in the reviewer's environment.
  3. Re-submit the same binary. Some developers have had to resubmit up to a dozen times during major sandbox outages before getting approved.
  4. Optionally, include an explanation in your Review Notes field describing that this is a known Apple sandbox instability and the flow works correctly in production.

Pre-Submission Checklist

Use this alongside RevenueCat's official App Subscription Launch Checklist:

  • [ ] All IAP products are "Ready to Submit" in App Store Connect / approved in Google Play Console
  • [ ] In-app purchases are included with the first binary submission
  • [ ] Tax forms, agreements, and banking info are complete on both platforms
  • [ ] A visible "Restore Purchases" button calls restorePurchases() — not wired to app launch
  • [ ] Subscription management is accessible in-app (use RevenueCat Customer Center)
  • [ ] Paywall shows full billed amount, intro offer terms, and cancellation language
  • [ ] Terms & Privacy Policy links are present and accessible
  • [ ] Tested end-to-end with platform sandbox (not just Test Store) using a platform-specific API key
  • [ ] getCustomerInfo() / entitlement check is called after purchase to unlock content
  • [ ] App is configured with the correct platform-specific API key — never the Test Store key — for the release build

Summary

Store rejections feel arbitrary, but most IAP rejections are systematic and fixable. RevenueCat's SDK handles the most error-prone parts of the purchase flow — sandbox/production environment detection, receipt validation, entitlement unlocking — automatically. The remaining rejections come down to UI disclosure and pre-submission hygiene.

Get the paywall copy right, wire up a restore button, add the Customer Center for subscription management, and run one full end-to-end test against a real platform sandbox before you submit. That combination eliminates the overwhelming majority of rejection risk for indie subscription apps.


Sources