| name | paywalls-monetization |
| description | Build, test, and ship iOS in-app purchases and subscription paywalls with StoreKit 2 or RevenueCat (iOS 26 / Swift 6 / Xcode 26): StoreManager, Transaction.updates, subscription state machine, restore, Family Sharing, App Store Server API, StoreKit Views, all four offer types incl. iOS 18+ win-back, SwiftUI paywall design, sandbox + .storekit testing, App Review 3.1/3.1.2 compliance. Use when adding a paywall, subscriptions, IAP, free trials, promo/offer codes, restore purchases, transaction verification, or fixing a 3.1.x rejection. Triggers: "add a paywall", "StoreKit 2", "IAP", "RevenueCat", "subscription not unlocking". Pricing strategy and PPP → ios-app-growth-marketing skill. |
Paywalls & Monetization (StoreKit 2 / RevenueCat)
The engineering-and-conversion playbook for monetizing an iOS app: pick a path,
wire purchases correctly, design a paywall that converts, add the offers that
recover revenue, and get through App Review the first time.
This skill is the implementation counterpart to the
ios-app-growth-marketing skill. Use this skill for the Swift code, the
paywall UI, the offer flows, testing, and review compliance. Use
ios-app-growth-marketing for the strategy questions it owns and this skill
does not duplicate: what price to charge (value-based pricing, unit economics,
Van Westendorp), how to display prices compliantly (the "never hardcode
prices" RevenueCat rule), and per-territory PPP pricing.
Decide the path first (StoreKit 2 vs RevenueCat)
Both are valid in 2026. Pick one before writing code — mixing them halfway is
the most common mess.
| Native StoreKit 2 | RevenueCat |
|---|
| Best for | Solo devs, simple catalogs, want zero third-party SDK / no MAU fee, full control | Cross-platform (iOS + Android + web), want a hosted dashboard, server-side entitlements without running a backend, A/B paywall tests |
| Receipt/entitlement source of truth | Transaction.currentEntitlements (on-device, cryptographically signed) | RevenueCat CustomerInfo.entitlements (server-validated) |
| Server validation | You run App Store Server API + Server Notifications, or trust on-device JWS | RevenueCat does it for you |
| Cost | Free | Free under a revenue threshold, then % of tracked revenue |
| Lock-in | None | SDK + dashboard; migratable but real |
Default recommendation: native StoreKit 2 for an iOS-only app with a small
catalog; RevenueCat the moment you need a second platform, want hosted paywall
A/B tests, or don't want to operate App Store Server Notifications yourself.
The rest of this skill is StoreKit-2-first (it's the foundation RevenueCat sits
on top of). RevenueCat-specific wiring is in
references/revenuecat.md.
The non-negotiables (every IAP integration)
These are the things App Review and your own bug reports punish you for. None
are optional.
- One centralized
StoreManager. Never scatter product.purchase() across
views. All purchasing, status, and entitlement logic lives in one
@MainActor @Observable (iOS 17+) type.
- A
Transaction.updates listener started at app launch. This is the ONLY
way you catch renewals, Ask-to-Buy approvals, Family Sharing, offer-code
redemptions, refunds, and purchases made on another device. A purchase made
only inside your purchase() method will miss all of these.
- Verify before granting. Check
VerificationResult — grant entitlement
only for .verified. Never trust an .unverified transaction.
- Always
await transaction.finish(). After granting (and even for
unverified/refunded), finish the transaction. Unfinished transactions are
redelivered on every launch forever, and the queue grows without bound.
- A visible "Restore Purchases" button. App Review rejects apps with
non-consumables or subscriptions that lack restore (
AppStore.sync()).
Guideline 3.1.1.
- Never hardcode prices, currency, period words, savings %, or trial
duration in Swift. Display them from the loaded
Product /
StoreProduct. A hardcoded "$2.50/mo" shows the wrong price the day ASC
pricing changes → Guideline 3.1.2 rejection + refund storm. (Full rule:
the ios-app-growth-marketing skill's paywall-pricing-display.md.)
- Server-side validation for anything that matters financially — granting
server-held consumables, syncing entitlements to a backend, refund decisions.
On-device JWS is fine for unlocking local features; it is not enough when
money or server state hangs on it.
Workflow
Phase 0 — Plan the products (before any App Store Connect work)
Ask the developer (use AskUserQuestion for a clean MCQ):
- Product type — auto-renewing subscription (recurring revenue, content/
productivity apps), consumable (credits/coins/tokens), or non-consumable
("remove ads", "unlock pro" forever).
- Tiers / durations — the strong default is weekly + annual, NO monthly
(monthly converts and retains worst of the three). Add tiers only if distinct
feature sets justify them.
- Free trial / intro offer — 7-day free trial is the industry default and
best converter; one introductory offer per subscription group per Apple ID.
- Paywall placement — first-launch (after value), hard feature gate, usage
limit, settings/upgrade, trial-expiration.
Capture the actual Product IDs and Subscription Group ID so generated
code is copy-paste ready with no YOUR_ID placeholders.
Phase 1 — Create products in App Store Connect + a local .storekit file
Set products up in ASC (subscription group, products, pricing, localization,
intro offers). Step-by-step is in
references/storekit2-implementation.md.
Then create a .storekit Configuration File and develop against it first
(File → New → File → StoreKit Configuration File → set it on the scheme). This
catches product-ID typos in Xcode rather than at runtime, removes the ASC
round-trip during development, and lets you simulate renewals/refunds/Ask-to-Buy
locally. Build the production code against the .storekit file, then validate in
sandbox.
Phase 2 — Implement the StoreManager + entitlement layer
Generate the centralized manager: product loading, the transaction listener,
verified purchase flow, currentEntitlements reconciliation, restore, and the
subscription state machine. The complete, modern (iOS 26 / Swift 6) reference is
references/storekit2-implementation.md.
Phase 3 — Build the paywall UI
Two routes:
- Apple's StoreKit Views (
SubscriptionStoreView, ProductView,
StoreView, SubscriptionOfferView) — least code, automatically correct
pricing/localization, native look, free A/B-able copy. Best when you don't
need a bespoke design.
- Custom SwiftUI paywall — full design control. Follow the conversion
structure and pricing psychology in
references/conversion-cro-playbook.md
and the SwiftUI building blocks in
references/storekit2-implementation.md.
Either way, the paywall MUST show: plan terms + exact charge + cadence, trial
terms, an obvious Restore Purchases, links to Terms and Privacy
Policy, "auto-renews unless cancelled", and a clear dismiss ("Maybe
Later", not a hidden X).
Phase 4 — Add offers (recover and grow revenue)
Introductory, promotional, offer-code, and win-back offers — taxonomy + full
StoreKit 2 implementation (including the iOS 18+ native win-back flow and the
.storeMessagesTask Message API) in
references/offers-and-winback.md.
Phase 5 — Test everything, then ship through review
Sandbox + .storekit testing, the 10-scenario payment test matrix, and the App
Review Guideline 3.1 compliance checklist in
references/testing-and-review-compliance.md.
Conversion at the paywall (tactical summary)
Deep CRO is in references/conversion-cro-playbook.md;
the load-bearing rules:
- Value before the ask. Let the user experience the core product (30–90s of
real value) before the paywall. Never paywall before demonstrating value.
- Outcome, not features. "Fix your 11 filler words" beats "Unlimited
analyses." Outcome copy lifts conversion meaningfully (Strava-style ~23%).
- Anchor high. Show the annual price first ($X/yr → "$1.15/week"), then
weekly. The annual then reads as a steal; a "SAVE 75%" badge lifts annual
adoption 20–40%.
- Two plans max. 3+ tiers cause decision paralysis. Weekly = conversion
driver, Annual = retention driver.
- Respect the no. A visible "Maybe Later" raises conversion — users feel
respected, not trapped.
- Day-0 intent. Purchase intent is highest right after install and decays
daily. Show the paywall on day 0, after value, during onboarding — don't
"save it for later."
- No fake social proof / fake urgency. No invented star ratings, user
counts, or "2 spots left!" Savvy users spot it and trust collapses. Add real
ratings only after 10+ genuine reviews.
Benchmarks to aim for: paywall view rate 50–70% of installs, trial-start 15–25%
of paywall views, trial-to-paid 44–60%, 60–70% annual mix.
Common failure modes (and the fix)
| Symptom | Cause | Fix |
|---|
| Products don't load (empty array) | Product IDs don't match ASC / .storekit; not Cleared-for-Sale; bundle-ID mismatch | Match IDs exactly (case-sensitive); verify in the .storekit file; try await AppStore.sync() to refresh |
| Bought, but feature stays locked | No Transaction.updates listener, or checkPurchasedProducts() not called after purchase | Start the listener at launch; reconcile currentEntitlements after every transaction |
| Entitlement reappears as missing on relaunch | transaction.finish() never called → redelivered & re-processed | Always await transaction.finish() after handling |
| Subscription shows expired but user still paying | Manual status tracking instead of Product.SubscriptionInfo.status(for:); ignoring inGracePeriod/inBillingRetryPeriod | Use the StoreKit status API; treat grace/billing-retry as still active |
| App rejected 3.1.1 | No Restore button | Add a visible Restore (AppStore.sync()) in settings + on the paywall |
| App rejected 3.1.2 | Hardcoded price/trial in the binary, or unclear auto-renew/terms disclosure | Display prices from Product; show charge + cadence + auto-renew + Terms/Privacy |
| Family-shared purchase not recognized | Using deprecated singular entitlement API | Use Transaction.currentEntitlements(for:) (the sequence) |
References
- references/storekit2-implementation.md — the complete native StoreKit 2 engineering reference: StoreManager, transactions & verification, subscription state machine (with the exact grace/billing-retry/offer-code constants), StoreKit Views, AppTransaction, App Store Server API + Server Notifications, SwiftUI paywall building blocks, the
.storekit JSON schema (incl. _storeKitErrors injection) and consumable per-unit pricing, phased migration from StoreKit 1.
- references/conversion-cro-playbook.md — paywall design, the quantitative screen-anatomy spec (spacing/type/thumb-zone/error-states), the onboarding→paywall handoff, pricing psychology, the weekly+annual strategy, Apple's price-point grid + FX/fee mechanics, benchmarks, anti-patterns, A/B testing priorities.
- references/offers-and-winback.md — the four offer types, the iOS 18+ native win-back flow, Message API (
.storeMessagesTask), promotional-offer JWS signing, eligibility, and a full win-back implementation.
- references/asc-rest-provisioning.md — creating the products you sell via the App Store Connect REST API: the dependency-ordered subscription/IAP/offer create flow, the v2 IAP endpoints, and the hard-won 409/ordering/casing lessons. (To sell them, use StoreKit 2; this covers making them exist.)
- references/lifecycle-messaging.md — post-paywall LTV: notification permission timing, trial/abandon/renewal-risk/win-back push+email sequences, the involuntary-churn dunning flow (grace 6→16 days), the cancellation save-flow, and refund decline strategy (Consumption API field values + the bottom-10% rule).
- references/tap-to-pay.md — accepting your customers' physical cards in person (ProximityReader): the managed-entitlement workflow (dev + distribution),
prepare()-on-foreground, and PSP-onboarding gotchas. A different rail from IAP.
- references/paywall-teardowns.md — 11 annotated big-app paywall teardowns (Calm, Duolingo, Noom, Cal AI, Tinder, Strava, Headspace, Blinkist, Flo, ChatGPT, AI companions) with per-app Apple-compliance flags + the screenshot-library list.
- references/revenuecat.md — the RevenueCat path: SDK setup, Offerings/Packages/Entitlements, paywalls, ASC↔RevenueCat catalog reconciliation (audit-first), and when to choose it over native.
- references/testing-and-review-compliance.md —
.storekit + sandbox testing, the 10-scenario payment test matrix, the Apple-Pay-vs-IAP rail table (incl. the P2P-1:1 / donation-approval / "via"-label / web-AUG-parity nuances), the two-place subscription-disclosure rule, the SKInternalErrorDomain Code=3 fix for tests run outside xcodebuild, accessibility, and the App Review Guideline 3.1 checklist.
WWDC sessions: 2025-241, 2025-249, 2024-10061, 2024-10062, 2023-10013, 2021-10114.
Apple docs: developer.apple.com/documentation/storekit, /appstoreserverapi.