# 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

# Provider

Wire the Superwall Web SDK into a React 19 app.

> **Warning:** **Beta**The Web SDK is in beta and its API may change between releases.

## Wrap your app

`SuperwallProvider` creates and holds the instance. It takes the same options as `createSuperwall`, plus `children`.

```tsx
import { SuperwallProvider } from "@superwall/paywalls-react";

function App() {
  return (
    <SuperwallProvider apiKey="pk_…">
      <Home />
    </SuperwallProvider>
  );
}
```

Everything below it can reach the instance through the hooks.

> **Note:** Instances are cached by `apiKey` in a module-level registry. Mounting two providers with the same
> key reuses one instance, and the instance survives Fast Refresh. The registry is never evicted:
> swapping `apiKey` leaves the old instance configured and running for the page's lifetime.

## Configuration is read once

The provider reads its options on the first mount for a given `apiKey`. A later provider with the same key and different options silently reuses the original instance's configuration. Changing other props afterwards does not reconfigure the SDK. Changing `apiKey` swaps to a different instance.

Pass configuration statically:

```tsx
<SuperwallProvider
  apiKey="pk_…"
  identity={{ appUserId: currentUser?.id }}
  delegate={myDelegate}
>
  <Home />
</SuperwallProvider>
```

If identity is not known at mount, leave it out and call `identify` from [`useUser`](/docs/web/react/hooks) once you have it.

## Gating render on configuration

Registering a placement before configuration lands fails, so gate the parts of your UI that present paywalls. One way is a Suspense boundary over `sw.ready`:

```tsx
import { use, Suspense } from "react";
import { useSuperwall } from "@superwall/paywalls-react";

function ConfigGate({ children }: { children: React.ReactNode }) {
  const sw = useSuperwall();
  use(sw.ready);
  return <>{children}</>;
}

function App() {
  return (
    <SuperwallProvider apiKey="pk_…">
      <ErrorBoundary fallback={<Offline />}>
        <Suspense fallback={<Splash />}>
          <ConfigGate>
            <Home />
          </ConfigGate>
        </Suspense>
      </ErrorBoundary>
    </SuperwallProvider>
  );
}
```

> **Warning:** `sw.ready` can reject, and `use()` rethrows. Without an error boundary the subtree unmounts. A
> failed config fetch does not reject `ready`; check
> `sw.configurationStatus.value === "failed"` for that. Do not gate with `use(sw.ready)` during
> server rendering.

## Server rendering

`@superwall/paywalls-react` is safe to import during SSR. Its entry pulls in the core package's `/browser` module, but nothing on that path touches the DOM at module load. Every access is inside a function and guarded. Paywall presentation happens after hydration.

The React package re-exports the entire public surface of `@superwall/paywalls-js`, so you never need to import both.

Next, [the hooks](/docs/web/react/hooks).