# 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

# SuperwallOptions

A configuration class for customizing paywall appearance and behavior.

> **Warning:** Only modify `networkEnvironment` if explicitly instructed by the Superwall team. Use `.release` (default) for production apps.

> **Tip:** Use different `SuperwallOptions` configurations for debug and release builds to optimize logging and behavior for each environment.

> **Note:** The SDK automatically chooses StoreKit 2 on iOS 15+ and falls back to StoreKit 1 on older versions, but you can override this with `storeKitVersion`.

## Purpose

Configures various aspects of Superwall behavior including paywall presentation, networking, logging, and StoreKit version preferences.

## Signature

```swift
@objcMembers
public final class SuperwallOptions: NSObject {
  public var paywalls: PaywallOptions
  public var storeKitVersion: StoreKitVersion
  public var networkEnvironment: NetworkEnvironment
  public var logging: LoggingOptions
  public var localeIdentifier: String?
  public var shouldBypassAppTransactionCheck: Bool
  public var eventTrackingBehavior: EventTrackingBehavior
  @available(*, deprecated, renamed: "eventTrackingBehavior")
  public var isExternalDataCollectionEnabled: Bool
  public var testModeBehavior: TestModeBehavior
  public var localResources: [String: AssetResource]
}
```

## Parameters

<TypeTable
  type="{
  paywalls: {
    type: &#x22;PaywallOptions&#x22;,
    typeDescriptionLink: &#x22;/ios/sdk-reference/PaywallOptions&#x22;,
    description: &#x22;Configuration for paywall appearance and behavior.&#x22;,
    required: true,
  },
  storeKitVersion: {
    type: &#x22;StoreKitVersion&#x22;,
    description: &#x22;Preferred StoreKit version (`.storeKit1` or `.storeKit2`).&#x22;,
    default: &#x22;StoreKit 2 on iOS 15+&#x22;,
  },
  networkEnvironment: {
    type: &#x22;NetworkEnvironment&#x22;,
    description: &#x22;Network environment (`.release`, `.releaseCandidate`, `.developer`, `.custom(String)`). **Use only if instructed by Superwall team.**&#x22;,
    required: true,
  },
  logging: {
    type: &#x22;LoggingOptions&#x22;,
    description: &#x22;Logging configuration including level and scopes.&#x22;,
    required: true,
  },
  localeIdentifier: {
    type: &#x22;String?&#x22;,
    description: &#x22;Override locale for paywall localization (e.g., \&#x22;en_GB\&#x22;).&#x22;,
  },
  shouldBypassAppTransactionCheck: {
    type: &#x22;Bool&#x22;,
    description: &#x22;Disables the app transaction check on SDK launch. Useful in testing environments to avoid triggering the Apple ID sign-in prompt. Available in version 4.9.0+.&#x22;,
    default: &#x22;false&#x22;,
  },
  eventTrackingBehavior: {
    type: &#x22;EventTrackingBehavior&#x22;,
    description:
      &#x22;Controls which SDK events are sent to Superwall. Options: `.all`, `.superwallOnly`, and `.none`. Available in version 4.16.0+.&#x22;,
    default: &#x22;.all&#x22;,
  },
  isExternalDataCollectionEnabled: {
    type: &#x22;Bool&#x22;,
    description:
      &#x22;Deprecated in version 4.16.0. Use `eventTrackingBehavior` instead. Setting this to `false` maps to `.superwallOnly` unless the current behavior is already `.none`.&#x22;,
    default: &#x22;true&#x22;,
  },
  testModeBehavior: {
    type: &#x22;TestModeBehavior&#x22;,
    description:
      &#x22;Controls when the SDK enters test mode. Options: `.automatic`, `.whenEnabledForUser`, `.always`, `.never`. See the [Test Mode guide](/ios/guides/test-mode) for details.&#x22;,
    default: &#x22;.automatic&#x22;,
  },
  localResources: {
    type: &#x22;[String: AssetResource]&#x22;,
    description:
      &#x22;A dictionary mapping resource IDs to local file URLs or asset catalog images. See [`localResources`](/ios/sdk-reference/localResources) for the property schema and the [Local Resources guide](/ios/guides/local-resources) for setup details.&#x22;,
    default: &#x22;[:]&#x22;,
  },
}"
/>

## Returns / State

This is a configuration object used when calling [`configure()`](/docs/ios/sdk-reference/configure).

## Usage

Basic options setup:

```swift
let options = SuperwallOptions()

// Configure paywall behavior
options.paywalls.shouldShowPurchaseFailureAlert = false
options.paywalls.shouldAutoShowPurchaseLoadingIndicator = true
options.paywalls.automaticallyDismiss = true

// Set StoreKit version preference
options.storeKitVersion = .storeKit2

// Configure logging
options.logging.level = .warn
options.logging.scopes = [.superwallCore, .paywallViewController]

// Set locale for testing
options.localeIdentifier = "en_GB"

// Bypass app transaction check (useful for testing)
options.shouldBypassAppTransactionCheck = true

// Control SDK event collection
options.eventTrackingBehavior = .superwallOnly

// Use with configure
Superwall.configure(
  apiKey: "pk_your_api_key",
  options: options
)
```

## Event Tracking Behavior

Use `eventTrackingBehavior` to decide which SDK events are sent to Superwall. Set it before `configure()` for the initial value, or update [`Superwall.shared.eventTrackingBehavior`](/docs/ios/sdk-reference/Superwall) later when the user changes a privacy or consent setting.

| Behavior         | Result                                                                                                                                                                                    |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.all`           | Sends all SDK event collection. This is the default.                                                                                                                                      |
| `.superwallOnly` | Keeps internal Superwall event collection, but suppresses user-initiated `Superwall.track(...)` calls, trigger-fire events, and user-attribute updates.                                   |
| `.none`          | Sends no SDK events to Superwall. Install-attribution matching is also skipped, so `acquisition_*` user attributes are not populated and audience rules that rely on them will not match. |

```swift
let options = SuperwallOptions()
options.eventTrackingBehavior = .none

Superwall.configure(
  apiKey: "pk_your_api_key",
  options: options
)
```

`isExternalDataCollectionEnabled` is deprecated. Existing code that sets it to `false` now maps to `.superwallOnly`; use `.none` when your app needs to stop SDK event collection entirely.

PaywallOptions configuration:

```swift
let paywallOptions = PaywallOptions()

// Presentation behavior
paywallOptions.shouldShowPurchaseFailureAlert = false
paywallOptions.shouldAutoShowPurchaseLoadingIndicator = true
paywallOptions.automaticallyDismiss = true

// Transaction behavior  
paywallOptions.transactionTimeout = 30.0 // seconds
paywallOptions.restoreFailedPurchaseAlert.title = "Restore Failed"
paywallOptions.restoreFailedPurchaseAlert.message = "Please try again"

// Product overrides
paywallOptions.overrideProductsByName = [
  "primary": "produceID_to_replace_primary_product"
]
// Assign to main options
options.paywalls = paywallOptions
```

Logging configuration:

```swift
let loggingOptions = LoggingOptions()
loggingOptions.level = .debug
loggingOptions.scopes = [.all] // or specific scopes like [.superwallCore, .network]

options.logging = loggingOptions
```

Real-world example for production:

```swift
func configureSuperwallForProduction() {
  let options = SuperwallOptions()
  
  // Minimal logging for production
  options.logging.level = .error
  
  // Customize paywall behavior
  options.paywalls.shouldShowPurchaseFailureAlert = true
  options.paywalls.automaticallyDismiss = true
  
  // Use StoreKit 2 for better performance on iOS 15+
  options.storeKitVersion = .storeKit2
  
  Superwall.configure(
    apiKey: "pk_your_production_api_key",
    options: options
  )
}
```

Debug configuration for development:

```swift
func configureSuperwallForDebug() {
  let options = SuperwallOptions()
  
  // Verbose logging for debugging
  options.logging.level = .debug
  options.logging.scopes = [.all]
  
  // Show detailed error alerts
  options.paywalls.shouldShowPurchaseFailureAlert = true
  
  // Test with specific locale
  options.localeIdentifier = "es_ES"
  
  Superwall.configure(
    apiKey: "pk_your_test_api_key",
    options: options
  )
}
```

## Related

* [`PaywallOptions`](/docs/ios/sdk-reference/PaywallOptions)
* [`localResources`](/docs/ios/sdk-reference/localResources)
* [`Superwall.eventTrackingBehavior`](/docs/ios/sdk-reference/Superwall)

## Runtime Interface Style Configuration

While `SuperwallOptions` provides initial configuration, you can dynamically change the interface style (light/dark mode) for paywalls at runtime using:

```swift
// Force dark mode for all paywalls
Superwall.shared.setInterfaceStyle(to: .dark)

// Force light mode for all paywalls  
Superwall.shared.setInterfaceStyle(to: .light)

// Revert to system default
Superwall.shared.setInterfaceStyle(to: nil)
```

Use this method if you have a themeing system that is different than
the system.

The change takes effect immediately and persists until changed again or the app restarts.