# 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

# SuperwallEvent

A sealed class representing analytical events that are automatically tracked by Superwall.

> **Info:** These events provide comprehensive analytics about user behavior and paywall performance. Use them to track conversion funnels, user engagement, and revenue metrics in your analytics platform.

> **Tip:** Common events to track for conversion analysis include `TriggerFire`, `PaywallOpen`, `TransactionStart`, and `TransactionComplete`.

## Purpose

Represents internal analytics events tracked by Superwall and sent to the [`SuperwallDelegate`](/docs/android/sdk-reference/SuperwallDelegate) for forwarding to your analytics platform.

## Signature

```kotlin
sealed class SuperwallEvent {
    // User lifecycle events
    object FirstSeen : SuperwallEvent()
    object AppOpen : SuperwallEvent()
    object AppLaunch : SuperwallEvent()
    object AppClose : SuperwallEvent()
    object SessionStart : SuperwallEvent()
    object IdentityAlias : SuperwallEvent()
    object AppInstall : SuperwallEvent()

    // Deep linking
    data class DeepLink(val url: String) : SuperwallEvent()

    // Paywall events
    data class TriggerFire(
        val placementName: String,
        val result: TriggerResult
    ) : SuperwallEvent()

    data class PaywallOpen(val paywallInfo: PaywallInfo) : SuperwallEvent()
    data class PaywallPageView(
        val paywallInfo: PaywallInfo,
        val data: PageViewData
    ) : SuperwallEvent()
    data class PaywallClose(val paywallInfo: PaywallInfo) : SuperwallEvent()
    data class PaywallDecline(val paywallInfo: PaywallInfo) : SuperwallEvent()

    // Transaction events
    data class TransactionStart(
        val product: StoreProduct,
        val paywallInfo: PaywallInfo
    ) : SuperwallEvent()

    data class TransactionComplete(
        val transaction: StoreTransaction?,
        val product: StoreProduct,
        val type: TransactionType,
        val paywallInfo: PaywallInfo
    ) : SuperwallEvent()

    data class TransactionFail(
        val error: TransactionError,
        val paywallInfo: PaywallInfo
    ) : SuperwallEvent()

    data class TransactionAbandon(
        val product: StoreProduct,
        val paywallInfo: PaywallInfo
    ) : SuperwallEvent()

    data class TransactionRestore(
        val restoreType: RestoreType,
        val paywallInfo: PaywallInfo
    ) : SuperwallEvent()

    data class TransactionTimeout(val paywallInfo: PaywallInfo) : SuperwallEvent()

    // Subscription events
    data class SubscriptionStart(
        val product: StoreProduct,
        val paywallInfo: PaywallInfo
    ) : SuperwallEvent()

    data class FreeTrialStart(
        val product: StoreProduct,
        val paywallInfo: PaywallInfo
    ) : SuperwallEvent()

    object SubscriptionStatusDidChange : SuperwallEvent()

    // Restore events
    sealed class Restore : SuperwallEvent() {
        object Start : Restore()
        data class Fail(val error: String) : Restore()
        object Complete : Restore()
    }

    // Customer and permission events
    data class CustomerInfoDidChange(
        val from: CustomerInfo,
        val to: CustomerInfo
    ) : SuperwallEvent()
    data class PermissionRequested(
        val permissionName: String,
        val paywallIdentifier: String
    ) : SuperwallEvent()
    data class PermissionGranted(
        val permissionName: String,
        val paywallIdentifier: String
    ) : SuperwallEvent()
    data class PermissionDenied(
        val permissionName: String,
        val paywallIdentifier: String
    ) : SuperwallEvent()

    // Preloading events
    data class PaywallPreloadStart(val paywallIdentifier: String) : SuperwallEvent()
    data class PaywallPreloadComplete(val paywallCount: Int) : SuperwallEvent()

    // Test mode events
    class TestModeModalOpen : SuperwallEvent()
    class TestModeModalClose : SuperwallEvent()

    // System events
    data class DeviceAttributes(val attributes: Map<String, Any>) : SuperwallEvent()
    data class SurveyResponse(val survey: Survey, val selectedOption: SurveyOption, val customResponse: String?, val paywallInfo: PaywallInfo) : SuperwallEvent()
    class SurveyClose : SuperwallEvent()
    data class CustomPlacement(val name: String, val params: Map<String, Any>, val paywallInfo: PaywallInfo) : SuperwallEvent()
    object Reset : SuperwallEvent()

    // And more...
}
```

```java
// Java - SuperwallEvent is a sealed class hierarchy
// Access via pattern matching or instanceof checks
```

## Parameters

Each event contains associated values with relevant information for that event type. Common parameters include:

* `paywallInfo: PaywallInfo` - Information about the paywall
* `data: PageViewData` - Metadata for multi-page paywall navigation, including the page name, position in the flow, navigation type, and previous-page timing when available
* `product: StoreProduct` - The product involved in transactions
* `url: String` - Deep link URLs
* `attributes: Map<String, Any>` - Device or user attributes

## Returns / State

This is a sealed class that represents different event types. Events are received via [`SuperwallDelegate.handleSuperwallEvent(eventInfo)`](/docs/android/sdk-reference/SuperwallDelegate).

## Usage

These events are received via [`SuperwallDelegate.handleSuperwallEvent(eventInfo)`](/docs/android/sdk-reference/SuperwallDelegate) for forwarding to your analytics platform.

## Deprecations in 2.7.0

* `PaywallWebviewLoadTimeout` is deprecated. This event was causing confusion due to its naming and has been removed from internal tracking. It will no longer fire.

## New events in 2.7.10

* `PaywallPageView` fires when a user navigates within a multi-page paywall. Use `event.data.pageName`, `event.data.flowPosition`, and `event.data.navigationType` to understand how they moved through the flow, and inspect the optional previous-page fields when you need timing context.

## New events in 2.6.6+

* `CustomerInfoDidChange` fires whenever the SDK merges device, web, and external purchase controller data into a new [`CustomerInfo`](/docs/android/quickstart/tracking-subscription-state#reading-detailed-purchase-history-2-6-6) snapshot. The event includes the previous and next objects so you can diff entitlements or transactions.
* `PermissionRequested`, `PermissionGranted`, and `PermissionDenied` correspond to the new **Request permission** action in the paywall editor. Each event carries the `permissionName` and `paywallIdentifier`.
* `PaywallPreloadStart` and `PaywallPreloadComplete` track when preloading kicks off and how many paywalls finished warming the cache.

Example handler:

```kotlin
override fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) {
    when (val event = eventInfo.event) {
        is SuperwallEvent.CustomerInfoDidChange -> {
            analytics.track("customer_info_updated", mapOf(
                "old_products" to event.from.activeSubscriptionProductIds.joinToString(),
                "new_products" to event.to.activeSubscriptionProductIds.joinToString()
            ))
        }
        is SuperwallEvent.PermissionRequested -> {
            analytics.track("permission_requested", mapOf(
                "permission" to event.permissionName,
                "paywall_id" to event.paywallIdentifier
            ))
        }
        is SuperwallEvent.PermissionGranted -> {
            featureFlags.unlock(event.permissionName)
        }
        is SuperwallEvent.PermissionDenied -> {
            showPermissionHelpSheet(event.permissionName)
        }
        is SuperwallEvent.PaywallPageView -> {
            analytics.track("paywall_page_view", mapOf(
                "paywall_id" to event.paywallInfo.id,
                "page_name" to event.data.pageName,
                "flow_position" to event.data.flowPosition,
                "navigation_type" to event.data.navigationType
            ))
        }
        is SuperwallEvent.PaywallPreloadComplete -> {
            Logger.i("Superwall", "Preloaded ${event.paywallCount} paywalls")
        }
        else -> Unit
    }
}
```