The 30-second answer
RevenueCat is a subscription management SDK that wraps StoreKit 2 for iOS apps. It is free up to 2,500 dollars in tracked monthly revenue, then 1 percent of MTR on the Scale plan. For SwiftUI apps, you install the package via SPM, call Purchases.configure(), and bind your views to CustomerInfo.entitlements. Most indie iOS developers should start with RevenueCat because it eliminates server side receipt validation, cross device sync, and analytics work that takes weeks to build natively. The Swift Kit ships RevenueCat fully wired so you skip the integration entirely.
This is the complete 2026 guide to RevenueCat in SwiftUI. It covers pricing math, the full SwiftUI integration with StoreKit 2, the new Paywalls v2 component, webhooks for server side state, restore purchases, free trial handling, RevenueCat alternatives, and a clear decision tree. By the end you will know exactly when to use RevenueCat, when to skip it, and how to wire it correctly the first time.
What RevenueCat Is, Plainly
RevenueCat is an SDK plus dashboard for iOS, Android, web, and cross platform subscription management. The SDK runs in your app, talks to StoreKit 2 on iOS for the actual purchase, and forwards every transaction to RevenueCat servers for validation and tracking. You configure products, entitlements, and offerings in the RevenueCat dashboard. Your app reads who has access via CustomerInfo.entitlements.
What RevenueCat adds on top of native StoreKit 2:
- Server side receipt validation. Apple signs every transaction with JWS. RevenueCat verifies the signature on its servers, so your iOS app does not need a backend just for receipt validation.
- Cross device entitlement sync. A user subscribes on iPhone, opens your app on iPad, and the entitlement appears immediately. Native StoreKit 2 only handles same Apple ID on the same device chain.
- Analytics dashboard. Real time MRR, churn, cohort retention, trial conversion, refund rate, country breakdown. App Store Connect reports lag by 24 to 48 hours and lack cohort analysis entirely.
- Paywalls v2. A no code paywall builder that ships server driven UI to your app. Update paywall design, run A/B tests, and target country specific variants without an app update.
- Webhooks. 11 event types that fire on subscription lifecycle events, with retry logic and signed payloads. App Store Server Notifications V2 is the native equivalent and is significantly more involved to host.
- Cross platform parity. The same dashboard, the same entitlement model, and the same API across iOS, Android, Flutter, React Native, Expo, Unity, and Web (via Stripe).
What RevenueCat does not do: it does not replace App Store Connect (your products still live there), it does not change the Apple commission you pay (still 30 percent or 15 percent for Small Business Program members), and it does not own the customer relationship (Apple still bills users and processes refunds).
RevenueCat Pricing in 2026, the Honest Breakdown
Three plans, simple math:
| Plan | Eligibility | Cost | Best for |
|---|---|---|---|
| Free | 0 to 2,500 USD MTR | 0 | Pre launch and early stage indies |
| Grow | 2,500 to 10,000 USD MTR | 120 USD per month flat | Indies hitting traction |
| Scale | 10,000 USD plus MTR | 1 percent of MTR | Established subscription apps |
| Enterprise | 1,000,000 USD plus MTR | Negotiated | Large publishers |
Concrete examples for an indie app:
- Earning 1,500 USD per month: 0 dollars to RevenueCat (free tier).
- Earning 4,800 USD per month: 120 dollars to RevenueCat (Grow plan flat fee).
- Earning 12,000 USD per month: 120 dollars (1 percent of MTR on Scale).
- Earning 50,000 USD per month: 500 dollars (1 percent of MTR on Scale).
The break even comparison against building the same infrastructure in house: receipt validation alone takes 40 to 80 engineering hours done correctly, plus another 30 to 60 hours for analytics, cross device sync, and webhook handling. At a 100 dollar per hour fully loaded cost, that is 7,000 to 14,000 dollars of one time work plus ongoing maintenance. RevenueCat pays for itself before the first 6 months for most apps.
For live pricing math, use the RevenueCat Pricing Calculator. Plug in your monthly subscription price, expected subscriber count, and country mix, and see the take home math after Apple and RevenueCat fees.
SwiftUI Integration in 5 Minutes
The minimum viable RevenueCat integration in SwiftUI takes about 5 minutes once your products are live in App Store Connect. Here is the full path.
Step 1: Add the SDK via Swift Package Manager
In Xcode, open your project, navigate to File → Add Package Dependencies, and paste:
https://github.com/RevenueCat/purchases-iosAdd the RevenueCat and RevenueCatUI products to your app target. RevenueCatUI ships the Paywalls v2 PaywallView component.
Step 2: Configure Products in App Store Connect, then RevenueCat
In App Store Connect, create your subscription group and the auto renewable subscription products. Set product IDs that match a clear pattern like com.yourapp.pro.monthly and com.yourapp.pro.annual. Submit them for review only after you have tested with the StoreKit configuration file.
In the RevenueCat dashboard, create a project, link your App Store Connect via the App Store Connect API key, import your products, and define an entitlement called pro. Group the products into an offering called default. The SDK will fetch this offering on app launch.
Step 3: Configure RevenueCat in Your SwiftUI App
In your App.swift entry point, configure RevenueCat once on launch:
import SwiftUI
import RevenueCat
@main
struct YourApp: App {
init() {
Purchases.logLevel = .info
Purchases.configure(withAPIKey: Secrets.revenueCatPublicKey)
}
var body: some Scene {
WindowGroup {
RootView()
.environment(SubscriptionStore())
}
}
}Use the public API key from your RevenueCat dashboard (it starts with appl_). The public key is safe to ship in your binary; the secret key never ships in the app.
Step 4: Build a SubscriptionStore Observable
The cleanest pattern for SwiftUI is a single @Observable store that exposes customerInfo and the active offerings:
import Foundation
import RevenueCat
import Observation
@MainActor
@Observable
final class SubscriptionStore {
var customerInfo: CustomerInfo?
var offerings: Offerings?
var isLoading = false
var lastError: Error?
init() {
Task { await refresh() }
Task { await listenForUpdates() }
}
var isPro: Bool {
customerInfo?.entitlements.active["pro"]?.isActive == true
}
func refresh() async {
isLoading = true
defer { isLoading = false }
do {
async let info = Purchases.shared.customerInfo()
async let offerings = Purchases.shared.offerings()
self.customerInfo = try await info
self.offerings = try await offerings
} catch {
self.lastError = error
}
}
func purchase(_ package: Package) async {
do {
let result = try await Purchases.shared.purchase(package: package)
if !result.userCancelled {
self.customerInfo = result.customerInfo
}
} catch {
self.lastError = error
}
}
func restore() async {
do {
self.customerInfo = try await Purchases.shared.restorePurchases()
} catch {
self.lastError = error
}
}
private func listenForUpdates() async {
for await info in Purchases.shared.customerInfoStream {
self.customerInfo = info
}
}
}The customerInfoStream AsyncSequence delivers updates whenever subscription state changes (renewals, refunds, family sharing changes), so your views automatically reflect the latest state without manual polling.
Step 5: Gate Features and Present a Paywall
In any SwiftUI view, read the store and gate accordingly:
struct ContentView: View {
@Environment(SubscriptionStore.self) private var store
@State private var showPaywall = false
var body: some View {
VStack {
if store.isPro {
ProFeaturesView()
} else {
Button("Unlock Pro") { showPaywall = true }
}
}
.sheet(isPresented: $showPaywall) {
PaywallSheet()
}
}
}For the paywall sheet itself, you have two paths in 2026: use RevenueCat Paywalls v2 (the dashboard managed templated paywall) or build your own. The next section covers both.
Paywalls v2: Server Driven Paywall UI
RevenueCat shipped Paywalls v2 in late 2024 and matured through 2025. It lets you design a paywall in the dashboard with a visual editor, target it by country or trial state, and update the design without an app submission. The SwiftUI integration is one line.
import RevenueCatUI
struct PaywallSheet: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
PaywallView()
.onPurchaseCompleted { _ in dismiss() }
.onRestoreCompleted { _ in dismiss() }
}
}When to use Paywalls v2 vs build your own:
| Use Paywalls v2 if | Build custom if |
|---|---|
| Templates fit your brand | You need brand specific layout or animations |
| You want A/B testing without app updates | Your paywall is part of an onboarding flow |
| Localization is high priority | You have multi step paywalls (free trial offer plus features) |
| You want country specific variants | You need pixel control over typography and motion |
See the dedicated guide on iOS Paywall Design Patterns 2026 for the five archetypes that work in 2026, with screenshots and SwiftUI code for each.
Webhooks: Server Side Subscription State
RevenueCat sends webhook events to a URL you configure in the dashboard. Webhooks fire on every subscription lifecycle event, so your backend learns about renewals, cancellations, and refunds without polling.
The 11 event types every indie should know:
INITIAL_PURCHASE: first time a user subscribes.RENEWAL: a subscription auto renews.NON_RENEWING_PURCHASE: a one time consumable or non consumable.CANCELLATION: the user toggles auto renew off (still active until period end).UNCANCELLATION: the user re enables auto renew before period end.BILLING_ISSUE: card declined; entitlement may be in a grace period.EXPIRATION: the subscription period ended without renewal.PRODUCT_CHANGE: user switched to a different product (upgrade, downgrade, crossgrade).PROMOTIONAL: a promotional or developer granted entitlement was added.REFUND: Apple processed a refund.TRANSFER: subscription transferred to a different App User ID.
A minimal Vercel handler in TypeScript:
// pages/api/revenuecat-webhook.ts
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') return res.status(405).end()
const auth = req.headers.authorization
if (auth !== process.env.REVENUECAT_WEBHOOK_SECRET) {
return res.status(401).end()
}
const event = req.body.event
const userId = event.app_user_id
const type = event.type
switch (type) {
case 'INITIAL_PURCHASE':
case 'RENEWAL':
case 'UNCANCELLATION':
await markUserPro(userId, true, event.expiration_at_ms)
break
case 'EXPIRATION':
case 'REFUND':
await markUserPro(userId, false)
break
}
return res.status(200).end()
}For Convex, Cloudflare Workers, or AWS Lambda, the shape is identical. Always verify the Authorization header matches the secret you configured. Reject any request that fails verification.
Restore Purchases Done Right
Apple App Review requires a visible Restore Purchases button in any app that sells subscriptions. The implementation is one line:
Button("Restore Purchases") {
Task { await store.restore() }
}Place the button in your paywall and in your settings screen. The restore call returns the latest CustomerInfo and triggers the customerInfoStream update so your views refresh automatically. If the user has nothing to restore, the call succeeds with empty entitlements; show a "No purchases to restore" toast in that case.
Common mistakes that get apps rejected on this:
- The button only appears for users who have not subscribed (review needs to find it on every fresh install).
- The button silently fails when there is nothing to restore (show feedback).
- The button is buried 4 taps deep in settings (put it directly in the paywall).
- The restore action does not update the UI after success (use
customerInfoStream).
Free Trials and Promo Codes
RevenueCat handles free trials automatically through the App Store. You configure the trial period in App Store Connect (3 day, 7 day, 14 day, 30 day, 2 month, 3 month, 6 month, 1 year), set it on the appropriate product, and the SDK reads it. Your paywall view should detect eligibility:
func trialEligibility(for package: Package) async -> IntroEligibility {
let identifier = package.storeProduct.productIdentifier
let result = await Purchases.shared.checkTrialOrIntroDiscountEligibility(
productIdentifiers: [identifier]
)
return result[identifier] ?? .unknown
}When eligibility is .eligible, show "Try free for 7 days" copy. When ineligible, show "5.99 USD per month" without trial language. Apple rejects paywalls that show free trial copy to users who already used their trial.
Promo codes (offer codes) are managed in the RevenueCat dashboard or directly in App Store Connect. The SDK fetches eligible offers automatically, and you can present them as alternative purchase paths in your paywall. iOS 16 plus also supports the system Redeem Code sheet via Purchases.shared.presentCodeRedemptionSheet().
RevenueCat vs Raw StoreKit 2 vs Superwall
The three most common subscription stacks for indie iOS apps in 2026:
| Dimension | Raw StoreKit 2 | RevenueCat | Superwall |
|---|---|---|---|
| Cost | Free | Free up to 2.5K MTR | Free up to 1K MTR |
| Server validation | DIY (40 to 80 hours) | Built in | Built in |
| Cross device sync | Same Apple ID only | Full sync | Full sync |
| Analytics dashboard | App Store Connect only | Full MRR plus cohorts | Strong conversion focus |
| Paywall A/B testing | Build it yourself | Paywalls v2 (templates) | Best in class |
| Webhooks | App Store Server Notifications V2 | 11 events, retry logic | Conversion events focus |
| Cross platform | Apple platforms only | iOS, Android, Web, RN, Flutter | iOS plus Android |
| Time to ship | 2 to 4 weeks | 2 to 4 hours | 4 to 8 hours |
| Lock in | None | Light (entitlement model) | Medium (paywalls server side) |
The three way decision tree:
- Pick raw StoreKit 2 if you have strict compliance, you only target Apple platforms, you have an experienced backend team, and you want zero per month fees at scale.
- Pick RevenueCat if you are an indie developer or small team, you want to ship subscriptions in one afternoon, and you value the analytics dashboard. This is the right default for 80 to 90 percent of iOS apps.
- Pick Superwall if paywall A/B testing is your top growth lever, you can pay 90 to 200 dollars per month minimum, and you want the most flexible no code paywall editor available.
- Pick RevenueCat plus Superwall together if you want RevenueCat for entitlements and analytics plus Superwall for the paywall surface. This is fully supported and how some 50K dollar plus MRR apps run today.
For the deeper comparison, see StoreKit 2 vs RevenueCat 2026 and RevenueCat vs Superwall.
RevenueCat Across Platforms
The same RevenueCat dashboard, the same offerings, and the same entitlement model work across iOS, Android, Flutter, React Native, Expo, Unity, Capacitor, and Web (via Stripe). For SwiftUI plus Android Kotlin teams, you ship one dashboard and consume it from both apps with shared product IDs.
A few things to keep in mind for cross platform:
- Apple and Google product IDs differ. RevenueCat lets you map both to a single product object so your code reads
package.identifiernot platform specific IDs. - App User IDs need to match. Use the same anonymous or authenticated user ID across iOS and Android so entitlements sync after sign in.
- Promo codes are platform specific. iOS uses Apple offer codes, Android uses Google promo codes. RevenueCat surfaces the right one per platform automatically.
- Web subscriptions via Stripe integrate through RevenueCat Billing (formerly Web Billing). Useful for apps that also sell via web for tax reasons.
Common RevenueCat Errors and How to Fix Them
The four errors you will hit during integration:
- "There is a problem with the App Store: configuration error". Your products are not yet approved in App Store Connect, you forgot to fill out tax and banking, or the bundle ID does not match between Xcode and App Store Connect. Check all three.
- Empty offerings on app launch. Your products exist in App Store Connect but you have not created an offering in the RevenueCat dashboard, or the products are not attached to the offering. Re check the dashboard.
- Sandbox purchases not appearing in CustomerInfo. You are running on a real device but logged out of the sandbox account, or you are running on Simulator without a StoreKit configuration file. Use the
.storekitfile for fastest feedback. - Webhook never fires. Your URL must be HTTPS with a valid certificate, the dashboard secret must match the
Authorizationheader your function checks, and the URL must respond with a 200 OK within 30 seconds. Test with the dashboard "Send Test Event" button.
The Swift Kit Ships RevenueCat Pre Wired
If you do not want to wire any of this yourself, The Swift Kit ships RevenueCat fully integrated with the patterns above. The SubscriptionStore observable, paywall component, restore button, free trial detection, and graceful fallback to demo offerings (when the API key is empty) are already in place.
Run the interactive setup CLI, paste your RevenueCat public key, and the entire subscription flow works on first launch. The 100 plus hours of integration work compresses to about 5 minutes of configuration. Lifetime updates as the SDK evolves.
Frequently Asked Questions
Is RevenueCat worth it for a small indie app?
Yes for almost every indie app under 10,000 USD MTR. The free tier covers 0 to 2,500 dollars revenue with full features. The 120 dollars per month Grow plan kicks in at 2,500 dollars MTR but the analytics, paywall A/B testing, and webhook infrastructure pay for themselves quickly. The only case where raw StoreKit 2 wins economically is at very large scale with a dedicated backend team.
Can I migrate from RevenueCat to native StoreKit 2 later?
Yes. Your products live in App Store Connect, not in RevenueCat. The migration cost is rebuilding the analytics, server validation, paywall logic, and cross device sync that RevenueCat handled. Most teams do not migrate because the per month fee stays cheaper than building and maintaining the equivalent infrastructure.
Does RevenueCat support visionOS and watchOS?
Yes. RevenueCat ships full support for iOS, iPadOS, macOS, watchOS, tvOS, and visionOS as part of the standard purchases-ios package. Universal Purchase pricing applies automatically when configured in App Store Connect. The paywall components on visionOS render with the standard window styling and respect ornaments and toolbars.
How do I test RevenueCat without TestFlight?
Use a StoreKit Configuration File (.storekit) in Xcode. Add your products with IDs and prices, set the scheme run options to use the configuration, and run on Simulator. Purchases happen instantly without sandbox accounts. RevenueCat detects the local configuration and routes purchases through it. The dashboard also has a Sandbox tab so you can verify the test transactions hit the dashboard.
What is the difference between an entitlement and a product in RevenueCat?
A product is the App Store Connect SKU (com.yourapp.pro.monthly). An entitlement is an abstract permission your app reads (pro). One entitlement can map to multiple products (monthly, annual, lifetime all grant pro). Your code reads customerInfo.entitlements.active["pro"] rather than checking specific product IDs. This decouples your business logic from your pricing structure.
Where to Go Next
You now have the full RevenueCat integration plan. Three next steps depending on where you are:
- Starting a new app: Skip the integration entirely and use The Swift Kit. RevenueCat ships pre wired with paywall, restore, free trial, and webhooks ready.
- Have an existing app, deciding stack: Read the deep comparison at StoreKit 2 vs RevenueCat 2026.
- Building your own paywall: Study the patterns in iOS Paywall Design Patterns 2026.
Have a question this guide did not cover? Email me directly through the contact link in the footer. I read every message.