Hard Paywall in Mobile Apps: Implementation Without Bypass
Recently, a fintech startup approached us with an already implemented paywall that had been rejected by the App Store three times. The issue was a violation of section 3.1.1: the paywall provided no functionality before subscription. We redesigned the onboarding, added a feature demo, and the paywall passed review on the first try. This situation is not uncommon. A hard paywall blocks access to content without payment, and even the slightest mistake leads to rejection. The key is not to violate Apple's rules while ensuring users don't feel deceived. We have implemented such solutions for 15+ projects of varying complexity, including fintech and health. Properly designed paywalls pay off through higher conversion, typically yielding a 15% revenue increase. For example, with 10,000 monthly active users and a $9.99/month subscription, that translates to an additional $15,000 monthly revenue. In this article, we dive into technical details: from the new StoreKit to back gesture prevention.
Why Does the App Store Reject Hard Paywalls?
Apple scrutinizes hard paywalls for compliance with App Store Review Guidelines (section 3.1.1). Rejection occurs if:
- The app does not work at all without purchase, but the description lacks explicit mention of payment.
- The user cannot tap anything without a subscription (not even "View Plans" or "Log In").
- The paywall appears immediately on first launch without minimal value demonstration.
Allowed schemes: onboarding showcasing key features → paywall, or trial period (3–7 days free) → hard paywall. The app description must clearly state it is subscription-based.
How to Build the Onboarding and Paywall Funnel?
A conversion-optimized flow proven on dozens of commercial projects:
- Onboarding (2–4 screens) — demonstrate value through specific features.
- Personalization step — ask about the user's goal (genre for a reading app, workout goal for fitness). Answers don't necessarily affect the algorithm—what matters is psychological investment.
- Hard paywall — after the user has customized the product.
On the client side: OnboardingCoordinator manages steps, PaywallCoordinator is the final step. After a successful purchase, transition to the main screen without the ability to go back.
Mandatory Elements of a Hard Paywall
Restore Purchases — mandatory. User reinstalls the app—they must restore their subscription. Use AppStore.sync() (StoreKit 2) or BillingClient.queryPurchasesAsync() on Android. Without this function, you risk rejection and negative reviews.
Check SKPaymentQueue.canMakePayments() before showing the paywall. On devices with corporate MDM profiles or Screen Time restrictions, purchases may be blocked. If canMakePayments == false, show an explanation—don't crash.
Terms of Service / Privacy Policy — at the bottom of the screen, small font. For subscriptions: clearly state the amount, period, and auto-renewal terms: "7 days free, then $9.99/month, cancel anytime."
How to Block Navigation?
A hard paywall must not be bypassed via back gesture or back button. On iOS: UIViewController.isModalInPresentation = true disables swipe-to-dismiss for .pageSheet/.formSheet. For full-screen presentation, back gesture is unavailable. On Android: onBackPressed() (Activity) or BackHandler (Compose)—either ignore it or show a confirmation: "Are you sure? Without a subscription, the app is unavailable."
In NavigationStack (SwiftUI) or NavHost (Compose), present the paywall on top of the main stack.
| Platform | Blocking Mechanism | Code Construction |
|---|---|---|
| iOS (UIKit) | isModalInPresentation = true |
viewController.isModalInPresentation = true |
| iOS (SwiftUI) | .interactiveDismissDisabled() |
Text("Paywall").interactiveDismissDisabled() |
| Android (View) | onBackPressed() |
override fun onBackPressed() {} |
| Android (Compose) | BackHandler |
BackHandler { showExitDialog() } |
StoreKit 2 vs StoreKit 1 for Hard Paywall
StoreKit 2 is 2x better than StoreKit 1 in sync speed, thanks to async/await and automatic status handling. For example, AppStore.sync() completes within a second, whereas the old SKReceiptRefreshRequest takes up to 10 seconds. This is critical on first launch after purchase. If you are unsure which library to choose, contact us—we will help you decide.
| Feature | StoreKit 1 | StoreKit 2 |
|---|---|---|
| Sync time | up to 10 sec | 1-2 sec |
| Code style | delegates | async/await |
| Support | deprecated | current |
What's Included in Hard Paywall Implementation?
- Product setup in App Store Connect / Google Play Console.
- Integration of StoreKit 2 / Play Billing with introductory offers (free trial, pay-as-you-go).
- Implementation of purchase restore with backend status sync.
- Navigation blocking (iOS/Android) with edge-case handling.
- Event analytics (paywall_shown, purchase_started, purchase_success, purchase_failed, restore_tapped).
- Documentation of flow diagrams and code.
- Post-release support (2 weeks free).
Analytics and Monitoring
Key events: paywall_shown, paywall_purchase_started, paywall_purchase_success, paywall_purchase_failed, paywall_restore_tapped, paywall_restore_success. A drop between purchase_started and purchase_success above 20% indicates a billing issue requiring immediate investigation. Our five years of experience show that timely monitoring reduces losses to under 5%.
Timeline Estimates
Hard paywall with full StoreKit 2 / Play Billing flow, restore, introductory offer, analytics, and navigation blocking — 2–3 working days. With a custom onboarding funnel — add 3–5 days. Contact us for an accurate estimate of your project. Get a free consultation from our certified developers: we will analyze your current implementation and propose the optimal solution with guaranteed approval.







