# 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

# Testing Purchases: Sandbox, TestFlight and Test Mode

Pick the right way to test your paywall and purchases: test mode, StoreKit files, Apple sandbox and TestFlight, Google Play testers, or Stripe sandbox.

There are several ways to test a Superwall paywall and the purchases it makes. Some simulate the purchase, some run a real store transaction in a test environment, and some only show you the paywall. This page compares them and links to the full guide for each.

## Compare the methods

| Method                                                                 | Platforms    | Real store transactions?                                     | What it's for                                                                                    |
| ---------------------------------------------------------------------- | ------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
| [Test mode](#test-mode)                                                | iOS, Android | No. Superwall simulates them.                                | The whole paywall flow, entitlement gating and restores, with no store setup.                    |
| [StoreKit configuration file](#storekit-configuration-files)           | iOS          | Local StoreKit transactions in Xcode, not App Store servers. | Real StoreKit behavior on a simulator or device, before products exist in App Store Connect.     |
| [Apple sandbox and TestFlight](#apple-sandbox-accounts-and-testflight) | iOS          | Yes, in Apple's sandbox. No money is charged.                | Purchases against App Store Connect products, renewals, and server-side events such as webhooks. |
| [Google Play testing](#google-play-testing)                            | Android      | Yes, as test purchases.                                      | Purchases against Google Play Console products.                                                  |
| [Web Checkout sandbox](#web-checkout-sandbox)                          | Web          | Yes, with Stripe sandbox products and test cards.            | Web paywalls, checkout and redemption in your app.                                               |
| [Paywall previews](#preview-a-paywall-without-purchasing)              | iOS, Android | No purchases.                                                | How a paywall looks on a device before it goes live.                                             |

## Test mode

Test mode is built into the Superwall SDK. When it's on, product data comes from the Superwall dashboard instead of the store, and a purchase opens a test mode drawer instead of the system payment sheet. You choose whether the purchase succeeds or fails, and the usual transaction events still fire. Restores open a drawer where you pick which entitlements to restore.

When test mode starts, a configuration modal lets you set each entitlement's starting state and override free trial availability, so you can test what different users see. Your choices persist across sessions.

### Requirements

| SDK     | Test mode               | `testModeBehavior` option |
| ------- | ----------------------- | ------------------------- |
| iOS     | 4.14.0 or later         | 4.14.0 or later           |
| Android | 2.7.4 or later          | 2.7.4 or later            |
| Flutter | Through the native SDKs | 2.4.11 or later           |
| Expo    | Through the native SDKs | 1.0.4 or later            |

### Turn it on

There are three ways to turn on test mode:

1. **From the dashboard.** Open **Users**, open the user, and turn on **Simulated purchases** under **Test mode**. When the SDK sees that user's ID, it starts test mode. Users in test mode are listed under **In test mode** at the top of the **Users** page. This option shows for iOS and Android apps. If you don't see it, use the SDK option instead.
2. **From the SDK.** Set `testModeBehavior` on `SuperwallOptions` before you configure the SDK. `always` turns it on for every launch, which is useful during development.
3. **From a local dev server (iOS).** When `SuperwallOptions.devServer` connects to a running `superwall dev` server, test mode turns on by itself and the intro modal is skipped. This needs iOS SDK 4.17.0 or later. See [Local paywall development](/docs/ios/sdk-reference/SuperwallOptions#local-paywall-development).

## Tab

```swift iOS
let options = SuperwallOptions()
options.testModeBehavior = .always

Superwall.configure(apiKey: "your-api-key", options: options)
```

## Tab

```kotlin Android
val options = SuperwallOptions().apply {
    testModeBehavior = TestModeBehavior.ALWAYS
}

Superwall.configure(this, "your-api-key", options = options)
```

## Tab

```dart Flutter
final options = SuperwallOptions()
  ..testModeBehavior = TestModeBehavior.always;

await Superwall.configure('your-api-key', options: options);
```

## Tab

```tsx Expo
<SuperwallProvider
  apiKeys={{ ios: "ios_key", android: "android_key" }}
  options={{ testModeBehavior: "always" }}
>
  {/* your app */}
</SuperwallProvider>
```

The default, `automatic`, also turns on test mode when the app's bundle ID (on Android, its application ID) doesn't match the one saved in the dashboard. `whenEnabledForUser` only turns it on for users marked in the dashboard, and `never` turns it off. The full behavior is in the [iOS](/docs/ios/guides/test-mode) and [Android](/docs/android/guides/test-mode) test mode guides.

### Things to know

* On iOS, test mode marks the device as sandbox, so its events go to sandbox and stay out of your production data.
* From iOS SDK 4.18.0, a bundle ID mismatch only starts test mode in simulator, TestFlight and development builds. App Store builds ignore it. Users marked in the dashboard still get test mode in any build. See [the bundle ID warning in Settings](/docs/dashboard/dashboard-settings/overview-settings#bundle-id-test-mode).
* `automatic` never starts test mode during UI tests on iOS, or when JUnit or Espresso is on the classpath on Android.
* A simulated purchase grants entitlements without a store transaction, so it can't exercise the [Customer Center](/docs/ios/guides/customer-center). Use a StoreKit configuration file or a sandbox account for that.

## StoreKit configuration files

A StoreKit configuration file is Xcode's local test environment for in-app purchases. It doesn't connect to App Store servers, and the products in it don't have to exist in App Store Connect yet. It works in the simulator, where you can't sign in to a real Apple Account.

To set it up, add a **StoreKit Configuration File** to your project, add your products to it, and select it under **Run → Options → StoreKit Configuration** in your scheme. Each product ID must match the product ID on your paywall. Then use Xcode's Transaction Manager, under &#x2A;*Debug → StoreKit → Manage Transactions...**, to delete or refund purchases and reset your subscription state without reinstalling.

The setup guide is per SDK: [iOS](/docs/ios/guides/testing-purchases), [Flutter](/docs/flutter/guides/testing-purchases) and [Expo](/docs/expo/guides/testing-purchases). Expo needs a development build, not Expo Go.

### Things to know

* StoreKit configuration file purchases don't trigger [webhooks](/docs/integrations/webhooks). Use TestFlight with a sandbox account to test those.
* If a test transaction looks stuck or stays active, see [StoreKit Transactions Not Clearing in Xcode](/docs/support/troubleshooting/troubleshooting-storekit-transaction-stuck).
* If you see **Sign in to Apple Account** in the simulator, the StoreKit file isn't set on your current scheme. See [Why am I seeing "Sign in to Apple Account"](/docs/support/troubleshooting/6204964383-why-am-i-seeing-sign-in-to-apple-account-when-testing-in-app-purchases).

## Apple sandbox accounts and TestFlight

On a physical device, development and TestFlight builds buy your App Store Connect products in Apple's sandbox. To use a sandbox account on a development build, go to **Settings → App Store → Sandbox Account** on the device and sign in.

Sandbox and TestFlight behave differently from the App Store:

* **Subscriptions renew faster.** In TestFlight, every subscription renews once a day, up to 6 times, then stops renewing.
* **Reinstalling resets the user.** After a reinstall, the user looks new and anonymous until they tap purchase, which restores their existing subscription.
* **Free trials can be missing after a reinstall.** The SDK can't read the receipt until a purchase or restore. Restore purchases first, then try again.
* **Entitlements can persist after a reset.** Apple's sandbox keeps subscription state on its servers, and `Superwall.shared.reset()` can't clear it. Use a fresh sandbox account for a clean test.
* **Prices can show in another currency.** In TestFlight, StoreKit can return prices from another storefront, often USD.

The details are in [TestFlight Subscriptions](/docs/support/troubleshooting/troubleshooting-testflight-subscriptions), [Sandbox Free Trial Isn't Appearing](/docs/support/troubleshooting/troubleshooting-sandbox-free-trial-not-showing), [Sandbox Entitlements Persist After Reset](/docs/support/troubleshooting/troubleshooting-sandbox-entitlements-persist), and [How to Test Paywalls with Local Currency](/docs/support/troubleshooting/how-to-test-paywalls-with-local-currency-for-a-specific-country).

## Google Play testing

To test real Google Play Billing, use Google Play's license test accounts. For your products to load, you need at least one build with the `com.android.vending.BILLING` permission uploaded to a test track, and the device or emulator must have the Play Store and be signed in. See [Products not loading on Android](/docs/support/troubleshooting/products-not-loading/3716347779-products-not-loading-android) for the full checklist.

> **Note:** Superwall treats an Android build as sandbox when it's debuggable. Release builds count as production, including builds installed from Google Play testing tracks.

## Web Checkout sandbox

To test a web paywall, create a [sandbox product](/docs/web-checkout/web-checkout-adding-a-stripe-product#sandbox-products) in Stripe, import it to Superwall, and add it to the paywall. Every product on the paywall must be a sandbox product. Then open the paywall URL, pay by card with `4242 4242 4242 4242`, any future expiration date and any CVC, and follow the redemption flow into your app.

Your Stripe test keys go under [Stripe Sandbox Configuration](/docs/dashboard/dashboard-settings/overview-settings#stripe-sandbox-configuration) in Settings. The full guide is [Sandbox Purchases](/docs/web-checkout/web-checkout-testing-purchases).

## Keep test data out of production metrics

The SDK reports whether each device is a sandbox device. On iOS, simulator, Xcode and TestFlight builds are sandbox, and App Store installs are production. On Android, debuggable builds are sandbox. You use the same API key for both.

To see sandbox data, choose **Sandbox** from the environment picker at the top of the **Overview** or **Campaigns** page. The top bar then shows a **Sandbox** badge. Production is the default.

To show testers a different paywall, add an audience to your campaign with the filter `isSandbox` is `true`, and put it above your production audiences. Superwall checks audiences from top to bottom, so sandbox devices match it first. See [How to set up sandbox testing without affecting production metrics](/docs/support/sdk/3107042948-how-to-setup-sandbox-testing-without-affecting-production-metrics).

## Preview a paywall without purchasing

To see a paywall on your own device before it goes live, click **Preview** in the paywall editor and scan the QR code with your phone. It opens the paywall in your app. This needs a URL scheme and the SDK's deep link handler. See [In-App Paywall Previews](/docs/sdk/quickstart/in-app-paywall-previews#previewing-paywalls) and [Previewing](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-previewing). Preview links only work for a few minutes, so scan a fresh code if one expires.

To check a layout without a device, use the editor's device preview. See [Device view vs. Canvas view](/docs/dashboard/dashboard-creating-flows/the-canvas#device-view-vs-canvas-view).

## Related

* [Test mode on iOS](/docs/ios/guides/test-mode)
* [Test mode on Android](/docs/android/guides/test-mode)
* [Setting up StoreKit testing](/docs/ios/guides/testing-purchases)
* [Sandbox Purchases for Web Checkout](/docs/web-checkout/web-checkout-testing-purchases)
* [Pre-Launch Checklist](/docs/dashboard/guides/pre-launch-checklist)