# 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

# Why is my paywall not updating after publishing?

Troubleshoot why users may still see an outdated paywall after you've made and published updates in the Superwall dashboard

## Quick Checklist

Before diving into detailed troubleshooting, verify these common causes:

* [ ] Have you **published** the paywall after making changes?
* [ ] Is the user **assigned to a different paywall** in an A/B test?
* [ ] Are you using **conditional visibility** (e.g., `hasIntroductoryOffer`) that shows different content?
* [ ] Has the user's **trial eligibility** changed, showing a different UI state?

***

## Step 1: Verify the Paywall is Published

Changes made in the paywall editor are saved locally but not live until published.

**To check:**

1. Open the paywall in the editor
2. Look for the **Publish** button in the top-right
3. If it's clickable, your changes haven't been published yet

**Note:** Publishing a paywall doesn't automatically update users who have already been assigned to it in an experiment.

***

## Step 2: Check Campaign and Experiment Setup

### Multiple Paywalls in a Campaign

If your campaign has multiple paywalls (an A/B test), users are randomly assigned to one variant. The user seeing an "outdated" paywall may simply be assigned to a different variant than the one you updated.

**To check:**

1. Go to **Campaigns** → Select your campaign
2. Look at the **Paywalls** tab for the relevant audience
3. Verify which paywalls are active and their distribution percentages

### Sticky Assignments

**Important:** Superwall assignments are "sticky." Once a user is assigned to a paywall variant, they continue seeing that same paywall regardless of:

* Changes you make to presentation percentages
* Updates you publish to other paywalls
* App reinstalls or calling `Superwall.reset()`

This is by design. It ensures experiment integrity and allows you to keep existing users on an old pricing while testing new pricing with new users.

***

## Step 3: Check Conditional Visibility and Dynamic Values

Paywalls often use **dynamic values** to show different content based on conditions. The most common scenario is **trial eligibility**.

### Trial Eligibility (`products.hasIntroductoryOffer`)

If your paywall has components with visibility controlled by `products.hasIntroductoryOffer`:

* **True:** User is eligible for a free trial/intro offer
* **False:** User has already used their trial (or the product has no trial)

**Common issue:** Apple App Store reviewers often test with accounts that have already used trials, so `hasIntroductoryOffer` is `false` for them, showing different UI than you expect.

**To check in the editor:**

1. Open your paywall in the editor
2. Click **Variables** in the floating toolbar
3. Toggle `products.hasIntroductoryOffer` between true/false
4. Observe which components appear/disappear

### Other Dynamic Conditions

Check if any components have visibility rules based on:

* Selected product index
* Device type
* User attributes
* Custom parameters

Look for components that have the **gear icon** indicating dynamic values are set.

***

## Step 4: Verify Products Are Correctly Configured

### Product Approval Status

Products must be approved in App Store Connect before they can be properly displayed:

* Products in "Waiting for Review" may not load correctly
* Sandbox testing uses different product states than production

**To check:**

1. Go to App Store Connect → Your App → Subscriptions
2. Verify all products show "Ready to Submit" or "Approved"

### Product Assignment on Paywall

Ensure the correct products are assigned to your paywall:

1. Open the paywall in the editor
2. Check the **Products** section on the left sidebar
3. Verify the intended products are selected as Primary, Secondary, etc.

***

## Step 5: Understand Caching Behavior

### Server-Side Caching

Superwall's static configuration is cached by CDN for up to **1 hour**. After publishing changes:

* New users get the update immediately (fresh cache)
* Existing users may see cached content for up to 1 hour

### Device-Side Caching

If your paywall has **Cache on Device** enabled in settings:

* The SDK stores the paywall locally for faster presentation
* Reinstalling the app clears this cache
* `Superwall.reset()` clears on-device data but NOT server-side assignments

***

## Step 6: Testing Checklist

When testing paywall updates, follow this process:

### For Fresh Testing

1. Wait a few minutes for cache propagation
2. Use a **new user ID** or test account
3. Delete and reinstall the app
4. Trigger the placement that shows the paywall

### For App Store Review

1. Remember reviewers may not be eligible for trials (trial already used)
2. Test your paywall with `hasIntroductoryOffer = false` in the editor
3. Ensure all UI states look correct for non-trial-eligible users

***

## Common Scenarios and Solutions

| Scenario                                       | Likely Cause                                  | Solution                                   |
| ---------------------------------------------- | --------------------------------------------- | ------------------------------------------ |
| User sees old paywall copy                     | Sticky assignment to old variant              | Wait for new users or use new test account |
| User sees different products                   | Assigned to different A/B variant             | Check which variant user is assigned to    |
| Trial text showing when user isn't eligible    | `hasIntroductoryOffer` conditional visibility | Check dynamic values on text components    |
| Non-trial text showing for trial-eligible user | Same as above, inverted                       | Verify conditional logic in editor         |
| Changes not visible after publishing           | CDN cache or device cache                     | Wait up to 1 hour, or reinstall app        |
| App Store reviewer sees wrong content          | Reviewer's trial eligibility differs          | Design for both trial/non-trial states     |

***

## Related Documentation

* [Publishing Paywalls](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-publishing)
* [A/B Testing and Experiments](/docs/dashboard/dashboard-campaigns/campaigns-starting-an-experiment)
* [Dynamic Values](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-dynamic-values)
* [Variables Reference](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-variables)
* [Campaign Audiences](/docs/dashboard/dashboard-campaigns/campaigns-audience)