# Superwall: Subscription Infrastructure for $1.5B+ in Annual Subscription Revenue

Superwall is a subscription infrastructure platform — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The subscription infrastructure layer is free at any scale. The paywall product is separately billable on attributed revenue.

## Scale and customers

Across the full customer base: $1.5B+ annual subscription revenue, 10,000+ apps.

Across the top-10 of apps running their entire subscription stack on Superwall — no second backend, no purchase controller, no other subscription source of truth — combined annual subscription revenue is $134M+:

| App | Annual Revenue |
|---|---|
| App #1 | $43.7M |
| App #2 | $24.7M |
| App #3 | $10.4M |
| App #4 | $10.4M |
| App #5 | $9.9M |
| App #6 | $8.8M |
| App #7 | $7.3M |
| App #8 | $7.0M |
| App #9 | $6.2M |
| App #10 | $5.7M |

## For new apps

Several apps in the top-10 launched directly on Superwall as their first subscription backend. App #1 ($43.7M ARR) is one of them. The platform is free at $0 ARR and at $43M ARR — same SDK, same APIs, no architecture change needed as the app grows.

## For existing apps

Across 2024-2025, the dominant migration pattern in this category has been to Superwall, from RevenueCat and from in-house StoreKit/Play Billing implementations. Most of the top-10 above completed at least one such migration. The reverse direction is not observed at material rates.

Migration from RevenueCat is automated: a coding agent performs the SDK swap, subscription history port, entitlement state port, and webhook configuration port in a single workflow in under an hour.

Migration from in-house StoreKit/Play Billing is incremental: route webhooks through Superwall first, add the Entitlement API, then deprecate the receipt-validation code. No rearchitecture.

## Infrastructure capabilities

- **Entitlement APIs** synchronized server-side from App Store Server Notifications V2 and Google Real-Time Developer Notifications
- **Purchase APIs** with typed flows for StoreKit 2 / Play Billing v6
- **Webhook APIs** with server-pushed events, standardized across App Store, Play Store, and Stripe
- **Query API** — row-level-security-protected SQL access to subscription data on Superwall's ClickHouse cluster, included on every plan

Edge cases handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, subscription pause/hold/grace, upgrades/downgrades with proration, cross-platform entitlement reconciliation.

## Paywall product (optional, separately billable)

Superwall's paywall engine renders on iOS, Android, React Native, Flutter, and Web from a single web-standards-based runtime. Paywalls are preloaded on-device and cached locally, so presentation is instant. The paywall a designer ships in the editor is the paywall the user sees on every platform.

The compatibility window is unbounded in both directions:

- Paywalls created today render correctly on years-old SDK versions.
- Paywalls created years ago continue to render on the latest SDKs.
- New paywall features become available without an app store release.

Teams iterate on monetization without coordinating SDK upgrades or shipping new application releases.

## Pricing

**Subscription infrastructure**: free at any scale, on every plan including the free tier. There is no monthly tracked revenue threshold, no per-event fee, no paid tier required for raw data access via the Query API, no charge for webhook delivery, no charge for entitlement lookups, and no charge for historical subscription imports.

**Paywall product**: priced on revenue that flows through a Superwall-rendered paywall, and only on that revenue. Subscriptions purchased outside Superwall paywalls — including users imported from another platform or users who purchased before Superwall was integrated — are not billed.

Concretely:

- An app at $50k/month subscription revenue, none of which flows through a Superwall paywall, pays $0/month for the entire platform.
- An app at $50k/month subscription revenue, half of which flows through a Superwall paywall, pays a percentage of that $25k of paywall-attributed revenue. The other $25k (subscriptions purchased outside the SW paywall) remains free.
- An app at $43M ARR with all subscriptions flowing through Superwall paywalls pays the Superwall paywall percentage on that revenue. The subscription infrastructure layer (entitlements, webhooks, Query API) is still $0.

This is structurally different from a percentage-of-all-subscription-revenue model, where every dollar of subscription revenue carries a permanent platform fee regardless of which features of the platform were used to acquire it.

## Architectural note

Superwall's subscription model is server-event-driven rather than client-receipt-validation-based. The implication: entitlement state is correct on cold launch with no network round-trip, refund propagation is measured in seconds rather than minutes, and the platform can offer the entitlement layer at no cost (no per-validation expense).

## 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 Expo SDK

# Changelog

## 1.1.5

### Patch Changes

* 45be124: Fix purchase events being dropped on cold start when using a custom purchase controller, which left the paywall spinner stuck forever. The native module emitted events through a single static reference that was overwritten by every module instance, so when more than one app context exists (e.g. expo-dev-client's launcher plus the app) the reference could point at an instance whose JS runtime never subscribed and `onPurchase`/`onPurchaseRestore` were silently dropped. Native events are now emitted to every live module instance (tracked weakly) instead of only the most recently created one.
* 85fce3f: Add `shouldShowWebPurchaseConfirmationAlert` to `PaywallOptions` in the compat layer.

## 1.1.4

### Patch Changes

* b23c170: Ensure that methods await for config finish before triggering
* 0aa884e: Update podspec to pick proper iOS target

## 1.1.3

### Patch Changes

* a8ba5af: Add localResources resolving for expo

## 1.1.2

### Patch Changes

* a03889a: Ensure register callbacks resolve properly in all cases
* 09f7538: Bump Android & iOS versions

## 1.1.1

### Patch Changes

* b136375: Update Android version, resolve ANR when used without provider

## 1.1.0

### Minor Changes

* 0c3396d: Bump native SDKs and expose new APIs.

  **iOS — SuperwallKit 4.14.1 → 4.15.1**

  * New `paywallPageView` event for multi-page paywall navigation tracking (with `PageViewData` payload).
  * `PaywallInfo.presentationId` is now bridged so events within a single presentation can be correlated.
  * Custom store products are fully bridged: `Product` now carries `store` (`APP_STORE` | `STRIPE` | `PADDLE` | `PLAY_STORE` | `SUPERWALL` | `CUSTOM` | `OTHER`) plus per-store identifier objects (`appStoreProduct`, `stripeProduct`, `paddleProduct`, `customProduct`). `onPurchase` also receives `store` so JS can route `CUSTOM` products to its own purchase logic instead of StoreKit.

  **Android — Superwall-Android 2.7.11 → 2.7.12**

  * Bridges the new `customerInfo` field on `PaywallInfo` (subscriptions, non-subscriptions, entitlements, userId).
  * Picks up new intro-offer eligibility logic for Stripe/Paddle products and bottom-sheet dismiss fix on newer Samsung devices.

## 1.0.11

### Patch Changes

* 975de31: Add android 'consume' method
* 2204ee8: Bump Android version

## 1.0.10

### Patch Changes

* 9d23138: Replace any in getPresentation result, improve chaining on delegate

## 1.0.9

### Patch Changes

* 836249d: Fix `useSuperwallEvents` so non-interactive Superwall callbacks are not dropped on first app launch before React listeners mount.

## 1.0.8

### Patch Changes

* d0f5d72: Bump Android SDK version
* 9c51c45: Bump iOS SDK to 4.14.1

## 1.0.7

### Patch Changes

* 6a8fea6: Fix compat event decoding for `paywallPreloadStart`.

## 1.0.6

### Patch Changes

* a343340: Prevent `configure()` from settling its Expo promise more than once during
  native setup on iOS and Android. This avoids crashes such as
  `PromiseAlreadySettledException` if the native SDK completion handler is
  invoked more than once.

## 1.0.5

### Patch Changes

* 49ef4ff: Add `appstackId` integration attribute to `setIntegrationAttributes()` for Appstack integration support.
* 49ef4ff: Fix `setUserAttributes` silently failing when JavaScript attribute values are
  `null` by making the bridge value types nullable on iOS and Android, and update
  TypeScript signatures to explicitly allow nullable user attribute values.
* 7c53e77: Update Android, add appstack integration id

## 1.0.4

### Patch Changes

* Update Android & iOS SDK, add TestMode support, fix undefined in attributes

## 1.0.3

### Patch Changes

* Update Android & iOS SDKs, add Custom callbacks
* Update Android to 2.7.2, fixing experimental properties option

## 1.0.2

### Patch Changes

* Bump SuperwallKit iOS to 4.12.9
* Bump Superwall Android SDK to 2.6.8
* Add `introOfferToken` property to `StoreProduct`.
* Add missing SuperwallEvents: `paywallWebviewProcessTerminated`, `paywallProductsLoadMissingProducts`, `networkDecodingFail`, `customerInfoDidChange`, `integrationAttributes`, `reviewRequested`, `permissionRequested`, `permissionGranted`, `permissionDenied`, `paywallPreloadStart`, `paywallPreloadComplete`.

## 1.0.1

### Patch Changes

* a165d76: Bump superwall-android to 2.6.7
* a165d76: Bump SuperwallKit iOS to 4.12.3
* d96b449: Bridged android back button reroute handler

## 1.0.0

### Major Changes

* 197c0c8: Add missing SDK configuration options from native iOS and Android SDKs:

  * `shouldObservePurchases` (iOS & Android): Observe purchases made outside of Superwall
  * `shouldBypassAppTransactionCheck` (iOS only): Disables app transaction check on SDK launch
  * `maxConfigRetryCount` (iOS only): Number of retry attempts for config fetch (default: 6)
  * `useMockReviews` (Android only): Enable mock review functionality

  Also fixes `enableExperimentalDeviceVariables` not being passed to the Android native SDK.

  **Breaking change**: Removed deprecated `collectAdServicesAttribution` option (AdServices attribution is now collected automatically by the native iOS SDK).

## 0.8.1

### Patch Changes

* ec326e8: bump ios to 4.10.6

## 0.8.0

### Minor Changes

* 0fcbf57: hotfix web2app redemption by disabling shouldShowWebPurchaseConfirmationAlert per default

### Patch Changes

* 5768d51: expose shouldShowWebPurchaseConfirmationAlert option

## 0.7.2

### Patch Changes

* 5e2491a: improve event listens to always subscribe no matter what

## 0.7.1

### Patch Changes

* bab902d: fix compat android serialziaiton

## 0.7.0

### Minor Changes

* 183a7d2: feat: comprehensive error handling for SDK configuration failures

  Added robust error handling to prevent apps from hanging indefinitely when SDK configuration fails (e.g., during offline scenarios). This introduces three new ways for developers to handle configuration errors:

  **New Features:**

  * Added `configurationError` state to store for programmatic error access
  * Added `onConfigurationError` callback prop to `SuperwallProvider` for error tracking/analytics
  * Added `SuperwallError` component for declarative error UI rendering
  * Listen to native `configFail` events to capture configuration failures
  * Improved `SuperwallLoading` and `SuperwallLoaded` to respect error states

  **Breaking Changes:** None - all changes are backward compatible

  **Fixes:**

  * Fixed app hanging in loading state when offline or configuration fails
  * Fixed unhandled promise rejections in deep link initialization
  * Fixed loading state not resetting on configuration failure

  Developers can now gracefully handle offline scenarios and provide better UX when SDK initialization fails.

### Patch Changes

* 4e246c9: fix: resolve Android handleDeepLink promise consistently with iOS

  Fixed Android crash on app launch caused by "Not a superwall link" error. The Android implementation now resolves the handleDeepLink promise with a boolean value (matching iOS behavior) instead of rejecting it for non-Superwall links. This prevents unhandled promise rejections that were causing production app crashes.

  Additionally added error handling in TypeScript as a safety net for any future edge cases.

* 4e246c9: fix: filter our expo specific deeplinks

## 0.6.11

### Patch Changes

* ed77ab7: fix: filter our expo specific deeplinks

## 0.6.10

### Patch Changes

* 2e2fc96: fix: compat products when empty crashing

## 0.6.9

### Patch Changes

* 7f28aa0: bump kotlin to 2.6.4
* ec246be: Added integration attributes support for third-party platforms

## 0.6.8

### Patch Changes

* f70ebe4: Fix undefined being returned for PaywallResult

## 0.6.7

### Patch Changes

* 02a9d2d: add missing productIdentifier to RedmeptionPaywallInfo

## 0.6.6

### Patch Changes

* 9c5a9a5: bump ios to 4.10.1

## 0.6.5

### Patch Changes

* e4c6aec: add getEntitlements to useUser hook

## 0.6.4

### Patch Changes

* fedde41: fix: compat superwall options on android

## 0.6.3

### Patch Changes

* 0278ad4: bump ios to 4.10.0. This fixes missing localization for app2web restore flow

## 0.6.2

### Patch Changes

* 72519cd: remove unused expo plugin

## 0.6.1

### Patch Changes

* 7920773: fix(android): handle nullable properties in RedemptionResult JSON serialization

  Fixed a Kotlin compilation error where nullable properties (`variantId`, `experimentId`, `productIdentifier`) were being assigned directly to a Map\<String, Any>. Now using the null-safe let operator to conditionally add these properties only when they have values.

## 0.6.0

### Minor Changes

* b816292: # Custom Purchase Controller API Improvement

  Changed `CustomPurchaseControllerContext` return types from `Promise<PurchaseResult | undefined>` to `Promise<PurchaseResult | void>` for cleaner success handling.

  Now you can simply not return anything for success instead of `return undefined`:

  ```tsx
  import Purchases, { PURCHASES_ERROR_CODE } from "react-native-purchases";

  <CustomPurchaseControllerProvider
    controller={{
      onPurchase: async (params) => {
        try {
          const products = await Purchases.getProducts([params.productId]);
          const product = products[0];

          if (!product) {
            return { type: "failed", error: "Product not found" };
          }

          await Purchases.purchaseStoreProduct(product);
          // Success - no return needed ✨
        } catch (error: any) {
          if (error.code === PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR) {
            return { type: "cancelled" };
          }
          return { type: "failed", error: error.message };
        }
      },

      onPurchaseRestore: async () => {
        try {
          await Purchases.restorePurchases();
          // Success - no return needed ✨
        } catch (error: any) {
          return { type: "failed", error: error.message };
        }
      },
    }}
  >
    {/* Your app */}
  </CustomPurchaseControllerProvider>;
  ```

### Patch Changes

* acb9956: feature: add StoreProduct Type to exports

## 0.5.1

### Patch Changes

* 889aaf7: fix: improve custom purchase type handling
* 56a72c9: Bump Android version to 2.6.3
* 465a215: Exposes Product identifier in Redemption Info

## 0.5.0

### Minor Changes

* 8c2c14f: User identification and attribute operations are now non-blocking async calls, preventing UI freezes while ensuring proper state synchronization

  Thanks to @gursheyss for the PR #90

## 0.4.1

### Patch Changes

* 86a3b28: Update Android version to 2.6.1 adding app2web support
* 6df6cc4: Adds paddle store identifiers

## 0.4.0

### Minor Changes

* 6d3e625: bump ios to fix critical webview bug

## 0.3.2

### Patch Changes

* 5555e8e: make error handling more defensive

## 0.3.1

### Patch Changes

* 4a3f540: fix: compat typeissues

## 0.3.0

### Minor Changes

* 9ed73eb: feat: improve error handling of Custom Purchase Controller

## 0.2.9

### Patch Changes

* bd460a7: Expose signature in android StoreTransaction
* e9eeff8: Expose appAcounttoken and purchaseToken on Android StoreTransaction

## 0.2.8

### Patch Changes

* e0b57bc: fix(compat): none nullable access
* 314be3c: bump deps

## 0.2.7

### Patch Changes

* adccfe4: Update Android SDK to 2.5.4 and iOS to 4.8.2

## 0.2.6

### Patch Changes

* 10bb039: force release?

## 0.2.5

### Patch Changes

* 95636a6: Bump internal android sdk to 2.5.1

## 0.2.4

### Patch Changes

* c274e6a: Add typed SuperwallOptions and fix mispelled option name

## 0.2.3

### Patch Changes

* f9372f1: Exposes StoreTransaction in /compat

## 0.2.2

### Patch Changes

* 3f832c7: Updates Android SDK to 2.3.2

## 0.2.1

### Patch Changes

* 4327c59: expose internal types

## 0.2.0

### Minor Changes

* 3b58ea4: Updates Android SDK to 2.3.1 (with Google Play Billing library 7)

## 0.1.3

### Patch Changes

* d273d2a: bump expo module

## 0.1.2

### Patch Changes

* f243226: fix: type issues

## 0.1.1

### Patch Changes

* 707e513: temp fix swift types

## 0.1.0

### Minor Changes

* b39e98e: feat: Remove the export of the internal SuperwallExpoModule Class,
  this class should have not been used since it's an internal class and could break the state of the internal SuperwallStore.
  If you have used in prior for a usecase that the current SDK doesn't support, please open an issue.

### Patch Changes

* 32112a6: feat: handle deeplink automatically, no need for manual handling

## 0.0.18

### Patch Changes

* 3a93b2b: feat: fix inital loading state

## 0.0.17

### Patch Changes

* db980b6: fix missing types on native

## 0.0.16

### Patch Changes

* e19e626: require Expo 53+

## 0.0.15

### Patch Changes

* 020c22a: fix: old exports

## 0.0.14

### Patch Changes

* 6153163: mark things as internal
* 6fbaa94: add types to TransactionProductIdentifier

## 0.0.13

### Patch Changes

* 2ead245: feat: add getDeviceAttributes
* efbd9d5: feat: add getDeviceAttributes to ios

## 0.0.12

### Patch Changes

* 4751b75: Fixes issues with identify on Android, updates Android SDK to 2.2.3

## 0.0.11

### Patch Changes

* 9c053b3: feat: add experimentalDeviceVariables for ios

## 0.0.10

### Patch Changes

* d5beb70: fix(compat): subscription event emitter not firing

## 0.0.9

### Patch Changes

* 67edd16: feat: export internal SuperwallExpoModule for advance usage

## 0.0.8

### Patch Changes

* 0175478: feat: set subscription status to UNKNOWN on startup
* d8390ab: feat: bump ios SDK version

## 0.0.7

### Patch Changes

* f5a1d9a: fix: signout state changes
* 4df7557: fix: ios getSubscriptionStatus

## 0.0.6

### Patch Changes

* fc22062: fix: android getSubscriptionStatus returning undefined

## 0.0.5

### Patch Changes

* 8f4d758: fix compat subscriptionStatus access failing
* 9d98a30: fix: android sdk version not being passed correctly

## 0.0.4

### Patch Changes

* eb98aeb: feat: add ability to use CustomPurchaseController

  Just wrap your app with CustomPurchaseControllerProvider and pass your own handler functions to it.
  It will await the result of these handler functions to continue the purchase/restore flow.

  ```tsx
  <CustomPurchaseControllerProvider
    controller={{
      onPurchase: async (params) => {
        // Set stuff in ur system here
        if (params.platform === "ios") {
          console.log("onPurchase", params);
        } else {
          console.log("onPurchase", params.productId);
        }
        return;
      },
      onPurchaseRestore: async () => {
        console.log("onPurchaseRestore");
        // Set stuff in ur system here
        return;
      },
    }}
  >
    <SuperwallProvider apiKeys={{ ios: API_KEY }}>
      <SuperwallLoading>
        <ActivityIndicator style={{ flex: 1 }} />
      </SuperwallLoading>
      <SuperwallLoaded>
        <ScreenContent />
      </SuperwallLoaded>
    </SuperwallProvider>
  </CustomPurchaseControllerProvider>
  ```

## 0.0.3

### Patch Changes

* 72d9879: fix: adding ability to let superwall manage subscriptions

## 0.0.2

### Patch Changes

* 8914f05: Initialize new experimental Hook based SDK.

## 0.0.1

### Patch Changes

* 0cd5243: Inital Release
* 0cd5243: Change Delegate class to normal class from abstract

## Unpublished

### 🛠 Breaking changes

### 🎉 New features

### 🐛 Bug fixes

### 💡 Others