# Flutter Documentation
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Changelog
Release notes for the Superwall Flutter SDK
# CHANGELOG
The changelog for `Superwall`. Also see the [releases](https://github.com/superwall/Superwall-Flutter/releases) on GitHub.
## 2.4.12
### Enhancements
* Updates Android SDK to 2.7.11 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.7.11).
* Updates iOS SDK to 4.14.2 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.14.2).
* Adds `preloadDeviceOverrides` option to `PaywallOptions` to override `shouldPreload` per device tier (Android only)
* Exports `PresentationResult`, `StoreTransaction`, `TransactionProduct` from the public package
* Fixes `PurchaserInfo.storeIdentifiers` leaking the internal Pigeon type instead of the public `StoreIdentifiers` sealed class
## 2.4.11
### Enhancements
* Updates iOS SDK to 4.14.1 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.14.1).
* Updates Android SDK to 2.7.8 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.7.8).
* Adds `appstackId` integration attribute for Appstack integration support
* Adds `testModeBehavior` option to `SuperwallOptions` to control when the SDK enters test mode (automatic, whenEnabledForUser, never, always)
* Adds `shouldObservePurchases` option to `SuperwallOptions` to observe purchases made outside of Superwall
* Adds `shouldBypassAppTransactionCheck` option to `SuperwallOptions` (iOS only)
* Adds `maxConfigRetryCount` option to `SuperwallOptions` (iOS only)
* Adds `useMockReviews` option to `SuperwallOptions` (Android only)
## 2.4.10
## Updates
* Updates pigeon to 26.1.5 to support analyzer 9.+.+ versions
## 2.4.9
## Enhancements
* Updates iOS SDK to 4.13.0 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.13.0).
* Updates Android SDK to 2.7.1 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.7.1).
## 2.4.8
## Enhancements
* Adds `onCustomCallback` method to `PaywallPresentationHandler` that allows invoking custom callbacks and passing data from and to the paywall
* Updates iOS SDK to 4.12.10 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.12.10).
* Updates Android SDK to 2.7.0 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.7.0).
### Fixes
* Fixes an issue where `IdentityOptions` passed to `identify()` were not being sent to the native SDK due to a variable shadowing bug.
## 2.4.7
### Enhancements
* Updates iOS SDK to 4.12.7 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.12.7).
* Updates Android SDK to 2.6.8 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.6.8).
## 2.4.6
## Enhancements
* Adds `isActive` convenience property on the `SubscriptionStatus` class
* Adds support for `SuperwallOptions.paywalls.onBackPressed` callback to react to back button presses on Android
## 2.4.5
### Enhancements
* Updates iOS SDK to 4.10.6 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.10.6).
* Updates Android SDK to 2.6.5 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.6.5).
## 2.4.4
## Enhancements
* Adds `Superwall.shared.consume(purchaseToken)` method to allow consuming inapp purchases
* Updates Android SDK to 2.6.2 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.6.2).
## 2.4.3
### Enhancements
* Updates Android SDK to 2.6.1 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.6.1).
## 2.4.2
### Enhancements
* Exposes `Superwall.shared.overrideProductsByName` variable.
## 2.4.1
### Enhancements
* Updates iOS SDK to 4.7.0 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.7.0).
* Updates Android SDK to 2.5.1 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.5.1). Note: This contains an update to Play Billing 8.
* Adds `overrideProductsByName` to `PaywallOptions` - allows globally overriding products on any paywall by product name.
* Adds `shouldShowWebPurchaseConfirmationAlert` to `PaywallOptions` - shows localized alert confirming successful web checkout purchases (iOS only).
### Fixes
* Fixes missing paywall info
## 2.4.0
## Enhancements
* Updates Android SDK to 2.3.1 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.3.1).
## 2.3.5
### Fixes
* Adds `params` mapping back to `EventInfo` for easier forwarding to analytics
## 2.3.4
### Enhancements
* Updates iOS SDK to 4.5.2 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.5.2).
* Updates Android SDK to 2.2.3 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.2.3).
### Fixes
* Fixes issue where `shouldShowWebRestorationAlert` wasn't getting through to the iOS `PaywallOptions`.
## 2.3.3
### Enhancements
* Exposes `shouldShowWebRestorationAlert` in `PaywallOptions`.
## 2.3.2
### Enhancements
* Expose `getPresentationResult` method
* Updates Android SDK to 2.2.2 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.2.2).
## 2.3.1
### Enhancements
* Updates Android SDK to 2.1.2 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.1.2).
### Fixes
* Fixes issue with `registerPlacement` blocking
## 2.3.0
### Enhancements
* Exposes `ConfirmedAssignments` and `StoreProduct` to `superwallkit_flutter`.
* Adds `Superwall.shared.getDeviceAttributes()`, which returns the device attributes that are used when evaluating audience filters.
* Updates iOS SDK to 4.4.1 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.4.1).
* Updates Android SDK to 2.1.1 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.1.1).
### Fixes
* Fixes issue where `SuperwallEventInfo` didn't contain all expected variables.
* Fixes issue when building for app that deploys with a minimum version of iOS 13.
## 2.2.2
### Fixes
* Fixes issue with paywall presentation handlers not working.
## 2.2.1
### Fixes
* Fixes issue with iOS side of project not building.
## 2.2.0
### Fixes
* Fixes missing Event info
### Enhancements
* Updates iOS SDK to 4.3.11 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.3.11)
* Updates Android SDK to 2.1.0 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.1.0)
## 2.2.0-beta.2
### Enhancements
* Updates iOS SDK to 4.3.8 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.3.8)
## 2.2.0-beta.1
### Enhancements
* Adds support for web checkout.
* Updates kotlin version of the underlying Android SDK to 2.0.21
* Updates iOS SDK to 4.3.5 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.3.5)
* Updates iOS SDK to 2.1.0-beta.2 [View iOS SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.1.0-beta.2)
## 2.1.0
### Enhancements
* Updates Android SDK to 2.0.8 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.0.8)
* Updates iOS SDK to 4.3.0 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.3.0)
## 2.1.0-beta.2
### Enhancements
* Adds `restorePurchases` function
* Updates Android SDK to 2.0.6 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.0.6)
* Updates iOS SDK to 4.2.0 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.2.0)
## 2.1.0-beta.1
### Enhancements
* Reworks the SDK's beneath the surface to rely on Pigeon codegen
* Improves stability
* Improved correctness of the SDK
### Note:
* This could include some potentially breaking changes. If you find some, please open an issue or reach out to the team.
## 2.0.8
### Enhancements
* Updates iOS SDK to 4.0.6 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.0.6)
* Updates Android SDK to 2.0.5 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.0.5)
## 2.0.7
### Fixes
* Fixes issue when accessing `presentedByPlacement*` properties from a custom placement
## 2.0.6
## Enhancements
* Adds a subscription stream example in the example app
## Fixes
* Fixes issue with second subscriber not receiving the `SubscriptionStatus` broadcast stream
* Fixes issue with `PurchasedPaywallResult` not properly deserialising in `onDismiss`
## 2.0.5
### Enhancements
* Update `SuperwallPlacement` to `SuperwallEvent`
* Update `PlacementType` to `EventType`
* Updates Android SDK to 2.0.3 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.0.3)
* Updates iOS SDK to 4.0.3 [View iOS SDK release notes](https://github.com/superwall/Superwall-iOS/releases/tag/4.0.3)
## 2.0.4
### Enhancements
* Updates onDismissHandler to now receive a PaywallResult also
* Updates Android SDK to 2.0.2 [View Android SDK release notes](https://github.com/superwall/Superwall-Android/releases/tag/2.0.2)
* `handleSuperwallPlacement(SuperwallPlacementInfo eventInfo)` has been replaced with `handleSuperwallEvent(SuperwallEventtInfo eventInfo)`
## 2.0.3
### Fixes
* Fixes BridgeInstance crashes
* Adds awaiting on shared instance of BridgeCreator to avoid crashes when plugin is yet to attach
## 2.0.2
* Updates iOS SDK to 1.5.4 [View iOS SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/4.0.1)
* Updates Android SDK to 2.0.1 [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/2.0.1)
## 2.0.1
* Upgrades iOS SDK to 4.0.0 [View iOS SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/4.0.0)
## 2.0.0
###
* Adds entitlements to subscriptions, allowing you to set them using:
* `await Superwall.shared.setSubscriptionStatus(status)` by passing in one of:
* `SubscriptionStatusActive(entitlements: entitlements))`
* Note: Passing in empty entitlements will set the subscription status to inactive
* `SubscriptionStatusInactive`
* `SubscriptionStatusUnknown`
* Subscribing to subscription status updates via stream `Superwall.shared.subscriptionStatus`
* `SuperwallBuilder` which will automatically update the state whenever subscription status changes
* `Superwall.shared.registerEvent` has now been replaced with `Superwall.shared.registerPlacement`
* `handleSuperwallEvent(SuperwallEventInfo eventInfo)` has been replaced with `handleSuperwallPlacement(SuperwallPlacementInfo placementInfo)`
* `PaywallPresentationHandler.onDismiss` now has 2 arguments, `PaywallInfo` and `PaywallResult`
## 2.0.0-alpha.1
* Adds entitlements to subscriptions, allowing you to set them using:
* `await Superwall.shared.setSubscriptionStatus(status)` by passing in one of:
* `SubscriptionStatusActive(entitlements: entitlements))`
* Note: Passing in empty entitlements will set the subscription status to inactive
* `SubscriptionStatusInactive`
* `SubscriptionStatusUnknown`
* Subscribing to subscription status updates via stream `Superwall.shared.subscriptionStatus`
* `SuperwallBuilder` which will automatically update the state whenever subscription status changes
* `Superwall.shared.registerEvent` has now been replaced with `Superwall.shared.registerPlacement`
* `handleSuperwallEvent(SuperwallEventInfo eventInfo)` has been replaced with `handleSuperwallPlacement(SuperwallPlacementInfo placementInfo)`
## 1.3.11
### Fixes
* Fixes BridgeInstance crashes
* Adds awaiting on shared instance of BridgeCreator to avoid crashes when plugin is yet to attach
## 1.3.10
### Fixes
* Fixes an issue on iOS where the plugin could get registered multiple times resulting in a `MissingPluginException`.
## 1.3.9
### Fixes
* Fixes a compatibility issue with `WorkManager` and other isolate-running libraries where the plugin would detach and cause a `Missing Activity` or `BridgeCreator` exception
### Enhancements
* Updates Android SDK to 1.5.4 [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.5.4)
## 1.3.8
### Fixes
* Removes unnecessary date comparison from PurchaseController example code.
## 1.3.7
### Enhancements
* Upgrades iOS SDK to 3.12.1 [View iOS SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/3.12.1)
## 1.3.6
### Enhancements
* Updates Android SDK to 1.5.1 [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.5.1)
## 1.3.5
### Enhancements
* Upgrades iOS SDK to 3.12.0 [View iOS SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/3.12.0)
* Updates Android SDK to 1.5.0 [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.5.0)
### Fixes
* Fixes issue with `PaywallInfoBridge` and other bridges throwing NPE when reattaching to activities from deep sleep.
## 1.3.4
### Enhancements
* Adds `setLocaleIdentifier(_:)` and `getLocaleIdentifier()`.
## 1.3.3
* Upgrades Android SDK to 1.3.1 [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.3.1)
* This fixes the issue when using Superwall with some SDK's would cause a crash (i.e. Smartlook SDK)
## 1.3.2
* Updates project linting and formatting to be consistent with the Flutter style
* Removes rogue logging statements by matching them with the provided Log level
* Adds `passIdentifiersToPlayStore` to `SuperwallOptions` which allows you to pass user identifiers to the Play Store purchases as account identifiers. This is useful for tracking user purchases in the Play Store console.
## 1.3.1
### Enhancements
* Upgrades Android SDK to 1.3.0 [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.3.0)
* Upgrades iOS SDK to 3.10.1 [View iOS SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/3.10.1)
* Adds `confirmAllAssignments` method to `Superwall` which confirms assignments for all placements and returns an array of all confirmed experiment assignments. Note that the assignments may be different when a placement is registered due to changes in user, placement, or device parameters used in audience filters.
## 1.3.0
### Enhancements
* Upgrades iOS SDK to 3.9.1. [View iOS SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/3.9.1)
* Upgrades Android SDK to 1.2.7 [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.2.7)
* Exposes the `SuperwallOption` `collectAdServicesAttribution` for iOS. When `true`, this collects the AdServices attribute token, which will be process by our backend. Adds `adServicesTokenRequest_start`, `adServicesTokenRequest_complete`, and `adServicesTokenRequest_fail`.
* Exposes `getConfigurationStatus()`, which replaces `getIsConfigured()`. This returns either `ConfigurationStatusPending`, `ConfigurationStatusConfigured`, or `ConfigurationStatusFailed`.
## 1.2.2
### Enhancements
* Upgrades Android SDK to 1.2.4 [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.2.4)
### Fixes
* Reapply the single bridge instance limit to fix issues with `setDelegate` where plugins with multiple isolates are used
* Fixes stateful bridges by applying new state when invoking create, resolving issues seen in #23
## 1.2.1
### Enhancements
* Upgrades Android SDK to 1.2.3 [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.2.3)
* Upgrades iOS SDK to 3.7.2. [View iOS SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/3.7.2)
## 1.2.0
### Enhancements
* Upgrades Android SDK to 1.2.0 [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.2.0)
### Fixes
* Updates `compileSDKVersion` to 34 fixing build issues on Android.
## 1.1.9
### Enhancements
* Upgrades Android SDK to 1.1.8. [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.1.8)
### Fixes
* Bumps minimum Kotlin version to 1.8.0 and Android Gradle Plugin to 8.1.0 to be able to be
compatible with the latest Android SDK. This was necessary for important bug fixes.
* SW-2868: Fixes transaction issues caused by hot restart.
## 1.1.8
### Enhancements
* Upgrades iOS SDK to 3.6.6. [View iOS SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/3.6.6)
* Upgrades Android SDK to 1.1.7. [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.1.7)
## 1.1.7
### Enhancements
* Upgrades iOS SDK to 3.6.5. [View iOS SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/3.6.5)
## 1.1.6
### Enhancements
* Upgrades Android SDK to 1.1.6. [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.1.6)
## 1.1.5
### Enhancements
* Upgrades Android SDK to 1.1.5. [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.1.5)
## 1.1.4
### Enhancements
* Upgrades Android SDK to 1.1.4. [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.1.4)
* Upgrades iOS SDK to 3.6.2. [View iOS SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/3.6.2)
* Adds `identityAlias` `SuperwallEvent`.
### Fixes
* Updates the UUID framework dependency to the latest version to prevent conflicts.
## 1.1.3
### Fixes
* Fixes crash that occurred if an Android foreground service was started and the app was relaunched from a cold start.
## 1.1.2
### Enhancements
* Upgrades Android SDK to 1.1.2. [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.1.2)
* Upgrades iOS SDK to 3.6.1. [View iOS SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/3.6.1)
## 1.1.1
### Fixes
* Fixes `LocalNotification` issue that was trying to initialise an `int` with a `double`.
## 1.1.0
### Enhancements
* Upgrades Android SDK to 1.1.1. [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.1.1)
* Upgrades iOS SDK to 3.6.0. [View iOS SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/3.6.0)
## 1.0.1
### Fixes
* Fixes issue with invalid imports due to a breaking change on Android.
## 1.0.0
### Enhancements
* Upgrades Android SDK to 1.0.0. [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.0.0)
* Upgrades iOS SDK to 3.5.0. [View iOS SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/3.5.0)
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Cohorting in 3rd Party Tools
To easily view Superwall cohorts in 3rd party tools, we recommend you set user attributes based on the experiments that users are included in. You can also use custom placements for creating analytics events for actions such as interacting with an element on a paywall.
:::android
```kotlin Kotlin
override fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) {
when(eventInfo.event) {
is SuperwallEvent.TriggerFire -> {
MyAnalyticsService.shared.setUserAttributes(
mapOf(
"sw_experiment_${eventInfo.params.get("experiment_id").toString()}" to true,
"sw_variant_${eventInfo.params.get("variant_id").toString()}" to true
)
)
}
else -> {}
}
}
```
:::
:::flutter
```dart Flutter
@override
void handleSuperwallEvent(SuperwallEventInfo eventInfo) async {
final experimentId = eventInfo.params?['experiment_id'];
final variantId = eventInfo.params?['variant_id'];
switch (eventInfo.event.type) {
case EventType.triggerFire:
MyAnalyticsService.shared.setUserAttributes({
"sw_experiment_$experimentId": true,
"sw_variant_$variantId": true
});
break;
default:
break;
}
}
```
:::
:::expo
```typescript React Native
handleSuperwallEvent(eventInfo: SuperwallEventInfo) {
const experimentId = eventInfo.params?['experiment_id']
const variantId = eventInfo.params?['variant_id']
if (!experimentId || !variantId) {
return
}
switch (eventInfo.event.type) {
case EventType.triggerFire:
MyAnalyticsService.shared.setUserAttributes({
`sw_experiment_${experimentId}`: true,
`sw_variant_${variantId}`: true
});
break;
default:
break;
}
}
```
:::
Once you've set this up, you can easily ask for all users who have an attribute `sw_experiment_1234` and breakdown by both variants to see how users in a Superwall experiment behave in other areas of your app.
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Custom Paywall Analytics
Learn how to log events from paywalls, such as a button tap or product change, to forward to your analytics service.
You can create customized analytics tracking for any paywall event by using custom placements. With them, you can get callbacks for actions such as interacting with an element on a paywall sent to your [Superwall delegate](/docs/sdk/guides/using-superwall-delegate). This can be useful for tracking how users interact with your paywall and how that affects their behavior in other areas of your app.
For example, in the paywall below, perhaps you're interested in tracking when people switch the plan from "Standard" and "Pro":

You could create a custom placement [tap behavior](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements#tap-behaviors) which fires when a segment is tapped:

Then, you can listen for this placement and forward it to your analytics service:
```swift Swift
extension SuperwallService: SuperwallDelegate {
func handleSuperwallEvent(withInfo eventInfo: SuperwallEventInfo) {
switch eventInfo.event {
case let .customPlacement(name, params, paywallInfo):
// Prints out didTapPro or didTapStandard
print("\(name) - \(params) - \(paywallInfo)")
MyAnalyticsService.shared.send(event: name, params: params)
default:
print("Default event: \(eventInfo.event.description)")
}
}
}
```
For a walkthrough example, check out this [video on YouTube](https://youtu.be/4rM1rGRqDL0).
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# 3rd Party Analytics
### Hooking up Superwall events to 3rd party tools
SuperwallKit automatically tracks some internal events. You can [view the list of events here](/docs/sdk/guides/3rd-party-analytics/tracking-analytics). We encourage you to also track them in your own analytics by implementing the [Superwall delegate](/docs/sdk/guides/using-superwall-delegate). Using the `handleSuperwallEvent(withInfo:)` function, you can forward events to your analytics service:
:::flutter
```dart
@override
void handleSuperwallEvent(SuperwallEventInfo eventInfo) async {
print("handleSuperwallEvent: $eventInfo");
// Example usage...
switch (eventInfo.event.type) {
case EventType.appOpen:
print("appOpen event");
case EventType.deviceAttributes:
print("deviceAttributes event: ${eventInfo.event.deviceAttributes} ");
case EventType.paywallOpen:
final paywallInfo = eventInfo.event.paywallInfo;
print("paywallOpen event: ${paywallInfo} ");
if (paywallInfo != null) {
final identifier = await paywallInfo.identifier;
print("paywallInfo.identifier: ${identifier} ");
final productIds = await paywallInfo.productIds;
print("paywallInfo.productIds: ${productIds} ");
}
default:
break;
}
}
```
:::
> **Note:** You might also want to set user attribute to allow for
> [Cohorting in 3rd Party Tools](/docs/sdk/guides/3rd-party-analytics/cohorting-in-3rd-party-tools).
Alternatively, if you want typed versions of all these events with associated values, you can access them via `eventInfo.event`:
:::flutter
```dart
@override
void handleSuperwallEvent(SuperwallEventInfo eventInfo) async {
// Example usage...
switch (eventInfo.event.type) {
case PlacementType.appOpen:
print("appOpen event");
case PlacementType.deviceAttributes:
print("deviceAttributes event: ${eventInfo.event.deviceAttributes} ");
case PlacementType.paywallOpen:
final paywallInfo = eventInfo.event.paywallInfo;
print("paywallOpen event: ${paywallInfo} ");
if (paywallInfo != null) {
final identifier = await paywallInfo.identifier;
print("paywallInfo.identifier: ${identifier} ");
final productIds = await paywallInfo.productIds;
print("paywallInfo.productIds: ${productIds} ");
}
default:
break;
}
}
```
:::
> **Info:** Wanting to use events to see which product was purchased on a paywall? Check out this
> [doc](/docs/sdk/guides/advanced/viewing-purchased-products).
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Superwall Events
The SDK automatically tracks some events, which power the charts in the dashboard.
We encourage you to track them in your own analytics as described in [3rd Party Analytics](..).
The following Superwall events can be used as placements to present paywalls:
* `app_install`
* `app_launch`
* `deepLink_open`
* `session_start`
* `paywall_decline`
* `transaction_fail`
* `transaction_abandon`
* `survey_response`
For more info about how to use these, check out [how to add them using a Placement](/docs/dashboard/dashboard-campaigns/campaigns-placements#adding-a-placement).
The full list of events is as follows:
| **Event Name** | **Action** | **Parameters** |
| ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `adServicesTokenRequest_complete` | When the AdServices token request finishes. | `["token": String]` |
| `adServicesTokenRequest_fail` | When the AdServices token request fails. | `["error_message": String]` |
| `adServicesTokenRequest_start` | When the AdServices token request starts. | None |
| `app_close` | Anytime the app leaves the foreground. | Same as `app_install` |
| `app_install` | When the SDK is configured for the first time. | `["is_superwall": true, "app_session_id": String, "using_purchase_controller": Bool]` |
| `app_launch` | When the app is launched from a cold start. | Same as `app_install` |
| `app_open` | Anytime the app enters the foreground. | Same as `app_install` |
| `attribution_match` | When install attribution is resolved or fails to resolve. | `["provider": String, "matched": Bool, "source": String?, "confidence": String?, "match_score": Double?, "reason": String?]` |
| `config_attributes` | When the attributes affecting Superwall's configuration are set or changed. | The SDK configuration options + `["using_purchase_controller": Bool, "has_delegate": Bool]` |
| `config_fail` | When the Superwall configuration fails to be retrieved. | `["error_message": String]` |
| `config_refresh` | When the Superwall configuration is refreshed. | `["config_build_id": String, "retry_count": Int, "cache_status": String, "fetch_duration": Double]` |
| `confirm_all_assignments` | When all experiment assignments are confirmed. | None |
| `custom_placement` | When the user taps on an element in the paywall that has a `custom_placement` action. | `["name": String, "params": [String: Any], "paywallInfo": PaywallInfo]` |
| `customerInfo_didChange` | When the user's customer info changes. | `["from": String, "to": String]` (JSON snapshots of the entitlements before and after the change) |
| [`deepLink_open`](/docs/dashboard/dashboard-campaigns/campaigns-standard-placements#using-the-deeplink-open-event) | When a user opens the app via a deep link. | `["url": String, "path": String", "pathExtension": String, "lastPathComponent": String, "host": String, "query": String, "fragment": String]` + any query parameters in the deep link URL |
| `device_attributes` | When device attributes are sent to the backend every session. | Includes `app_session_id`, `app_version`, `os_version`, `device_model`, `device_locale`, and various hardware/software details. |
| `enrichment_complete` | When the enrichment request completes. | `user_` and `device_` entries for each returned enrichment value |
| `enrichment_fail` | When the enrichment request fails. | None |
| `enrichment_start` | When the enrichment request starts. | None |
| `first_seen` | When the user is first seen in the app, regardless of login status. | Same as `app_install` |
| `freeTrial_start` | When a user completes a transaction for a subscription product with an introductory offer. | Same as `subscription_start` |
| `identity_alias` | When the user's identity aliases after calling `identify`. | None |
| `integration_attributes` | When the integration attributes are set. | The provided integration attributes |
| `networkDecoding_fail` | When a response from the network fails to decode. | `["request_url": String, "response": String]` |
| `nonRecurringProduct_purchase` | When the user purchases a non-recurring product. | Same as `subscription_start` |
| `paywall_close` | When a paywall is closed (either manually or after a transaction succeeds). | \[“paywall\_webview\_load\_complete\_time”: String?, “paywall\_url”: String, “paywall\_response\_load\_start\_time”: String?, “paywall\_products\_load\_fail\_time”: String?, “secondary\_product\_id”: String, “feature\_gating”: Int, “paywall\_response\_load\_complete\_time”: String?, “is\_free\_trial\_available”: Bool, “is\_superwall”: true, “presented\_by”: String, “paywall\_name”: String, “paywall\_response\_load\_duration”: String?, “paywall\_identifier”: String, “paywall\_webview\_load\_start\_time”: String?, “paywall\_webview\_load\_duration”: String?, “paywall\_products\_load\_complete\_time”: String?, “paywall\_products\_load\_duration”: String?, “paywall\_product\_ids”: String, “tertiary\_product\_id”: String, “paywall\_id”: String, “app\_session\_id”: String, “paywall\_products\_load\_start\_time”: String?, “primary\_product\_id”: String, “survey\_attached”: Bool, “survey\_presentation”: String?, “presentation\_id”: String?, “close\_reason”: String, “cache\_key”: String, “build\_id”: String, “is\_scroll\_enabled”: Bool, “intro\_offer\_eligibility”: String, “shimmerView\_load\_start\_time”: String, “shimmerView\_load\_complete\_time”: String] |
| [`paywall_decline`](/docs/dashboard/dashboard-campaigns/campaigns-standard-placements#using-the-paywall-decline-event) | When a user manually dismisses a paywall. | Same as `paywall_close` |
| `paywall_open` | When a paywall is opened. | Same as `paywall_close` |
| `paywall_page_view` | When a user views a page in a multi-page paywall (including the first page). | Same as `paywall_close` + `["page_node_id": String, "flow_position": Int, "page_name": String, "navigation_node_id": String, "navigation_type": String, "previous_page_node_id": String?, "previous_flow_position": Int?, "time_on_previous_page_ms": Int?]` |
| `paywallPresentationRequest` | When something happened during the paywall presentation, whether a success or failure. | `[“source_event_name”: String, “status”: String, “is_superwall”: true, “app_session_id”: String, “pipeline_type”: String, “status_reason”: String]` |
| `paywallPreload_complete` | When paywall preloading completes. | `["paywall_count": Int]` |
| `paywallPreload_start` | When paywall preloading starts. | `["paywall_count": Int]` |
| `paywallProductsLoad_complete` | When the request to load a paywall's products completes. | Same as `paywallResponseLoad_start` |
| `paywallProductsLoad_fail` | When the request to load a paywall's products fails. | Same as `paywallResponseLoad_start` |
| `paywallProductsLoad_missingProducts` | When some of the paywall's products are missing from the store. | Same as `paywallResponseLoad_start` |
| `paywallProductsLoad_retry` | When the request to load a paywall's products fails and is being retried. | `["triggeredPlacementName": String?, "paywallInfo": PaywallInfo, "attempt": Int]` |
| `paywallProductsLoad_start` | When the request to load a paywall's products starts. | Same as `paywallResponseLoad_start` |
| `paywallResourceLoad_fail` | When a resource on the paywall's webpage fails to load (Android only). | `["url": String, "error": String]` |
| `paywallResponseLoad_complete` | When a paywall request to Superwall's servers completes. | Same as `paywallResponseLoad_start` |
| `paywallResponseLoad_fail` | When a paywall request to Superwall's servers fails. | Same as `paywallResponseLoad_start` |
| `paywallResponseLoad_notFound` | When a paywall request returns a 404 error. | Same as `paywallResponseLoad_start` |
| `paywallResponseLoad_start` | When a paywall request to Superwall's servers has started. | Same as `app_install` + `["is_triggered_from_event": Bool]` |
| `paywallWebviewLoad_complete` | When a paywall's webpage completes loading. | Same as `paywall_close` |
| `paywallWebviewLoad_fail` | When a paywall's webpage fails to load. | Same as `paywall_close` |
| `paywallWebviewLoad_fallback` | When a paywall's webpage fails and loads a fallback version. | Same as `paywall_close` |
| `paywallWebviewLoad_start` | When a paywall's webpage begins to load. | Same as `paywall_close` |
| `paywallWebviewLoad_timeout` | When the loading of a paywall's webpage times out. | Same as `paywall_close` |
| `paywallWebviewLoad_processTerminated` | When the paywall's web view content process terminates. | Same as `paywall_close` |
| `permission_denied` | When a permission is denied after being requested from a paywall. | `["permission_name": String, "paywall_identifier": String]` |
| `permission_granted` | When a permission is granted after being requested from a paywall. | `["permission_name": String, "paywall_identifier": String]` |
| `permission_requested` | When a permission is requested from a paywall. | `["permission_name": String, "paywall_identifier": String]` |
| `redemption_complete` | When the redemption of a code completes. | `["type": String, "code": String?]` |
| `redemption_fail` | When the redemption of a code fails. | `["type": String, "code": String?]` |
| `redemption_start` | When the redemption of a code is initiated. | `["type": String, "code": String?]` |
| `reset` | When `Superwall.reset()` is called. | None |
| `restore_complete` | When a restore completes successfully. | Same as `paywall_close` |
| `restore_fail` | When a restore fails. | Same as `paywall_close` + `["error_message": String]` |
| `restore_start` | When a restore is initiated. | Same as `paywall_close` |
| `review_requested` | When a review is requested from the user. | `["count": Int, "type": String]` |
| `session_start` | When the app is opened after at least 60 minutes since last `app_close`. | Same as `app_install` |
| `shimmerView_complete` | When the shimmer view stops showing. | `["paywall_id": String, "visible_duration": Double]` |
| `shimmerView_start` | When the shimmer view starts showing. | `["paywall_id": String]` |
| `stripeCheckout_complete` | When a Stripe checkout session completes. | Same as `paywall_close` + `["store": "STRIPE", "product_identifier": String, "is_triggered_from_event": Bool]` |
| `stripeCheckout_fail` | When a Stripe checkout session fails. | Same as `paywall_close` + `["store": "STRIPE", "product_identifier": String, "is_triggered_from_event": Bool]` |
| `stripeCheckout_start` | When a Stripe checkout session starts. | Same as `paywall_close` + `["store": "STRIPE", "product_identifier": String, "is_triggered_from_event": Bool]` |
| `stripeCheckout_submit` | When a Stripe checkout form is submitted. | Same as `paywall_close` + `["store": "STRIPE", "product_identifier": String, "is_triggered_from_event": Bool]` |
| `subscription_start` | When a user completes a transaction for a subscription product without an introductory offer. | \[“product\_period\_days”: String, “product\_price”: String, “presentation\_source\_type”: String?, “paywall\_response\_load\_complete\_time”: String?, “product\_language\_code”: String, “product\_trial\_period\_monthly\_price”: String, “paywall\_products\_load\_duration”: String?, “product\_currency\_symbol”: String, “is\_superwall”: true, “app\_session\_id”: String, “product\_period\_months”: String, “presented\_by\_event\_id”: String?, “product\_id”: String, “trigger\_session\_id”: String, “paywall\_webview\_load\_complete\_time”: String?, “paywall\_response\_load\_start\_time”: String?, “product\_raw\_trial\_period\_price”: String, “feature\_gating”: Int, “paywall\_id”: String, “product\_trial\_period\_daily\_price”: String, “product\_period\_years”: String, “presented\_by”: String, “product\_period”: String, “paywall\_url”: String, “paywall\_name”: String, “paywall\_identifier”: String, “paywall\_products\_load\_start\_time”: String?, “product\_trial\_period\_months”: String, “product\_currency\_code”: String, “product\_period\_weeks”: String, “product\_periodly”: String, “product\_trial\_period\_text”: String, “paywall\_webview\_load\_start\_time”: String?, “paywall\_products\_load\_complete\_time”: String?, “primary\_product\_id”: String, “product\_trial\_period\_yearly\_price”: String, “paywalljs\_version”: String?, “product\_trial\_period\_years”: String, “tertiary\_product\_id”: String, “paywall\_products\_load\_fail\_time”: String?, “product\_trial\_period\_end\_date”: String, “product\_weekly\_price”: String, “variant\_id”: String, “presented\_by\_event\_timestamp”: String?, “paywall\_response\_load\_duration”: String?, “secondary\_product\_id”: String, “product\_trial\_period\_days”: String, “product\_monthly\_price”: String, “paywall\_product\_ids”: String, “product\_locale”: String, “product\_daily\_price”: String, “product\_raw\_price”: String, “product\_yearly\_price”: String, “product\_trial\_period\_price”: String, “product\_localized\_period”: String, “product\_identifier”: String, “experiment\_id”: String, “is\_free\_trial\_available”: Bool, “product\_trial\_period\_weeks”: String, “paywall\_webview\_load\_duration”: String?, “product\_period\_alt”: String, “product\_trial\_period\_weekly\_price”: String, “presented\_by\_event\_name”: String?] |
| `subscriptionStatus_didChange` | When a user's subscription status changes. | `["is_superwall": true, "app_session_id": String, "subscription_status": String]` |
| `survey_close` | When the user chooses to close a survey instead of responding. | None |
| [`survey_response`](/docs/dashboard/dashboard-campaigns/campaigns-standard-placements#using-the-survey-response-event) | When a user responds to a paywall survey. | `["survey_selected_option_title": String, "survey_custom_response": String, "survey_id": String, "survey_assignment_key": String, "survey_selected_option_id": String]` |
| `testModeModal_open` | When the test mode modal is opened. | None |
| `testModeModal_close` | When the test mode modal is closed. | `["free_trial_override": String]` + `entitlement__state` and `entitlement__offer_type` for each entitlement |
| `touches_began` | When the user touches the app's UIWindow for the first time (if tracked by a campaign). | Same as `app_install` |
| `transaction_abandon` | When the user cancels a transaction. | Same as `subscription_start` |
| `transaction_complete` | When the user completes checkout and any product is purchased. | Same as subscription\_start + \[“web\_order\_line\_item\_id”: String, “app\_bundle\_id”: String, “config\_request\_id”: String, “state”: String, “subscription\_group\_id”: String, “is\_upgraded”: String, “expiration\_date”: String, “trigger\_session\_id”: String, “original\_transaction\_identifier”: String, “id”: String, “transaction\_date”: String, “is\_superwall”: true, “store\_transaction\_id”: String, “original\_transaction\_date”: String, “app\_session\_id”: String] |
| `transaction_fail` | When the payment sheet fails to complete a transaction (ignores user cancellation). | Same as `subscription_start` + `["message": String]` |
| `transaction_restore` | When the user successfully restores their purchases. | Same as `subscription_start` |
| `transaction_start` | When the payment sheet is displayed to the user. | Same as `subscription_start` |
| `transaction_timeout` | When the transaction takes longer than 5 seconds to display the payment sheet. | `["paywallInfo": PaywallInfo]` |
| `trigger_fire` | When a registered placement triggers a paywall. | `[“trigger_name”: String, “trigger_session_id”: String, “variant_id”: String?, “experiment_id”: String?, “paywall_identifier”: String?, “result”: String, “unmatched_rule_”: “”]. unmatched_rule_ indicates why a rule (with a specfiic experiment id) didn’t match. It will only exist if the result is no_rule_match. Its outcome will either be OCCURRENCE, referring to the limit applied to a rule, or EXPRESSION.` |
| `user_attributes` | When the user attributes are set. | `[“aliasId”: String, “seed”: Int, “app_session_id”: String, “applicationInstalledAt”: String, “is_superwall”: true, “application_installed_at”: String] + provided attributes` |
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Advanced Purchasing
If you need fine-grain control over the purchasing pipeline, use a purchase controller to manually handle purchases and subscription status.
> **Warning:** Using a `PurchaseController` is only recommended for **advanced** use cases. By default, Superwall handles all
> subscription-related logic and purchasing operations for you out of the box.
By default, Superwall handles basic subscription-related logic for you:
1. **Purchasing**: When the user initiates a checkout on a paywall.
2. **Restoring**: When the user restores previously purchased products.
3. **Subscription Status**: When the user's subscription status changes to active or expired (by checking the local receipt).
However, if you want more control, you can pass in a `PurchaseController` when configuring the SDK via `configure(apiKey:purchaseController:options:)` and manually set `Superwall.shared.subscriptionStatus` to take over this responsibility.
On iOS, starting in `4.15.0`, a `PurchaseController` is also how you handle custom products attached to Superwall paywalls. Those products are not purchased with StoreKit, so your controller must route them through your own billing system. See [Custom Store Products](/docs/ios/guides/custom-store-products) for the full iOS setup.
### Step 1: Creating a `PurchaseController`
A `PurchaseController` handles purchasing and restoring via protocol methods that you implement.
:::flutter
```dart Flutter
// MyPurchaseController.dart
class MyPurchaseController extends PurchaseController {
// 1
@override
Future purchaseFromAppStore(String productId) async {
// TODO
// ----
// Purchase via StoreKit, RevenueCat, Qonversion or however
// you like and return a valid PurchaseResult
return PurchaseResult.purchased;
}
@override
Future purchaseFromGooglePlay(
String productId,
String? basePlanId,
String? offerId
) async {
// TODO
// ----
// Purchase via Google Billing, RevenueCat, Qonversion or however
// you like and return a valid PurchaseResult
return PurchaseResult.purchased;
}
// 2
@override
Future restorePurchases() async {
// TODO
// ----
// Restore purchases and return true if successful.
return RestorationResult.restored;
}
}
```
:::
Here’s what each method is responsible for:
1. Purchasing a given product. In here, enter your code that you use to purchase a product. Then, return the result of the purchase as a `PurchaseResult`. For Flutter, this is separated into purchasing from the App Store and Google Play. This is an enum that contains the following cases, all of which must be handled:
1. `.cancelled`: The purchase was cancelled.
2. `.purchased`: The product was purchased.
3. `.pending`: The purchase is pending/deferred and requires action from the developer.
4. `.failed(Error)`: The purchase failed for a reason other than the user cancelling or the payment pending.
2. Restoring purchases. Here, you restore purchases and return a `RestorationResult` indicating whether the restoration was successful or not. If it was, return `.restore`, or `failed` along with the error reason.
### Step 2: Configuring the SDK With Your `PurchaseController`
Pass your purchase controller to the `configure(apiKey:purchaseController:options:)` method:
:::flutter
```dart Flutter
// main.dart
void initState() {
// Determine Superwall API Key for platform
String apiKey = Platform.isIOS ? "MY_IOS_API_KEY" : "MY_ANDROID_API_KEY";
// Create the purchase controller
MyPurchaseController purchaseController = MyPurchaseController();
Superwall.configure(apiKey, purchaseController);
}
```
:::
### Step 3: Keeping `subscriptionStatus` Up-To-Date
You **must** set `Superwall.shared.subscriptionStatus` every time the user's subscription status changes, otherwise the SDK won't know who to show a paywall to. This is an enum that has three possible cases:
1. **`.unknown`**: This is the default value. In this state, paywalls will not show and their presentation will be ***automatically delayed*** until `subscriptionStatus` changes to a different value.
2. **`.active(let entitlements)`**: Indicates that the user has an active entitlement. Paywalls will not show in this state unless you remotely set the paywall to ignore subscription status. A user can have one or more active entitlement.
3. **`.inactive`**: Indicates that the user doesn't have an active entitlement. Paywalls can show in this state.
Here's how you might do this:
:::flutter
```dart Flutter
// When a subscription is purchased, restored, validated, expired, etc...
myService.addSubscriptionStatusListener((subscriptionInfo) {
var entitlements = subscriptionInfo.entitlements.active.keys
.map((id) => Entitlement(id: id))
.toSet();
var hasActiveSubscription = subscriptionInfo.isActive;
if (hasActiveSubscription) {
Superwall.shared.setSubscriptionStatus(SubscriptionStatusActive(entitlements: entitlements));
} else {
Superwall.shared.setSubscriptionStatus(SubscriptionStatusInactive());
}
});
```
:::
> **Note:** `subscriptionStatus` is cached between app launches
### Listening for subscription status changes
If you need a simple way to observe when a user's subscription status changes, on iOS you can use the `Publisher` for it. Here's an example:
:::flutter
```dart Flutter
Superwall.shared.subscriptionStatus.listen((status) {
// React to changes
}
//Or use SuperwallBuilder widget which triggers the builder closure when subscription status changes
SuperwallBuilder(
builder: (context, status) => Center(
child: Text('Subscription Status: ${status}'),
)
)
```
:::
You can do similar tasks with the `SuperwallDelegate`, such as [viewing which product was purchased from a paywall](/docs/sdk/guides/3rd-party-analytics#using-events-to-see-purchased-products).
### Product Overrides
> **Note:** Product overrides allow you to dynamically substitute products on paywalls without modifying the paywall design in the Superwall dashboard.
When using a `PurchaseController`, you may want to override specific products shown on your paywalls. This is useful for:
* A/B testing different subscription tiers
* Showing region-specific products
* Dynamically changing products based on user segments
* Testing promotional pricing without modifying paywalls
**How Product Overrides Work:**
1. Product names (e.g., "primary", "secondary") must match exactly as defined in the Superwall dashboard's Paywall Editor
2. The SDK substitutes the original product IDs with your override IDs before fetching from the App Store
3. The paywall maintains its visual design while showing the substituted products
4. Your `PurchaseController` will receive the overridden products when `purchase(product:)` is called
> **Warning:** Product overrides only affect the products shown on paywalls. They don't change your subscription logic or entitlement validation.
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Custom Paywall Actions
For example, adding a custom action called `help_center` to a button in your paywall gives you the opportunity to present a help center whenever that button is pressed. To set this up, implement `handleCustomPaywallAction(withName:)` in your `SuperwallDelegate`:
:::flutter
```dart
@override
void handleCustomPaywallAction(String name) {
if (name == "help_center") {
HelpCenterManager.present();
}
}
```
:::
> **Note:** Remember to set `Superwall.shared.delegate`! For implementation details, see the [Superwall Delegate](/docs/sdk/guides/using-superwall-delegate) guide.
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Game Controller Support
:::android
First set the `SuperwallOption` `isGameControllerEnabled` to `true`:
```kotlin
Superwall.instance.options.isGameControllerEnabled = true
```
Then Superwall will automatically listen for gamepad events and forward them to your paywall!
:::
:::flutter
First set the `SuperwallOption` `isGameControllerEnabled` to `true`:
```dart
Superwall.instance.options.isGameControllerEnabled = true
```
Then Superwall will automatically listen for gamepad events and forward them to your paywall!
:::
:::expo
First set the `SuperwallOption` `isGameControllerEnabled` to `true`:
```typescript
Superwall.instance.options.isGameControllerEnabled = true
```
Then Superwall will automatically listen for gamepad events and forward them to your paywall!
:::
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Observer Mode
If you wish to make purchases outside of Superwall's SDK and paywalls, you can use **observer mode** to report purchases that will appear in the Superwall dashboard, such as transactions:

This is useful if you are using Superwall solely for revenue tracking, and you're making purchases using frameworks like StoreKit or Google Play Billing Library directly. Observer mode will also properly link user identifiers to transactions. To enable observer mode, set it using `SuperwallOptions` when configuring the SDK:
There are a few things to keep in mind when using observer mode:
1. On iOS, if you're using StoreKit 2, then Superwall solely reports transaction completions. If you're using StoreKit 1, then Superwall will report transaction starts, abandons, and completions.
2. When using observer mode, you can't make purchases using our SDK — such as `Superwall.shared.purchase(aProduct)`.
For more on setting up revenue tracking, check out this [doc](/docs/dashboard/dashboard-settings/overview-settings-revenue-tracking).
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Retrieving and Presenting a Paywall Yourself
If you want complete control over the paywall presentation process, you can use `getPaywall(forPlacement:params:paywallOverrides:delegate:)`. This returns the `UIViewController` subclass `PaywallViewController`, which you can then present however you like. Or, you can use a SwiftUI `View` via `PaywallView`. The following is code is how you'd mimic [register](/docs/sdk/quickstart/feature-gating):
## Tab
```swift Swift
final class MyViewController: UIViewController {
private func presentPaywall() async {
do {
// 1
let paywallVc = try await Superwall.shared.getPaywall(
forPlacement: "campaign_trigger",
delegate: self
)
self.present(paywallVc, animated: true)
} catch let skippedReason as PaywallSkippedReason {
// 2
switch skippedReason {
case .holdout,
.noAudienceMatch,
.placementNotFound:
break
}
} catch {
// 3
print(error)
}
}
private func launchFeature() {
// Insert code to launch a feature that's behind your paywall.
}
}
// 4
extension MyViewController: PaywallViewControllerDelegate {
func paywall(
_ paywall: PaywallViewController,
didFinishWith result: PaywallResult,
shouldDismiss: Bool
) {
if shouldDismiss {
paywall.dismiss(animated: true)
}
switch result {
case .purchased,
.restored:
launchFeature()
case .declined:
let closeReason = paywall.info.closeReason
let featureGating = paywall.info.featureGatingBehavior
if closeReason != .forNextPaywall && featureGating == .nonGated {
launchFeature()
}
}
}
}
```
## Tab
```swift Objective-C
@interface MyViewController : UIViewController
- (void)presentPaywall;
@end
@interface MyViewController ()
@end
@implementation MyViewController
- (void)presentPaywall {
// 1
[[Superwall sharedInstance] getPaywallForEvent:@"campaign_trigger" params:nil paywallOverrides:nil delegate:self completion:^(SWKGetPaywallResult * _Nonnull result) {
if (result.paywall != nil) {
[self presentViewController:result.paywall animated:YES completion:nil];
} else if (result.skippedReason != SWKPaywallSkippedReasonNone) {
switch (result.skippedReason) {
// 2
case SWKPaywallSkippedReasonHoldout:
case SWKPaywallSkippedReasonUserIsSubscribed:
case SWKPaywallSkippedReasonEventNotFound:
case SWKPaywallSkippedReasonNoRuleMatch:
case SWKPaywallSkippedReasonNone:
break;
};
} else if (result.error) {
// 3
NSLog(@"%@", result.error);
}
}];
}
-(void)launchFeature {
// Insert code to launch a feature that's behind your paywall.
}
// 4
- (void)paywall:(SWKPaywallViewController *)paywall didFinishWithResult:(enum SWKPaywallResult)result shouldDismiss:(BOOL)shouldDismiss {
if (shouldDismiss) {
[paywall dismissViewControllerAnimated:true completion:nil];
}
SWKPaywallCloseReason closeReason;
SWKFeatureGatingBehavior featureGating;
switch (result) {
case SWKPaywallResultPurchased:
case SWKPaywallResultRestored:
[self launchFeature];
break;
case SWKPaywallResultDeclined:
closeReason = paywall.info.closeReason;
featureGating = paywall.info.featureGatingBehavior;
if (closeReason != SWKPaywallCloseReasonForNextPaywall && featureGating == SWKFeatureGatingBehaviorNonGated) {
[self launchFeature];
}
break;
}
}
@end
```
## Tab
```swift SwiftUI
import SuperwallKit
struct MyAwesomeApp: App {
@State var store: AppStore = .init()
init() {
Superwall.configure(apiKey: "MyAPIKey")
}
var body: some Scene {
WindowGroup {
ContentView()
.fullScreenCover(isPresented: $store.showPaywall) {
// You can just use 'placement' at a minimum. The 'feature'
// Closure fires if they convert
PaywallView(placement: "a_placement", onSkippedView: { skip in
switch skip {
case .userIsSubscribed,
.holdout(_),
.noRuleMatch,
.eventNotFound:
MySkipView()
}
}, onErrorView: { error in
MyErrorView()
}, feature: {
// User is subscribed as a result of the paywall purchase
// Or they already were (which would happen in `onSkippedView`)
})
}
}
}
}
```
## Tab
```kotlin Kotlin
// This is an example of how to use `getPaywall` to use a composable`
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import com.superwall.sdk.Superwall
import com.superwall.sdk.paywall.presentation.get_paywall.getPaywall
import com.superwall.sdk.paywall.presentation.internal.request.PaywallOverrides
import com.superwall.sdk.paywall.vc.PaywallView
import com.superwall.sdk.paywall.vc.delegate.PaywallViewCallback
@Composable
fun PaywallComposable(
event: String,
params: Map? = null,
paywallOverrides: PaywallOverrides? = null,
callback: PaywallViewCallback,
errorComposable: @Composable ((Throwable) -> Unit) = { error: Throwable ->
// Default error composable
Text(text = "No paywall to display")
},
loadingComposable: @Composable (() -> Unit) = {
// Default loading composable
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier.align(Alignment.Center),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator()
}
}
}
) {
val viewState = remember { mutableStateOf(null) }
val errorState = remember { mutableStateOf(null) }
val context = LocalContext.current
LaunchedEffect(Unit) {
PaywallBuilder(event)
.params(params)
.overrides(paywallOverrides)
.delegate(delegate)
.activity(context as Activity)
.build()
.fold(onSuccess = {
viewState.value = it
}, onFailure = {
errorState.value = it
})
}
when {
viewState.value != null -> {
viewState.value?.let { viewToRender ->
DisposableEffect(viewToRender) {
viewToRender.onViewCreated()
onDispose {
viewToRender.beforeOnDestroy()
viewToRender.encapsulatingActivity = null
CoroutineScope(Dispatchers.Main).launch {
viewToRender.destroyed()
}
}
}
AndroidView(
factory = { context ->
viewToRender
}
)
}
}
errorState.value != null -> {
errorComposable(errorState.value!!)
}
else -> {
loadingComposable()
}
}
}
```
This does the following:
1. Gets the paywall view controller.
2. Handles the cases where the paywall was skipped.
3. Catches any presentation errors.
4. Implements the delegate. This is called when the user is finished with the paywall. First, it checks `shouldDismiss`. If this is true then is dismissed the paywall from view before launching any features. This may depend on the `result` depending on how you first presented your view. Then, it switches over the `result`. If the result is `purchased` or `restored` the feature can be launched. However, if the result is `declined`, it checks that the the `featureGating` property of `paywall.info` is `nonGated` and that the `closeReason` isn't `.forNextPaywall`.
### Best practices
1. **Make sure to prevent a paywall from being accessed after a purchase has occurred**.
If a user purchases from a paywall, it is your responsibility to make sure that the user can't access that paywall again. For example, if after successful purchase you decide to push a new view on to the navigation stack, you should make sure that the user can't go back to access the paywall.
2. **Make sure the paywall view controller deallocates before presenting it elsewhere**.
If you have a paywall view controller presented somewhere and you try to present
the same view controller elsewhere, you will get a crash. For example, you may
have a paywall in a tab bar controller, and then you also try to present it
modally. We plan on improving this, but currently it's your responsibility to
ensure this doesn't happen.
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Using the Presentation Handler
You can provide a `PaywallPresentationHandler` to `register`, whose functions provide status updates for a paywall:
* `onDismiss`: Called when the paywall is dismissed. Accepts a `PaywallInfo` object containing info about the dismissed paywall, and there is a `PaywallResult` informing you of any transaction.
* `onPresent`: Called when the paywall did present. Accepts a `PaywallInfo` object containing info about the presented paywall.
* `onError`: Called when an error occurred when trying to present a paywall. Accepts an `Error` indicating why the paywall could not present.
* `onSkip`: Called when a paywall is skipped. Accepts a `PaywallSkippedReason` enum indicating why the paywall was skipped.
* `onCustomCallback` *(Android 2.7.0+)*: Called when the paywall requests a custom callback. Accepts a `CustomCallback` containing the callback name and optional variables, and returns a `CustomCallbackResult` indicating success or failure with optional data to pass back to the paywall.
## Tab
```swift Swift
let handler = PaywallPresentationHandler()
handler.onDismiss { paywallInfo, result in
print("The paywall dismissed. PaywallInfo: \(paywallInfo). Result: \(result)")
}
handler.onPresent { paywallInfo in
print("The paywall presented. PaywallInfo:", paywallInfo)
}
handler.onError { error in
print("The paywall presentation failed with error \(error)")
}
handler.onSkip { reason in
switch reason {
case .holdout(let experiment):
print("Paywall not shown because user is in a holdout group in Experiment: \(experiment.id)")
case .noAudienceMatch:
print("Paywall not shown because user doesn't match any audiences.")
case .placementNotFound:
print("Paywall not shown because this placement isn't part of a campaign.")
}
}
Superwall.shared.register(placement: "campaign_trigger", handler: handler) {
// Feature launched
}
```
## Tab
```swift Objective-C
SWKPaywallPresentationHandler *handler = [[SWKPaywallPresentationHandler alloc] init];
[handler onDismiss:^(SWKPaywallInfo * _Nonnull paywallInfo,
enum SWKPaywallResult result,
SWKStoreProduct * _Nullable product) {
NSLog(@"The paywall presented. PaywallInfo: %@ - result: %ld", paywallInfo, (long)result);
}];
[handler onPresent:^(SWKPaywallInfo * _Nonnull paywallInfo) {
NSLog(@"The paywall presented. PaywallInfo: %@", paywallInfo);
}];
[handler onError:^(NSError * _Nonnull error) {
NSLog(@"The paywall presentation failed with error %@", error);
}];
[handler onSkip:^(enum SWKPaywallSkippedReason reason) {
switch (reason) {
case SWKPaywallSkippedReasonUserIsSubscribed:
NSLog(@"Paywall not shown because user is subscribed.");
break;
case SWKPaywallSkippedReasonHoldout:
NSLog(@"Paywall not shown because user is in a holdout group.");
break;
case SWKPaywallSkippedReasonNoAudienceMatch:
NSLog(@"Paywall not shown because user doesn't match any audiences.");
break;
case SWKPaywallSkippedReasonPlacementNotFound:
NSLog(@"Paywall not shown because this placement isn't part of a campaign.");
break;
case SWKPaywallSkippedReasonNone:
// The paywall wasn't skipped.
break;
}
}];
[[Superwall sharedInstance] registerWithPlacement:@"campaign_trigger" params:nil handler:handler feature:^{
// Feature launched.
}];
```
## Tab
```kotlin Kotlin
val handler = PaywallPresentationHandler()
handler.onDismiss { paywallInfo, result ->
println("The paywall dismissed. PaywallInfo: ${it}")
}
handler.onPresent {
println("The paywall presented. PaywallInfo: ${it}")
}
handler.onError {
println("The paywall errored. Error: ${it}")
}
handler.onSkip {
when (it) {
is PaywallSkippedReason.PlacementNotFound -> {
println("The paywall was skipped because the placement was not found.")
}
is PaywallSkippedReason.Holdout -> {
println("The paywall was skipped because the user is in a holdout group.")
}
is PaywallSkippedReason.NoAudienceMatch -> {
println("The paywall was skipped because no audience matched.")
}
}
}
Superwall.instance.register(placement = "campaign_trigger", handler = handler) {
// Feature launched
}
```
## Tab
```dart Flutter
PaywallPresentationHandler handler = PaywallPresentationHandler();
handler.onPresent((paywallInfo) async {
String name = await paywallInfo.name;
print("Handler (onPresent): $name");
});
handler.onDismiss((paywallInfo, paywallResult) async {
String name = await paywallInfo.name;
print("Handler (onDismiss): $name");
});
handler.onError((error) {
print("Handler (onError): ${error}");
});
handler.onSkip((skipReason) async {
String description = await skipReason.description;
if (skipReason is PaywallSkippedReasonHoldout) {
print("Handler (onSkip): $description");
final experiment = await skipReason.experiment;
final experimentId = await experiment.id;
print("Holdout with experiment: ${experimentId}");
} else if (skipReason is PaywallSkippedReasonNoAudienceMatch) {
print("Handler (onSkip): $description");
} else if (skipReason is PaywallSkippedReasonPlacementNotFound) {
print("Handler (onSkip): $description");
} else {
print("Handler (onSkip): Unknown skip reason");
}
});
Superwall.shared.registerPlacement("campaign_trigger", handler: handler, feature: () {
// Feature launched
});
```
## Tab
```typescript React Native
const handler = new PaywallPresentationHandler()
handler.onPresent((paywallInfo) => {
const name = paywallInfo.name
console.log(`Handler (onPresent): ${name}`)
})
handler.onDismiss((paywallInfo, paywallResult) => {
const name = paywallInfo.name
console.log(`Handler (onDismiss): ${name}`)
})
handler.onError((error) => {
console.log(`Handler (onError): ${error}`)
})
handler.onSkip((skipReason) => {
const description = skipReason.description
if (skipReason instanceof PaywallSkippedReasonHoldout) {
console.log(`Handler (onSkip): ${description}`)
const experiment = skipReason.experiment
const experimentId = experiment.id
console.log(`Holdout with experiment: ${experimentId}`)
} else if (skipReason instanceof PaywallSkippedReasonNoAudienceMatch) {
console.log(`Handler (onSkip): ${description}`)
} else if (skipReason instanceof PaywallSkippedReasonPlacementNotFound) {
console.log(`Handler (onSkip): ${description}`)
} else {
console.log(`Handler (onSkip): Unknown skip reason`)
}
})
Superwall.shared.register({
placement: 'campaign_trigger',
handler: handler,
feature: () => {
// Feature launched
}
});
```
> **Tip:** Wanting to see which product was just purchased from a paywall? Use `onDismiss` and the `result`
> parameter. Or, you can use the
> [SuperwallDelegate](/docs/sdk/guides/3rd-party-analytics#using-events-to-see-purchased-products).
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Viewing Purchased Products
When a paywall is presenting and a user converts, you can view the purchased products in several different ways.
### Use the `PaywallPresentationHandler`
Arguably the easiest of the options — simply pass in a presentation handler and check out the product within the `onDismiss` block.
## Tab
```swift Swift
let handler = PaywallPresentationHandler()
handler.onDismiss { _, result in
switch result {
case .declined:
print("No purchased occurred.")
case .purchased(let product):
print("Purchased \(product.productIdentifier)")
case .restored:
print("Restored purchases.")
}
}
Superwall.shared.register(placement: "caffeineLogged", handler: handler) {
logCaffeine()
}
```
## Tab
```swift Objective-C
SWKPaywallPresentationHandler *handler = [SWKPaywallPresentationHandler new];
[handler onDismiss:^(SWKPaywallInfo * _Nonnull info,
enum SWKPaywallResult result,
SWKStoreProduct * _Nullable product) {
switch (result) {
case SWKPaywallResultPurchased:
NSLog(@"Purchased %@", product.productIdentifier);
default:
NSLog(@"Unhandled event.");
}
}];
[[Superwall sharedInstance] registerWithPlacement:@"caffeineLogged"
params:@{}
handler:handler
feature:^{
[self logCaffeine];
}];
```
## Tab
```kotlin Android
val handler = PaywallPresentationHandler()
handler.onDismiss { _, paywallResult ->
when (paywallResult) {
is PaywallResult.Purchased -> {
// The user made a purchase!
val purchasedProductId = paywallResult.productId
println("User purchased product: $purchasedProductId")
// ... do something with the purchased product ID ...
}
is PaywallResult.Declined -> {
// The user declined to make a purchase.
println("User declined to make a purchase.")
// ... handle the declined case ...
}
is PaywallResult.Restored -> {
// The user restored a purchase.
println("User restored a purchase.")
// ... handle the restored case ...
}
}
}
Superwall.instance.register(placement = "caffeineLogged", handler = handler) {
logCaffeine()
}
```
## Tab
```dart Flutter
PaywallPresentationHandler handler = PaywallPresentationHandler();
handler.onDismiss((paywallInfo, paywallResult) async {
String name = await paywallInfo.name;
print("Handler (onDismiss): $name");
switch (paywallResult) {
case PurchasedPaywallResult(productId: var id):
// The user made a purchase!
print('User purchased product: $id');
// ... do something with the purchased product ID ...
break;
case DeclinedPaywallResult():
// The user declined to make a purchase.
print('User declined the paywall.');
// ... handle the declined case ...
break;
case RestoredPaywallResult():
// The user restored a purchase.
print('User restored a previous purchase.');
// ... handle the restored case ...
break;
}
});
Superwall.shared.registerPlacement(
"caffeineLogged", handler: handler, feature: () {
logCaffeine();
});
```
## Tab
```typescript React Native
import * as React from "react"
import Superwall from "../../src"
import { PaywallPresentationHandler, PaywallInfo } from "../../src"
import type { PaywallResult } from "../../src/public/PaywallResult"
const Home = () => {
const navigation = useNavigation()
const presentationHandler: PaywallPresentationHandler = {
onDismiss: (handler: (info: PaywallInfo, result: PaywallResult) => void) => {
handler = (info, result) => {
console.log("Paywall dismissed with info:", info, "and result:", result)
if (result.type === "purchased") {
console.log("Product purchased with ID:", result.productId)
}
}
},
onPresent: (handler: (info: PaywallInfo) => void) => {
handler = (info) => {
console.log("Paywall presented with info:", info)
// Add logic for when the paywall is presented
}
},
onError: (handler: (error: string) => void) => {
handler = (error) => {
console.error("Error presenting paywall:", error)
// Handle any errors that occur during presentation
}
},
onSkip: () => {
console.log("Paywall presentation skipped")
// Handle the case where the paywall presentation is skipped
},
}
const nonGated = () => {
Superwall.shared.register({ placement: "non_gated", handler: presentationHandler, feature: () => {
navigation.navigate("caffeineLogged", {
value: "Go for caffeine logging",
})
});
}
return // Your view code here
}
```
### Use `SuperwallDelegate`
Next, the [SuperwallDelegate](/docs/sdk/guides/using-superwall-delegate) offers up much more information, and can inform you of virtually any Superwall event that occurred:
## Tab
```swift Swift
class SWDelegate: SuperwallDelegate {
func handleSuperwallEvent(withInfo eventInfo: SuperwallEventInfo) {
switch eventInfo.event {
case .transactionComplete(_, let product, _, _):
print("Transaction complete: product: \(product.productIdentifier)")
case .subscriptionStart(let product, _):
print("Subscription start: product: \(product.productIdentifier)")
case .freeTrialStart(let product, _):
print("Free trial start: product: \(product.productIdentifier)")
case .transactionRestore(_, _):
print("Transaction restored")
case .nonRecurringProductPurchase(let product, _):
print("Consumable product purchased: \(product.id)")
default:
print("Unhandled event.")
}
}
}
@main
struct Caffeine_PalApp: App {
@State private var swDelegate: SWDelegate = .init()
init() {
Superwall.configure(apiKey: "my_api_key")
Superwall.shared.delegate = swDelegate
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
```
## Tab
```swift Objective-C
// SWDelegate.h...
#import
@import SuperwallKit;
NS_ASSUME_NONNULL_BEGIN
@interface SWDelegate : NSObject
@end
NS_ASSUME_NONNULL_END
// SWDelegate.m...
@implementation SWDelegate
- (void)handleSuperwallEventWithInfo:(SWKSuperwallEventInfo *)eventInfo {
switch(eventInfo.event) {
case SWKSuperwallEventTransactionComplete:
NSLog(@"Transaction complete: %@", eventInfo.params[@"primary_product_id"]);
}
}
// In AppDelegate.m...
#import "AppDelegate.h"
#import "SWDelegate.h"
@import SuperwallKit;
@interface AppDelegate ()
@property (strong, nonatomic) SWDelegate *delegate;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.delegate = [SWDelegate new];
[Superwall configureWithApiKey:@"my_api_key"];
[Superwall sharedInstance].delegate = self.delegate;
return YES;
}
```
## Tab
```kotlin Android
class SWDelegate : SuperwallDelegate {
override fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) {
when (eventInfo.event) {
is SuperwallPlacement.TransactionComplete -> {
val transaction = (eventInfo.event as SuperwallPlacement.TransactionComplete).transaction
val product = (eventInfo.event as SuperwallPlacement.TransactionComplete).product
val paywallInfo = (eventInfo.event as SuperwallPlacement.TransactionComplete).paywallInfo
println("Transaction Complete: $transaction, Product: $product, Paywall Info: $paywallInfo")
}
else -> {
// Handle other cases
}
}
}
}
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Superwall.configure(this, "my_api_key")
Superwall.instance.delegate = SWDelegate()
}
}
```
## Tab
```dart Flutter
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:superwallkit_flutter/superwallkit_flutter.dart';
class _MyAppState extends State implements SuperwallDelegate {
final logging = Logging();
@override
void initState() {
super.initState();
configureSuperwall(useRevenueCat);
}
Future configureSuperwall(bool useRevenueCat) async {
try {
final apiKey = Platform.isIOS
? 'ios_api_project_key'
: 'android_api_project_key';
final logging = Logging();
logging.level = LogLevel.warn;
logging.scopes = {LogScope.all};
final options = SuperwallOptions();
options.paywalls.shouldPreload = false;
options.logging = logging;
Superwall.configure(apiKey,
purchaseController: null,
options: options, completion: () {
logging.info('Executing Superwall configure completion block');
});
Superwall.shared.setDelegate(this);
} catch (e) {
// Handle any errors that occur during configuration
logging.error('Failed to configure Superwall:', e);
}
}
@override
Future handleSuperwallEvent(SuperwallEventInfo eventInfo) async {
switch (eventInfo.event.type) {
case PlacementType.transactionComplete:
final product = eventInfo.params?['product'];
logging.info('Transaction complete event received with product: $product');
// Add any additional logic you need to handle the transaction complete event
break;
// Handle other events if necessary
default:
logging.info('Unhandled event type: ${eventInfo.event.type}');
break;
}
}
}
```
## Tab
```typescript React Native
import {
PaywallInfo,
SubscriptionStatus,
SuperwallDelegate,
SuperwallPlacementInfo,
PlacementType,
} from '../../src';
export class MySuperwallDelegate extends SuperwallDelegate {
handleSuperwallPlacement(placementInfo: SuperwallPlacementInfo) {
console.log('Handling Superwall placement:', placementInfo);
switch (placementInfo.placement.type) {
case PlacementType.transactionComplete:
const product = placementInfo.params?.["product"];
if (product) {
console.log(`Product: ${product}`);
} else {
console.log("Product not found in params.");
}
break;
default:
break;
}
}
}
export default function App() {
const delegate = new MySuperwallDelegate();
React.useEffect(() => {
const setupSuperwall = async () => {
const apiKey =
Platform.OS === 'ios'
? 'ios_api_project_key'
: 'android_api_project_key';
Superwall.configure({
apiKey: apiKey,
});
Superwall.shared.setDelegate(delegate);
};
}
}
```
### Use a purchase controller
If you are controlling the purchasing pipeline yourself via a [purchase controller](/docs/sdk/guides/advanced-configuration), then naturally the purchased product is available:
## Tab
```swift Swift
final class MyPurchaseController: PurchaseController {
func purchase(product: StoreProduct) async -> PurchaseResult {
print("Kicking off purchase of \(product.productIdentifier)")
do {
let result = try await MyPurchaseLogic.purchase(product: product)
return .purchased // .cancelled, .pending, .failed(Error)
} catch {
return .failed(error)
}
}
// 2
func restorePurchases() async -> RestorationResult {
print("Restoring purchases")
return .restored // false
}
}
@main
struct Caffeine_PalApp: App {
private let pc: MyPurchaseController = .init()
init() {
Superwall.configure(apiKey: "my_api_key", purchaseController: pc)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
```
## Tab
```swift Objective-C
// In MyPurchaseController.h...
#import
@import SuperwallKit;
@import StoreKit;
NS_ASSUME_NONNULL_BEGIN
@interface MyPurchaseController : NSObject
+ (instancetype)sharedInstance;
@end
NS_ASSUME_NONNULL_END
// In MyPurchaseController.m...
#import "MyPurchaseController.h"
@implementation MyPurchaseController
+ (instancetype)sharedInstance
{
static MyPurchaseController *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [MyPurchaseController new];
});
return sharedInstance;
}
- (void)purchaseWithProduct:(SWKStoreProduct * _Nonnull)product
completion:(void (^ _Nonnull)(enum SWKPurchaseResult, NSError * _Nullable))completion {
NSLog(@"Kicking off purchase of %@", product.productIdentifier);
// Do purchase logic here
completion(SWKPurchaseResultPurchased, nil);
}
- (void)restorePurchasesWithCompletion:(void (^ _Nonnull)(enum SWKRestorationResult, NSError * _Nullable))completion {
// Do restore logic here
completion(SWKRestorationResultRestored, nil);
}
@end
// In AppDelegate.m...
#import "AppDelegate.h"
#import "MyPurchaseController.h"
@import SuperwallKit;
@interface AppDelegate ()
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
[Superwall configureWithApiKey:@"my_api_key"
purchaseController:[MyPurchaseController sharedInstance]
options:nil
completion:^{
}];
return YES;
}
```
## Tab
```kotlin Android
class MyPurchaseController(val context: Context): PurchaseController {
override suspend fun purchase(
activity: Activity,
product: StoreProduct,
basePlanId: String?,
offerId: String?
): PurchaseResult {
if (product.isCustomProduct) {
// Purchase custom products with your own billing system, then update
// Superwall.instance.setSubscriptionStatus(...).
return PurchaseResult.Failed("Custom product handling is not implemented")
}
val productDetails = product.rawStoreProduct?.underlyingProductDetails
?: return PurchaseResult.Failed("Missing Google Play product details")
println("Kicking off purchase of $basePlanId")
return PurchaseResult.Purchased()
}
override suspend fun restorePurchases(): RestorationResult {
TODO("Not yet implemented")
}
}
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
Superwall.configure(this, "my_api_key", purchaseController = MyPurchaseController(this))
}
}
```
## Tab
```dart Flutter
class MyPurchaseController extends PurchaseController {
// 1
@override
Future purchaseFromAppStore(String productId) async {
print('Attempting to purchase product with ID: $productId');
// Do purchase logic
return PurchaseResult.purchased;
}
@override
Future purchaseFromGooglePlay(
String productId,
String? basePlanId,
String? offerId
) async {
print('Attempting to purchase product with ID: $productId and basePlanId: $basePlanId');
// Do purchase logic
return PurchaseResult.purchased;
}
@override
Future restorePurchases() async {
// Do resture logic
}
}
```
## Tab
```typescript React Native
export class MyPurchaseController extends PurchaseController {
// 1
async purchaseFromAppStore(productId: string): Promise {
console.log("Kicking off purchase of ", productId)
// Purchase logic
return await this._purchaseStoreProduct(storeProduct)
}
async purchaseFromGooglePlay(
productId: string,
basePlanId?: string,
offerId?: string
): Promise {
console.log("Kicking off purchase of ", productId, " base plan ID", basePlanId)
// Purchase logic
return await this._purchaseStoreProduct(storeProduct)
}
// 2
async restorePurchases(): Promise {
// TODO
// ----
// Restore purchases and return true if successful.
}
}
```
### SwiftUI - Use `PaywallView`
The `PaywallView` allows you to show a paywall by sending it a placement. It also has a dismiss handler where the purchased product will be vended:
```swift
@main
struct Caffeine_PalApp: App {
@State private var presentPaywall: Bool = false
init() {
Superwall.configure(apiKey: "my_api_key")
}
var body: some Scene {
WindowGroup {
Button("Log") {
presentPaywall.toggle()
}
.sheet(isPresented: $presentPaywall) {
PaywallView(placement: "caffeineLogged", params: nil, paywallOverrides: nil) { info, result in
switch result {
case .declined:
print("No purchased occurred.")
case .purchased(let product):
print("Purchased \(product.productIdentifier)")
case .restored:
print("Restored purchases.")
}
} feature: {
print("Converted")
presentPaywall.toggle()
}
}
}
}
}
```
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Advanced Configuration
When configuring the SDK you can pass in options that configure Superwall, the paywall presentation, and its appearance.
### Logging
Logging is enabled by default in the SDK and is controlled by two properties: `level` and `scopes`.
`level` determines the minimum log level to print to the console. There are five types of log level:
1. **debug**: Prints all logs from the SDK to the console. Useful for debugging your app if something isn't working as expected.
2. **info**: Prints errors, warnings, and useful information from the SDK to the console.
3. **warn**: Prints errors and warnings from the SDK to the console.
4. **error**: Only prints errors from the SDK to the console.
5. **none**: Turns off all logs.
The SDK defaults to `info`.
`scopes` defines the scope of logs to print to the console. For example, you might only care about logs relating to `paywallPresentation` and `paywallTransactions`. This defaults to `.all`. Check out [LogScope](https://sdk.superwall.me/documentation/superwallkit/logscope) for all possible cases.
You set these properties like this:
:::flutter
```dart
SuperwallOptions options = SuperwallOptions();
options.logging.level = LogLevel.warn;
options.logging.scopes = { LogScope.paywallPresentation, LogScope.paywallEvents };
Superwall.configure(
"MY_API_KEY",
options: options
);
// Or you can set:
Superwall.logging.logLevel = LogLevel.warn;
```
:::
### Preloading Paywalls
Paywalls are preloaded by default when the app is launched from a cold start. The paywalls that are preloaded are determined by the list of placements that result in a paywall for the user when [registered](/docs/sdk/quickstart/feature-gating). Preloading is smart, only preloading paywalls that belong to audiences that could be matched.
Paywalls are cached by default, which means after they load once, they don't need to be reloaded from the network unless you make a change to them on the dashboard. However, if you have a lot of paywalls, preloading may increase network usage of your app on first load of the paywalls and result in slower loading times overall.
> **Tip:** To make an onboarding or first-launch paywall load before the rest of your campaigns, prioritize the campaign from the dashboard with [Priority Placements](/docs/dashboard/dashboard-campaigns/campaigns-placements-prioritized). Use the SDK methods below when you need to disable automatic preloading or manually preload specific placements.
You can turn off preloading by setting `shouldPreload` to `false`:
:::flutter
```dart
SuperwallOptions options = SuperwallOptions();
options.paywalls.shouldPreload = false;
Superwall.configure(
"MY_API_KEY",
options: options
);
```
:::
Then, if you'd like to preload paywalls for specific placements you can use `preloadPaywalls(forPlacements:)`:
:::flutter
```dart
var placements = {"campaign_trigger"};
Superwall.shared.preloadPaywallsForPlacements(placements);
```
:::
If you'd like to preload all paywalls you can use `preloadAllPaywalls()`:
:::flutter
```dart
Superwall.shared.preloadAllPaywalls();
```
:::
Note: These methods will not reload any paywalls that have already been preloaded.
### Event Tracking Behavior
By default, Superwall allows the SDK to send event collection that supports paywall analytics, reporting, and targeting. Use the platform-specific options below when your app needs to control which SDK events are sent to Superwall.
:::flutter
Flutter currently uses `isExternalDataCollectionEnabled`. Setting it to `false` suppresses user-initiated tracking, trigger-fire events, and user-attribute updates while keeping internal Superwall event collection enabled.
```dart
SuperwallOptions options = SuperwallOptions();
options.isExternalDataCollectionEnabled = false;
Superwall.configure(
"MY_API_KEY",
options: options
);
```
:::
### Automatically Dismissing the Paywall
By default, Superwall automatically dismisses the paywall when a product is purchased or restored. You can disable this by setting `automaticallyDismiss` to `false`:
:::flutter
```dart
SuperwallOptions options = SuperwallOptions();
options.paywalls.automaticallyDismiss = false;
Superwall.configure(
"MY_API_KEY",
options: options
);
```
:::
To manually dismiss the paywall , call `Superwall.shared.dismiss()`.
### Custom Restore Failure Message
You can set the title, message and close button title for the alert that appears after a restoration failure:
:::flutter
```dart
SuperwallOptions options = SuperwallOptions();
options.paywalls.restoreFailed.title = "My Title";
options.paywalls.restoreFailed.message = "My message";
options.paywalls.restoreFailed.closeButtonTitle = "Close";
Superwall.configure(
"MY_API_KEY",
options: options
);
```
:::
### Haptic Feedback
On iOS, the paywall uses haptic feedback by default after a user purchases or restores a product, opens a URL from the paywall, or closes the paywall. To disable this, set the `isHapticFeedbackEnabled` `PaywallOption` to false:
:::flutter
```dart
SuperwallOptions options = SuperwallOptions();
options.paywalls.isHapticFeedbackEnabled = false;
Superwall.configure(
"MY_API_KEY",
options: options
);
```
:::
Note: Android does not use haptic feedback.
### Transaction Background View
During a transaction, we add a `UIActivityIndicator` behind the view to indicate a loading status. However, you can remove this by setting the `transactionBackgroundView` to `nil`:
:::flutter
```dart
SuperwallOptions options = SuperwallOptions();
options.paywalls.transactionBackgroundView = TransactionBackgroundView.none;
Superwall.configure(
"MY_API_KEY",
options: options
);
```
:::
### Purchase Failure Alert
When a purchase fails, we automatically present an alert with the error message. If you'd like to show your own alert after failure, set the `shouldShowPurchaseFailureAlert` `PaywallOption` to `false`:
:::flutter
```dart
SuperwallOptions options = SuperwallOptions();
options.paywalls.shouldShowPurchaseFailureAlert = false;
Superwall.configure(
"MY_API_KEY",
options: options
);
```
:::
### Web Purchase Confirmation Alert
When a user completes a purchase via web checkout (app2web flow), you can control whether to show a confirmation alert. By default, this is set to `false` to prevent duplicate alerts. Set `shouldShowWebPurchaseConfirmationAlert` to `true` if you want to show the native confirmation alert:
:::flutter
```dart
SuperwallOptions options = SuperwallOptions();
options.paywalls.shouldShowWebPurchaseConfirmationAlert = true;
Superwall.configure(
"MY_API_KEY",
options: options
);
```
:::
### Locale Identifier
When evaluating rules, the device locale identifier is set to `autoupdatingCurrent`. However, you can override this if you want to test a specific locale:
:::flutter
```dart
SuperwallOptions options = SuperwallOptions();
options.localeIdentifier = "en_GB";
Superwall.configure(
"MY_API_KEY",
options: options
);
// Or you can set:
Superwall.shared.setLocaleIdentifier("en_GB");
// To revert to default:
Superwall.shared.setLocaleIdentifier(null);
```
:::
For a list of locales that are available on iOS, take a look at [this list](https://gist.github.com/jacobbubu/1836273). You can also preview your paywall in different locales using [In-App Previews](/docs/sdk/quickstart/in-app-paywall-previews).
### Game Controller
If you're using a game controller, you can enable this in `SuperwallOptions` too. Check out our [Game Controller Support](/docs/sdk/guides/advanced/game-controller-support) article.
Take a look at [SuperwallOptions](https://sdk.superwall.me/documentation/superwallkit/superwalloptions) in our SDK reference for more info.
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Consumable Products
Set up consumable products for Superwall paywalls in Flutter apps.
Use consumable products when a purchase should grant a quantity that can be used up, such as credits, coins, boosts, or tokens.
This guide covers the Superwall-only flow where purchases are started from paywalls and you are not using a `PurchaseController`.
The platform sections below explain the iOS and Android store requirements that still apply to Flutter apps.
## iOS
> **Note:** This guide assumes purchases are made from Superwall paywalls and that you are not using a `PurchaseController`.
Consumable products are one-time purchases that users can buy repeatedly, such as credits, tokens, boosts, or packs. Non-consumable products are also one-time purchases, but they grant permanent access, such as a lifetime unlock.
Superwall uses entitlements to decide whether a user has ongoing access. Because consumables are meant to be used up, they should usually not grant entitlements. Your app should listen for the purchase, grant the consumable benefit in your own system, and treat Superwall's purchase history as a record of what happened.
## Dashboard Setup
1. Create the consumable in App Store Connect.
2. Add the product in Superwall from **Products**.
3. Use the App Store product identifier.
4. Set **Period** to **None (Lifetime / Consumable)**.
5. Leave **Entitlements** empty.
6. Add the product to any paywall that should sell it.
> **Warning:** Do not attach an entitlement to a consumable unless the purchase should also unlock ongoing access. If a consumable has no entitlement, buying it does not make the user's subscription status active.
## Include Consumables In Purchase History
Apple excludes consumable purchases from App Store purchase history unless you opt in. Add `SKIncludeConsumableInAppPurchaseHistory` to your app's `Info.plist` as a Boolean set to `YES`.
```xml Info.plist
SKIncludeConsumableInAppPurchaseHistory
```
> **Note:** When this key is present and set to `YES`, Superwall uses StoreKit 2 on iOS 18 and later. On earlier iOS versions, the SDK falls back to StoreKit 1 for purchase history support.
## Grant The Consumable Benefit
Superwall does not maintain balances for consumables. Grant credits, tokens, or other benefits from your app or backend after the `transactionComplete` event. Make this operation idempotent so retries do not double-credit the user.
## Tab
```swift Swift
import SuperwallKit
final class SWDelegate: SuperwallDelegate {
func handleSuperwallEvent(withInfo eventInfo: SuperwallEventInfo) {
guard case let .transactionComplete(transaction, product, _, _) = eventInfo.event else {
return
}
guard product.productIdentifier == "com.example.credits_100" else {
return
}
Task {
await ConsumablesService.shared.grantCredits(
count: 100,
productId: product.productIdentifier,
transactionId: transaction?.storeTransactionId
)
}
}
}
Superwall.shared.delegate = SWDelegate()
```
## Tab
```swift Objective-C
#import
@interface SWDelegate : NSObject
@end
@implementation SWDelegate
- (void)handleSuperwallEventWithInfo:(SWKSuperwallEventInfo *)eventInfo {
if (eventInfo.event != SWKSuperwallEventTransactionComplete) {
return;
}
NSString *productId = eventInfo.params[@"primary_product_id"];
if (![productId isEqualToString:@"com.example.credits_100"]) {
return;
}
NSString *transactionId = eventInfo.params[@"store_transaction_id"];
[[ConsumablesService shared] grantCredits:100
productId:productId
transactionId:transactionId];
}
@end
[Superwall sharedInstance].delegate = [SWDelegate new];
```
## Read Purchase History
Consumable and non-consumable purchases appear in `customerInfo.nonSubscriptions`. Use `isConsumable` to distinguish consumables from lifetime purchases.
## Tab
```swift Swift
let customerInfo = Superwall.shared.customerInfo
let consumables = customerInfo.nonSubscriptions.filter { $0.isConsumable }
for purchase in consumables {
print("Consumable purchased: \(purchase.productId)")
}
```
## Tab
```swift Objective-C
SWKCustomerInfo *customerInfo = [Superwall sharedInstance].customerInfo;
for (SWKNonSubscriptionTransaction *purchase in customerInfo.nonSubscriptions) {
if (purchase.isConsumable) {
NSLog(@"Consumable purchased: %@", purchase.productId);
}
}
```
## Android
> **Note:** This guide assumes purchases are made from Superwall paywalls and that you are not using a `PurchaseController`.
Consumable products are one-time purchases that users can buy repeatedly, such as credits, tokens, boosts, or packs. Non-consumable products are also one-time purchases, but they grant permanent access, such as a lifetime unlock.
Superwall uses entitlements to decide whether a user has ongoing access. Because consumables are meant to be used up, they should usually not grant entitlements. Your app should listen for the purchase, grant the consumable benefit in your own system, and then consume the Google Play purchase token so the item can be purchased again.
## Dashboard Setup
1. Create the product as an in-app product in Google Play Console.
2. Add the product in Superwall from **Products**.
3. Use the Google Play product ID.
4. Set **Period** to **None (Lifetime / Consumable)**.
5. Leave **Entitlements** empty.
6. Add the product to any paywall that should sell it.
> **Warning:** Do not attach an entitlement to a consumable unless the purchase should also unlock ongoing access. If a consumable has no entitlement, buying it does not make the user's subscription status active.
## Consume The Purchase Token
Google Play in-app products must be consumed after you grant the benefit. If you do not consume the purchase token, the user may not be able to buy that same consumable again.
Use the `purchaseToken` from the `TransactionComplete` event, grant the benefit, then call `Superwall.instance.consume(purchaseToken)`.
> **Note:** `Superwall.instance.consume(purchaseToken)` is available in Android SDK 2.6.2 and later.
## Tab
```kotlin Kotlin
import androidx.lifecycle.lifecycleScope
import com.superwall.sdk.Superwall
import com.superwall.sdk.analytics.superwall.SuperwallEvent
import com.superwall.sdk.analytics.superwall.SuperwallEventInfo
import com.superwall.sdk.delegate.SuperwallDelegate
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity(), SuperwallDelegate {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Superwall.instance.delegate = this
}
override fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) {
val event = eventInfo.event
if (event !is SuperwallEvent.TransactionComplete) {
return
}
if (event.product.productIdentifier != "coins_100") {
return
}
val purchaseToken = event.transaction?.purchaseToken ?: return
lifecycleScope.launch {
ConsumablesService.grantCoins(
count = 100,
productId = event.product.productIdentifier,
purchaseToken = purchaseToken,
)
Superwall.instance.consume(purchaseToken)
.onFailure { error ->
// Retry consumption after confirming the benefit was granted.
println("Failed to consume purchase: ${error.message}")
}
}
}
}
```
## Tab
```java Java
import com.superwall.sdk.Superwall;
import com.superwall.sdk.analytics.superwall.SuperwallEvent;
import com.superwall.sdk.analytics.superwall.SuperwallEventInfo;
import com.superwall.sdk.delegate.SuperwallDelegateJava;
import com.superwall.sdk.store.abstractions.transactions.StoreTransactionType;
import kotlin.Unit;
public class MainActivity extends AppCompatActivity implements SuperwallDelegateJava {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Superwall.getInstance().setJavaDelegate(this);
}
@Override
public void handleSuperwallEvent(SuperwallEventInfo eventInfo) {
if (!(eventInfo.getEvent() instanceof SuperwallEvent.TransactionComplete)) {
return;
}
SuperwallEvent.TransactionComplete event =
(SuperwallEvent.TransactionComplete) eventInfo.getEvent();
if (!event.getProduct().getProductIdentifier().equals("coins_100")) {
return;
}
StoreTransactionType transaction = event.getTransaction();
if (transaction == null) {
return;
}
String purchaseToken = transaction.getPurchaseToken();
ConsumablesService.grantCoins(100, event.getProduct().getProductIdentifier(), purchaseToken);
Superwall.getInstance().consume(purchaseToken, result -> {
// Check the Result in your app and retry if consumption fails.
return Unit.INSTANCE;
});
}
}
```
> **Warning:** Grant the benefit before consuming the token. If your grant call fails, leave the purchase unconsumed and retry after your app confirms the user received the benefit.
## Read Purchase History
Consumable and non-consumable purchases appear in `customerInfo.nonSubscriptions`. Use `isConsumable` to distinguish consumables from lifetime purchases.
## Tab
```kotlin Kotlin
val customerInfo = Superwall.instance.getCustomerInfo()
val consumables = customerInfo.nonSubscriptions.filter { it.isConsumable }
for (purchase in consumables) {
println("Consumable purchased: ${purchase.productId}")
}
```
## Tab
```java Java
CustomerInfo customerInfo = Superwall.getInstance().getCustomerInfo();
for (NonSubscriptionTransaction purchase : customerInfo.getNonSubscriptions()) {
if (purchase.isConsumable()) {
System.out.println("Consumable purchased: " + purchase.getProductId());
}
}
```
## Flutter Event Handling
In Flutter, listen for purchase events with [`SuperwallDelegate`](/docs/flutter/sdk-reference/SuperwallDelegate) or a [`PaywallPresentationHandler`](/docs/flutter/sdk-reference/PaywallPresentationHandler). Android consumables still need the Google Play purchase token consumed after you grant the benefit.
Use [`Superwall.shared.consume(purchaseToken)`](/docs/flutter/sdk-reference/consume) for the Android consumption call. The current Flutter SDK exposes `consume()` directly, but its public `StoreTransaction` wrapper does not expose the Android `purchaseToken` from Superwall's `transactionComplete` event. If your Flutter app sells Android consumables, expose that token from your Android purchase handling path before calling `consume()`.
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Experimental Flags
Experimental flags in Superwall's SDK allow you to opt into features that are safe for production but are still being refined. These features may undergo naming changes or internal restructuring in future SDK versions. We expose them behind flags to give you early access while preserving flexibility for ongoing development.
These flags are configured via the `SuperwallOptions` struct:
```swift
let options = SuperwallOptions()
options.enableExperimentalDeviceVariables = true
Superwall.configure(apiKey: "my_api_key",
options: options)
```
## Available experimental flags
When these flags are enabled and the user runs your app, these values become available in campaign filters. Currently, these include:
**Latest Subscription Period Type (String)**:
Represents whether the user is in a trial, promotional, or a similar phase. Possible values include:
* `trial`
* `code`
* `subscription`
* `promotional`
* `winback`
* `revoked`
Represented as `latestSubscriptionPeriodType` in campaign filters.
**Latest Subscription State (String)**:
Represents what *state* the actual subscription is in. Possible values include:
* `inGracePeriod`
* `subscribed`
* `expired`
* `inBillingRetryPeriod`
* `revoked`
Represented as `latestSubscriptionState` in campaign filters.
**Latest Subscription Will Auto Renew (Bool)**:
If the user is set to renew or not. Either `true` or `false`
Represented as `latestSubscriptionWillAutoRenew` in campaign filters.
### Detecting users who've cancelled an active trial
One common use case for these flags is detecting users who've cancelled an active trial. In that case, the filter in the campaign would check for `latestSubscriptionWillAutoRenew` to be `false` and `latestSubscriptionPeriodType` to be `trial`.
:::flutter
### Platform Availability
These variables are currently only available on **iOS**, support for Android is not yet available.
:::
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Handling Deep Links
Use handleDeepLink and campaign rules to present paywalls from deep links without hardcoding logic in your app.
When your app receives a deep link, you might be tempted to write a switch statement that maps each URL to a specific placement and calls `register`. This works, but it means every time you add a new link or change which paywall shows, you have to ship an app update.
A better approach is to pass the URL to `handleDeepLink` and let Superwall's [`deepLink_open`](/docs/dashboard/dashboard-campaigns/campaigns-standard-placements#deeplink_open) standard placement handle the rest. The SDK extracts the URL's path, query parameters, and other components, then fires `deepLink_open` as a placement. You write campaign rules on the dashboard to decide which paywall to show, which means there is no app update required.
## The problem
Here's a common pattern where deep link routing is hardcoded in the app:
:::flutter
```dart
void handleUrl(Uri url) {
final placement = switch (url.path) {
'/promo' => 'promoPlacement',
'/onboarding' => 'onboardingPlacement',
'/upgrade' => 'upgradePlacement',
'/special-offer' => 'specialOfferPlacement',
_ => null,
};
if (placement != null) {
Superwall.shared.register(placement: placement);
}
}
```
:::
Every new URL path means a code change, a build, and an app store review. If you want to change which paywall shows for `/promo`, that's another update too.
## The solution: `handleDeepLink` + campaign rules
Instead, pass the URL to `handleDeepLink`. The SDK fires the `deepLink_open` standard placement with all of the URL's components as parameters. Then, on the Superwall dashboard, you create campaign rules that match on those parameters to decide what to show.
:::flutter
```dart
Future handleUrl(Uri url) async {
await Superwall.shared.handleDeepLink(url);
}
```
:::
That's it on the app side. The routing logic lives on the dashboard.
## Setting up campaign rules
Once `handleDeepLink` is wired up, the `deepLink_open` placement fires every time a deep link arrives. The URL's path, host, query parameters, and other components are available as parameters you can match against in your campaign's audience filters.
## Create a campaign
On the Superwall dashboard, create a new [campaign](/docs/dashboard/dashboard-campaigns/campaigns) — for example, "Deep Link Paywalls".
## Add the deepLink_open placement
In your campaign, [add a placement](/docs/dashboard/dashboard-campaigns/campaigns-placements#adding-a-placement) and select `deepLink_open` from the standard placements list.
## Add audience filters
Edit the default audience and add filters that match the URL components you care about. For example, if your deep link is `myapp://promo?offer=summer`:- Set `params.path` **is** `promo` to match the path.
- Set `params.offer` **is** `summer` to match the query parameter.See [`deepLink_open` parameters](/docs/dashboard/dashboard-campaigns/campaigns-standard-placements#deeplink_open) for the full list of available fields.
## Attach a paywall
Click **Paywalls** at the top of the campaign and choose which paywall to present when the filters match.
Now when a user opens `myapp://promo?offer=summer`, the SDK fires `deepLink_open`, the campaign rule matches, and the paywall shows. That's all without touching your app code. To add a new deep link path or change which paywall it shows, just update the campaign on the dashboard.
## Multiple deep link routes
You can handle several deep link patterns from a single campaign by adding multiple audiences, each with its own filters and paywalls. For example:
| Deep link | Filter | Paywall |
| ----------------------------- | -------------------------------------------------------- | --------------- |
| `myapp://promo?offer=summer` | `params.path` is `promo` AND `params.offer` is `summer` | Summer Sale |
| `myapp://promo?offer=newyear` | `params.path` is `promo` AND `params.offer` is `newyear` | New Year Offer |
| `myapp://upgrade` | `params.path` is `upgrade` | Upgrade Paywall |
Each audience evaluates independently. When you need to add a new route, create a new audience on the dashboard — no app update needed.
## Prerequisites
To use `handleDeepLink`, your app needs deep link handling set up first. If you haven't done that yet, follow the setup guide:
:::flutter
* [Deep link setup](/docs/sdk/quickstart/in-app-paywall-previews)
:::
## Related deep link guides
:::flutter
* [Deep Link Setup](/docs/sdk/quickstart/in-app-paywall-previews) — Configure URL schemes, universal links, and wire `handleDeepLink` into your app so Superwall can respond to incoming links.
* [Using Superwall Deep Links](/docs/sdk/guides/superwall-deep-links) — Trigger paywalls or custom in-app behavior using Superwall-hosted URLs at `*.superwall.app/app-link/...`.
:::
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Migrating from v1 to v2 - Flutter
SuperwallKit 2.0 is a major release of Superwall's Flutter SDK. This introduces breaking changes.
## Migration steps
## 1\. Update code references
### 1.1 Rename references from `event` to `placement`
In some cases, you should be able to update references using the automatic renaming suggestions that Xcode provides. For other cases where this hasn't been possible, you'll need to run through this list to manually update your code.
| Before | After |
| ------------------------------------ | ---------------------------------------- |
| fun registerEvent(event:) | fun registerPlacement(placement:) |
| fun preloadPaywalls(forEvents:) | fun preloadPaywalls(forPlacements:) |
| fun getPaywall(forEvent:) | fun getPaywall(forPlacement:) |
| fun getPresentationResult(forEvent:) | fun getPresentationResult(forPlacement:) |
| TriggerResult.eventNotFound | TriggerResult.placementNotFound |
## 2\. SuperwallBuilder
To make tracking and reacting to subscription status changes easier, we've introduced a new `SuperwallBuilder` widget.
Using it is quite simple - just add it to your widget tree and every time the subscription status is changed, the builder function
will be invoked, triggering a re-render of it's child widgets.
For example, here's a simple implementation that will change the text based on the subscription status:
```dart Flutter
SuperwallBuilder(
builder: (context, status) => Center(
child: Text('Subscription Status: ${status}'),
)
)
```
### 3\. Getting the purchased product
The `onDismiss` block of the `PaywallPresentationHandler` now accepts both a `PaywallInfo` object and a `PaywallResult` object. This allows you to easily access
the purchased product from the result when the paywall dismisses.
### 4\. Entitlements
The `subscriptionStatus` has been changed to accept a set of `Entitlement` objects. This allows you to give access to entitlements based on products purchased.
For example, in your app you might have Bronze, Silver, and Gold subscription tiers, i.e. entitlements, which entitle a user to access a certain set of features within your app.
Every subscription product must be associated with one or more entitlements, which is controlled via the dashboard. Superwall will already have associated all your
products with a default entitlement. If you don't use more than one entitlement tier within your app and you only use subscription products, you don't need to do anything extra.
However, if you use one-time purchases or multiple entitlements, you should review your products and their entitlements. In general, consumables should not be associated with an
entitlement, whereas non-consumables should be. Check your products [here](https://superwall.com/applications/\:app/products/v2).
If you're using a `PurchaseController`, you'll need to set the `entitlements` with the `subscriptionStatus`:
| Before | After |
| ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| Superwall.shared.setSubscriptionStatus(SubscriptionStatus.active) | Superwall.shared.setSubscriptionStatus(SubscriptionStatusActive(entitlements: entitlements)) |
Here is an example of how you'd sync your subscription status with Superwall using these methods:
## Tab
```dart RevenueCat
// Only necessary if you're using a PurchaseController.
// Otherwise, Superwall does this automatically.
fun syncSubscriptionStatus() {
Purchases.addCustomerInfoUpdateListener((customerInfo) async {
// Gets called whenever new CustomerInfo is available
final entitlements = customerInfo.entitlements.active.keys
.map((id) => Entitlement(id: id))
.toSet();
final hasActiveEntitlementOrSubscription = customerInfo
.hasActiveEntitlementOrSubscription(); // Why? -> https://www.revenuecat.com/docs/entitlements#entitlements
if (hasActiveEntitlementOrSubscription) {
await Superwall.shared.setSubscriptionStatus(
SubscriptionStatusActive(entitlements: entitlements));
} else {
await Superwall.shared
.setSubscriptionStatus(SubscriptionStatusInactive());
}
});
}
```
You can listen to the published property `Superwall.shared.subscriptionStatus` to be notified when the subscriptionStatus changes. Or you can use the `SuperwallDelegate`
method `subscriptionStatusDidChange(from:to:)`, which replaces `subscriptionStatusDidChange(to:)`.
### 5\. Paywall Presentation Condition
In the Paywall Editor you can choose whether to always present a paywall or ask the SDK to check the user subscription before presenting a paywall.
For users on v1 of the SDK, this is replaced with a check on the entitlements within the audience filter. As you migrate your users from v1 to v2 of the
SDK, you'll need to make sure you set both the entitlements check and the paywall presentation condition in the paywall editor.

## 6\. Check out the full change log
You can view this on [our GitHub page](https://github.com/superwall/Superwall-Flutter/blob/main/CHANGELOG.md).
## 7\. Check out our updated example apps
All of our [example apps](https://github.com/superwall/Superwall-Flutter/tree/main/example) have been updated to use the latest SDK. We now only have two apps: Basic and Advanced. Basic shows you the basic integration of Superwall
without needing a purchase controller or multiple entitlements. Advanced shows you how to use entitlements within your app as well as optionally using a purchase controller with StoreKit or RevenueCat.
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Using Superwall Deep Links
(iOS only) How to use Superwall Deep Links to trigger paywalls or custom in-app behavior.
A Superwall Deep Link is a URL hosted at `https://.superwall.app/app-link/...` that opens your app to trigger a paywall as configured on the Superwall dashboard, or custom in-app behavior via the Superwall delegate.
## Prerequisites
:::flutter
1. Set up [deep link handling](/docs/sdk/quickstart/in-app-paywall-previews)
:::
2. Create a [Web Checkout app](/docs/web-checkout/web-checkout-creating-an-app), even if you do not plan to charge through Web Checkout, this provisions the `*.superwall.app` domain that powers Superwall Deep Links.
## Handling incoming links
* Always call `handleDeepLink` first. It returns `true` when the SDK recognizes the URL and plans to take over presentation, or `false` when you should continue routing inside your own app.
* When a recognized link arrives before `Superwall.configure(...)` finishes, the SDK caches it and replays it immediately after configuration completes, so it is safe to forward links during cold launch.
* If the return value is `false`, continue with your normal router—those links are not associated with any Superwall experience.
:::flutter
```dart
Future _handleIncomingLink(Uri uri) async {
final handled = await Superwall.shared.handleDeepLink(uri);
if (!handled) {
_routeInternally(uri);
}
}
void _listenForLinks() {
uriLinkStream.listen((uri) {
if (uri != null) {
_handleIncomingLink(uri);
}
});
}
```
:::
## Link formats and campaigns
Deep link URLs are hosted at `https://.superwall.app/app-link/...`, you can have anything after the `/app-link/` path, including query parameters. These values will be availble to you in audience filters on the Superwall dashboard, or in the `handleSuperwallDeepLink` delegate method.
:::flutter
```dart
class PaywallDelegate extends SuperwallDelegate {
@override
void handleSuperwallDeepLink(
Uri fullURL,
List pathComponents,
Map queryParameters,
) {
if (pathComponents.isEmpty) {
return;
}
switch (pathComponents.first) {
case 'campaign':
final placementId =
pathComponents.length > 1 ? pathComponents[1] : null;
if (placementId != null) {
_routeToPlacement(placementId, queryParameters);
}
break;
default:
break;
}
}
}
```
:::
Keep your own routing logic in place for non-Superwall URLs and for any additional behaviors you want to stack on top of Superwall's default presentation flow.
## Related deep link guides
:::flutter
* [Deep Link Setup](/docs/sdk/quickstart/in-app-paywall-previews) — Configure URL schemes, universal links, and wire `handleDeepLink` into your app so Superwall can respond to incoming links.
* [Handling Deep Links](/docs/sdk/guides/handling-deep-links) — Use `handleDeepLink` with the `deepLink_open` standard placement and dashboard campaign rules to present paywalls from your own deep links, without hardcoding routing logic.
:::
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# StoreKit testing (iOS only)
How to set up StoreKit testing for iOS when using the Flutter SDK.
StoreKit testing in Xcode is a local test environment for testing in-app purchases without requiring a connection to App Store servers. Set up in-app purchases in a local StoreKit configuration file in your Xcode project, or create a synced StoreKit configuration file in Xcode from your in-app purchase settings in App Store Connect. After you enable the configuration file, the test environment uses this local data on your paywalls when your app calls StoreKit APIs.
### Add a StoreKit Configuration File
Go to **File ▸ New ▸ File...** in the menu bar , select **StoreKit Configuration File** and hit **Next**:

Give it the name **Products**. For a configuration file synced with an app on App Store Connect, select the checkbox, specify your team and app in the drop-down menus that appear, then click **Next**. For a local configuration, leave the checkbox unselected, then click **Next**. Save the file in the top-level folder of your project. You don't need to add it to your target.
### Create a New Scheme for StoreKit Testing
It's best practice to create a new scheme in Xcode to be used for StoreKit testing. This allows you to separate out staging and production environments.
Click the scheme in the scheme menu and click **Manage Schemes...**:

If you haven't already got a Staging scheme, select your current scheme and click **Duplicate**:

In the scheme editor, add the StoreKit Configuration file to your scheme by clicking on **Run** in the side bar, selecting the **Options** tab and choosing your configuration file in **StoreKit Configuration**. Then, click **Close**:

You can rename your scheme to **MyAppName (Staging)**.
### Setting up the StoreKit Configuration File
If you've chosen to sync your configuration file with the App Store, your apps will automatically be loaded into your StoreKit Configuration file. When you add new products, just sync again.
If you're using a local configuration, open **Products.storekit**, click the **+** button at the bottom and create a new product. In this tutorial, we'll create an auto-renewable subscription:

Enter a name for a new subscription group and click **Done**. The subscription group name should match one that is set up for your app in App Store Connect, but it's not a requirement. That means you can test your subscription groups and products in the simulator and then create the products in App Store Connect later:

Configure the subscription as needed by filling in the **Reference Name**, **Product ID**, **Price**, **Subscription Duration**, and optionally an **Introductory Offer**. Again, this product doesn't have to exist in App Store Connect for you to test purchasing in the simulator. Here is a sample configuration:

Repeat this for all of your products. When configuring a paywall, the product ID you enter here must match the product ID on the paywall.
You're now all set!
## Testing purchases with Transaction Manager
Once you've set up your StoreKit configuration file, you can leverage Xcode's Transaction Manager. Find it under **Debug -> StoreKit -> Manage Transactions...**:

Use this to quickly test purchasing your products. Once you make a purchase, you can open Transaction Manager to delete it, refund it, request parental approval and much more. Most commonly, you'll probably delete the transaction to reset your subscription state:

This makes everything a little faster, saving you the trouble of having to delete and reinstall your app to test these states. If you'd like to see a video over how to use it, check this one out:
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Using RevenueCat
> **Note:** Not using RevenueCat? No problem! Superwall works out of the box without any additional SDKs.
> **Warning:** You only need to use a `PurchaseController` if you want end-to-end control of the purchasing pipeline. The recommended way to use RevenueCat with Superwall is by putting it in observer mode.
You can integrate RevenueCat with Superwall using several approaches:
1. [**Using a purchase controller:**](#using-a-purchase-controller) Use this route if you want to maintain control over purchasing logic and code.
2. [**Using PurchasesAreCompletedBy:**](#using-purchasesarecompletedby) Here, you don't use a purchase controller and you tell RevenueCat that purchases are completed by your app using StoreKit. In this mode, RevenueCat will observe the purchases that the Superwall SDK makes. For more info [see here](https://www.revenuecat.com/docs/migrating-to-revenuecat/sdk-or-not/finishing-transactions).
## Using a purchase controller
### 1\. Create a `PurchaseController`
Create a new file called `RCPurchaseController`, then copy and paste the following:
:::flutter
```dart
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'package:superwallkit_flutter/superwallkit_flutter.dart' hide LogLevel;
class RCPurchaseController extends PurchaseController {
// MARK: Configure and sync subscription Status
/// Makes sure that Superwall knows the customers subscription status by
/// changing `Superwall.shared.subscriptionStatus`
Future configureAndSyncSubscriptionStatus() async {
// Configure RevenueCat
await Purchases.setLogLevel(LogLevel.debug);
final configuration = Platform.isIOS
? PurchasesConfiguration('ios_rc_key')
: PurchasesConfiguration('android_rc_key');
await Purchases.configure(configuration);
// Listen for changes
Purchases.addCustomerInfoUpdateListener((customerInfo) async {
// Gets called whenever new CustomerInfo is available
final entitlements = customerInfo.entitlements.active.keys
.map((id) => Entitlement(id: id))
.toSet();
final hasActiveEntitlementOrSubscription = customerInfo
.hasActiveEntitlementOrSubscription(); // Why? -> https://www.revenuecat.com/docs/entitlements#entitlements
if (hasActiveEntitlementOrSubscription) {
await Superwall.shared.setSubscriptionStatus(
SubscriptionStatusActive(entitlements: entitlements));
} else {
await Superwall.shared
.setSubscriptionStatus(SubscriptionStatusInactive());
}
});
}
// MARK: Handle Purchases
/// Makes a purchase from App Store with RevenueCat and returns its
/// result. This gets called when someone tries to purchase a product on
/// one of your paywalls from iOS.
@override
Future purchaseFromAppStore(String productId) async {
// Find products matching productId from RevenueCat
final products = await PurchasesAdditions.getAllProducts([productId]);
// Get first product for product ID (this will properly throw if empty)
final storeProduct = products.firstOrNull;
if (storeProduct == null) {
return PurchaseResult.failed(
'Failed to find store product for $productId');
}
final purchaseResult = await _purchaseStoreProduct(storeProduct);
return purchaseResult;
}
/// Makes a purchase from Google Play with RevenueCat and returns its
/// result. This gets called when someone tries to purchase a product on
/// one of your paywalls from Android.
@override
Future purchaseFromGooglePlay(
String productId, String? basePlanId, String? offerId) async {
// Find products matching productId from RevenueCat
List products =
await PurchasesAdditions.getAllProducts([productId]);
// Choose the product which matches the given base plan.
// If no base plan set, select first product or fail.
String storeProductId = "$productId:$basePlanId";
// Try to find the first product where the googleProduct's basePlanId matches the given basePlanId.
StoreProduct? matchingProduct;
// Loop through each product in the products list.
for (final product in products) {
// Check if the current product's basePlanId matches the given basePlanId.
if (product.identifier == storeProductId) {
// If a match is found, assign this product to matchingProduct.
matchingProduct = product;
// Break the loop as we found our matching product.
break;
}
}
// If a matching product is not found, then try to get the first product from the list.
StoreProduct? storeProduct =
matchingProduct ?? (products.isNotEmpty ? products.first : null);
// If no product is found (either matching or the first one), return a failed purchase result.
if (storeProduct == null) {
return PurchaseResult.failed("Product not found");
}
switch (storeProduct.productCategory) {
case ProductCategory.subscription:
SubscriptionOption? subscriptionOption =
await _fetchGooglePlaySubscriptionOption(
storeProduct, basePlanId, offerId);
if (subscriptionOption == null) {
return PurchaseResult.failed(
"Valid subscription option not found for product.");
}
return await _purchaseSubscriptionOption(subscriptionOption);
case ProductCategory.nonSubscription:
return await _purchaseStoreProduct(storeProduct);
case null:
return PurchaseResult.failed("Unable to determine product category");
}
}
Future _fetchGooglePlaySubscriptionOption(
StoreProduct storeProduct,
String? basePlanId,
String? offerId,
) async {
final subscriptionOptions = storeProduct.subscriptionOptions;
if (subscriptionOptions != null && subscriptionOptions.isNotEmpty) {
// Concatenate base + offer ID
final subscriptionOptionId =
_buildSubscriptionOptionId(basePlanId, offerId);
// Find first subscription option that matches the subscription option ID or use the default offer
SubscriptionOption? subscriptionOption;
// Search for the subscription option with the matching ID
for (final option in subscriptionOptions) {
if (option.id == subscriptionOptionId) {
subscriptionOption = option;
break;
}
}
// If no matching subscription option is found, use the default option
subscriptionOption ??= storeProduct.defaultOption;
// Return the subscription option
return subscriptionOption;
}
return null;
}
Future _purchaseSubscriptionOption(
SubscriptionOption subscriptionOption) async {
// Define the async perform purchase function
// For versions of Flutter Purchases <9
/*Future performPurchase() async {
// Attempt to purchase product
CustomerInfo customerInfo =
await Purchases.purchaseSubscriptionOption(subscriptionOption);
return customerInfo;
}*/
// Flutter Purchases version 9+
Future performPurchase() async {
// Attempt to purchase product
final result = await Purchases.purchase(
PurchaseParams.storeProduct(storeProduct),
);
return result.customerInfo;
}
PurchaseResult purchaseResult =
await _handleSharedPurchase(performPurchase);
return purchaseResult;
}
Future _purchaseStoreProduct(
StoreProduct storeProduct) async {
// Define the async perform purchase function
/* For Flutter Purchases <9
Future performPurchase() async {
// Attempt to purchase product
CustomerInfo customerInfo =
await Purchases.purchaseStoreProduct(storeProduct);
return customerInfo;
}
*/
// For flutter purchases 9+
Future performPurchase() async {
// flutter_purchases 9+ API: use purchase(PurchaseParams) instead
final result = await Purchases.purchase(
PurchaseParams.storeProduct(storeProduct),
);
return result.customerInfo;
}
PurchaseResult purchaseResult =
await _handleSharedPurchase(performPurchase);
return purchaseResult;
}
// MARK: Shared purchase
Future _handleSharedPurchase(
Future Function() performPurchase) async {
try {
// Perform the purchase using the function provided
CustomerInfo customerInfo = await performPurchase();
// Handle the results
if (customerInfo.hasActiveEntitlementOrSubscription()) {
return PurchaseResult.purchased;
} else {
return PurchaseResult.failed("No active subscriptions found.");
}
} on PlatformException catch (e) {
var errorCode = PurchasesErrorHelper.getErrorCode(e);
if (errorCode == PurchasesErrorCode.paymentPendingError) {
return PurchaseResult.pending;
} else if (errorCode == PurchasesErrorCode.purchaseCancelledError) {
return PurchaseResult.cancelled;
} else {
return PurchaseResult.failed(
e.message ?? "Purchase failed in RCPurchaseController");
}
}
}
// MARK: Handle Restores
/// Makes a restore with RevenueCat and returns `.restored`, unless an error is thrown.
/// This gets called when someone tries to restore purchases on one of your paywalls.
@override
Future restorePurchases() async {
try {
await Purchases.restorePurchases();
return RestorationResult.restored;
} on PlatformException catch (e) {
// Error restoring purchases
return RestorationResult.failed(
e.message ?? "Restore failed in RCPurchaseController");
}
}
}
// MARK: Helpers
String _buildSubscriptionOptionId(String? basePlanId, String? offerId) {
String result = '';
if (basePlanId != null) {
result += basePlanId;
}
if (offerId != null) {
if (basePlanId != null) {
result += ':';
}
result += offerId;
}
return result;
}
extension CustomerInfoAdditions on CustomerInfo {
bool hasActiveEntitlementOrSubscription() {
return (activeSubscriptions.isNotEmpty || entitlements.active.isNotEmpty);
}
}
extension PurchasesAdditions on Purchases {
static Future> getAllProducts(
List productIdentifiers) async {
final subscriptionProducts = await Purchases.getProducts(productIdentifiers,
productCategory: ProductCategory.subscription);
final nonSubscriptionProducts = await Purchases.getProducts(
productIdentifiers,
productCategory: ProductCategory.nonSubscription);
final combinedProducts = [
...subscriptionProducts,
...nonSubscriptionProducts
];
return combinedProducts;
}
}
```
:::
As discussed in [Purchases and Subscription Status](/docs/sdk/guides/advanced-configuration), this `CustomPurchaseControllerProvider` is responsible for handling the subscription-related logic using the modern hooks-based approach.
### 2\. Configure Superwall (Continued)
The example above shows the complete setup. The `CustomPurchaseControllerProvider` wraps your `SuperwallProvider` and handles all purchase and restore logic through RevenueCat.
For more advanced implementations, see the [example app](https://github.com/superwall/expo-superwall/tree/main/example).
> **Note:** **Legacy Approach**: If you're migrating from the old SDK or need the class-based purchase controller, you can use `expo-superwall/compat`. However, we recommend using the modern `CustomPurchaseControllerProvider` approach shown above.
### Removed Legacy Code Section
The following section contains the legacy class-based approach. Skip to the next section for the modern configuration.
As discussed in [Purchases and Subscription Status](/docs/sdk/guides/advanced-configuration), this `PurchaseController` is responsible for handling the subscription-related logic. Take a few moments to look through the code to understand how it does this.
### 2\. Configure Superwall
Initialize an instance of `RCPurchaseController` and pass it in to `Superwall.configure(apiKey:purchaseController)`:
:::flutter
```dart
RCPurchaseController purchaseController = RCPurchaseController();
Superwall.configure(
apiKey,
purchaseController: purchaseController
);
await purchaseController.configureAndSyncSubscriptionStatus();
```
:::
### 3\. Sync the subscription status
Then, call `purchaseController.syncSubscriptionStatus()` to keep Superwall's subscription status up to date with RevenueCat.
That's it! Check out our sample app for working examples:
:::flutter
* [Flutter](https://github.com/superwall/Superwall-Flutter/blob/main/example/lib/RCPurchaseController.dart)
:::
## Using PurchasesAreCompletedBy
If you're using RevenueCat's [PurchasesAreCompletedBy](https://www.revenuecat.com/docs/migrating-to-revenuecat/sdk-or-not/finishing-transactions), you don't need to create a purchase controller. Register your placements, present a paywall — and Superwall will take care of completing any purchase the user starts. However, there are a few things to note if you use this setup:
1. Here, you aren't using RevenueCat's [entitlements](https://www.revenuecat.com/docs/getting-started/entitlements#entitlements) as a source of truth. If your app is multiplatform, you'll need to consider how to link up pro features or purchased products for users.
2. If you require custom logic when purchases occur, then you'll want to add a purchase controller. In that case, Superwall handles purchasing flows and RevenueCat will still observe transactions to power their analytics and charts.
3. Be sure that user identifiers are set the same way across Superwall and RevenueCat.
For more information on observer mode, visit [RevenueCat's docs](https://www.revenuecat.com/docs/migrating-to-revenuecat/sdk-or-not/finishing-transactions).
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Using the Superwall Delegate
Use Superwall's delegate to extend our SDK's functionality across several surface areas by assigning to the `delegate` property:
:::flutter
```dart
import 'package:superwallkit_flutter/superwallkit_flutter.dart';
class SWDelegate extends SuperwallDelegate {
// Implement delegate methods here
}
// When configuring the SDK...
void configureSDK() {
Superwall.shared.setDelegate(SWDelegate());
}
```
:::
Some common use cases for using the Superwall delegate include:
* **Custom actions:** [Respond to custom tap actions from a paywall.](/docs/sdk/guides/advanced/custom-paywall-actions#custom-paywall-actions)
* **Respond to purchases:** [See which product was purchased from the presented paywall.](/docs/sdk/guides/advanced/viewing-purchased-products)
* **Analytics:** [Forward events from Superwall to your own analytics.](/docs/sdk/guides/3rd-party-analytics)
Below are some commonly used implementations when using the delegate.
### Superwall Events
Most of what occurs in Superwall can be viewed using the delegate method to respond to events:
:::flutter
```dart
class _MyAppState extends State implements SuperwallDelegate {
@override
Future handleSuperwallEvent(SuperwallEventInfo eventInfo) async {
switch (eventInfo.event.type) {
// Handle any other event types as needed
case PlacementType.transactionComplete:
final product = eventInfo.params?['product'];
logging.info('Transaction complete event received with product: $product');
break;
default:
logging.info('Unhandled event type: ${eventInfo.event.type}');
break;
}
}
}
```
:::
### Paywall Custom Actions
Using the [custom tap action](/docs/sdk/guides/advanced/custom-paywall-actions#custom-paywall-actions), you can respond to any arbitrary event from a paywall:
:::flutter
```dart
class _MyAppState extends State implements SuperwallDelegate {
final logging = Logging();
@override
void handleCustomPaywallAction(String name) {
logging.info('handleCustomPaywallAction: $name');
}
}
```
:::
### Subscription status changes
You can be informed of subscription status changes using the delegate. If you need to set or handle the status on your own, use a [purchase controller](/docs/sdk/guides/advanced-configuration) — this function is only for informational, tracking or similar purposes:
:::flutter
```dart
class _MyAppState extends State implements SuperwallDelegate {
final logging = Logging();
@override
void subscriptionStatusDidChange(SubscriptionStatus newValue) {
logging.info('subscriptionStatusDidChange: $newValue');
}
}
```
:::
### Paywall events
The delegate also has callbacks for several paywall events, such dismissing, presenting, and more. Here's an example:
:::flutter
```dart
class _MyAppState extends State implements SuperwallDelegate {
final logging = Logging();
@override
void didPresentPaywall(PaywallInfo paywallInfo) {
logging.info('didPresentPaywall: $paywallInfo');
}
}
```
:::
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Using your own backend
If you handle subscription logic and receipt validation on your own backend, follow this guide to keep Superwall in sync.
> **Note:** Superwall works out of the box without any backend integration. You only need this guide if your own server is involved in subscription logic.
Common reasons to integrate your own backend include:
* You validate receipts server-side (via the App Store Server API or the Google Play Developer API) instead of trusting the device.
* Your app's entitlements are served from your own API rather than derived from the local receipt.
* You share subscription state across platforms (for example, a subscription purchased on the web that unlocks features in your app).
You can integrate your own backend with Superwall in one of two ways:
1. [**Letting Superwall handle purchases (recommended):**](#letting-superwall-handle-purchases) Keep Superwall's default purchasing behavior and forward completed transactions to your backend for validation and record-keeping.
2. [**Using a purchase controller:**](#using-a-purchase-controller) Take end-to-end control of the purchasing pipeline: you make the purchase, validate it on your server, and tell Superwall the user's subscription status.
> **Warning:** You only need a `PurchaseController` if your backend must be the source of truth for the subscription status Superwall uses on-device. If you just need your server to know about purchases, the first option is much simpler.
## Letting Superwall handle purchases
By default, Superwall handles purchasing, restoring, and subscription status for you — there's nothing to configure. To keep your backend informed, listen for completed transactions and send them to your server for validation.
### 1\. Forward transactions to your backend
Use the [Superwall delegate](/docs/sdk/guides/using-superwall-delegate) to respond to completed transactions and send their identifiers to your backend. Your backend can then verify the purchase directly with Apple or Google:
:::flutter
```dart
class _MyAppState extends State implements SuperwallDelegate {
@override
Future handleSuperwallEvent(SuperwallEventInfo eventInfo) async {
switch (eventInfo.event.type) {
case PlacementType.transactionComplete:
final product = eventInfo.params?['product'];
// Send the transaction details to your backend for validation.
await sendTransactionToBackend(eventInfo.params);
break;
default:
break;
}
}
}
```
:::
### 2\. Keep your backend in sync with webhooks
Renewals, cancellations, refunds, and billing issues happen off-device, so listening in the app isn't enough. Superwall can send [webhooks](/docs/integrations/webhooks) to your backend for subscription and payment events — use them to keep your server's subscription records up to date without polling store APIs yourself.
> **Warning:** When Superwall handles purchases, don't set `subscriptionStatus` yourself — the SDK updates it automatically after purchases, restores, and receipt checks. If your backend needs to control the status Superwall uses on-device, use a [purchase controller](#using-a-purchase-controller) instead.
## Using a purchase controller
With a `PurchaseController`, you take over purchasing and restoring, and you become responsible for setting `Superwall.shared.subscriptionStatus` from your backend's entitlement state. See [Purchases and Subscription Status](/docs/sdk/guides/advanced-configuration) for a full explanation of this mode.
### 1\. Create a `PurchaseController`
Create a new file called `MyBackendPurchaseController`, then copy and paste the following. Replace `fetchSubscriptionStatusFromBackend()` and `sendTransactionToBackend(...)` with calls to your own API — they're placeholders for whatever your backend exposes:
:::flutter
```dart
import 'package:superwallkit_flutter/superwallkit_flutter.dart';
class MyBackendPurchaseController extends PurchaseController {
// MARK: Sync Subscription Status
/// Fetches the user's entitlements from your backend and makes sure
/// Superwall knows about them.
Future syncSubscriptionStatus() async {
// Ask your backend which entitlements this user has, e.g. ["pro"].
final entitlementIds = await fetchSubscriptionStatusFromBackend();
if (entitlementIds.isNotEmpty) {
final entitlements =
entitlementIds.map((id) => Entitlement(id: id)).toSet();
await Superwall.shared.setSubscriptionStatus(
SubscriptionStatusActive(entitlements: entitlements));
} else {
await Superwall.shared
.setSubscriptionStatus(SubscriptionStatusInactive());
}
}
// MARK: Handle Purchases
/// Makes a purchase from the App Store and returns its result. This gets
/// called when someone tries to purchase a product on one of your paywalls
/// from iOS.
@override
Future purchaseFromAppStore(String productId) async {
// TODO
// ----
// Purchase `productId` with your StoreKit billing implementation
// (e.g. the in_app_purchase package), then send the transaction to
// your backend for validation.
//
// await sendTransactionToBackend(...);
await syncSubscriptionStatus();
return PurchaseResult.purchased;
}
/// Makes a purchase from Google Play and returns its result. This gets
/// called when someone tries to purchase a product on one of your paywalls
/// from Android.
@override
Future purchaseFromGooglePlay(
String productId, String? basePlanId, String? offerId) async {
// TODO
// ----
// Launch the Google Play billing flow for `productId`, using
// `basePlanId` and `offerId` to select the right subscription offer.
// Then send the purchase token to your backend for validation and
// acknowledgement.
//
// await sendTransactionToBackend(...);
await syncSubscriptionStatus();
return PurchaseResult.purchased;
}
// MARK: Handle Restores
/// Restores purchases and returns the result. This gets called when someone
/// tries to restore purchases on one of your paywalls.
@override
Future restorePurchases() async {
// TODO
// ----
// Restore purchases with your billing implementation and send any
// transactions your backend doesn't know about to your backend.
await syncSubscriptionStatus();
return RestorationResult.restored;
}
}
```
:::
As discussed in [Purchases and Subscription Status](/docs/sdk/guides/advanced-configuration), this `PurchaseController` is responsible for handling the subscription-related logic. Take a few moments to look through the code to understand how it does this.
### 2\. Configure Superwall
Initialize an instance of `MyBackendPurchaseController` and pass it in to `Superwall.configure(apiKey:purchaseController:)`:
:::flutter
```dart
MyBackendPurchaseController purchaseController = MyBackendPurchaseController();
Superwall.configure(
apiKey,
purchaseController: purchaseController
);
await purchaseController.syncSubscriptionStatus();
```
:::
### 3\. Sync the subscription status
Until you set the subscription status, it's `.unknown` — and paywall presentation is automatically delayed until it changes. Call `syncSubscriptionStatus()` when the app launches and whenever your backend's entitlement state changes, so Superwall always knows whether to show a paywall.
## Keeping the status in sync across launches and devices
Your backend is the source of truth, so treat the on-device status as a cache of it:
* **Identify users consistently.** Call `identify` with the same user ID your backend uses so entitlements follow the user across devices. See [User Management](/docs/sdk/quickstart/user-management).
* **Fetch entitlements on launch.** Call your backend's entitlement endpoint when the app launches (and when it returns to the foreground) and update the subscription status with the result. `subscriptionStatus` is cached between app launches, so the previous value is used until your fetch completes.
* **Make server notifications your source of truth.** Renewals, cancellations, refunds, and billing issues happen off-device. Subscribe your backend to [App Store Server Notifications](https://developer.apple.com/documentation/appstoreservernotifications) and Google Play's [Real-time Developer Notifications](https://developer.android.com/google/play/billing/rtdn-reference) so its entitlement records stay correct — the app then picks up changes the next time it syncs.
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Vibe Coding
How to Vibe Code using the knowledge of the Superwall Docs
## Overview
We've built a few tools to help you Vibe Code using the knowledge of the Superwall Docs, access your Superwall account, and more right in your favorite AI tools:
* [Superwall AI](#superwall-ai): The assistant built into the dashboard, these docs, and superwall.com. Ask about Superwall, your account, or your data, with nothing to set up. Start here.
* [Superwall Agents](/docs/agents): An AI workspace for analyzing Superwall data exposed by connected tooling, reviewing experiments, working with available files, scheduling recurring reports, and connecting webhooks.
* [Superwall Skill (Recommended for coding agents)](#superwall-skill-recommended-for-coding-agents): Gives AI agents live docs, API access, and step-by-step SDK integration guides. If you are working in an external coding agent, pick this one.
* [Superwall CLI](#superwall-cli): Set up the SDK, manage your account, and query analytics from the terminal. Also the fastest way to integrate: `npx superwall integrate`.
* [Superwall MCP](#superwall-mcp): Expose your Superwall account (projects, paywalls, campaigns) to work with AI tools.
* [Editor AI](#editor-ai): Build and refine a paywall directly inside the visual editor using AI Chat or an external MCP-compatible agent.
And right here in the Superwall Docs:
* [Docs Links](#docs-links)
* [LLMs.txt](#llmstxt)
## Superwall AI
[Superwall AI](/docs/dashboard/superwall-ai) is the assistant in the bottom right 💬 of these docs, superwall.com, and the dashboard, and behind the **Ask AI** button in the docs search. It's an agent: it reads the docs and SDK source before answering, links what it used, and holds a conversation so you can follow up. In the dashboard it can also work with your account and analytics data. See the [Superwall AI guide](/docs/dashboard/superwall-ai).
## Superwall Skill (Recommended for Coding Agents)
The [Superwall Skill](/docs/dashboard/guides/superwall-skill) is the best way to give external AI coding agents full context on Superwall. It bundles live documentation, API access, dashboard links, and guided SDK integration flows for every platform, all in one install. If you are already working in Claude Code, Cursor, Codex, or another coding agent, pick this one.
```bash
npx superwall skills
```
Once installed, your agent can look up any Superwall doc on demand, call the API to inspect your projects and applications, and walk through a complete SDK integration step by step. It supports iOS, Android, Flutter, and Expo out of the box, and also includes the `superwall-editor` skill for editing live paywalls and the `wwdc` skill for WWDC session lookup.
If you're only going to set up one tool, this is the one to use. See the full [Superwall Skill guide](/docs/dashboard/guides/superwall-skill) for details.
## Superwall CLI
The [Superwall CLI](/docs/dashboard/guides/superwall-cli) puts your whole Superwall account in the terminal, for you and your coding agent: SDK setup, products, campaigns, paywalls, SQL analytics, and the Superwall and App Store Connect APIs.
```bash
npx superwall integrate # sign up & integrate the SDK in one command
```
`integrate` detects your app, connects it to your Superwall account (creating one from the terminal if needed), and runs the Claude Code or Codex on your machine to install and configure the SDK. See the full [Superwall CLI guide](/docs/dashboard/guides/superwall-cli) for every command.
## Superwall MCP
The Superwall MCP connects AI tools to your **Superwall account**, letting agents create and manage projects, paywalls, campaigns, products, entitlements, and webhooks directly. Instead of switching to the dashboard, your AI assistant can set everything up for you.
If you also want live docs access and guided SDK integration help, use the [Superwall Skill](/docs/dashboard/guides/superwall-skill). The MCP is focused on account and resource management.
See the full [Superwall MCP guide](/docs/dashboard/guides/superwall-mcp) for installation, a step-by-step quick setup, and the complete tool reference.
## Editor AI
The paywall editor has AI tools for working on the paywall currently open in your browser:
* [AI Chat Builder](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-ai-chat): Open **AI Chat** in the editor sidebar, describe what you want changed, attach reference images, and let Superwall make draft edits for you.
* [Editor MCP](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-mcp): Connect Claude Code, Codex, Cursor, or another MCP-compatible agent to the live editor with a pairing code.
Use Editor AI when the task is about changing the design, layout, products, variables, tap behaviors, navigation, or localization of the current paywall. Use the account-level [Superwall MCP](/docs/dashboard/guides/superwall-mcp) when the task is about creating or managing dashboard resources such as projects, applications, campaigns, products, entitlements, or webhooks.
## Docs Links
At the top of each page of the Superwall Docs (including this one!):
* **Copy Markdown**: to copy the page in Markdown format.
Also in the **Open** dropdown menu, you can access these options:
* **View as Markdown**: to view the page in Markdown format
* **Open in ChatGPT**, **Open in Claude**: to open the page in the respective AI tool and add the page as context for your conversation
You can also add `.md` to the end of any docs page URL to open that page as Markdown. For example, `https://superwall.com/docs/ios/quickstart/install.md`.
## LLMs.txt
The Superwall Docs website has `llms.txt` and `llms-full.txt` files, in total and for each SDK, that you can use to add context to your LLMs.
`llms.txt` is a summary of the docs with links to each page.
`llms-full.txt` is the full text of all of the docs.
| SDK | Summary | Full Text |
| ------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| All | [`llms.txt`](https://superwall.com/docs/llms.txt) | [`llms-full.txt`](https://superwall.com/docs/llms-full.txt) |
| Dashboard | [`dashboard/llms.txt`](https://superwall.com/docs/dashboard/llms.txt) | [`dashboard/llms-full.txt`](https://superwall.com/docs/dashboard/llms-full.txt) |
| iOS | [`ios/llms.txt`](https://superwall.com/docs/ios/llms.txt) | [`ios/llms-full.txt`](https://superwall.com/docs/ios/llms-full.txt) |
| Android | [`android/llms.txt`](https://superwall.com/docs/android/llms.txt) | [`android/llms-full.txt`](https://superwall.com/docs/android/llms-full.txt) |
| Flutter | [`flutter/llms.txt`](https://superwall.com/docs/flutter/llms.txt) | [`flutter/llms-full.txt`](https://superwall.com/docs/flutter/llms-full.txt) |
| Expo | [`expo/llms.txt`](https://superwall.com/docs/expo/llms.txt) | [`expo/llms-full.txt`](https://superwall.com/docs/expo/llms-full.txt) |
| React Native (Deprecated) | [`react-native/llms.txt`](https://superwall.com/docs/react-native/llms.txt) | [`react-native/llms-full.txt`](https://superwall.com/docs/react-native/llms-full.txt) |
| Integrations | [`integrations/llms.txt`](https://superwall.com/docs/integrations/llms.txt) | [`integrations/llms-full.txt`](https://superwall.com/docs/integrations/llms-full.txt) |
| Web Checkout | [`web-checkout/llms.txt`](https://superwall.com/docs/web-checkout/llms.txt) | [`web-checkout/llms-full.txt`](https://superwall.com/docs/web-checkout/llms-full.txt) |
To minimize token use, we recommend using the files specific to your SDK.
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Web Checkout
Integrate Superwall web checkout with your iOS app for seamless cross-platform subscriptions
## Dashboard Setup
1. [Set up Web Checkout in the dashboard](/docs/web-checkout)
2. [Add web products to your paywall](/docs/web-checkout/web-checkout-direct-stripe-checkout)
## SDK Setup
:::flutter
1. [Set up deep links](/docs/sdk/quickstart/in-app-paywall-previews)
2. [Handle Post-Checkout redirecting](/docs/sdk/guides/web-checkout/post-checkout-redirecting)
3. **Only if you're using RevenueCat:** [Using RevenueCat](/docs/sdk/guides/web-checkout/using-revenuecat)
4. **Only if you're using your own PurchaseController:** [Redeeming In-App](/docs/sdk/guides/web-checkout/linking-membership-to-iOS-app)
:::
## Testing
1. [Testing Purchases](/docs/web-checkout/web-checkout-testing-purchases)
2. [Managing Memberships](/docs/web-checkout/web-checkout-managing-memberships)
## Troubleshooting
If a user has issues accessing their purchase in your app after paying via web checkout, direct them to your plan management page to retrieve their redemption link or manage billing:
For example: `http://yourapp.superwall.app/manage`
## FAQ
[Web Checkout FAQ](/docs/web-checkout/web-checkout-faq)
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Redeeming In-App
Handle a deep link in your app and use the delegate methods.
After purchasing from a web paywall, the user will be redirected to your app by a deep link to redeem their purchase on device.
Please follow our [Post-Checkout Redirecting](/docs/flutter/guides/web-checkout/post-checkout-redirecting) guide to handle this user experience.
> **Note:** If you're using Superwall to handle purchases, then you don't need to do anything here.
If you're using your own `PurchaseController`, you will need to update the subscription status with the redeemed web entitlements. If you're using RevenueCat, you should follow our [Using RevenueCat](/docs/flutter/guides/web-checkout/using-revenuecat) guide.
### Using a PurchaseController
If you're using a custom PurchaseController (with either iOS StoreKit or Android Play Billing), you'll need to merge the web entitlements with the device entitlements before setting the subscription status.
Here's an example of how you might do this:
```dart
import 'package:superwallkit_flutter/superwallkit_flutter.dart';
Future syncSubscriptionStatus() async {
// Get the device entitlements from your purchase controller
// This will vary based on whether you're using RevenueCat, StoreKit, or Play Billing
final deviceEntitlements = await getDeviceEntitlements();
// Get the web entitlements from Superwall
final webEntitlements = Superwall.shared.entitlements?.web ?? [];
// Merge the two sets of entitlements
final allEntitlementIds = {
...deviceEntitlements,
...webEntitlements.map((e) => e.id),
};
// Update subscription status
if (allEntitlementIds.isNotEmpty) {
final entitlements = allEntitlementIds
.map((id) => Entitlement(id: id))
.toSet();
await Superwall.shared.setSubscriptionStatus(
SubscriptionStatusActive(entitlements: entitlements),
);
} else {
await Superwall.shared.setSubscriptionStatus(
SubscriptionStatusInactive(),
);
}
}
// Helper function to get device entitlements
// This is a simplified example - your implementation will depend on your purchase system
Future> getDeviceEntitlements() async {
// For RevenueCat:
// final customerInfo = await Purchases.getCustomerInfo();
// return customerInfo.entitlements.active.keys.toList();
// For custom StoreKit/Play Billing:
// Query your store's API for current purchases
// Extract entitlement IDs from those purchases
// Return list of entitlement IDs
return []; // Replace with your actual implementation
}
```
In addition to syncing the subscription status when purchasing and restoring, you'll need to sync it whenever `didRedeemLink(result)` is called:
```dart
import 'package:superwallkit_flutter/superwallkit_flutter.dart';
class MySuperwallDelegate extends SuperwallDelegate {
@override
void didRedeemLink(RedemptionResult result) {
// Don't use async here directly, spawn a separate task
_handleRedemption(result);
}
Future _handleRedemption(RedemptionResult result) async {
await syncSubscriptionStatus();
}
}
```
### Refreshing of web entitlements
If you aren't using a Purchase Controller, the SDK will refresh the web entitlements every 24 hours.
### Redeeming while a paywall is open
If a redeem event occurs when a paywall is open, the SDK will track that as a restore event and the paywall will close.
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Post-Checkout Redirecting
Learn how to handle users redirecting back to your app after a web purchase.
After a user completes a web purchase, Superwall needs to redirect them back to your app. You can configure this behavior in two ways:
## Post-Purchase Behavior Modes
You can configure how users are redirected after checkout in your [Application Settings](/docs/web-checkout/web-checkout-configuring-stripe-keys-and-settings#post-purchase-behavior):
### Redeem Mode (Default)
Superwall manages the entire redemption experience:
* Users are automatically deep linked to your app with a redemption code
* Fallback to App Store/Play Store if the app isn't installed
* Redemption emails are sent automatically
* The SDK handles redemption via delegate methods (detailed below)
This is the recommended mode for most apps.
### Redirect Mode
Redirect users to your own custom URL with purchase information:
* **When to use**: You want to show a custom success page, perform additional actions before redemption, or have your own deep linking infrastructure
* **What you receive**: Purchase data is passed as query parameters to your URL
**Query Parameters Included**:
* `app_user_id` - The user's identifier from your app
* `email` - User's email address
* `stripe_subscription_id` - The Stripe subscription ID, or the Stripe Checkout session ID for one-time purchases
* Any custom placement parameters you set
**Example**:
```
https://yourapp.com/success?
app_user_id=user_123&
email=user@example.com&
stripe_subscription_id=sub_1234567890&
campaign_id=summer_sale
```
You'll need to implement your own logic to handle the redirect and deep link users into your app.
***
## Setting Up Deep Links
Whether checkout starts from a web link or from a paywall that opens an external browser, the Superwall SDK relies on deep links to redirect back to your app.
#### Prerequisites
1. [Configuring Stripe Keys and Settings](/docs/web-checkout/web-checkout-configuring-stripe-keys-and-settings)
2. [Deep Links](/docs/flutter/quickstart/in-app-paywall-previews)
> **Warning:** If you're not using Superwall to handle purchases, then you'll need to follow extra steps to redeem the web purchase in your app.
* [Using RevenueCat](/docs/flutter/guides/web-checkout/using-revenuecat)
* [Using a PurchaseController](/docs/flutter/guides/web-checkout/linking-membership-to-iOS-app#using-a-purchasecontroller)
***
## Handling Redemption (Redeem Mode)
When using Redeem mode (the default), handle the user experience when they're redirected back to your app using `SuperwallDelegate` methods:
### willRedeemLink
When your app opens via the deep link, we will call the delegate method `willRedeemLink()` before making a network call to redeem the code.
At this point, you might wish to display a loading indicator in your app so the user knows that the purchase is being redeemed.
```dart
import 'package:superwallkit_flutter/superwallkit_flutter.dart';
import 'package:flutter/material.dart';
class MySuperwallDelegate extends SuperwallDelegate {
@override
void willRedeemLink() {
// Show a loading indicator to the user
print('Activating your purchase...');
// You might show a SnackBar or loading dialog here
}
}
```
You can manually dismiss the paywall at this point if needed, but note that the paywall will be dismissed automatically when the `didRedeemLink` method is called.
### didRedeemLink
After receiving a response from the network, we will call `didRedeemLink(result)` with the result of redeeming the code. The result is a `RedemptionResult` subclass, which can be one of:
* `RedemptionResultSuccess`: The redemption succeeded and contains information about the redeemed code.
* `RedemptionResultError`: An error occurred while redeeming. You can check the error message via the error parameter.
* `RedemptionResultExpiredCode`: The code expired and contains information about whether a redemption email has been resent and an optional obfuscated email address.
* `RedemptionResultInvalidCode`: The code that was redeemed was invalid.
* `RedemptionResultExpiredSubscription`: The subscription that the code redeemed has expired.
On network failure, the SDK will retry up to 6 times before returning a `RedemptionResultError` in `didRedeemLink(result)`.
Here, you should remove any loading UI you added in `willRedeemLink` and show a message to the user based on the result. If a paywall is presented, it will be dismissed automatically.
```dart
import 'package:superwallkit_flutter/superwallkit_flutter.dart';
import 'package:flutter/material.dart';
class MySuperwallDelegate extends SuperwallDelegate {
final BuildContext context; // Pass context if you need to show dialogs/snackbars
MySuperwallDelegate(this.context);
@override
void didRedeemLink(RedemptionResult result) {
switch (result) {
case RedemptionResultExpiredCode():
_showMessage('Expired Link');
print('[!] code expired: ${result.code}, ${result.info}');
break;
case RedemptionResultError():
_showMessage(result.error.message);
print('[!] error: ${result.code}, ${result.error}');
break;
case RedemptionResultExpiredSubscription():
_showMessage('Expired Subscription');
print('[!] expired subscription: ${result.code}, ${result.redemptionInfo}');
break;
case RedemptionResultInvalidCode():
_showMessage('Invalid Link');
print('[!] invalid code: ${result.code}');
break;
case RedemptionResultSuccess():
final purchaserInfo = result.redemptionInfo.purchaserInfo;
final email = purchaserInfo.email;
if (email != null) {
Superwall.shared.setUserAttributes({'email': email});
_showMessage('Welcome, $email!');
} else {
_showMessage('Welcome!');
}
switch (purchaserInfo.storeIdentifiers) {
case StripeStoreIdentifiers(subscriptionIds: final ids):
print('[!] redeemed Stripe subscriptions: $ids');
case PaddleStoreIdentifiers(subscriptionIds: final ids):
print('[!] redeemed Paddle subscriptions: $ids');
case UnknownStoreIdentifiers(store: final store):
print('[!] redeemed purchase from $store');
}
break;
}
}
void _showMessage(String message) {
// Show a snackbar or toast message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
}
```
### Setting up the delegate
Make sure to set your delegate when configuring Superwall:
```dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Superwall.configure('pk_your_api_key');
// Set the delegate (you'll need access to BuildContext for UI operations)
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
State createState() => _MyAppState();
}
class _MyAppState extends State {
@override
void initState() {
super.initState();
// Set the delegate after the widget is initialized
WidgetsBinding.instance.addPostFrameCallback((_) {
Superwall.shared.setDelegate(MySuperwallDelegate(context));
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: YourHomeScreen(),
);
}
}
```
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Using RevenueCat
Handle a deep link in your app and use the delegate methods to link web checkouts with RevenueCat in Flutter.
After purchasing from a web paywall, the user will be redirected to your app by a deep link to redeem their purchase on device. Please follow our [Post-Checkout Redirecting](/docs/flutter/guides/web-checkout/post-checkout-redirecting) guide to handle this user experience.
> **Note:** If you're using Superwall to handle purchases, then you don't need to do anything here.
> **Warning:** You only need to use a `PurchaseController` if you want end-to-end control of the purchasing pipeline. The recommended way to use RevenueCat with Superwall is by putting it in observer mode.
If you're using your own `PurchaseController`, you should follow our [Redeeming In-App](/docs/flutter/guides/web-checkout/linking-membership-to-iOS-app) guide.
### Using a PurchaseController with RevenueCat
If you're using RevenueCat, you'll need to follow [steps 1 to 4 in their guide](https://www.revenuecat.com/docs/web/integrations/stripe) to set up Stripe with RevenueCat. Then, you'll need to
associate the RevenueCat customer with the Stripe subscription IDs returned from redeeming the code. You can do this by extracting the ids from the `RedemptionResult` and sending them to RevenueCat's API
by using the `didRedeemLink()` delegate method:
> **Warning:** This flow is for Stripe subscriptions. Stripe one-time purchases can return Stripe Checkout session IDs through the same legacy `stripeSubscriptionIds` field, but those IDs are not Stripe subscription IDs. Handle one-time purchases with Superwall entitlements or your own backend instead of sending those IDs to RevenueCat's Stripe subscription endpoint.
```dart
import 'package:superwallkit_flutter/superwallkit_flutter.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class MySuperwallDelegate extends SuperwallDelegate {
// The user tapped on a deep link to redeem a code
@override
void willRedeemLink() {
print('[!] willRedeemLink');
// Optionally show a loading indicator here
}
// Superwall received a redemption result and validated the purchase with Stripe.
@override
void didRedeemLink(RedemptionResult result) async {
print('[!] didRedeemLink: $result');
// Send Stripe IDs to RevenueCat to link purchases to the customer
if (result is! RedemptionResultSuccess) {
return;
}
final storeIdentifiers = result.redemptionInfo.purchaserInfo.storeIdentifiers;
if (storeIdentifiers is! StripeStoreIdentifiers) {
return;
}
// Get a list of Stripe subscription ids tied to the customer
final stripeSubscriptionIds = storeIdentifiers.subscriptionIds;
if (stripeSubscriptionIds.isEmpty) {
return;
}
const revenueCatStripePublicAPIKey = 'strp.....'; // replace with your RevenueCat Stripe Public API Key
final appUserId = await Purchases.appUserID;
// In the background, send requests to RevenueCat
for (final stripeSubscriptionId in stripeSubscriptionIds) {
try {
final url = Uri.parse('https://api.revenuecat.com/v1/receipts');
final response = await http.post(
url,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Platform': 'stripe',
'Authorization': 'Bearer $revenueCatStripePublicAPIKey',
},
body: jsonEncode({
'app_user_id': appUserId,
'fetch_token': stripeSubscriptionId,
}),
);
if (response.statusCode != 200) {
throw Exception(
'RevenueCat responded with ${response.statusCode}: ${response.body}',
);
}
final json = jsonDecode(response.body);
print('[!] Success: linked $stripeSubscriptionId to user $appUserId: $json');
} catch (error) {
print('[!] Error: unable to link $stripeSubscriptionId to user $appUserId: $error');
}
}
// After all network calls complete, invalidate the cache
try {
final customerInfo = await Purchases.getCustomerInfo();
/// If you're using Purchases.customerInfoStream, or keeping Superwall Entitlements in sync
/// via RevenueCat's PurchasesDelegate methods, you don't need to do anything here. Those methods will be
/// called automatically when this call fetches the most up to date customer info, ignoring any local caches.
/// Otherwise, if you're manually calling Purchases.getCustomerInfo to keep Superwall's entitlements
/// in sync, you should use the newly updated customer info here to do so.
/// You could always access web entitlements here as well
/// final webEntitlements = Superwall.shared.entitlements?.web;
// Perform UI updates, like letting the user know their subscription was redeemed
print('[!] Customer info updated after redemption');
} catch (error) {
print('[!] Error fetching customer info: $error');
}
}
}
```
> **Note:** The example throws when RevenueCat responds with an error so you can add retries, alerts, or custom
> UI. Adapt the error-handling strategy to your networking and logging requirements.
Set up the delegate when configuring Superwall:
```dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Superwall.configure('pk_your_api_key');
// Set the delegate
Superwall.shared.setDelegate(MySuperwallDelegate());
runApp(MyApp());
}
```
> **Warning:** If you call `logIn` from RevenueCat's SDK, then you need to call the logic you've implemented
> inside `didRedeemLink()` again. For example, that means if `logIn` was invoked from
> RevenueCat, you'd either abstract out this logic above into a function to call again, or simply
> call this function directly.
The web entitlements will be returned along with other existing entitlements in the `CustomerInfo` object accessible via RevenueCat's SDK.
If you're logging in and out of RevenueCat, make sure to resend the Stripe subscription IDs to RevenueCat's endpoint after logging in.
### Alternative implementation using async/await properly
Here's a cleaner implementation that properly handles async operations:
```dart
class MySuperwallDelegate extends SuperwallDelegate {
@override
void willRedeemLink() {
print('[!] willRedeemLink');
// Show loading indicator
}
@override
void didRedeemLink(RedemptionResult result) {
// Don't use async here directly, spawn a separate task
_handleRedemption(result);
}
Future _handleRedemption(RedemptionResult result) async {
if (result is! RedemptionResultSuccess) {
print('[!] Redemption did not succeed');
return;
}
final storeIdentifiers = result.redemptionInfo.purchaserInfo.storeIdentifiers;
if (storeIdentifiers is! StripeStoreIdentifiers) {
print('[!] Redemption did not come from Stripe');
return;
}
final stripeSubscriptionIds = storeIdentifiers.subscriptionIds;
if (stripeSubscriptionIds.isEmpty) {
print('[!] No Stripe subscription IDs found');
return;
}
const revenueCatStripePublicAPIKey = 'strp.....';
final appUserId = await Purchases.appUserID;
// Link each subscription to RevenueCat
await Future.wait(
stripeSubscriptionIds.map((stripeSubscriptionId) async {
try {
await _linkSubscriptionToRevenueCat(
stripeSubscriptionId,
appUserId,
revenueCatStripePublicAPIKey,
);
} catch (e) {
print('[!] Failed to link $stripeSubscriptionId: $e');
}
}),
);
// Refresh customer info
try {
await Purchases.getCustomerInfo();
print('[!] Successfully refreshed customer info');
} catch (e) {
print('[!] Failed to refresh customer info: $e');
}
}
Future _linkSubscriptionToRevenueCat(
String stripeSubscriptionId,
String appUserId,
String apiKey,
) async {
final url = Uri.parse('https://api.revenuecat.com/v1/receipts');
final response = await http.post(
url,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Platform': 'stripe',
'Authorization': 'Bearer $apiKey',
},
body: jsonEncode({
'app_user_id': appUserId,
'fetch_token': stripeSubscriptionId,
}),
);
if (response.statusCode != 200) {
throw Exception(
'RevenueCat responded with ${response.statusCode}: ${response.body}',
);
}
print('[!] Successfully linked $stripeSubscriptionId to $appUserId');
}
}
```
Remember to add the `http` package to your `pubspec.yaml`:
```yaml
dependencies:
http: ^1.1.0
purchases_flutter: ^6.0.0
superwallkit_flutter: ^2.4.12
```
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Welcome
Welcome to the Superwall Flutter SDK documentation
## Quick Links
## Quickstart
/docs/flutter/quickstart/install
Get up and running with the Superwall Flutter SDK
## Common Use Cases
/docs/flutter/guides/web-checkout
Most common features and use cases
## SDK Reference
/docs/flutter/sdk-reference
Reference the Superwall Flutter SDK
## Guides
/docs/flutter/guides/using-revenuecat
Guides for specific use cases
## Example App
https://github.com/superwall/Superwall-Flutter/tree/main/example
Example app for the Superwall Flutter SDK
## Troubleshooting
https://support.superwall.com/articles/6999598520-troubleshooting-flutter-sdk
Guides for troubleshooting common issues
## Feedback
We are always improving our SDKs and documentation!
If you have feedback on any of our docs, please leave a rating and message at the bottom of the page.
If you have any issues with the SDK, please [open an issue on GitHub](https://github.com/superwall/superwall-flutter/issues).
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Configure the SDK
As soon as your app launches, you need to configure the SDK with your **Public API Key**. You'll retrieve this from the Superwall settings page.
### Sign Up & Grab Keys
If you haven't already, [sign up for a free account](https://superwall.com/sign-up) on Superwall. Then, when you're through to the Dashboard, click **Settings** from the panel on the left, click **Keys** and copy your **Public API Key**:

### Initialize Superwall in your app
Begin by editing your main Application entrypoint. Depending on the
platform this could be `AppDelegate.swift` or `SceneDelegate.swift` for iOS,
`MainApplication.kt` for Android, `main.dart` in Flutter, or `App.tsx` for React Native:
:::flutter
```dart Flutter
// main.dart
void initState() {
// Determine Superwall API Key for platform
String apiKey = Platform.isIOS ? "MY_IOS_API_KEY" : "MY_ANDROID_API_KEY";
Superwall.configure(apiKey);
}
```
:::
This configures a shared instance of `Superwall`, the primary class for interacting with the SDK's API. Make sure to replace `MY_API_KEY` with your public API key that you just retrieved.
> **Note:** By default, Superwall handles basic subscription-related logic for you. However, if you’d like
> greater control over this process (e.g. if you’re using RevenueCat), you’ll want to pass in a
> `PurchaseController` to your configuration call and manually set the `subscriptionStatus`. You can
> also pass in `SuperwallOptions` to customize the appearance and behavior of the SDK. See
> [Purchases and Subscription Status](/docs/sdk/guides/advanced-configuration) for more.
You've now configured Superwall!
:::flutter
For further help, check out our [Flutter example apps](https://github.com/superwall/Superwall-Flutter/tree/master/example) for working examples of implementing the Superwall SDK.
:::
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Presenting Paywalls
Control access to premium features with Superwall placements.
This allows you to register a [placement](/docs/dashboard/dashboard-campaigns/campaigns-placements) to access a feature that may or may not be paywalled later in time. It also allows you to choose whether the user can access the feature even if they don't make a purchase.
Here's an example.
#### With Superwall
:::flutter
```dart Flutter
void pressedWorkoutButton() {
// remotely decide if a paywall is shown and if
// navigation.startWorkout() is a paid-only feature
Superwall.shared.registerPlacement('StartWorkout', feature: () {
navigation.startWorkout();
});
}
```
:::
#### Without Superwall
:::flutter
```dart Flutter
void pressedWorkoutButton() {
if (user.hasActiveSubscription) {
navigation.startWorkout();
} else {
navigation.presentPaywall().then((result) {
if (result) {
navigation.startWorkout();
} else {
// user didn't pay, developer decides what to do
}
});
}
}
```
:::
### How registering placements presents paywalls
You can configure `"StartWorkout"` to present a paywall by [creating a campaign, adding the placement, and adding a paywall to an audience](/docs/dashboard/dashboard-campaigns/campaigns) in the dashboard.
1. The SDK retrieves your campaign settings from the dashboard on app launch.
2. When a placement is called that belongs to a campaign, audiences are evaluated ***on device*** and the user enters an experiment — this means there's no delay between registering a placement and presenting a paywall.
3. If it's the first time a user is entering an experiment, a paywall is decided for the user based on the percentages you set in the dashboard
4. Once a user is assigned a paywall for an audience, they will continue to see that paywall until you remove the paywall from the audience or reset assignments to the paywall.
5. After the paywall is closed, the Superwall SDK looks at the *Feature Gating* value associated with your paywall, configurable from the paywall editor under General > Feature Gating (more on this below)
1. If the paywall is set to ***Non Gated***, the `feature:` closure on `register(placement: ...)` gets called when the paywall is dismissed (whether they paid or not)
2. If the paywall is set to ***Gated***, the `feature:` closure on `register(placement: ...)` gets called only if the user is already paying or if they begin paying.
6. If no paywall is configured, the feature gets executed immediately without any additional network calls.
Given the low cost nature of how register works, we strongly recommend registering **all core functionality** in order to remotely configure which features you want to gate – **without an app update**.
:::flutter
```dart Flutter
// on the welcome screen
void pressedSignUp() {
Superwall.shared.registerPlacement("SignUp", feature: () {
navigation.beginOnboarding();
});
}
// In another view controller
void pressedWorkoutButton() {
Superwall.shared.registerPlacement("StartWorkout", feature: () {
navigation.startWorkout();
});
}
```
:::
### Automatically Registered Placements
The SDK [automatically registers](/docs/sdk/guides/3rd-party-analytics/tracking-analytics) some internal placements which can be used to present paywalls:
### Register. Everything.
To provide your team with ultimate flexibility, we recommend registering *all* of your analytics events, even if you don't pass feature blocks through. This way you can retroactively add a paywall almost anywhere – **without an app update**!
If you're already set up with an analytics provider, you'll typically have an `Analytics.swift` singleton (or similar) to disperse all your events from. Here's how that file might look:
### Getting a presentation result
Use `getPresentationResult(forPlacement:params:)` when you need to ask the SDK what would happen when registering a placement — without actually showing a paywall. Superwall evaluates the placement and its audience filters then returns a `PresentationResult`. You can use this to adapt your app's behavior based on the outcome (such as showing a lock icon next to a pro feature if they aren't subscribed).
In short, this lets you peek at the outcome first and decide how your app should respond:
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Handling Deep Links
1. Previewing paywalls on your device before going live.
2. Deep linking to specific [campaigns](/docs/dashboard/dashboard-campaigns/campaigns).
:::flutter
3) Web Checkout [Post-Checkout Redirecting](/docs/sdk/guides/web-checkout/post-checkout-redirecting)
:::
## Setup
:::flutter
There are two ways to deep link into your app: URL Schemes and Universal Links (iOS only).
:::
### Adding a Custom URL Scheme
:::flutter
#### iOS
Open **Xcode**. In your **info.plist**, add a row called **URL Types**. Expand the automatically created **Item 0**, and inside the **URL identifier** value field, type your **Bundle ID**, e.g., **com.superwall.Superwall-SwiftUI**. Add another row to **Item 0** called **URL Schemes** and set its **Item 0** to a URL scheme you'd like to use for your app, e.g., **exampleapp**. Your structure should look like this:

With this example, the app will open in response to a deep link with the format **exampleapp\://**. You can [view Apple's documentation](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app) to learn more about custom URL schemes.
:::
:::flutter
#### Android
Add the following to your `AndroidManifest.xml` file:
```xml
```
This configuration allows your app to open in response to a deep link with the format `exampleapp://` from your `MainActivity` class.
:::
:::flutter
### Adding a Universal Link (iOS only)
> **Note:** Only required for [Web Checkout](/docs/web-checkout), otherwise you can skip this step.
Before configuring in your app, first [create](/docs/web-checkout/web-checkout-creating-an-app) and [configure](/docs/web-checkout/web-checkout-configuring-stripe-keys-and-settings) your Stripe app on the Superwall Dashboard.
#### Add a new capability in Xcode
Select your target in Xcode, then select the **Signing & Capabilities** tab. Click on the **+ Capability** button and select **Associated Domains**. This will add a new capability to your app.

#### Set the domain
Next, enter in the domain using the format `applinks:[your-web-checkout-url]`. This is the domain that Superwall will use to handle universal links. Your `your-web-checkout-url` value should match what's under the "Web Paywall Domain" section.

#### Testing
> **Warning:** If your Stripe app's iOS Configuration is incomplete or incorrect, universal links **will not work**
You can verify that your universal links are working a few different ways. Keep in mind that it usually takes a few minutes for the associated domain file to propagate:
1. **Use Branch's online validator:** If you visit [branch.io's online validator](https://branch.io/resources/aasa-validator//) and enter in your web checkout URL, it'll run a similar check and provide the same output.
2. **Test opening a universal link:** If the validation passes from either of the two steps above, make sure visiting a universal link opens your app. Your link should be formatted as `https://[your web checkout link]/app-link/` — which is simply your web checkout link with `/app-link/` at the end. This is easiest to test on device, since you have to tap an actual link instead of visiting one directly in Safari or another browser. In the iOS simulator, adding the link in the Reminders app works too:

:::
### Handling Deep Links
:::flutter
In your `pubspec.yaml` file, add the `uni_links` package to your dependencies:
```yaml
dependencies:
uni_links: ^0.5.1
```
Then, run `flutter pub get` to install the package. Next, in your Flutter app, use the Superwall SDK to handle the deep link via `Superwall.shared.handleDeepLink(theLink);`. Here's a complete example:
```dart
import 'package:flutter/material.dart';
import 'package:superwallkit_flutter/superwallkit_flutter.dart';
import 'package:uni_links/uni_links.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
MyApp();
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State {
@override
void initState() {
super.initState();
Superwall.configure('YOUR_SUPERWALL_API_KEY');
_handleIncomingLinks();
}
void _handleIncomingLinks() {
uriLinkStream.listen((Uri? uri) {
if (uri != null) {
Superwall.shared.handleDeepLink(uri);
}
}, onError: (Object err) {
print('Error receiving incoming link: $err');
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Text(
'Deep Link Preview Example',
style: TextStyle(fontSize: 24),
),
),
),
);
}
}
```
:::
## Previewing Paywalls
Next, build and run your app on your phone.
Then, head to the Superwall Dashboard. Click on **Settings** from the Dashboard panel on the left, then select **General**:

With the **General** tab selected, type your custom URL scheme, without slashes, into the **Apple Custom URL Scheme** field:

Next, open your paywall from the dashboard and click **Preview**. You'll see a QR code appear in a pop-up:


On your device, scan this QR code. You can do this via Apple's Camera app. This will take you to a paywall viewer within your app, where you can preview all your paywalls in different configurations.
## Using Deep Links to Present Paywalls
Deep links can also be used as a placement in a campaign to present paywalls. Simply add `deepLink_open` as an placement, and the URL parameters of the deep link can be used as parameters! You can also use custom placements for this purpose. [Read this doc](/docs/dashboard/guides/presenting-paywalls-from-one-another) for examples of both.
## Related deep link guides
:::flutter
* [Handling Deep Links](/docs/sdk/guides/handling-deep-links) — Use `handleDeepLink` with the `deepLink_open` standard placement and dashboard campaign rules to present paywalls from deep links, without hardcoding routing logic in your app.
* [Using Superwall Deep Links](/docs/sdk/guides/superwall-deep-links) — Trigger paywalls or custom in-app behavior using Superwall-hosted URLs at `*.superwall.app/app-link/...`.
:::
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Install the SDK
Install the Superwall Flutter SDK via pub package manager.
## Overview
To see the latest release, [check out the repository](https://github.com/superwall/Superwall-Flutter).
## Install via pubspec.yaml
To use Superwall in your Flutter project, add `superwallkit_flutter` as a dependency in your `pubspec.yaml` file:
```yaml
dependencies:
superwallkit_flutter: ^2.4.12
```
After adding the dependency, run `dart pub get` in your terminal to fetch the package.
## Install via Command Line (Alternative)
You can also add the dependency directly from your terminal using the following command:
```bash
$ flutter pub add superwallkit_flutter
```
### iOS Deployment Target
Superwall requires iOS 14.0 or higher. Ensure your Flutter project's iOS deployment target is 14.0 or higher by updating ios/Podfile.
```ruby
platform :ios, '14.0'
```
### Android Configuration
First, add our SuperwallActivity to your `AndroidManifest.xml`:
```xml
```
Superwall requires a minimum SDK version of 26 or higher and a minimum compile SDK target of 34. Ensure your Flutter project's Android minimal SDK target is set to 26 or higher and that your compilation SDK target is 34 by updating `android/app/build.gradle`.
```groovy gradle
android {
...
compileSdkVersion 34
...
defaultConfig {
...
minSdkVersion 26
...
}
}
```
To use the compile target SDK 34, you'll also need to ensure your Gradle version is 8.6 or higher and your Android Gradle plugin version is 8.4 or higher.
You can do that by checking your `gradle/wrapepr/gradle-wrapper.properties` file and ensuring it is updated to use the latest Gradle version:
```properties
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
```
And your `android/build.gradle` file is updated to use the latest Android Gradle plugin version:
```groovy gradle
plugins {
id 'com.android.application' version '8.4.1' apply false
}
```
To find the latest compatible versions, you can always check the [Gradle Plugin Release Notes](https://developer.android.com/build/releases/gradle-plugin).
**And you're done!**
Now you're ready to configure the SDK 👇
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Setting User Attributes
By setting user attributes, you can display information about the user on the paywall. You can also define [audiences](/docs/dashboard/dashboard-campaigns/campaigns-audience) in a campaign to determine which paywall to show to a user, based on their user attributes.
:::flutter
> **Note:** If a paywall uses the **Set user attributes** action, the merged attributes are sent back to your app via `SuperwallDelegate.userAttributesDidChange(newAttributes:)`.
:::
You do this by passing an attributes dictionary or object to the SDK:
:::flutter
```dart Flutter
Map attributes = {
"name": user.name,
"apnsToken": user.apnsTokenString,
"email": user.email,
"username": user.username,
"profilePic": user.profilePicUrl,
"stripe_customer_id": user.stripeCustomerId, // Optional: For Stripe checkout prefilling
};
Superwall.shared.setUserAttributes(attributes); // (merges existing attributes)
```
:::
## Usage
This is a merge operation, such that if the existing user attributes
already has a value for a given property, the old value is overwritten. Other
existing properties will not be affected.
:::flutter
The Flutter SDK accepts non-null user attribute values. It does not currently support deleting an attribute by passing `null`.
:::
You can reference user attributes in [audience filters](/docs/dashboard/dashboard-campaigns/campaigns-audience) to help decide when to display your paywall. When you configure your paywall, you can also reference the user attributes in its text variables. For more information on how to that, see [Configuring a Paywall](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-overview).
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Tracking Subscription State
Here's how to view whether or not a user is on a paid plan in Flutter.
Superwall tracks the subscription state of a user for you. However, there are times in your app where you need to know if a user is on a paid plan or not. For example, you might want to conditionally show certain UI elements or enable premium features based on their subscription status.
## Using subscriptionStatus stream
The easiest way to track subscription status in Flutter is by listening to the `subscriptionStatus` stream:
```dart
class _MyAppState extends State {
StreamSubscription? _subscription;
SubscriptionStatus _currentStatus = SubscriptionStatus.unknown;
@override
void initState() {
super.initState();
_subscription = Superwall.shared.subscriptionStatus.listen((status) {
setState(() {
_currentStatus = status;
});
switch (status) {
case SubscriptionStatus.active:
print('User has active subscription');
_showPremiumContent();
break;
case SubscriptionStatus.inactive:
print('User is on free plan');
_showFreeContent();
break;
case SubscriptionStatus.unknown:
print('Subscription status unknown');
_showLoadingState();
break;
}
});
}
@override
void dispose() {
_subscription?.cancel();
super.dispose();
}
}
```
The `SubscriptionStatus` enum has three possible values:
* `SubscriptionStatus.unknown` - Status is not yet determined
* `SubscriptionStatus.active` - User has an active subscription
* `SubscriptionStatus.inactive` - User has no active subscription
Use the `isActive` convenience property when you only need to know if the user is subscribed:
```dart
Superwall.shared.subscriptionStatus.listen((status) {
if (status.isActive) {
_showPremiumContent();
} else {
_showFreeContent();
}
});
```
## Using SuperwallBuilder widget
For reactive UI updates based on subscription status, use the `SuperwallBuilder` widget:
```dart
SuperwallBuilder(
builder: (context, subscriptionStatus) {
switch (subscriptionStatus) {
case SubscriptionStatus.active:
return PremiumContent();
case SubscriptionStatus.inactive:
return FreeContent();
default:
return LoadingIndicator();
}
},
)
```
This widget automatically rebuilds whenever the subscription status changes, making it perfect for conditionally rendering UI:
```dart
class SubscriptionStatusDisplay extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SuperwallBuilder(
builder: (context, status) => Center(
child: Text('Subscription Status: $status'),
),
);
}
}
```
## Using StreamBuilder
You can also use Flutter's `StreamBuilder` for more control over the stream subscription:
```dart
class PremiumFeatureButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StreamBuilder(
stream: Superwall.shared.subscriptionStatus,
builder: (context, snapshot) {
final status = snapshot.data ?? SubscriptionStatus.unknown;
final isActive = status.isActive;
return ElevatedButton(
onPressed: isActive
? _accessPremiumFeature
: _showPaywall,
child: Text(
isActive
? 'Access Premium Feature'
: 'Upgrade to Premium',
),
);
},
);
}
void _accessPremiumFeature() {
// Access premium feature
}
void _showPaywall() {
Superwall.shared.registerPlacement('premium_feature');
}
}
```
## Checking subscription status programmatically
If you need to check the subscription status at a specific moment without listening to the stream:
```dart
Future checkSubscription() async {
// Note: You'll need to get the current value from the stream
final subscription = Superwall.shared.subscriptionStatus.listen((status) {
if (status.isActive) {
// User is subscribed
enablePremiumFeatures();
} else {
// User is not subscribed
showUpgradePrompt();
}
});
// Remember to cancel when done
subscription.cancel();
}
```
## Setting subscription status
When using Superwall with a custom purchase controller or third-party billing service, you need to manually update the subscription status. Here's how to sync with RevenueCat:
```dart
class RCPurchaseController extends PurchaseController {
Future syncSubscriptionStatus() async {
try {
final customerInfo = await Purchases.getCustomerInfo();
final hasActiveSubscription = customerInfo.entitlements.active.isNotEmpty;
if (hasActiveSubscription) {
final entitlements = customerInfo.entitlements.active.keys
.map((id) => Entitlement(id: id))
.toSet();
await Superwall.shared.setSubscriptionStatus(
SubscriptionStatusActive(entitlements: entitlements)
);
} else {
await Superwall.shared.setSubscriptionStatus(
SubscriptionStatusInactive()
);
}
} catch (e) {
print('Failed to sync subscription status: $e');
}
}
@override
Future purchaseFromAppStore(String productId) async {
try {
final result = await Purchases.purchaseProduct(productId);
if (result.isSuccess) {
// Sync status after successful purchase
await syncSubscriptionStatus();
return PurchaseResult.purchased;
}
return PurchaseResult.failed;
} catch (e) {
return PurchaseResult.failed;
}
}
}
```
You can also listen for subscription changes from your payment service:
```dart
void setupSubscriptionListener() {
myPaymentService.addSubscriptionStatusListener((subscriptionInfo) {
final entitlements = subscriptionInfo.entitlements.active.keys
.map((id) => Entitlement(id: id))
.toSet();
final hasActiveSubscription = subscriptionInfo.isActive;
if (hasActiveSubscription) {
Superwall.shared.setSubscriptionStatus(
SubscriptionStatusActive(entitlements: entitlements)
);
} else {
Superwall.shared.setSubscriptionStatus(
SubscriptionStatusInactive()
);
}
});
}
```
## Using SuperwallDelegate
You can also listen for subscription status changes using the `SuperwallDelegate`:
```dart
class _MyAppState extends State implements SuperwallDelegate {
@override
void initState() {
super.initState();
// Set delegate
Superwall.shared.setDelegate(this);
}
@override
void subscriptionStatusDidChange(SubscriptionStatus newValue) {
print('Subscription status changed to: $newValue');
switch (newValue) {
case SubscriptionStatus.active:
print('User is now premium');
_handlePremiumUser();
break;
case SubscriptionStatus.inactive:
print('User is now free');
_handleFreeUser();
break;
case SubscriptionStatus.unknown:
print('Status unknown');
break;
}
}
void _handlePremiumUser() {
// Update UI or app state for premium user
}
void _handleFreeUser() {
// Update UI or app state for free user
}
}
```
## Handling subscription expiry
If you need to check for subscription expiry manually:
```dart
Future checkSubscriptionExpiry() async {
final expiryDate = await MyPaymentService.getSubscriptionExpiry();
if (expiryDate.isBefore(DateTime.now())) {
// Subscription has expired
await Superwall.shared.setSubscriptionStatus(
SubscriptionStatusInactive()
);
// Show renewal prompt
_showRenewalPrompt();
}
}
```
## Superwall checks subscription status for you
Remember that the Superwall SDK uses its [audience filters](/docs/dashboard/dashboard-campaigns/campaigns-audience#matching-to-entitlements) for determining when to show paywalls. You generally don't need to wrap your calls to register placements with subscription status checks:
```dart
// ❌ Unnecessary
final subscription = Superwall.shared.subscriptionStatus.listen((status) {
if (status != SubscriptionStatus.active) {
Superwall.shared.registerPlacement('campaign_trigger');
}
});
// ✅ Just register the placement
Superwall.shared.registerPlacement('campaign_trigger');
```
In your [audience filters](/docs/dashboard/dashboard-campaigns/campaigns-audience#matching-to-entitlements), you can specify whether the subscription state should be considered, which keeps your codebase cleaner and puts the "Should this paywall show?" logic where it belongs—in the Superwall dashboard.
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# User Management
### Anonymous Users
Superwall automatically generates a random user ID that persists internally until the user deletes/reinstalls your app.
You can call `Superwall.shared.reset()` to reset this ID and clear any paywall assignments.
### Identified Users
If you use your own user management system, call `identify(userId:options:)` when you have a user's identity. This will alias your `userId` with the anonymous Superwall ID enabling us to load the user’s assigned paywalls.
Calling `Superwall.shared.reset()` will reset the on-device userId to a random ID and clear the paywall assignments.
:::flutter
When you ship the Flutter SDK on Android, you must explicitly set `options.passIdentifiersToPlayStore = true` (for example, `final options = SuperwallOptions()..passIdentifiersToPlayStore = true;`) if you need the un-hashed `appUserId` to flow through Google Play's **`obfuscatedExternalAccountId`** field. This flag has no effect on iOS builds. Follow [Google's rules](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setObfuscatedAccountId) so the value is not rejected.
:::
:::flutter
```dart Flutter
// After retrieving a user's ID, e.g. from logging in or creating an account
Superwall.shared.identify(user.id);
// When the user signs out
Superwall.shared.reset();
```
:::
> **Note:** **Advanced Use Case**You can supply an `IdentityOptions` object, whose property `restorePaywallAssignments` you can set to `true`. This tells the SDK to wait to restore paywall assignments from the server before presenting any paywalls. This should only be used in advanced use cases. If you expect users of your app to switch accounts or delete/reinstall a lot, you'd set this when users log in to an existing account.
### Best Practices for a Unique User ID
* Do NOT make your User IDs guessable – they are public facing.
* Do NOT set emails as User IDs – this isn't GDPR compliant.
* Do NOT set IDFA or DeviceIds as User IDs – these are device specific / easily rotated by the operating system.
* Do NOT hardcode strings as User IDs – this will cause every user to be treated as the same user by Superwall.
### Identifying users from App Store server events
On iOS, Superwall always supplies an [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/3749440-appaccounttoken) with every StoreKit 2 transaction:
| Scenario | Value used for `appAccountToken` |
| -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| You’ve called `Superwall.shared.identify(userId:)` | The exact `userId` you passed |
| You *haven’t* called `identify` yet | The UUID automatically generated for the anonymous user (the **alias ID**), **without** the `$SuperwallAlias:` prefix |
| You passed a non‑UUID `userId` to `identify` | StoreKit rejects it; Superwall falls back to the alias UUID |
Because the SDK falls back to the alias UUID, purchase notifications sent to your server always include a stable, unique identifier—even before the user signs in.
:::flutter
> **Warning:** On iOS, `appAccountToken` must be a UUID to be accepted by StoreKit.If the `userId` you pass to `identify` is not a valid UUID string, StoreKit will not accept it for `appAccountToken` and the SDK will fall back to the anonymous alias UUID. This can cause the identifier in App Store Server Notifications to differ from the `userId` you passed. See Apple's docs: [appAccountToken](https://developer.apple.com/documentation/appstoreserverapi/appaccounttoken).
:::
```swift
// Generate and use a UUID user ID in Swift
let userId = UUID().uuidString
Superwall.shared.identify(userId: userId)
```
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# CustomerInfo
Contains the latest subscription and entitlement information about the customer.
## Purpose
Represents the complete customer information including all subscription transactions, non-subscription transactions, entitlements, and user identification.
## Signature
```dart
class CustomerInfo {
final List subscriptions;
final List nonSubscriptions;
final List entitlements;
final String userId;
}
```
## Properties
## Usage
Getting customer info:
```dart
final customerInfo = await Superwall.shared.getCustomerInfo();
// Access user ID
print('User ID: ${customerInfo.userId}');
// Check entitlements
final activeEntitlements = customerInfo.entitlements
.where((e) => e.isActive)
.toList();
// Access subscriptions
for (final subscription in customerInfo.subscriptions) {
if (subscription.isActive) {
print('Active subscription: ${subscription.productId}');
}
}
```
Checking for specific entitlements:
```dart
final customerInfo = await Superwall.shared.getCustomerInfo();
final hasPremium = customerInfo.entitlements.any(
(entitlement) =>
entitlement.id == 'premium' &&
entitlement.isActive,
);
if (hasPremium) {
// User has premium access
showPremiumContent();
}
```
Filtering active subscriptions:
```dart
final customerInfo = await Superwall.shared.getCustomerInfo();
final activeSubscriptions = customerInfo.subscriptions
.where((sub) => sub.isActive && !sub.isRevoked)
.toList();
print('User has ${activeSubscriptions.length} active subscriptions');
```
## Related
* [`getCustomerInfo()`](/docs/flutter/sdk-reference/getCustomerInfo) - Method to retrieve customer info
* [`SubscriptionTransaction`](/docs/flutter/sdk-reference/SubscriptionTransaction) - Subscription transaction details
* [`NonSubscriptionTransaction`](/docs/flutter/sdk-reference/NonSubscriptionTransaction) - Non-subscription transaction details
* [`Entitlements`](/docs/flutter/sdk-reference/Entitlements) - Entitlement information
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Entitlements
Container for all entitlements available to the user, organized by status.
## Purpose
Provides organized access to user entitlements with methods to filter and query them. Returned by [`getEntitlements()`](/docs/flutter/sdk-reference/getEntitlements).
## Signature
```dart
class Entitlements {
final Set active;
final Set inactive;
final Set all;
final Set web;
Future> byProductIds(Set productIds);
}
```
## Properties
## Methods
### byProductIds()
Filters entitlements by product IDs. Returns all entitlements that contain any of the specified product IDs.
**Signature:**
```dart
Future> byProductIds(Set productIds)
```
**Parameters:**
* `productIds` - A set of product identifiers to search for
**Returns:** A future that resolves to a set of entitlements that contain any of the specified product IDs.
## Usage
Accessing different entitlement sets:
```dart
final entitlements = await Superwall.shared.getEntitlements();
// Check active entitlements
if (entitlements.active.isNotEmpty) {
print('User has ${entitlements.active.length} active entitlements');
}
// Check web checkout entitlements
if (entitlements.web.isNotEmpty) {
print('User has web checkout entitlements');
}
```
Filtering by product IDs:
```dart
final entitlements = await Superwall.shared.getEntitlements();
// Find entitlements for specific products
final premiumEntitlements = await entitlements.byProductIds({
'premium_monthly',
'premium_yearly',
'premium_lifetime',
});
if (premiumEntitlements.isNotEmpty) {
print('User has premium access');
for (final entitlement in premiumEntitlements) {
print('Premium entitlement: ${entitlement.id}');
}
}
```
Checking for specific entitlement:
```dart
final entitlements = await Superwall.shared.getEntitlements();
final hasPro = entitlements.all.any(
(entitlement) => entitlement.id == 'pro' && entitlement.isActive,
);
if (hasPro) {
// User has pro access
showProFeatures();
}
```
## Related
* [`getEntitlements()`](/docs/flutter/sdk-reference/getEntitlements) - Method to retrieve entitlements
* [`Entitlements`](/docs/flutter/sdk-reference/Entitlements) - Entitlement information
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# IntegrationAttribute
Attributes for third-party integrations with Superwall.
## Purpose
Enumeration of integration attributes that allow you to sync user identifiers from your analytics and attribution providers with Superwall. This enables better user tracking and attribution across platforms.
## Signature
```dart
enum IntegrationAttribute {
adjustId,
amplitudeDeviceId,
amplitudeUserId,
appsflyerId,
appstackId,
brazeAliasName,
brazeAliasLabel,
onesignalId,
fbAnonId,
firebaseAppInstanceId,
iterableUserId,
iterableCampaignId,
iterableTemplateId,
mixpanelDistinctId,
mparticleId,
clevertapId,
airshipChannelId,
kochavaDeviceId,
tenjinId,
posthogUserId,
customerioId;
}
```
## Values
## Usage
Setting a single integration attribute:
```dart
await Superwall.shared.setIntegrationAttribute(
IntegrationAttribute.mixpanelDistinctId,
'user_123',
);
```
Setting multiple integration attributes:
```dart
await Superwall.shared.setIntegrationAttributes({
IntegrationAttribute.mixpanelDistinctId: 'user_123',
IntegrationAttribute.amplitudeUserId: 'amp_456',
IntegrationAttribute.adjustId: 'adjust_789',
IntegrationAttribute.appstackId: 'appstack_abc',
});
```
Removing an integration attribute:
```dart
// Set to null to remove
await Superwall.shared.setIntegrationAttribute(
IntegrationAttribute.mixpanelDistinctId,
null,
);
```
Syncing with analytics providers:
```dart
void _syncAnalyticsIds() async {
// Get IDs from your analytics SDKs
final mixpanelId = await MixpanelSDK.getDistinctId();
final amplitudeId = await AmplitudeSDK.getUserId();
// Sync with Superwall
await Superwall.shared.setIntegrationAttributes({
IntegrationAttribute.mixpanelDistinctId: mixpanelId,
IntegrationAttribute.amplitudeUserId: amplitudeId,
});
}
```
## Related
* [`setIntegrationAttribute()`](/docs/flutter/sdk-reference/setIntegrationAttribute) - Set a single integration attribute
* [`setIntegrationAttributes()`](/docs/flutter/sdk-reference/setIntegrationAttributes) - Set multiple integration attributes at once
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# NonSubscriptionTransaction
Represents a non-subscription transaction (consumables and non-consumables).
> **Info:** The `store` field was added in 2.4.7.
## Purpose
Provides details about one-time purchases in [`CustomerInfo`](/docs/flutter/sdk-reference/CustomerInfo), including which store fulfilled the purchase.
## Properties
## Store values (2.4.7+)
`appStore`, `stripe`, `paddle`, `playStore`, `superwall`, `other`.
## Usage
Inspect non-subscription purchases:
```dart
final customerInfo = await Superwall.shared.getCustomerInfo();
for (final purchase in customerInfo.nonSubscriptions) {
print('Product: ${purchase.productId}');
print('Store: ${purchase.store}');
print('Consumable: ${purchase.isConsumable}');
print('Revoked: ${purchase.isRevoked}');
}
```
## Related
* [`CustomerInfo`](/docs/flutter/sdk-reference/CustomerInfo) - Source of transaction data
* [`SubscriptionTransaction`](/docs/flutter/sdk-reference/SubscriptionTransaction) - Subscription transactions
* [`getCustomerInfo()`](/docs/flutter/sdk-reference/getCustomerInfo) - Fetch customer info
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# PaywallOptions
Configuration for paywall presentation and behavior in the Superwall Flutter SDK.
> **Tip:** `PaywallOptions` is provided via the `paywalls` parameter on [`SuperwallOptions`](/docs/flutter/sdk-reference/SuperwallOptions) and is passed when calling [`configure`](/docs/flutter/sdk-reference/configure).
## Purpose
Customize how paywalls look and behave, including preload behavior, alerts, dismissal, and haptics.
## Signature
```dart
class PaywallOptions {
bool isHapticFeedbackEnabled = true;
RestoreFailed restoreFailed = RestoreFailed();
bool shouldShowPurchaseFailureAlert = true;
bool shouldPreload = true;
Map preloadDeviceOverrides = const {};
bool automaticallyDismiss = true;
TransactionBackgroundView transactionBackgroundView = TransactionBackgroundView.spinner;
bool shouldShowWebRestorationAlert = true;
Map? overrideProductsByName;
bool shouldShowWebPurchaseConfirmationAlert = true;
void Function(PaywallInfo?)? onBackPressed;
}
class RestoreFailed {
String title = 'No Subscription Found';
String message = "We couldn't find an active subscription for your account.";
String closeButtonTitle = 'Okay';
}
enum TransactionBackgroundView { spinner, none }
enum DeviceTier { ultraLow, low, mid, high, ultraHigh, unknown }
```
## Parameters
## Usage
```dart
final paywallOptions = PaywallOptions()
..isHapticFeedbackEnabled = true
..shouldShowPurchaseFailureAlert = false
..shouldPreload = true
..automaticallyDismiss = true
..transactionBackgroundView = TransactionBackgroundView.spinner
..overrideProductsByName = {
'primary': 'com.example.premium_monthly',
'tertiary': 'com.example.premium_annual',
}
..shouldShowWebRestorationAlert = true
..shouldShowWebPurchaseConfirmationAlert = true
..onBackPressed = (paywallInfo) {
// Android-only callback
Superwall.shared.dismiss();
};
final options = SuperwallOptions(
paywalls: paywallOptions,
);
await Superwall.configure(
'pk_your_api_key',
options: options,
);
```
## Related
* [`SuperwallOptions`](/docs/flutter/sdk-reference/SuperwallOptions)
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# PaywallPresentationHandler
A handler class that provides status updates for paywall presentation in registerPlacement() calls.
> **Info:** Use this handler when you need fine-grained control over paywall events for a specific [`registerPlacement()`](/docs/flutter/sdk-reference/register) call, rather than global events via [`SuperwallDelegate`](/docs/flutter/sdk-reference/SuperwallDelegate).
> **Note:** This handler is specific to the individual `registerPlacement()` call. For global paywall events across your app, use [`SuperwallDelegate`](/docs/flutter/sdk-reference/SuperwallDelegate) instead.
## Purpose
Provides callbacks for paywall lifecycle events when using [`registerPlacement()`](/docs/flutter/sdk-reference/register) with a specific handler instance.
## Signature
```dart
class PaywallPresentationHandler {
void onPresent(Function(PaywallInfo) handler);
void onDismiss(Function(PaywallInfo, PaywallResult) handler);
void onSkip(Function(PaywallSkippedReason) handler);
void onError(Function(String) handler);
void onCustomCallback(
Future Function(CustomCallback) handler,
);
}
```
## Parameters
## Returns / State
Each method returns `void` and configures the handler for the specific paywall lifecycle event.
## CustomCallback (2.4.8+)
The `onCustomCallback` handler receives a `CustomCallback` object.
## CustomCallbackResult (2.4.8+)
Return a `CustomCallbackResult` from your callback handler to control paywall flow:
```dart
CustomCallbackResult.success([Map? data])
CustomCallbackResult.failure([Map? data])
```
Use `data` to send values back to the paywall, available as `callbacks..data.`.
## PaywallInfo State (2.4.8+)
`PaywallInfo` now includes `state`, a `Map?` with current paywall state values.
## Usage
Basic handler setup:
```dart
Future _registerFeatureWithHandler() async {
final handler = PaywallPresentationHandler();
handler.onPresent((paywallInfo) {
print('Paywall presented: ${paywallInfo.identifier}');
print('Paywall state: ${paywallInfo.state}');
});
handler.onDismiss((paywallInfo, result) {
print('Paywall dismissed with result: $result');
switch (result) {
case PaywallResult.purchased:
_showSuccessMessage();
break;
case PaywallResult.cancelled:
_showPromotionalOffer();
break;
case PaywallResult.restored:
_updateUIForActiveSubscription();
break;
}
});
await Superwall.shared.registerPlacement(
'premium_feature',
params: {'source': 'feature_screen'},
handler: handler,
feature: () {
_unlockPremiumFeature();
},
);
}
```
Handle skip, error, and custom callbacks:
```dart
Future _setupComprehensiveHandler() async {
final handler = PaywallPresentationHandler();
handler.onSkip((reason) {
print('Paywall skipped: $reason');
switch (reason) {
case PaywallSkippedReason.userIsSubscribed:
_proceedToFeature();
break;
case PaywallSkippedReason.holdout:
_proceedToFeature();
break;
default:
break;
}
});
handler.onError((error) {
print('Paywall error: $error');
_showErrorDialog(error);
});
handler.onCustomCallback((callback) async {
switch (callback.name) {
case 'validate_email':
final email = callback.variables?['email'] as String?;
if (email != null && email.contains('@')) {
return CustomCallbackResult.success({'validated': true});
}
return CustomCallbackResult.failure({'error': 'Invalid email'});
default:
return CustomCallbackResult.failure({'error': 'Unknown callback'});
}
});
await Superwall.shared.registerPlacement(
'remove_ads',
handler: handler,
feature: () {
_hideAdsFromUI();
},
);
}
```
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# PresentationResult
The result of a paywall presentation attempt.
## Purpose
Represents the possible outcomes when checking or presenting a paywall. Used by [`getPresentationResult()`](/docs/flutter/sdk-reference/getPresentationResult) and paywall presentation handlers.
## Signature
```dart
sealed class PresentationResult {
factory PresentationResult.placementNotFound() = PlacementNotFoundPresentationResult;
factory PresentationResult.noAudienceMatch() = NoAudienceMatchPresentationResult;
factory PresentationResult.paywall(Experiment experiment) = PaywallPresentationResult;
factory PresentationResult.holdout(Experiment experiment) = HoldoutPresentationResult;
factory PresentationResult.paywallNotAvailable() = PaywallNotAvailablePresentationResult;
}
class PlacementNotFoundPresentationResult extends PresentationResult;
class NoAudienceMatchPresentationResult extends PresentationResult;
class PaywallPresentationResult extends PresentationResult {
final Experiment experiment;
}
class HoldoutPresentationResult extends PresentationResult {
final Experiment experiment;
}
class PaywallNotAvailablePresentationResult extends PresentationResult;
```
## Cases
## Experiment Information
When a paywall is presented or the user is in a holdout group, the result includes an `Experiment` object:
```dart
class Experiment {
final String id;
final String groupId;
}
```
* `id` - The unique identifier for the experiment
* `groupId` - The identifier for the experiment group the user is in
## Usage
Handling different presentation results:
```dart
final result = await Superwall.shared.getPresentationResult('premium_feature');
if (result is PaywallPresentationResult) {
// Paywall would be shown
final experimentId = result.experiment.id;
final groupId = result.experiment.groupId;
print('Experiment: $experimentId, Group: $groupId');
} else if (result is HoldoutPresentationResult) {
// User is in holdout
print('User in holdout for experiment ${result.experiment.id}');
} else if (result is NoAudienceMatchPresentationResult) {
// No audience match
print('No audience match for placement');
} else if (result is PlacementNotFoundPresentationResult) {
// Placement not found
print('Placement not found');
} else if (result is PaywallNotAvailablePresentationResult) {
// Paywall not available
print('Paywall not available');
}
```
Using pattern matching:
```dart
final result = await Superwall.shared.getPresentationResult('premium_feature');
switch (result) {
case PaywallPresentationResult(experiment: final exp):
// Handle paywall presentation
print('Paywall for experiment ${exp.id}');
case HoldoutPresentationResult(experiment: final exp):
// Handle holdout
print('Holdout for experiment ${exp.id}');
case NoAudienceMatchPresentationResult():
// Handle no match
print('No audience match');
case PlacementNotFoundPresentationResult():
// Handle not found
print('Placement not found');
case PaywallNotAvailablePresentationResult():
// Handle not available
print('Paywall not available');
}
```
## Related
* [`getPresentationResult()`](/docs/flutter/sdk-reference/getPresentationResult) - Gets presentation result without showing paywall
* [`registerPlacement()`](/docs/flutter/sdk-reference/register) - Registers and presents a paywall
* [`PaywallPresentationHandler`](/docs/flutter/sdk-reference/PaywallPresentationHandler) - Handles paywall presentation lifecycle
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# PurchaseController
An abstract class for handling custom purchase flows and subscription management.
> **Warning:** Implementing a custom PurchaseController is advanced functionality. Most developers should use the default purchase controller with RevenueCat integration.
> **Info:** For RevenueCat integration, see the [Using RevenueCat guide](/docs/flutter/guides/using-revenuecat) instead of implementing a custom PurchaseController.
## Purpose
Allows custom implementation of purchase flows, subscription validation, and cross-platform purchase handling.
## Signature
```dart
abstract class PurchaseController {
Future purchaseFromAppStore(String productId);
Future purchaseFromGooglePlay(
String productId,
String? basePlanId,
String? offerId,
);
Future restorePurchases();
}
```
## Parameters
## Returns / State
* `purchaseFromAppStore` and `purchaseFromGooglePlay` return `Future`
* `restorePurchases` returns `Future`
## Usage
For most use cases, use RevenueCat integration instead:
See the [Using RevenueCat guide](/docs/flutter/guides/using-revenuecat) for complete setup instructions.
Custom implementation is only needed for advanced use cases where you have your own purchase handling system.
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# RedemptionResult
Result types returned when the Flutter SDK redeems a web checkout link.
## Purpose
Represents the result passed to [`didRedeemLink()`](/docs/flutter/sdk-reference/SuperwallDelegate) after the SDK handles a web checkout redemption link.
## Signature
```dart
sealed class RedemptionResult {}
class RedemptionResultSuccess extends RedemptionResult {
final String code;
final RedemptionInfo redemptionInfo;
}
class RedemptionResultError extends RedemptionResult {
final String code;
final ErrorInfo error;
}
class RedemptionResultExpiredCode extends RedemptionResult {
final String code;
final ExpiredCodeInfo info;
}
class RedemptionResultInvalidCode extends RedemptionResult {
final String code;
}
class RedemptionResultExpiredSubscription extends RedemptionResult {
final String code;
final RedemptionInfo redemptionInfo;
}
```
## Types
## Success Data
## Usage
```dart
void didRedeemLink(RedemptionResult result) {
switch (result) {
case RedemptionResultSuccess(redemptionInfo: final info):
final purchaser = info.purchaserInfo;
print('Redeemed for ${purchaser.appUserId}');
break;
case RedemptionResultError(error: final error):
print('Redemption failed: ${error.message}');
break;
case RedemptionResultExpiredCode(info: final info):
print('Code expired; resent email: ${info.resent}');
break;
case RedemptionResultInvalidCode():
print('Invalid code');
break;
case RedemptionResultExpiredSubscription():
print('Expired subscription');
break;
}
}
```
## Related
* [`StoreIdentifiers`](/docs/flutter/sdk-reference/StoreIdentifiers)
* [`SuperwallDelegate`](/docs/flutter/sdk-reference/SuperwallDelegate)
* [Post-Checkout Redirecting](/docs/flutter/guides/web-checkout/post-checkout-redirecting)
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# StoreIdentifiers
Store-specific identifiers returned with successful Flutter web checkout redemptions.
## Purpose
Identifies the store that fulfilled a redeemed web checkout purchase. Access this from `RedemptionResultSuccess.redemptionInfo.purchaserInfo.storeIdentifiers`.
## Signature
```dart
sealed class StoreIdentifiers {}
class StripeStoreIdentifiers extends StoreIdentifiers {
final String customerId;
final List subscriptionIds;
}
class PaddleStoreIdentifiers extends StoreIdentifiers {
final String customerId;
final List subscriptionIds;
}
class UnknownStoreIdentifiers extends StoreIdentifiers {
final String store;
final Map additionalInfo;
}
```
## Values
## Usage
```dart
void didRedeemLink(RedemptionResult result) {
if (result is! RedemptionResultSuccess) {
return;
}
final identifiers = result.redemptionInfo.purchaserInfo.storeIdentifiers;
switch (identifiers) {
case StripeStoreIdentifiers(
customerId: final customerId,
subscriptionIds: final subscriptionIds,
):
print('Stripe customer: $customerId');
print('Stripe subscriptions: $subscriptionIds');
break;
case PaddleStoreIdentifiers(
customerId: final customerId,
subscriptionIds: final subscriptionIds,
):
print('Paddle customer: $customerId');
print('Paddle subscriptions: $subscriptionIds');
break;
case UnknownStoreIdentifiers(
store: final store,
additionalInfo: final info,
):
print('Unknown store: $store, $info');
break;
}
}
```
## Related
* [`RedemptionResult`](/docs/flutter/sdk-reference/RedemptionResult)
* [Using RevenueCat](/docs/flutter/guides/web-checkout/using-revenuecat)
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# StoreTransaction
A public wrapper around store transaction data in Flutter SDK events.
## Purpose
Represents store transaction metadata attached to Superwall events and restore events.
## Signature
```dart
class StoreTransaction {
final String configRequestId;
final String appSessionId;
final DateTime? transactionDate;
final String originalTransactionIdentifier;
final String? storeTransactionId;
final DateTime? originalTransactionDate;
final String? webOrderLineItemID;
final String? appBundleId;
final String? subscriptionGroupId;
final bool? isUpgraded;
final DateTime? expirationDate;
final String? offerId;
final DateTime? revocationDate;
}
```
## Properties
## Usage
```dart
void handleSuperwallEvent(SuperwallEventInfo eventInfo) {
final transaction = eventInfo.event.transaction;
if (transaction == null) {
return;
}
print('Transaction: ${transaction.storeTransactionId}');
print('Original ID: ${transaction.originalTransactionIdentifier}');
}
```
## Related
* [`SuperwallDelegate`](/docs/flutter/sdk-reference/SuperwallDelegate)
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# SubscriptionTransaction
Represents a subscription transaction in the customer's purchase history.
> **Info:** The `offerType`, `subscriptionGroupId`, and `store` fields were added in 2.4.7.
## Purpose
Provides details about a single subscription transaction returned from [`CustomerInfo`](/docs/flutter/sdk-reference/CustomerInfo). Use this to understand renewal status, applied offers, and the store that fulfilled the purchase.
## Properties
## Offer types (2.4.7+)
* `trial` - introductory offer.
* `code` - offer redeemed with a promo code.
* `promotional` - promotional offer.
* `winback` - win-back offer (iOS 17.2+ only).
## Store values (2.4.7+)
`appStore`, `stripe`, `paddle`, `playStore`, `superwall`, `other`.
## Usage
Inspect subscription transactions:
```dart
final customerInfo = await Superwall.shared.getCustomerInfo();
for (final subscription in customerInfo.subscriptions) {
print('Product: ${subscription.productId}');
print('Active: ${subscription.isActive}');
print('Store: ${subscription.store}');
print('Offer: ${subscription.offerType}');
print('Group: ${subscription.subscriptionGroupId ?? "unknown"}');
}
```
## Related
* [`CustomerInfo`](/docs/flutter/sdk-reference/CustomerInfo) - Source of subscription data
* [`NonSubscriptionTransaction`](/docs/flutter/sdk-reference/NonSubscriptionTransaction) - Non-subscription transactions
* [`getCustomerInfo()`](/docs/flutter/sdk-reference/getCustomerInfo) - Fetch customer info
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# Superwall.shared
The shared Superwall instance that provides access to all SDK methods.
> **Warning:** You must call [`configure()`](/docs/flutter/sdk-reference/configure) before accessing `Superwall.shared`, or the app will crash.
## Purpose
Provides access to the configured Superwall instance after calling `configure()`.
## Signature
```dart
static Superwall get shared
```
## Returns / State
Returns the configured `Superwall` instance that can be used to access all SDK methods.
## Usage
Accessing the shared instance:
```dart
// After calling configure()
await Superwall.shared.registerPlacement('premium_feature');
await Superwall.shared.identify('user_123');
```
Reset the user:
```dart
await Superwall.shared.reset();
```
> **Note:** Avoid calling `Superwall.shared.reset()` repeatedly. Resetting rotates the anonymous user ID, clears local paywall assignments, and requires the SDK to re-download configuration state. Only trigger a reset when a user explicitly logs out or you intentionally need to forget their identity. See [User Management](/docs/flutter/quickstart/user-management) for more guidance.
Common usage pattern:
```dart
void _upgradeUser() async {
await Superwall.shared.registerPlacement(
'upgrade_prompt',
feature: () {
// Feature unlocked after purchase
Navigator.pushNamed(context, '/premium-content');
},
);
}
```
With subscription status:
```dart
class _MyWidgetState extends State {
@override
void initState() {
super.initState();
// Listen to subscription status changes
Superwall.shared.subscriptionStatus.listen((status) {
setState(() {
// Update UI based on subscription status
});
});
}
}
```
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# SuperwallDelegate
An abstract class that receives global SDK events for analytics and lifecycle management.
> **Info:** Use this delegate for global events across your entire app. For events specific to individual [`registerPlacement()`](/docs/flutter/sdk-reference/register) calls, use [`PaywallPresentationHandler`](/docs/flutter/sdk-reference/PaywallPresentationHandler) instead.
## Purpose
Receives global SDK events including paywall lifecycle, subscription changes, and custom actions.
## Signature
```dart
abstract class SuperwallDelegate {
void subscriptionStatusDidChange(SubscriptionStatus newValue);
void handleSuperwallEvent(SuperwallEventInfo eventInfo);
void handleCustomPaywallAction(String name);
void willDismissPaywall(PaywallInfo paywallInfo);
void willPresentPaywall(PaywallInfo paywallInfo);
void didDismissPaywall(PaywallInfo paywallInfo);
void didPresentPaywall(PaywallInfo paywallInfo);
void paywallWillOpenURL(Uri url);
void paywallWillOpenDeepLink(Uri url);
void handleSuperwallDeepLink(Uri fullURL, List pathComponents, Map queryParameters);
void customerInfoDidChange(CustomerInfo from, CustomerInfo to);
void userAttributesDidChange(Map newAttributes);
}
```
## Implementation
Extend the abstract class and implement the methods you need:
```dart
class MySuperwallDelegate extends SuperwallDelegate {
@override
void subscriptionStatusDidChange(SubscriptionStatus newValue) {
print('Subscription status changed to: $newValue');
// Update user interface, send analytics, etc.
}
@override
void handleSuperwallEvent(SuperwallEventInfo eventInfo) {
print('Superwall event: ${eventInfo.event}');
// Send to your analytics platform
Analytics.track(eventInfo.event.rawName, eventInfo.params);
}
@override
void willPresentPaywall(PaywallInfo paywallInfo) {
print('About to present paywall: ${paywallInfo.identifier}');
// Pause video, hide overlays, etc.
}
@override
void didDismissPaywall(PaywallInfo paywallInfo) {
print('Paywall dismissed: ${paywallInfo.identifier}');
// Resume video, show overlays, etc.
}
@override
void handleCustomPaywallAction(String name) {
print('Custom action triggered: $name');
switch (name) {
case 'contact_support':
_openSupportChat();
break;
case 'share_app':
_shareApp();
break;
}
}
@override
void handleSuperwallDeepLink(Uri fullURL, List pathComponents, Map queryParameters) {
print('Superwall deep link: $fullURL');
print('Path: $pathComponents');
print('Query: $queryParameters');
// Handle deep link navigation
}
@override
void customerInfoDidChange(CustomerInfo from, CustomerInfo to) {
print('Customer info changed');
// Sync with your backend, update UI, etc.
}
@override
void userAttributesDidChange(Map newAttributes) {
print('User attributes updated: $newAttributes');
// Sync with analytics or update in-memory state.
}
}
```
## Usage
Set up the delegate:
```dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Superwall.configure('pk_your_api_key');
// Set the delegate
Superwall.shared.setDelegate(MySuperwallDelegate());
runApp(MyApp());
}
```
Minimal delegate implementation:
```dart
class MinimalDelegate extends SuperwallDelegate {
@override
void subscriptionStatusDidChange(SubscriptionStatus newValue) {
// Required: Handle subscription status changes
}
@override
void handleSuperwallEvent(SuperwallEventInfo eventInfo) {
// Required: Handle SDK events
}
// All other methods have default implementations
}
```
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# SuperwallOptions
Configuration options for customizing Superwall SDK behavior.
## Purpose
Configures various aspects of the Superwall SDK including paywall behavior, logging, and network settings.
## Signature
```dart
class SuperwallOptions {
PaywallOptions paywalls = PaywallOptions();
NetworkEnvironment networkEnvironment = NetworkEnvironment.release;
bool isExternalDataCollectionEnabled = true;
String? localeIdentifier;
bool isGameControllerEnabled = false;
Logging logging = Logging();
bool passIdentifiersToPlayStore = false;
TestModeBehavior testModeBehavior = TestModeBehavior.automatic;
bool shouldObservePurchases = false;
bool shouldBypassAppTransactionCheck = false; // iOS only
int maxConfigRetryCount = 6; // iOS only
bool useMockReviews = false; // Android only
}
```
## Parameters
## Android-only: `passIdentifiersToPlayStore`
Flutter apps can target both iOS and Android. Google Play always consumes the identifier you send through `BillingFlowParams.Builder.setObfuscatedAccountId`, which the SDK sources from `Superwall.instance.externalAccountId`.
* When `passIdentifiersToPlayStore` is **`false`** (default) we SHA-256 hash your `userId` before sending it. Play Console and the Superwall backend will show the hashed value.
* When it is **`true`**, we pass the exact `appUserId` you supplied to `Superwall.shared.identify`. This only changes behavior on Android—the flag is ignored on iOS builds.
Set the option at configuration time when you specifically need the un-hashed identifier:
```dart
final options = SuperwallOptions()
..passIdentifiersToPlayStore = true;
await Superwall.configure(
apiKey,
options: options,
);
```
Make sure the identifier complies with [Google's policy](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.Builder#setObfuscatedAccountId) and never contains personally identifiable information.
## Usage
Basic options:
```dart
final options = SuperwallOptions()
..paywalls = PaywallOptions()
..logging = (Logging()..level = LogLevel.debug);
await Superwall.configure(
'pk_your_api_key',
options: options,
);
```
Production configuration:
```dart
final productionOptions = SuperwallOptions()
..paywalls = (PaywallOptions()
..shouldPreload = true
..automaticallyDismiss = true)
..networkEnvironment = NetworkEnvironment.release
..isExternalDataCollectionEnabled = true
..logging = (Logging()..level = LogLevel.warn);
```
Development configuration (with Play Store IDs on Android):
```dart
final developmentOptions = SuperwallOptions()
..paywalls = (PaywallOptions()
..shouldPreload = false
..automaticallyDismiss = false)
..networkEnvironment = NetworkEnvironment.developer
..logging = (Logging()
..level = LogLevel.debug
..scopes = {LogScope.all})
..passIdentifiersToPlayStore = true; // Android only
```
Custom locale:
```dart
final localizedOptions = SuperwallOptions()
..localeIdentifier = 'es_ES' // Spanish (Spain)
..paywalls = (PaywallOptions()..shouldPreload = true);
```
## Related
* [`PaywallOptions`](/docs/flutter/sdk-reference/PaywallOptions)
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# TransactionProduct
A public product identifier wrapper exported by the Flutter SDK.
## Purpose
Represents a product identifier payload. The Flutter SDK exports this type from `package:superwallkit_flutter/superwallkit_flutter.dart` as of 2.4.12.
## Signature
```dart
class TransactionProduct {
final String id;
}
```
## Properties
## Availability
`TransactionProduct` is available from the public package export.
## Related
* [`SuperwallDelegate`](/docs/flutter/sdk-reference/SuperwallDelegate)
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# setSubscriptionStatus()
Manually sets the user's subscription status when using a custom PurchaseController.
> **Warning:** This method should only be used with a custom [`PurchaseController`](/docs/flutter/sdk-reference/PurchaseController). The default purchase controller manages subscription status automatically.
## Purpose
Manually updates the user's subscription status when implementing custom purchase logic.
## Signature
```dart
Future setSubscriptionStatus(SubscriptionStatus status)
```
## Parameters
## Returns / State
Returns a `Future` that completes when the subscription status is updated.
## Usage
After custom purchase:
```dart
class MyPurchaseController extends PurchaseController {
@override
Future purchaseFromAppStore(String productId) async {
try {
// Custom purchase logic
final result = await MyPaymentService.purchase(productId);
if (result.success) {
// Update subscription status after successful purchase
await Superwall.shared.setSubscriptionStatus(
SubscriptionStatus.active,
);
return PurchaseResult.purchased;
}
return PurchaseResult.failed;
} catch (e) {
return PurchaseResult.failed;
}
}
}
```
Subscription expiry handling:
```dart
Future _checkSubscriptionExpiry() async {
final expiryDate = await MyPaymentService.getSubscriptionExpiry();
if (expiryDate.isBefore(DateTime.now())) {
// Subscription has expired
await Superwall.shared.setSubscriptionStatus(
SubscriptionStatus.inactive,
);
// Show renewal prompt
_showRenewalPrompt();
}
}
```
Manual status sync:
```dart
Future _syncSubscriptionStatus() async {
try {
final serverStatus = await MyAPI.getUserSubscriptionStatus();
final superwallStatus = serverStatus.isActive
? SubscriptionStatus.active
: SubscriptionStatus.inactive;
await Superwall.shared.setSubscriptionStatus(superwallStatus);
} catch (e) {
print('Failed to sync subscription status: $e');
}
}
```
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# configure()
A static method that configures the Superwall SDK with your API key.
> **Note:** This method is typically called once in your app's initialization, such as in your `main()` function or during app startup.
## Purpose
Configures the Superwall SDK with your API key and optional configuration settings.
## Signature
## Tab
```dart Flutter
static Superwall configure(
String apiKey, {
PurchaseController? purchaseController,
SuperwallOptions? options,
Function? completion,
})
```
## Tab
```swift iOS
static func configure(
apiKey: String,
purchaseController: PurchaseController? = nil,
options: SuperwallOptions? = nil,
completion: ((Result) -> Void)? = nil
)
```
## Tab
```kotlin Android
fun configure(
application: Application,
apiKey: String,
purchaseController: PurchaseController? = null,
options: SuperwallOptions? = null,
completion: ((Result) -> Unit)? = null
)
```
## Parameters
## Returns / State
Returns a `Superwall` instance that is immediately configured and ready to use.
## Usage
Basic configuration:
```dart
import 'package:superwallkit_flutter/superwallkit_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Superwall.configure('pk_your_api_key_here');
runApp(MyApp());
}
```
With options:
```dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final options = SuperwallOptions(
paywalls: PaywallOptions(
shouldPreload: true,
automaticallyDismiss: false,
),
logging: Logging(
level: LogLevel.debug,
),
);
Superwall.configure(
'pk_your_api_key_here',
options: options,
);
runApp(MyApp());
}
```
With completion callback:
```dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Superwall.configure(
'pk_your_api_key_here',
completion: () {
print('Superwall configuration completed');
},
);
runApp(MyApp());
}
```
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# consume()
Consumes an in-app purchase by its purchase token.
## Purpose
Consumes a consumable in-app purchase using its purchase token. This is typically used for Google Play Store purchases that need to be consumed before they can be purchased again.
## Signature
```dart
Future consume(String purchaseToken)
```
## Parameters
## Returns / State
Returns a `Future` that resolves to the purchase token of the consumed purchase.
## Usage
Consuming a purchase after you have granted the benefit:
```dart
Future consumeGrantedPurchase(String purchaseToken) async {
try {
final consumedToken = await Superwall.shared.consume(purchaseToken);
print('Purchase consumed: $consumedToken');
} catch (e) {
print('Failed to consume purchase: $e');
}
}
```
> **Note:** On Android, `purchaseToken` comes from Google Play's purchase result. The current Flutter `StoreTransaction` wrapper does not expose this token from Superwall's `transactionComplete` event.
## Related
* [`PurchaseController`](/docs/flutter/sdk-reference/PurchaseController) - Handles purchase logic
* [`Consumable Products`](/docs/flutter/guides/consumable-products) - End-to-end setup for consumable products
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# getCustomerInfo()
Gets the latest customer information including subscriptions, transactions, and entitlements.
## Purpose
Retrieves the most up-to-date customer information including subscription transactions, non-subscription transactions, entitlements, and user ID. This is useful for displaying subscription status, checking entitlements, or syncing with other services.
## Signature
```dart
Future getCustomerInfo()
```
## Returns / State
Returns a `Future` containing:
* `subscriptions` - List of subscription transactions
* `nonSubscriptions` - List of non-subscription transactions (consumables, non-consumables)
* `entitlements` - List of all entitlements available to the user
* `userId` - The ID of the user
## Usage
Getting customer info:
```dart
final customerInfo = await Superwall.shared.getCustomerInfo();
print('User ID: ${customerInfo.userId}');
print('Active subscriptions: ${customerInfo.subscriptions.length}');
print('Entitlements: ${customerInfo.entitlements.length}');
```
Checking for specific entitlements:
```dart
final customerInfo = await Superwall.shared.getCustomerInfo();
final hasPremium = customerInfo.entitlements.any(
(entitlement) => entitlement.id == 'premium' && entitlement.isActive,
);
if (hasPremium) {
print('User has premium access');
}
```
Accessing subscription transactions:
```dart
final customerInfo = await Superwall.shared.getCustomerInfo();
for (final subscription in customerInfo.subscriptions) {
print('Product: ${subscription.productId}');
print('Active: ${subscription.isActive}');
print('Will renew: ${subscription.willRenew}');
if (subscription.expirationDate != null) {
print('Expires: ${subscription.expirationDate}');
}
}
```
## Related
* [`CustomerInfo`](/docs/flutter/sdk-reference/CustomerInfo) - The customer information class
* [`getEntitlements()`](/docs/flutter/sdk-reference/getEntitlements) - Gets entitlements directly
* [`subscriptionStatus`](/docs/flutter/sdk-reference/subscriptionStatus) - Stream of subscription status changes
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# getEntitlements()
Gets all entitlements available to the user, organized by status.
## Purpose
Retrieves all entitlements available to the user, organized into active, inactive, all, and web entitlements. Also provides a method to filter entitlements by product IDs.
## Signature
```dart
Future getEntitlements()
```
## Returns / State
Returns a `Future` containing:
* `active` - Set of active entitlements
* `inactive` - Set of inactive entitlements
* `all` - Set of all entitlements (active and inactive)
* `web` - Set of entitlements from web checkout
* `byProductIds(productIds)` - Method to filter entitlements by product IDs
## Usage
Getting all entitlements:
```dart
final entitlements = await Superwall.shared.getEntitlements();
print('Active: ${entitlements.active.length}');
print('Inactive: ${entitlements.inactive.length}');
print('Total: ${entitlements.all.length}');
print('Web: ${entitlements.web.length}');
```
Checking for specific entitlements:
```dart
final entitlements = await Superwall.shared.getEntitlements();
final hasPremium = entitlements.active.any(
(entitlement) => entitlement.id == 'premium',
);
if (hasPremium) {
print('User has premium access');
}
```
Filtering entitlements by product IDs:
```dart
final entitlements = await Superwall.shared.getEntitlements();
// Get entitlements that contain any of these product IDs
final filtered = await entitlements.byProductIds({
'premium_monthly',
'premium_yearly',
});
print('Found ${filtered.length} entitlements for those products');
```
Checking web checkout entitlements:
```dart
final entitlements = await Superwall.shared.getEntitlements();
if (entitlements.web.isNotEmpty) {
print('User has ${entitlements.web.length} web checkout entitlements');
for (final entitlement in entitlements.web) {
print('Web entitlement: ${entitlement.id}');
}
}
```
## Related
* [`Entitlements`](/docs/flutter/sdk-reference/Entitlements) - The entitlements container class
* [`Entitlements`](/docs/flutter/sdk-reference/Entitlements) - Entitlement information
* [`getCustomerInfo()`](/docs/flutter/sdk-reference/getCustomerInfo) - Get customer info including entitlements
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# getPresentationResult()
Check the outcome of a placement without presenting a paywall.
## Purpose
Retrieves the presentation result for a placement without presenting the paywall. Call this when you need to know whether a placement would show a paywall, send the user to a holdout, or fail due to missing configuration before you decide how to render UI.
## Signature
```dart
Future getPresentationResult(
String placement, {
Map? params,
})
```
## Parameters
## Returns / State
Returns a `Future` that resolves to one of the following:
* `PlacementNotFoundPresentationResult` - The placement was not found
* `NoAudienceMatchPresentationResult` - No audience match for the placement
* `PaywallPresentationResult` - A paywall would be presented (contains experiment information)
* `HoldoutPresentationResult` - User is in a holdout group (contains experiment information)
* `PaywallNotAvailablePresentationResult` - Paywall is not available
## Usage
Checking if a paywall would be shown:
```dart
final result = await Superwall.shared.getPresentationResult(
'premium_feature',
params: {'source': 'onboarding'},
);
if (result is PaywallPresentationResult) {
print('Paywall would be shown');
print('Experiment ID: ${result.experiment.id}');
print('Group ID: ${result.experiment.groupId}');
} else if (result is HoldoutPresentationResult) {
print('User is in holdout group');
print('Experiment ID: ${result.experiment.id}');
} else if (result is NoAudienceMatchPresentationResult) {
print('No audience match');
} else if (result is PlacementNotFoundPresentationResult) {
print('Placement not found');
} else if (result is PaywallNotAvailablePresentationResult) {
print('Paywall not available');
}
```
Using with switch expressions:
```dart
final result = await Superwall.shared.getPresentationResult('premium_feature');
switch (result) {
case PaywallPresentationResult(experiment: final exp):
print('Paywall would show for experiment ${exp.id}');
case HoldoutPresentationResult(experiment: final exp):
print('User in holdout for experiment ${exp.id}');
case NoAudienceMatchPresentationResult():
print('No audience match');
case PlacementNotFoundPresentationResult():
print('Placement not found');
case PaywallNotAvailablePresentationResult():
print('Paywall not available');
}
```
## Related
* [`registerPlacement()`](/docs/flutter/sdk-reference/register) - Registers and presents a paywall
* [`PresentationResult`](/docs/flutter/sdk-reference/PresentationResult) - The result type returned by this method
# Superwall: Subscription Infrastructure for iOS, Android, and Web
Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.
## Pricing
- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.
Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.
## Scale
$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.
## Infrastructure capabilities
- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan
Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.
## Migration
Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).
## Paywall product (optional, separately billable)
One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.
## Architecture
Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.
## Docs
* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing
# getUserId()
Gets the current user ID that was set via identify().
## Purpose
Retrieves the current user ID that was previously set using [`identify()`](/docs/flutter/sdk-reference/identify).
## Signature
```dart
Future getUserId()
```
## Returns / State
Returns a `Future` containing the current user ID, or an empty string if no user has been identified.
## Usage
Basic usage:
```dart
final userId = await Superwall.shared.getUserId();
print('Current user ID: $userId');
```
Conditional logic:
```dart
Future _checkUserStatus() async {
final userId = await Superwall.shared.getUserId();
if (userId.isNotEmpty) {
print('User is logged in: $userId');
// Show personalized content
_loadUserSpecificData();
} else {
print('No user logged in');
// Show login prompt
_showLoginDialog();
}
}
```
With user attributes:
```dart
Future