# 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

# CustomerCenterConfiguration

Configure the Customer Center screens, actions, support details, and appearance.

## Purpose

`CustomerCenterConfiguration` controls the management and no-purchases screens shown by [`Superwall.instance.presentCustomerCenter()`](/docs/android/sdk-reference/Superwall#present-the-customer-center-285). Set the default on [`SuperwallOptions.customerCenter`](/docs/android/sdk-reference/SuperwallOptions#parameters), or pass a configuration to a single presentation.

## Signature

```kotlin
data class CustomerCenterConfiguration(
    val managementScreen: Screen,
    val noPurchasesScreen: Screen,
    val support: Support = Support(),
    val appearance: Appearance = Appearance(),
    val showsAccountDetails: Boolean = true,
    val warnsAboutDuplicateSubscriptions: Boolean = true
)
```

`CustomerCenterConfiguration.default` creates the built-in configuration: the management screen has restore, change plan, refund, manage subscription with the built-in cancellation survey, and contact support; the no-purchases screen has restore. Purchase-specific actions appear when they apply. The contact support path is hidden until `support.email` is set.

## Parameters

<TypeTable
  type="{
  managementScreen: {
    type: &#x22;Screen&#x22;,
    description: &#x22;Screen shown when the customer has purchase history.&#x22;,
    required: true,
  },
  noPurchasesScreen: {
    type: &#x22;Screen&#x22;,
    description: &#x22;Screen shown when the customer has no purchases.&#x22;,
    required: true,
  },
  support: {
    type: &#x22;Support&#x22;,
    description: &#x22;Support email, app update banner, and web subscription management URL settings.&#x22;,
    default: &#x22;Support()&#x22;,
  },
  appearance: {
    type: &#x22;Appearance&#x22;,
    description: &#x22;Optional light and dark accent colors.&#x22;,
    default: &#x22;Appearance()&#x22;,
  },
  showsAccountDetails: {
    type: &#x22;Boolean&#x22;,
    description: &#x22;Shows the account details section, including the user ID and original download date.&#x22;,
    default: &#x22;true&#x22;,
  },
  warnsAboutDuplicateSubscriptions: {
    type: &#x22;Boolean&#x22;,
    description: &#x22;Shows a warning when Google Play and web subscriptions are both active.&#x22;,
    default: &#x22;true&#x22;,
  },
}"
/>

## Screens and paths

Each `Screen` has an optional title and subtitle plus an ordered list of paths:

```kotlin
data class Screen(
    val title: String? = null,
    val subtitle: String? = null,
    val paths: List<Path>
)
```

When a title is `null`, the screen uses its localized default title. A `null` subtitle uses the localized no-purchases subtitle or no subtitle on the management screen.

Each `Path` has a type, optional row title, optional survey, and an analytics ID:

```kotlin
data class Path(
    val type: PathType,
    val title: String? = null,
    val survey: FeedbackSurvey? = null,
    val id: String = type.defaultId
)
```

Use the `Path` factory methods to create common paths:

```kotlin
import com.superwall.sdk.customercenter.CustomerCenterConfiguration.FeedbackSurvey
import com.superwall.sdk.customercenter.CustomerCenterConfiguration.Path

Path.restore()
Path.manageSubscription(survey = FeedbackSurvey.cancellation)
Path.refund(windowMillis = 7L * 24 * 60 * 60 * 1000)
Path.changePlan()
Path.contactSupport()
Path.url(url = "https://example.com/help", title = "Help")
Path.custom(identifier = "billing_settings", title = "Billing settings")
```

* `restore()` starts a purchase restore.
* `manageSubscription()` opens the available subscription management flow. A `webManagementUrl` can supply the web management destination for web subscriptions.
* `refund(windowMillis)` opens Google Play's order history; `windowMillis` optionally limits eligible purchases by age.
* `changePlan(productIds)` opens the subscription's page in Google Play. `productIds` is accepted but has no effect on Android.
* `contactSupport()` is shown when `support.email` is set.
* `url(url, title, openMethod)` opens a URL. `title` is required; `IN_APP` is the default for HTTP and HTTPS links, and `EXTERNAL` hands the URL to another app.
* `custom(identifier, title)` calls `CustomerCenterDelegate.customerCenterDidSelectAction()` with a `CustomerCenterAction.Custom`. Handle navigation or other behavior in your app.

`id` defaults to the path type's ID and is reported in Customer Center events as `path_id`. Supply a distinct ID when a screen contains multiple paths of the same type. URL path IDs omit query strings and fragments.

## Surveys

`FeedbackSurvey` is an optional single-choice survey shown before a path runs:

```kotlin
data class FeedbackSurvey(
    val id: String,
    val title: String? = null,
    val options: List<Option>
)
```

`FeedbackSurvey.cancellation` is the built-in localized cancellation survey. It asks why the customer is cancelling and provides `Option.tooExpensive`, `Option.dontUse`, and `Option.boughtByMistake`. For custom surveys, provide an `id`, optional `title`, and a list of `Option(id, title)` values. Only manage-subscription paths have a localized default question; a custom survey with no title shows no question. Custom option titles are required for visible text; built-in option IDs use localized titles when their title is `null`.

## Support and appearance

```kotlin
data class Support(
    val email: String? = null,
    val latestAppVersion: String? = null,
    val warnsAboutUpdates: Boolean = true,
    val webManagementUrl: String? = null
)

data class Appearance(
    val accent: ColorPair? = null
)

data class ColorPair(val light: String, val dark: String)
```

* Set `support.email` to show the contact support path.
* Set `support.latestAppVersion` to show an update banner when it is newer than the installed version. `warnsAboutUpdates` controls that banner.
* Set `support.webManagementUrl` to override the web subscription management destination.
* `appearance.accent` tints buttons and links. Provide `light` and `dark` colors as `#RRGGBB` or `#RRGGBBAA` strings; `null` uses the theme accent.

## Related

* [Customer Center guide](/docs/android/guides/customer-center)
* [`CustomerCenterDelegate`](/docs/android/sdk-reference/CustomerCenterDelegate)
* [`SuperwallOptions`](/docs/android/sdk-reference/SuperwallOptions)