# 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

# Hooks Reference

Every hook the framework provides — signatures, what each returns, and the semantics that matter.

Everything a paywall reads or triggers comes through hooks. One concern each — there is deliberately no kitchen-sink hook.

```tsx
import {
  useProducts, usePurchase, useActions, useHaptics, useTranslation,
  useTrialEligibility, useDevice, useUser, useVariables, useColorScheme,
  useSuperwallEvent, useSuperwallSnapshot, useSuperwallSession,
  type ProductReference,
} from "superwall/hooks";

import { useRouter, useIsFocused } from "superwall/navigation";
```

## `useProducts()`

```tsx
const { products, getProduct } = useProducts();
const annual = getProduct("annual");   // typed reference — typos are compile errors
```

Products, keyed by the reference declared in `config.ts`, each carrying store-owned `variables` (`price`, `period`, `trialPeriodDays`, …). A declared reference always exists, but its variables may not have arrived — guard every read and design the empty state. The full variable list and reading rules are in [Products](/docs/framework/products).

## `usePurchase()`

```tsx
const { purchase, prefetch, isPurchasing, transaction, failure } = usePurchase();

const result = await purchase("annual");
// { status: "completed" | "abandoned" | "failed" } — never throws for flow outcomes
```

The whole purchase flow — outcomes, the no-loading-state rule, web checkout, and `prefetch` — is in [Purchases](/docs/framework/purchases).

## `useActions()`

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

Also on the object: `openExternalUrl`, `openDeepLink`, `customPlacement`, `requestStoreReview`. Everything a paywall asks its host to do — [Actions](/docs/framework/actions) has the full table, the permission types, and the callback pattern.

## `useHaptics()`

```tsx
const haptics = useHaptics();
haptics.light();      // navigation, CTAs
haptics.selection();  // changing a choice
haptics.success();    // purchase landed
```

Also available: `medium`, `heavy`, `warning`, `error`. Fire one on every meaningful tap — iOS produces no feedback of its own inside a paywall. No-ops where haptics are unavailable, so call them unconditionally.

## `useTranslation()`

```tsx
const { t, locale, setLocale, locales } = useTranslation();
t("paywall.cta", { price });
```

Localized copy from `messages/<locale>.ts` catalogs — the catalog system, fallback rules, and interpolation are in [Localization](/docs/framework/localization).

## `useTrialEligibility()`

```tsx
const { eligible } = useTrialEligibility();   // boolean | undefined — the store decides
```

`undefined` until the SDK reports, so gate trial-only UI on `eligible === true`. Splits the paywall into eligible and ineligible versions — both must read as intentional. See [Free trials](/docs/framework/trials).

## `useVariables()`

```tsx
const { device, user, params } = useVariables();
```

Everything the app and SDK told this paywall about the presentation: the SDK-filled `device` record, `user` attributes your app set, and the placement's `params`. All three are host-filled — guard every read. The records, fields, and guarding doctrine are in [Variables & personalization](/docs/framework/variables).

## `useUser()`

```tsx
const user = useUser();
```

Shorthand for `useVariables().user` when the device and params records aren't needed.

## `useDevice()`

```tsx
const { orientation, platform, deviceModel } = useDevice();
```

The same device record as `useVariables().device`, plus `orientation` (`"portrait" | "landscape"`) — measured in the page, so it updates the moment the device turns. See [Variables & personalization](/docs/framework/variables).

## `useColorScheme()`

```tsx
const scheme = useColorScheme();   // "light" | "dark"
```

Rarely needed: the framework already keeps a `dark`/`light` class on `<html>` from what the device reports, so style with plain CSS (`:root.dark { … }`). Reach for the hook only when you need the scheme in JavaScript. Never use `@media (prefers-color-scheme: dark)` as the mechanism — see [Styling & mobile design](/docs/framework/styling).

## `useSuperwallEvent(name, handler)`

```tsx
useSuperwallEvent("transaction_complete", () => haptics.success());
```

Typed SDK events, subscribed for the component's lifetime; an inline arrow handler is fine. The event list and when each fires: [Lifecycle & events](/docs/framework/lifecycle). For anything a dedicated hook covers (products, trial, variables), use the hook — it cannot miss data that arrived before your component subscribed.

## `useSuperwallSnapshot()`

```tsx
const snapshot = useSuperwallSnapshot();
const opened = snapshot.paywall !== undefined;
```

The whole runtime state as one subscribed object. Its most common use is gating entry animations on presentation — paywalls are preloaded hidden, and `snapshot.paywall` flips when the paywall is actually shown ([Lifecycle & events](/docs/framework/lifecycle)). It also carries `experiment` (the A/B assignment), `locale`, and the current purchase and transaction state.

## `useSuperwallSession()`

```tsx
const session = useSuperwallSession();
session.setUserAttributes({ onboardingCompleted: "true" });
```

The full session for advanced work — the few methods no hook surfaces (`setUserAttributes`, raw protocol messaging) and use outside React components. If you're reaching for it for products, purchases, actions, or events, use the dedicated hook instead.

## `useRouter()`

```tsx
import { useRouter } from "superwall/navigation";

const router = useRouter();
router.push("plans");
router.replace("terms");
router.back();
router.canGoBack();
router.dismiss(2);
router.dismissAll();
router.dismissTo("goals");
router.name;    // current page
router.depth;   // pages underneath (index = 0)
```

The stack router for multi-page flows — expo-router's API, method for method. Page names autocomplete and reject typos via the generated `superwall.d.ts`. [Pages & navigation](/docs/framework/navigation) covers the stack model, state between pages, and shared chrome.

## `useIsFocused()`

```tsx
import { useIsFocused } from "superwall/navigation";

const focused = useIsFocused();
```

Whether this page is on top of the stack. Pages you navigate away from stay alive — a covered page can't be clicked or focused, and `useIsFocused()` tells it so, so it can pause video or timers. See [Pages & navigation](/docs/framework/navigation).

## `ProductReference`

```tsx
import { type ProductReference } from "superwall/hooks";

const [selected, setSelected] = React.useState<ProductReference>("annual");
```

The union of product references declared in your `config.ts` — the type behind `getProduct`, `purchase`, and `prefetch`. Use it for selection state so an invalid reference is a compile error.