# 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

# Events

Listen to SDK lifecycle events and register a global delegate.

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

## Listening

`sw.events` is a native `EventTarget` with typed overloads, including `AbortSignal` for cleanup.

```ts
const ac = new AbortController();

sw.events.addEventListener(
  "subscriptionStatus_didChange",
  (event) => console.log(event.detail),
  { signal: ac.signal },
);

// Removes every listener registered with this signal.
ac.abort();
```

Handlers receive a typed `CustomEvent`. The payload is on `event.detail`.

> **Tip:** In React, use [`useSuperwallEvent`](/docs/web/react/hooks), which ties the subscription to the
> component lifecycle.

## What is emitted

The event map covers the same lifecycle the mobile SDKs report:

| Area                    | Events                                                                                                                                                                                                                                                              |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Session                 | `first_seen`, `app_open`, `app_close`, `app_launch`, `app_install`, `session_start`, `reset`                                                                                                                                                                        |
| Configuration           | `config_refresh`, `config_fail`, `config_attributes`                                                                                                                                                                                                                |
| Identity and attributes | `identity_alias`, `user_attributes`, `device_attributes`, `integration_attributes`                                                                                                                                                                                  |
| Subscription            | `subscriptionStatus_didChange`, `customerInfo_didChange`                                                                                                                                                                                                            |
| Placements              | `trigger_fire`, `paywallPresentationRequest`, `confirm_all_assignments`                                                                                                                                                                                             |
| Paywall lifecycle       | `paywall_open`, `paywall_close`, `paywall_page_view`, `paywall_decline`                                                                                                                                                                                             |
| Transactions            | `transaction_start`, `transaction_complete`, `transaction_fail`, `transaction_abandon`, `transaction_timeout`, `transaction_restore`, `subscription_start`, `freeTrial_start`, `nonRecurringProduct_purchase`                                                       |
| Restore                 | `restore_start`, `restore_complete`, `restore_fail`                                                                                                                                                                                                                 |
| Discounts               | `discount_redeem_complete`, `discount_redeem_fail`                                                                                                                                                                                                                  |
| Paywall loading         | `paywallPreload_start`, `paywallPreload_complete`, `paywallResponseLoad_start`, `paywallResponseLoad_complete`, `paywallResponseLoad_notFound`, `paywallResponseLoad_fail`, `paywallProductsLoad_start`, `paywallProductsLoad_complete`, `paywallProductsLoad_fail` |
| Webview                 | `paywallWebviewLoad_start`, `paywallWebviewLoad_complete`, `paywallWebviewLoad_fail`, `paywallWebviewLoad_timeout`                                                                                                                                                  |
| Surveys                 | `survey_response`, `survey_close`                                                                                                                                                                                                                                   |
| Custom                  | `custom_placement`, `customPaywallAction`                                                                                                                                                                                                                           |
| Other                   | `deepLink_open`, `page_view`, `enrichment_start`, `enrichment_complete`, `enrichment_fail`                                                                                                                                                                          |

Some events are local-only and never reach Superwall's servers. Those live in `LocalSuperwallEventMap`. `AllSuperwallEvents` is the union of both.

## Global delegate

The delegate receives global callbacks from the SDK. Register one at creation, or swap it later.

```ts
const sw = createSuperwall({
  apiKey: "pk_…",
  delegate: {
    onSubscriptionStatusChange: (from, to) => {
      console.log("subscription", from.status, "->", to.status);
    },
  },
});

sw.setDelegate(otherDelegate);
sw.setDelegate(null);
```

The delegate covers around a dozen callbacks: subscription and customer changes, the paywall present and dismiss pairs, URL and deep-link interception, link redemption, and `onLog`.

Per-call handlers passed to `register` fire alongside the delegate. The two are separate surfaces covering different callbacks. `onSkip` exists only on a per-call handler, and the delegate's methods cannot be supplied per call. Neither overrides the other, and there is no fallback between them.

> **Note:** Custom actions reach you two ways. `onCustomPaywallAction(name)` on the delegate receives the
> legacy `custom` action from the paywall. Actions defined as custom placements arrive as the
> `custom_placement` event, whose detail carries `placementName`, `paywall_info`, and `params`.

## Forwarding to your analytics

The delegate has a catch-all for this. It receives every wire-bound event, fully typed.

```ts
const sw = createSuperwall({
  apiKey: "pk_…",
  delegate: {
    onEvent: (name, detail) => analytics.track(name, detail),
  },
});
```

To subscribe to a specific set instead, type the names so `detail` narrows:

```ts
import type { AllSuperwallEvents } from "@superwall/paywalls-js";

const forward = (type: keyof AllSuperwallEvents) =>
  sw.events.addEventListener(type, (e) => analytics.track(type, e.detail));

["paywall_open", "paywall_close", "subscriptionStatus_didChange"].forEach(forward);
```