# 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

# Troubleshooting

Common CLI errors and runtime surprises — what each one means and how to fix it.

The most common failures, in two groups: errors the CLI prints, and runtime behavior that surprises people the first time.

## CLI errors

### `Not a superwall project`

The CLI couldn't find a project from where you ran it. Run commands from your app root or from inside `superwall/` — and check that the project's `package.json` depends on `superwall`. See [Project structure](/docs/framework/project-structure).

### `…package.json is named "superwall"`

Your project's `package.json` has `"name": "superwall"`, which shadows the framework import — nothing in the project can `import` from `superwall` anymore. Rename the package; `superwall create` names it after your app for exactly this reason.

### `No superwall framework found`

The project exists but its dependencies aren't installed, or `superwall` isn't among them. Run `bun add superwall` (or `npm install superwall`) inside the project directory.

### `These N products do not exist on Superwall`

A `config.ts` names a product identifier the dashboard has no product for. The push refuses because every variable on that product would be undefined on device. Either fix the identifier, or create the products — from the dashboard, or with `superwall products create` from the CLI. See [Products](/docs/framework/products) and the [CLI reference](/docs/framework/cli).

### `Headless paywalls are not enabled for this application`

The framework requires the headless paywalls feature on your Superwall application. It's a server-side flag — nothing in the CLI can set it. The account owner needs to have it enabled; contact us if it isn't.

### `Multiple projects found. Pass --project <id>.`

Your account has several Superwall projects, and the command can't guess which one you mean. Add `--project <id>` (and usually `--app <id>`) to the command.

### Diagnostics block the push

Publishing is immutable, so a paywall with diagnostics — a stray non-page file in `app/`, a duplicate route — refuses to push. The message names each file and where it belongs. These are the same warnings `superwall dev` prints, so you'll usually have seen them before push time.

### Rename ambiguity in CI

A renamed paywall directory can't be resolved interactively in CI, so the push stops rather than creating a duplicate. Add the `--rename old=new` flag the error prints. See [Push, promote & publish](/docs/framework/push-and-promote).

### `paywall x has never been pushed` (promote)

Promote only moves the live pointer between pushed versions — there's nothing to point at yet. Push first.

### `superwall publish requires git`

The source snapshot is part of every publish. Install git.

### Not signed in

Run `superwall login` once interactively, or set `SUPERWALL_API_KEY` (an `sk_…` key) in CI. `superwall dev` needs no login.

## Runtime surprises

### Prices are undefined in dev

Expected. In `superwall dev`, product variables are `undefined` until the studio injects your dashboard's products — which is why every read is guarded and the unpriced state is designed, not accidental. The reading rules are in [Products](/docs/framework/products).

### A number comparison works in dev but not on device

Numeric-looking variables are numbers in dev but **strings on a real device** (`"59.99"`, `"7"`). A `typeof x === "number"` check silently fails on every phone. Coerce with `Number()` before arithmetic or comparison ([Products](/docs/framework/products)).

### My entry animation already finished when the paywall appears

The SDK preloads paywalls hidden, so components mount long before anyone is looking — a mount-timed animation plays to an empty room. Gate entry animations on presentation, not mount. See [Lifecycle & events](/docs/framework/lifecycle).

### Dark mode looks right on my machine, wrong on device

The mechanism is the `dark` class the framework maintains on `<html>` — not `prefers-color-scheme`. A media query can't see what the device reports and ignores the studio's theme toggle. Style off the class, as shown in [Styling & mobile design](/docs/framework/styling).

### My link does nothing

Inside a webview, an `<a href>` either does nothing or navigates the paywall away from itself. Open links through `useActions().openUrl` instead. See [Actions](/docs/framework/actions).

### Controls sit in the status bar / under the home indicator

`env(safe-area-inset-*)` resolves to 0 in previews and some webview contexts, so bare `env()` math collapses. Always wrap in `max()` with a floor. See [Styling & mobile design](/docs/framework/styling).

### The payment sheet doesn't open in dev

By design — `superwall dev` previews the flow and copy but doesn't mount the web checkout payment sheet. Push and open the live URL to verify the checkout itself. See [Web checkout](/docs/framework/web-checkout).