# 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

# Push, Promote & Publish

Ship paywalls with git semantics: push seals an immutable version, promote points production at it, publish does both.

Shipping has git semantics on purpose: &#x2A;*push saves, promote ships.** Every push mints a sealed, immutable version; nothing your users see changes until promote points production at it.

```bash
superwall push                    # build + version. Production untouched.
superwall promote                 # point production at the latest push
superwall publish                 # push + promote in one step
superwall publish -m "Q3 test"    # record why
```

The scaffolded project mirrors these as package scripts (`dev`, `push`, `promote`, `ship`).

> **Note:** Superwall for Agents is in **private beta**: pushing needs it enabled on each Superwall app you push to. If a push says it isn't enabled for an app yet, contact [support@superwall.com](mailto\:support@superwall.com) for access.

## `superwall push`

Builds every paywall, versions the changed ones, and leaves production alone. Re-running with nothing changed is a no-op.

| Flag                   | What it does                                                                 |
| ---------------------- | ---------------------------------------------------------------------------- |
| `--id <id>`            | Limit the push to one paywall (repeatable)                                   |
| `--platform <p>`       | Limit the push to one platform (repeatable), see [below](#several-platforms) |
| `--rename <old>=<new>` | Declare a directory rename (see below)                                       |
| `-m <note>`            | Record why this version exists                                               |

The **first push binds** each paywall, creating it on Superwall if needed, and records the binding in `superwall.lock`. Commit that file: it's what makes every machine and CI push to the same paywalls. After that, push always updates the same paywall; no IDs ever appear in your code.

A push refuses, before anything is written, when:

* **A selected paywall has diagnostics.** Publishing is immutable; fix the named problems first. They're the same warnings `superwall dev` prints.
* **A product in `config.ts` doesn't exist on the dashboard.** Every variable on it would be undefined on device. Create the products first, see [Products](/docs/framework/products) and the [CLI reference](/docs/framework/cli).
* **A directory rename is unresolved** (below).

## Renames

Renaming a paywall directory is detected, never guessed. Interactively, push asks:

```
? `pro-upgrade` is not in superwall.lock. Is it a new paywall, or renamed?
  › Renamed from plus-upgrade   paywall 208540
    Create a new paywall
```

Choosing the rename keeps the live paywall attached to the new directory. In CI there's no one to ask, so declare it, anything unresolved stops the push rather than silently creating a duplicate:

```bash
superwall push --rename plus-upgrade=pro-upgrade
```

Deleting a paywall directory never blocks a push: the dashboard paywall keeps serving, and restoring the directory re-binds it.

Every pushed version is previewable in the dashboard: opening the paywall there opens the [hosted studio](/docs/framework/studio#in-the-dashboard) on the live version, with the others one switch away.

## Several platforms

One project can push to several platforms (an iOS app, an Android app, a web app) as long as the apps belong to the same Superwall project. Each paywall's `config.ts` lists the platforms it's for:

```ts
export default definePaywall({ platforms: ["ios", "android"], products: { … } });
```

`superwall.lock` binds one Superwall app per platform under `apps`. The first push that meets a new platform binds it: when your account has exactly one app of that platform, it's picked for you; when it has several, push asks; when it has none, push says so. In CI there's no one to ask, so bind it once interactively, or add the line yourself, `"web": "<app id>"`, with ids from `superwall apps list`.

A paywall pushed to two platforms becomes two dashboard paywalls, each with its own versions, and the output names them:

```
plus-upgrade · ios
  Linked to paywall 208551
  Pushed version 4 (3f2a9c1)
plus-upgrade · android
  Linked to paywall 208560
  Pushed version 2 (8b71e04)
```

`--platform <p>` narrows push, promote, and publish to one platform (the bound app's id works too), and `promote --version` needs it when the paywall is on several:

```bash
superwall push --platform android
superwall promote --id plus-upgrade --platform ios --version 3
```

Until a project is bound on a second platform, `platforms` is optional and everything pushes to the one app. Once it is, every paywall has to say which platforms it's for; a push stops on a silent one rather than create a paywall on the wrong app.

## Source snapshots

Every push also snapshots your `superwall/` source to Superwall, so the exact code each version was built from is recoverable. The `-m "why"` note is recorded on that source commit, it only lands when the source actually changed.

What never leaves your machine: `.env` files, `node_modules/`, `.superwall/`, `.git/`, and anything `superwall/.gitignore` lists.

> **Warning:** If any import reaches outside the project directory, the push warns and records the version as non-portable. The pushed source can't be rebuilt elsewhere. The warning names the first five offenders, then counts the rest. Either copy the shared code into `superwall/components/`, or install it from a package registry. See [Project structure](/docs/framework/project-structure).

## `superwall promote`

Points production at a pushed version. Promote never rebuilds. It only moves the live pointer, so it's instant, and rollback is the same move in reverse:

```bash
superwall promote                              # latest push, every paywall
superwall promote --id plus-upgrade            # just one
superwall promote --id plus-upgrade --version 5
# → Rolled back version 7 → 5
superwall promote --id plus-upgrade --platform ios --version 5   # a paywall on several platforms
```

`--version`/`-v` (with a single `--id`, plus `--platform` when that paywall is on several platforms) picks a specific version. Pinning forward and rolling back are the same operation.

## `superwall publish`

Push + promote in one step. It also warns about other paywalls that are pushed-but-not-live, so nothing ships half-forgotten.

`push` and `publish` both require git. The source snapshot is part of each.

## CI

Interactive machines authenticate once with `superwall login`. In CI, set `SUPERWALL_API_KEY` (an `sk_…` key) in the environment. `superwall/.env` works locally and is gitignored. `dev` needs no login at all.

A typical CI ship step:

```bash
superwall push --rename old=new -m "$COMMIT_MESSAGE"   # renames declared, reason recorded
superwall promote
```

Because `superwall.lock` is committed, CI pushes to exactly the same paywalls as every developer machine.