# 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

# Video and Lottie Animations on Paywalls

Add video and Lottie animations to a paywall in the editor: upload or link a file, set autoplay, loop, mute and speed, and keep media loading fast.

The paywall editor has a **Video** element and a **Lottie** element for motion on your paywall: a product demo, a looping background, an animated icon or a celebration at the end of an onboarding step. You add them like any other element, and they play inside the paywall on the device.

For shorter effects, such as a button that shrinks when tapped or text that fades in, use tap behavior [animations](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements#animation) or [Effects](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements#effects) instead.

## Requirements

* Video and Lottie elements need no SDK setup. They play inside the paywall.
* To bundle a video with your app instead of downloading it, you need [local resources](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-local-resources): iOS SDK 4.13.0 or later, Android SDK 2.7.7 or later, or Expo SDK 1.1.3 or later.

## Add a video

1. In the paywall editor, click &#x2A;*+** in the sidebar or the **Layout** tab, then choose **Video**.
2. Select the video. In the component editor, under **Video**, do one of these:
   * Click the upload button and choose a video file.
   * Paste the video's URL into the field.
   * Click **Pick from Assets** to use a video from your [Assets](/docs/dashboard/assets) library.
3. Turn the playback options on or off. See [Video options](#video-options).
4. [Publish](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-publishing) the paywall.

The **Video** header shows the file size and the video's dimensions, so you can check how large it is before you publish.

### What happens when you upload

When you upload a video from the video field, the editor converts it in your browser before uploading:

* It re-encodes the video as an H.264 MP4. Videos longer than 2560 pixels on their long edge are scaled down to 2560.
* It saves the first frame as the video's poster.
* It uploads the original file too. The paywall plays the converted MP4 first, and the original only if the MP4 can't play.

Keep the tab open while the upload runs. If your browser can't encode H.264, the editor uploads the original file as is, with no poster.

Even after conversion, a long video takes time to download. Keep videos short, around 3 to 10 seconds, and a few megabytes.

### Video options

| Option                       | What it does                                                                                                                                                                                                                         |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Autoplay**                 | Plays the video as soon as the paywall opens. Turning it on also turns on **Muted**, and **Muted** stays on while **Autoplay** is on.                                                                                                |
| **Loop**                     | Starts the video again from the beginning when it ends.                                                                                                                                                                              |
| **Muted**                    | Plays the video without sound. You can only turn it off when **Autoplay** is off.                                                                                                                                                    |
| **Controls**                 | Shows the playback controls. When **Autoplay** is off, **Controls** is always on, so people can start the video.                                                                                                                     |
| **+ Add Poster**             | Sets an image to show in the video's place until the video starts. Upload one or paste its URL.                                                                                                                                      |
| **+ Add Local Resource**     | Plays a video bundled in your app. Shows for iOS and Android apps. See [Local Resources](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-local-resources).                                                                |
| **Pause When Page Inactive** | Pauses the video while its page isn't showing, and plays it again when it is. Shows when the video is inside a [Navigation](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-navigation-component) element. On by default. |

A new video element starts with **Autoplay**, **Loop** and **Muted** on and **Controls** off. Every time the paywall opens, the video starts again from the beginning. Videos play inline in the paywall, not full screen.

You can also insert a [variable](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-variables) into the video URL with the variable picker, for example to show a different video per user.

## Add a Lottie animation

1. In the paywall editor, click &#x2A;*+** in the sidebar or the **Layout** tab, then choose **Lottie**.
2. Select the element. In the component editor, under **Animation**, paste the animation's URL, or click the upload button and choose a Lottie `.json` file.
3. Set the options. See [Lottie options](#lottie-options).
4. Publish the paywall.

### Lottie options

| Option                       | What it does                                                                                                                                                                                                  |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Loop**                     | Starts the animation again when it ends.                                                                                                                                                                      |
| **Speed**                    | Plays the animation from 0.1x to 4.0x speed. The reset button sets it back to 1x.                                                                                                                             |
| **Pause When Page Inactive** | Pauses the animation while its page isn't showing. Shows when the animation is inside a [Navigation](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-navigation-component) element. On by default. |

A new Lottie element plays as soon as the paywall opens and loops. Every time the paywall opens, it starts again from the beginning.

### Play, pause or react to the end

Each Lottie element has two element variables, under its name in the &#x2A;*[Variables](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-variables)** tab:

* **Is Playing:** `true` while the animation plays. Set it to `false` to pause the animation and `true` to play it.
* **Playback Progress:** how far the animation has played, from `0` to `100`. Set it to jump to that point.

To play or pause the animation when someone taps a button, add a [tap behavior](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements#tap-behaviors) to the button with **Update Variable**, choose the Lottie's **Is Playing** variable, and set the **Operation** to **Toggle**.

## Know when a Lottie animation ends

With **Loop** off, when the animation reaches its end, **Is Playing** becomes `false` and **Playback Progress** becomes `100`. Use either one in a [dynamic value](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-dynamic-values) to change the paywall when the animation finishes. For example, to show a **Continue** button only after the animation ends, give the button a dynamic value that hides it until **Playback Progress** is `100`.

With **Loop** on, the animation never ends, and **Is Playing** stays `true`.

## A video shows a black or blank frame before it plays

Until the video's first frame loads, the element shows its poster. If the video has no poster, nothing fills that space yet. To fix it:

1. Select the video and check for a poster. Videos uploaded from the video field get one from their first frame. Videos added by URL or from **Pick from Assets*&#x2A; don't, so click **+ Add Poster** and add an image of the first frame.
2. Give the video a fixed height under **Size**, or an `aspect-ratio` that matches the video under **Custom CSS**, so the element keeps its size while the video loads.
3. Make the video load sooner. Turn on [**Cache on Device**](/docs/dashboard/guides/paywall-preloading-and-caching#cache-on-device) for the paywall, or bundle the video with your app as a [local resource](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-local-resources).

## A Lottie animation blocks taps on a close button

A Lottie element catches taps like any other element. If it sits on top of a close button, for example as a full-screen overlay, the button under it can't be tapped. Do one of these:

* Select the close button and, under &#x2A;*[Position](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements#position)**, give it a higher **Z Index** than the Lottie element.
* Select the Lottie element and, under &#x2A;*[Custom CSS](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements#custom-css)**, add `pointer-events` with the value `none`. Taps then go through the animation to whatever is under it. Don't do this if the Lottie element has its own tap behavior, because it stops receiving taps.

## Things to know

* A video always fills its element and is cropped to fit, like `object-fit: cover`. Size the element to the video's aspect ratio to avoid cropping.
* Videos you link by URL, rather than upload, aren't converted, and the editor warns that external URLs can slow the paywall down and add CDN costs.
* A video picked from **Assets** plays the file as it's stored in Assets, without the converted MP4 or a poster.
* The Lottie element plays Lottie JSON files. The player and the animation file load over the network when the paywall loads. Lottie animations can't use local resources.
* Large videos and animations are the most common reason a paywall opens slowly. See [Paywall Preloading and Caching](/docs/dashboard/guides/paywall-preloading-and-caching).
* For [Superwall Framework](/docs/framework) paywalls, which are built in code, see [Lottie in Assets](/docs/framework/assets#lottie).

## Related

* [Local Resources](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-local-resources)
* [Paywall Preloading and Caching](/docs/dashboard/guides/paywall-preloading-and-caching)
* [Assets](/docs/dashboard/assets)
* [Navigation component](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-navigation-component)
* [Styling elements](/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements)