# 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

# setSubscriptionStatus()

A function that manually sets the subscription status when using a custom PurchaseController.

> **Warning:** This function should only be used when implementing a custom [`PurchaseController`](/docs/android/sdk-reference/PurchaseController). When using Superwall's built-in purchase handling, the subscription status is managed automatically.

> **Info:** You must call this function whenever the user's entitlements change to keep Superwall's subscription status synchronized with your purchase system.

## Purpose

Manually updates the subscription status when using a custom [`PurchaseController`](/docs/android/sdk-reference/PurchaseController) to ensure paywall gating and analytics work correctly.

## Signature

```kotlin
fun Superwall.setSubscriptionStatus(status: SubscriptionStatus)
```

```java
// Java
public void setSubscriptionStatus(SubscriptionStatus status)
```

## Parameters

<TypeTable
  type="{
  status: {
    type: &#x22;SubscriptionStatus&#x22;,
    description: &#x22;The subscription status to set. Can be `SubscriptionStatus.Unknown`, `SubscriptionStatus.Active(entitlements)`, or `SubscriptionStatus.Inactive`.&#x22;,
    required: true,
  },
}"
/>

## Returns / State

This function returns `Unit`. The new status will be reflected in the [`subscriptionStatus`](/docs/android/sdk-reference/subscriptionStatus) StateFlow and will trigger the [`SuperwallDelegate.subscriptionStatusDidChange`](/docs/android/sdk-reference/SuperwallDelegate) callback.

## Usage

Set active subscription with entitlements:

```kotlin
// User purchased premium subscription
Superwall.instance.setSubscriptionStatus(
    SubscriptionStatus.Active(setOf("premium", "pro_features"))
)
```

Set inactive subscription:

```kotlin
// User's subscription expired or was cancelled
Superwall.instance.setSubscriptionStatus(SubscriptionStatus.Inactive)
```

Set unknown status during initialization:

```kotlin
// While checking subscription status on app launch
Superwall.instance.setSubscriptionStatus(SubscriptionStatus.Unknown)
```

Usage with RevenueCat:

```kotlin
class RevenueCatPurchaseController : PurchaseController {
    
    override suspend fun purchase(
        activity: Activity,
        product: StoreProduct
    ): PurchaseResult {
        return try {
            val result = Purchases.sharedInstance.purchase(activity, product.sku)
            
            // Update Superwall subscription status based on RevenueCat result
            if (result.isSuccessful) {
                val entitlements = result.customerInfo.entitlements.active.keys
                Superwall.instance.setSubscriptionStatus(
                    SubscriptionStatus.Active(entitlements)
                )
                PurchaseResult.Purchased
            } else {
                PurchaseResult.Failed(Exception("Purchase failed"))
            }
        } catch (e: Exception) {
            PurchaseResult.Failed(e)
        }
    }
    
    override suspend fun restorePurchases(): RestorationResult {
        return try {
            val customerInfo = Purchases.sharedInstance.restorePurchases()
            val activeEntitlements = customerInfo.entitlements.active.keys
            
            if (activeEntitlements.isNotEmpty()) {
                Superwall.instance.setSubscriptionStatus(
                    SubscriptionStatus.Active(activeEntitlements)
                )
            } else {
                Superwall.instance.setSubscriptionStatus(SubscriptionStatus.Inactive)
            }
            
            RestorationResult.Restored
        } catch (e: Exception) {
            RestorationResult.Failed(e)
        }
    }
}
```

Listen for external subscription changes:

```kotlin
class SubscriptionManager {
    
    fun onSubscriptionStatusChanged(isActive: Boolean, entitlements: Set<String>) {
        val status = if (isActive) {
            SubscriptionStatus.Active(entitlements)
        } else {
            SubscriptionStatus.Inactive
        }
        
        // Update Superwall whenever subscription status changes externally
        Superwall.instance.setSubscriptionStatus(status)
    }
}
```

Java usage:

```java
// Set active subscription
Set<String> entitlements = Set.of("premium", "pro_features");
Superwall.getInstance().setSubscriptionStatus(
    new SubscriptionStatus.Active(entitlements)
);

// Set inactive subscription
Superwall.getInstance().setSubscriptionStatus(
    SubscriptionStatus.Inactive.INSTANCE
);
```