# 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

# Delete User Data

Delete everything Superwall holds about one of your app's users, for GDPR and other privacy requests, from the dashboard or the API, and cancel it if needed.

When one of your users asks you to delete their data, you can have Superwall delete it from the user's page in the dashboard or with the API. Superwall holds the request for 25 days before it deletes the user's events, attributes, aliases and subscription records from every app in the project. You can cancel the request any time during those 25 days.

## Requirements

* Your organization role is **Owner**, **Admin** or **User**. If your access is limited to some projects, you also need a Project Admin grant on this project. Every role sees the **Delete user data** button, but the request fails for Editors, Readers and Analysts. See [Organization roles](/docs/dashboard/dashboard-settings/overview-settings-access-controls#organization-roles).
* To use the API, an API key with the &#x2A;*User erasure (GDPR)** write scope (`app_users:erase`). Reading requests needs `users:read`.

## Delete a user's data

1. Click **Users** in the sidebar.
2. Find the user and click their row to open their page.
3. Click **Delete user data** at the top right.
4. Optional: in &#x2A;*Reference (optional)**, enter your own ID for the request, such as a support ticket number. It's kept as proof of deletion.
5. Click **Delete user data**.

![The Delete user data dialog on a user](https://superwall.com/docs/images/delete-user-data-dialog.jpg)

A toast shows the scheduled date. The user's page then shows a **User data deletion scheduled** notice with that date, and the **Delete user data** button is hidden until the request finishes or is canceled.

### If the user has an active subscription

Deleting a user's data never cancels their subscriptions. If the user has an active, renewing subscription billed through your own payment account (Stripe, Paddle or a custom store), the dialog lists it and stops you. App Store and Google Play subscriptions don't block the request, since only the user can cancel those.

1. Cancel the subscription in your payment provider if the user shouldn't be charged again.
2. Check **Delete their data anyway**.
3. Click **Delete user data**.

## Cancel a deletion

You can cancel until the date in the **User data deletion scheduled** notice. After that date, or once the deletion starts, it can't be canceled or undone.

1. Open the user's page under **Users**.
2. Click **Cancel deletion** in the **User data deletion scheduled** notice.

The notice goes away and the **Delete user data** button comes back.

## What happens after you request it

Until the 25 days are up, nothing changes. The user stays in the dashboard with all their data, and they can keep using your app.

After that, Superwall deletes the user from every app in the project, because a user's subscriptions and web purchases can span the project's apps. While the deletion is in progress, the notice says **Deleting user data**.

When it's done, opening the user's page shows **User data deleted** with the date and your reference. If the user comes back and sends new events, their page shows that new data with a **User data deleted** notice above it.

## What Superwall keeps

Some data stays after a deletion:

* **Revenue records.** Amounts and dates stay so your revenue charts don't change. The user ID, device IDs, user attributes, IP addresses and user agents are removed from them.
* **Data shared with another identified user.** An alias, device or store subscription that another identified user is also linked to stays.
* **Aliases over the limit.** See [Users with more than 50 aliases](#users-with-more-than-50-aliases).
* **Placeholder IDs.** If the ID is a shared placeholder such as `null`, nothing is deleted, since it stands for many people.
* **Data outside Superwall.** Data already sent to your integrations (such as Mixpanel, Amplitude or Meta) and your own Stripe account. Delete it there yourself.
* **Subscriptions.** App Store and Google Play renewals keep arriving. With the user's data gone, Superwall can't link them back to the user, so they show as unattributed revenue.

Once the deletion starts, the user's page lists anything that stayed, for example "Some data stays: 2 aliases shared with another user."

## Users with more than 50 aliases

A deletion covers at most 50 anonymous aliases. A user with more than that is more likely a shared login than one person, so Superwall deletes none of their aliases' anonymous events. It still removes the link between those aliases and the user.

When you request a deletion for a user over the limit, the toast warns you. If you know it's one person, delete each alias's data before the deletion runs:

1. Open the user's page and note the IDs under **Aliases**.
2. Request a deletion for each alias ID with the [API](#use-the-api), using the alias as `app_user_id`.

Do this before the 25 days are up. Once the user's deletion runs, their alias list is gone. A request for an alias deletes only that alias, not the identified user it belongs to.

## Use the API

The endpoints live under `https://api.superwall.com/v2/applications/{application_id}/user_erasures` and use an [API key](/docs/agents/api#authentication) as a bearer token.

| Method | Path                                                          | Scope             | What it does                                                         |
| ------ | ------------------------------------------------------------- | ----------------- | -------------------------------------------------------------------- |
| `POST` | `/v2/applications/{application_id}/user_erasures`             | `app_users:erase` | Requests a deletion.                                                 |
| `GET`  | `/v2/applications/{application_id}/user_erasures`             | `users:read`      | Lists requests, newest first. Add `?app_user_id=` to check one user. |
| `GET`  | `/v2/applications/{application_id}/user_erasures/{id}`        | `users:read`      | Returns one request.                                                 |
| `POST` | `/v2/applications/{application_id}/user_erasures/{id}/cancel` | `app_users:erase` | Cancels a scheduled request.                                         |

To request a deletion:

```bash
curl -X POST https://api.superwall.com/v2/applications/$APPLICATION_ID/user_erasures \
  -H "Authorization: Bearer $SUPERWALL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"app_user_id": "user_123", "external_reference": "DSAR-1042"}'
```

The body takes:

* `app_user_id` (required): the user's ID, exactly as your app identifies them.
* `external_reference`: your own ID for the request, up to 191 characters.
* `acknowledge_active_subscriptions`: set to `true` to delete a user who has an active subscription billed through your own payment account. Without it, the request fails with a `409` that lists those subscriptions.

If the user already has a scheduled or running request, you get that request back instead of a new one. The response looks like this:

```json
{
  "id": 4201,
  "object": "app_user_erasure",
  "application_id": 1234,
  "app_user_id": "user_123",
  "status": "scheduled",
  "source": "api",
  "external_reference": "DSAR-1042",
  "created_at": "2026-11-12T15:00:00.000Z",
  "eligible_at": "2026-12-07T15:00:00.000Z",
  "cancelled_at": null,
  "started_at": null,
  "completed_at": null,
  "steps": [],
  "over_alias_limit": false,
  "scope": null
}
```

* `status` starts as `scheduled`, changes to `running` while the deletion is in progress, then to `completed`. A canceled request is `cancelled`.
* `over_alias_limit` is `true` when the user has more than 50 aliases across the project. It's only returned when you request a deletion.
* `scope` is filled in when the deletion starts. It counts what was deleted and what stayed, and `partial` is `true` when anything stayed.
* After the deletion completes, `app_user_id` is `null`. You can still find the request with `?app_user_id=`.
* Canceling fails with a `409` once `eligible_at` has passed, or when the request already started, finished or was canceled.

## Related

* [Users and User Profiles](/docs/dashboard/overview-users)
* [Access controls](/docs/dashboard/dashboard-settings/overview-settings-access-controls)
* [Superwall API](/docs/agents/api)