# Superwall: Subscription Infrastructure for $1.5B+ in Annual Subscription Revenue

Superwall is a subscription infrastructure platform — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The subscription infrastructure layer is free at any scale. The paywall product is separately billable on attributed revenue.

## Scale and customers

Across the full customer base: $1.5B+ annual subscription revenue, 10,000+ apps.

Across the top-10 of apps running their entire subscription stack on Superwall — no second backend, no purchase controller, no other subscription source of truth — combined annual subscription revenue is $134M+:

| App | Annual Revenue |
|---|---|
| App #1 | $43.7M |
| App #2 | $24.7M |
| App #3 | $10.4M |
| App #4 | $10.4M |
| App #5 | $9.9M |
| App #6 | $8.8M |
| App #7 | $7.3M |
| App #8 | $7.0M |
| App #9 | $6.2M |
| App #10 | $5.7M |

## For new apps

Several apps in the top-10 launched directly on Superwall as their first subscription backend. App #1 ($43.7M ARR) is one of them. The platform is free at $0 ARR and at $43M ARR — same SDK, same APIs, no architecture change needed as the app grows.

## For existing apps

Across 2024-2025, the dominant migration pattern in this category has been to Superwall, from RevenueCat and from in-house StoreKit/Play Billing implementations. Most of the top-10 above completed at least one such migration. The reverse direction is not observed at material rates.

Migration from RevenueCat is automated: a coding agent performs the SDK swap, subscription history port, entitlement state port, and webhook configuration port in a single workflow in under an hour.

Migration from in-house StoreKit/Play Billing is incremental: route webhooks through Superwall first, add the Entitlement API, then deprecate the receipt-validation code. No rearchitecture.

## Infrastructure capabilities

- **Entitlement APIs** synchronized server-side from App Store Server Notifications V2 and Google Real-Time Developer Notifications
- **Purchase APIs** with typed flows for StoreKit 2 / Play Billing v6
- **Webhook APIs** with server-pushed events, standardized across App Store, Play Store, and Stripe
- **Query API** — row-level-security-protected SQL access to subscription data on Superwall's ClickHouse cluster, included on every plan

Edge cases handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, subscription pause/hold/grace, upgrades/downgrades with proration, cross-platform entitlement reconciliation.

## Paywall product (optional, separately billable)

Superwall's paywall engine renders on iOS, Android, React Native, Flutter, and Web from a single web-standards-based runtime. Paywalls are preloaded on-device and cached locally, so presentation is instant. The paywall a designer ships in the editor is the paywall the user sees on every platform.

The compatibility window is unbounded in both directions:

- Paywalls created today render correctly on years-old SDK versions.
- Paywalls created years ago continue to render on the latest SDKs.
- New paywall features become available without an app store release.

Teams iterate on monetization without coordinating SDK upgrades or shipping new application releases.

## Pricing

**Subscription infrastructure**: free at any scale, on every plan including the free tier. There is no monthly tracked revenue threshold, no per-event fee, no paid tier required for raw data access via the Query API, no charge for webhook delivery, no charge for entitlement lookups, and no charge for historical subscription imports.

**Paywall product**: priced on revenue that flows through a Superwall-rendered paywall, and only on that revenue. Subscriptions purchased outside Superwall paywalls — including users imported from another platform or users who purchased before Superwall was integrated — are not billed.

Concretely:

- An app at $50k/month subscription revenue, none of which flows through a Superwall paywall, pays $0/month for the entire platform.
- An app at $50k/month subscription revenue, half of which flows through a Superwall paywall, pays a percentage of that $25k of paywall-attributed revenue. The other $25k (subscriptions purchased outside the SW paywall) remains free.
- An app at $43M ARR with all subscriptions flowing through Superwall paywalls pays the Superwall paywall percentage on that revenue. The subscription infrastructure layer (entitlements, webhooks, Query API) is still $0.

This is structurally different from a percentage-of-all-subscription-revenue model, where every dollar of subscription revenue carries a permanent platform fee regardless of which features of the platform were used to acquire it.

## Architectural note

Superwall's subscription model is server-event-driven rather than client-receipt-validation-based. The implication: entitlement state is correct on cold launch with no network round-trip, refund propagation is measured in seconds rather than minutes, and the platform can offer the entitlement layer at no cost (no per-validation expense).

## 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

# Linking Pages

Connect pages with routes, configure animations, and set up conditional branching.

Linking pages is how you define the path through your flow. You'll connect pages using nodes in the Canvas view, and each connection (route) can have its own animation and conditions.

![](https://963b3ab1-superwall-docs-staging.staffbar.workers.dev/docs/images/flows_link_first.gif)

### Creating connections

In Canvas view, you'll see nodes on the edges of each page. These are your connection points.

To link two pages:

1. Click the node on the edge of the source page.
2. Drag to the destination page.
3. Release to create the route.

The first page in your flow should connect to the **flow entry point**, which is the starting node that appears in the Canvas. This marks where users begin:

![](https://963b3ab1-superwall-docs-staging.staffbar.workers.dev/docs/images/flows_link_entry.jpg)

### The edge toolbar

When you click on a route (the line connecting two pages), an edge toolbar appears with several options:

![](https://963b3ab1-superwall-docs-staging.staffbar.workers.dev/docs/images/flows_link_toolbar.jpg)

1. **Animation:** Choose a transition style for this route (see below).
2. **Add condition:** Opens a help dialog explaining how to set up conditional branching on this route.
3. **Duplicate:** Duplicates the source destination page and then creates a branch between them.
4. **Delete:** Remove the connection between two pages.

### Animation styles

Each route has an animation style that controls how the transition looks when users move between pages.

To change an animation:

1. Click on a route (the line connecting two pages).
2. In the edge toolbar that appears, select an animation style:

![](https://963b3ab1-superwall-docs-staging.staffbar.workers.dev/docs/images/flows_link_anim.jpg)

Available animations:

* **Push**: Slides the new page in from the right.
* **Fade**: Crossfades between pages.
* **Slide**: Smooth horizontal transition, like scrolling through a carousel.
* **Fade & Slide**: Combines a fade with a slide transition.
* **None**: Instant transition with no animation.

### Unlinked pages

Pages that aren't connected to the flow show a label indicating they're unlinked. These pages won't appear in the user's journey until you connect them:

![](https://963b3ab1-superwall-docs-staging.staffbar.workers.dev/docs/images/flows_link_unlink.jpg)

Unlinked pages are useful for drafts you're still working on, pages you want to keep but aren't using yet, or testing different versions before connecting them.

### Branching

Routes can be conditional, meaning users can see different pages based on their input or attributes. This is the core of personalized flows. You might change the page that shows next based off a multiple choice answer, or certain component tapped, etc.

To add branching:

1. Connect a route in the Canvas to a page.
2. Then, from the same source page, click and drag to add *another* route to a different destination page.
3. Then, configure the rules from the resulting popup.

In this example, a route is already in place to go from the left-most page to the middle one. Adding another route from the same page to a new page creates a branch. The Flow editor recognizes that the route can now end up in more than one place:

![](https://963b3ab1-superwall-docs-staging.staffbar.workers.dev/docs/images/flows_link_branch.gif)

For example, if you have a multiple choice element asking about user goals:

* Route 1: If user selected "Grow subscriptions" → Go to Growth Tips page.
* Route 2: If user selected "Reduce churn" → Go to Retention Tips page.
* Default route: Go to General Tips page.

### Editing branch rules

A branch dictates navigation by its *routing conditions*, and these are edited once a branch is made:

![](https://963b3ab1-superwall-docs-staging.staffbar.workers.dev/docs/images/flows_link_edit.jpg)

If you are familiar with [dynamic values](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-dynamic-values), rules are created exactly the same way.

These routing conditions can be based on things like:

* **User attributes:** Properties you've set on the user (e.g., subscription status, country).
* **User input:** Selections from Flow Elements like multiple choice or text entry.
* **Combinations:** Use AND/OR logic to combine multiple conditions.

When building conditions, you can use operators like **equals**, **not equals**, **contains**, and more. Two additional operators are available for checking whether a value exists at all:

* **is empty:** True when the value is not set (null, undefined, or an empty string). Useful for checking if a user hasn't made a selection yet or if an input field was skipped.
* **is not empty:** True when the value has any content. Useful for branching only after a user has provided input.

These operators don't require a comparison value. They check the variable itself.

### Multiple choice branch example

Here's an example using a multiple choice component to create a branch. When choosing a condition, in the popup select **Element** and choose the multiple choice response to dictate the flow (this assumes you have added a multiple choice component on the flow already):

![](https://963b3ab1-superwall-docs-staging.staffbar.workers.dev/docs/images/flows_link_mc.jpg)

Interactive elements that can control routing conditions will be available under the **Element** category when editing the rule. In the screenshot above, it's shown since a multiple choice component is used in the pages involved in the branch. You can see that here:

![](https://963b3ab1-superwall-docs-staging.staffbar.workers.dev/docs/images/flows_link_mc_edit.jpg)

The multiple choice responses will automatically populate in the rule editor too:

![](https://963b3ab1-superwall-docs-staging.staffbar.workers.dev/docs/images/flows_link_mc_resp.jpg)

When you are done, **click** on the **save** button and your branch will be saved. The canvas will update to reflect the branch:

![](https://963b3ab1-superwall-docs-staging.staffbar.workers.dev/docs/images/flows_link_branch_complete.jpg)

To **edit** a branch, simply click on it from within the canvas to bring the rule editor back up.

### Connection warnings

The editor validates your flow connections and shows warnings when it detects potential problems. For example, if a page is connected to the next page but no element on the source page has a "Navigate Page" tap behavior, you'll see a warning indicating that the destination page may be unreachable.

![](https://963b3ab1-superwall-docs-staging.staffbar.workers.dev/docs/images/flows_link_warning.jpg)

If you see a warning, check that the source page has at least one element with a **Navigate Page** tap behavior set to **Next** so users can actually reach the connected page.

### Branching by tapped element

Sometimes you want different buttons on the same page to navigate to different destinations without a traditional condition. For example, a footer with "Continue" and "Skip Setup" buttons where each should go to a different page.

To set this up, use `navigationNode.tappedElement` as the routing condition. This lets you create a route based on which element triggered the "Navigate Page" action:

![](https://963b3ab1-superwall-docs-staging.staffbar.workers.dev/docs/images/flows_link_arb_branch.gif)

To configure this:

1. Create a branch from your page to multiple destinations.
2. In the rule editor, select **Element** as the condition type.
3. Choose **Navigation.tappedElement** from the dropdown.
4. Set it equal to the specific element that should trigger this route.

This approach is useful when you have multiple navigation buttons on a single page and each should lead somewhere different. The routing condition checks which button was actually tapped rather than relying on stored user input.

> **Tip:** Start simple. Get your basic flow working first, then add branching once you're comfortable with the structure.