# 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

# Actions

Close the paywall, restore purchases, open links, request OS permissions, and call back into your app — everything a paywall asks its host to do.

A paywall runs inside your app, and some things only the host can do: dismiss the paywall, open a link, prompt for a permission, run your app's code. All of it goes through `useActions()`:

```tsx
import { useActions } from "superwall/hooks";

const { close, restore, openUrl, requestPermission, requestCallback } = useActions();
```

## The actions

| Action                                       | Use it for                                                                                                                                                                                    |
| -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `close()`                                    | Closing the paywall — the X button. Closing is not navigation.                                                                                                                                |
| `restore()`                                  | Restore purchases. Fire-and-forget: success arrives as a `transaction_complete` event or a dismissed paywall — there is no return value to await. See [Purchases](/docs/framework/purchases). |
| `openUrl(url)`                               | Terms, privacy, any link. &#x2A;*Always this, never `<a href>`.**                                                                                                                             |
| `openExternalUrl(url)`                       | Open in the system browser instead of in-app.                                                                                                                                                 |
| `openDeepLink(link)`                         | Deep link into the app.                                                                                                                                                                       |
| `customPlacement(name, params?)`             | Fire a Superwall placement — which can present another paywall.                                                                                                                               |
| `requestPermission(type)`                    | OS permission prompt. Resolves `"granted" \| "denied" \| "unsupported"`.                                                                                                                      |
| `requestCallback(name, options?)`            | Run **your app's** code and await its answer. Resolves `{ status: "success" \| "failure", data? }`.                                                                                           |
| `requestStoreReview("in-app" \| "external")` | Store review prompt.                                                                                                                                                                          |

> **Warning:** Links go through `openUrl`, never an `<a href>`. Inside a webview, an anchor either does nothing or navigates the paywall away from itself — `openUrl` hands the URL to the host so it opens the way the platform expects.

Closing works the same way: the paywall lives on a navigation stack of its own pages, but *leaving* the paywall isn't a navigation — it's `close()`. See [Pages & navigation](/docs/framework/navigation).

## Permissions

```tsx
const status = await requestPermission("notification");
// "granted" | "denied" | "unsupported"
```

Permission types: `notification`, `camera`, `microphone`, `location`, `background_location`, `contacts`, `read_images`, `read_video` (Android only), `tracking`.

## Callbacks — ask your app a question

A callback runs code *in your app* and hands the answer back to the paywall — anything the paywall cannot know on its own: does this account exist, is this referral code valid, what did the user pick during signup.

```tsx
const result = await requestCallback<{ exists: boolean }>("checkAccount");

if (result.status === "success" && result.data?.exists) {
  router.push("welcome-back");
}
```

Type the answer with a claim, as above — the generic is your statement of what the app returns.

### Permission vs callback

A **permission** asks the OS; a **callback** asks your app. Both resolve from code the paywall does not control, which shapes how you use them:

* **Show something while they run.** The OS prompt or your app's code takes as long as it takes.
* **Treat a denial as an ordinary outcome**, not an error. A user who declines notifications is still a user — design the path that continues without.

## In development

In `superwall dev`, actions don't reach a real host — they're logged in the studio's event log, and permission, callback, and purchase requests prompt **you** to pick the outcome. That makes both branches of every flow testable before a device ever sees it. See [The studio](/docs/framework/studio).

> **Tip:** The permissions example shows `requestPermission` and `requestCallback` side by side, with a denial treated as an outcome rather than an error. See [Examples](/docs/framework/examples).