# Integrations Documentation # 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 # Adjust The Adjust integration automatically sends Superwall subscription and payment events to Adjust via the server-to-server (S2S) event API. Configure per-platform app tokens, map individual event tokens, and track subscription lifecycle events with device-level attribution. In the **Analytics** section within **Integrations**, you can connect your Adjust account to Superwall. ## Features * **Per-Platform Configuration**: Separate app tokens and S2S security tokens for iOS and Android * **Per-Event Token Mapping**: Configure an individual Adjust event token for each of the 14 subscription event types. Leave a token blank to skip that event. * **Revenue Tracking**: Automatic revenue attribution for purchase and renewal events * **S2S Event API**: Events sent server-to-server to `https://s2s.adjust.com/event` * **Device Identification**: Supports Adjust device ID, IDFA, IDFV, GPS advertising ID, and Google App Set ID * **Callback Parameters**: Product ID, transaction ID, and offer code sent as JSON with each event * **Meta AEM Parameters**: Optional IP address, device name, OS version, and ATT status for Meta attribution * **Sandbox Support**: Sandbox events are forwarded with `environment: "sandbox"` ## Configuration Adjust requires separate credentials for iOS and Android. You can configure one or both platforms. ### Platform settings | Field | Platform | Description | | ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------- | | `app_token_ios` | iOS | Adjust app token for your iOS app. If iOS and Android share the same Adjust app, enter the same value in both fields. | | `app_token_android` | Android | Adjust app token for your Android app. | | `s2s_token_ios` | iOS | Optional S2S security token from Adjust Dashboard > Settings > S2S Security. | | `s2s_token_android` | Android | Optional S2S security token for Android. | | `sales_reporting` | All | Revenue reporting mode: `"Revenue"` (gross) or `"Proceeds"` (net after store fees). | ### Event tokens Each event type has its own token field. Create the corresponding event in Adjust's AppView, then paste the 6-character token here. Leave blank to skip that event type. | Field | Event | Triggered when | | --------------------------------- | ------------------------- | --------------------------------------------------------------------- | | `event_token_trial_start` | Trial Start | `initial_purchase` with TRIAL period | | `event_token_direct_sub_start` | Direct Subscription Start | `initial_purchase` with NORMAL or INTRO period | | `event_token_trial_convert` | Trial Convert | `renewal` with `isTrialConversion = true` | | `event_token_renewal` | Renewal | `renewal` (not a trial conversion) | | `event_token_trial_cancel` | Trial Cancel | `cancellation` with TRIAL period | | `event_token_cancel` | Cancel | `cancellation` with NORMAL or INTRO period | | `event_token_uncancel` | Uncancel | `uncancellation` | | `event_token_billing_issue` | Billing Issue | `billing_issue` | | `event_token_product_change` | Product Change | `product_change` | | `event_token_subscription_paused` | Subscription Paused | `subscription_paused` | | `event_token_one_time_purchase` | One-Time Purchase | `non_renewing_purchase` | | `event_token_trial_expire` | Trial Expire | `expiration` with TRIAL period | | `event_token_expire` | Expire | `expiration` with NORMAL or INTRO period | | `event_token_refund` | Refund | Any event with `price < 0` (takes precedence over all other mappings) | ### Example configuration ```json { "app_token_ios": "your_ios_app_token", "app_token_android": "your_android_app_token", "s2s_token_ios": "your_ios_s2s_token", "s2s_token_android": "your_android_s2s_token", "sales_reporting": "Revenue", "event_token_trial_start": "abc123", "event_token_direct_sub_start": "def456", "event_token_trial_convert": "ghi789", "event_token_renewal": "jkl012", "event_token_cancel": "mno345", "event_token_refund": "pqr678" } ``` ## SDK Setup The Adjust integration requires the Adjust device ID to be set in Superwall so that events can be attributed to the correct device. Set this as early as possible after the Adjust SDK initializes. ### Setting the Adjust device ID **iOS (Swift):** ```swift import Adjust // After Adjust SDK initializes if let adid = Adjust.adid() { Superwall.shared.setIntegrationAttributes([ .adjustId: adid ]) } ``` **Android (Kotlin):** ```kotlin import com.adjust.sdk.Adjust // After Adjust SDK initializes Adjust.getAdid()?.let { adid -> Superwall.instance.setIntegrationAttributes(mapOf( IntegrationAttribute.ADJUST_ID to adid )) } ``` **Flutter (Dart):** ```dart import 'package:adjust_sdk/adjust.dart'; // After Adjust SDK initializes final adid = await Adjust.getAdid(); if (adid != null) { await Superwall.shared.setIntegrationAttributes({ IntegrationAttribute.adjustId: adid, }); } ``` ### Setting additional device identifiers For richer attribution data, pass platform-specific advertising identifiers via `setUserAttributes`: **iOS (Swift):** ```swift import AdSupport import AppTrackingTransparency // After ATT consent is granted let idfa = ASIdentifierManager.shared().advertisingIdentifier.uuidString let idfv = UIDevice.current.identifierForVendor?.uuidString var attrs: [String: String] = [:] if idfa != "00000000-0000-0000-0000-000000000000" { attrs["idfa"] = idfa } if let idfv { attrs["idfv"] = idfv } Superwall.shared.setUserAttributes(attrs) ``` **Android (Kotlin):** ```kotlin import com.google.android.gms.ads.identifier.AdvertisingIdClient // On a background thread val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(context) if (!adInfo.isLimitAdTrackingEnabled) { Superwall.instance.setUserAttributes(mapOf( "advertisingId" to adInfo.id )) } ``` ### What happens without it If no device identifiers are found in `userAttributes`, the event is **skipped** and not sent to Adjust. At least one of the following must be present: `adjustAdid` (or `adjustId`), `idfa`, `idfv`, `advertisingId`, `gps_adid`, or `google_app_set_id`. ## Event Mapping Superwall events are mapped to 14 discrete Adjust event types. INTRO and NORMAL periods are treated identically. Refund detection (`price < 0`) takes precedence over all other mappings. | Superwall Event | Condition | Adjust Event | Revenue? | | ----------------------- | --------------------------- | --------------------- | -------- | | `initial_purchase` | TRIAL period | `trial_start` | No | | `initial_purchase` | NORMAL or INTRO period | `direct_sub_start` | Yes | | `renewal` | `isTrialConversion` = true | `trial_convert` | Yes | | `renewal` | `isTrialConversion` = false | `renewal` | Yes | | `cancellation` | TRIAL period | `trial_cancel` | No | | `cancellation` | NORMAL or INTRO period | `cancel` | No | | `uncancellation` | any | `uncancel` | No | | `billing_issue` | any | `billing_issue` | No | | `product_change` | any | `product_change` | No | | `subscription_paused` | any | `subscription_paused` | No | | `non_renewing_purchase` | any | `one_time_purchase` | Yes | | `expiration` | TRIAL period | `trial_expire` | No | | `expiration` | NORMAL or INTRO period | `expire` | No | | Any event | `price < 0` | `refund` | No | Each Adjust event type corresponds to the `event_token_*` field in your configuration. If no token is configured for an event type, that event is skipped. ## Callback Parameters Every Adjust event includes a `callback_params` field containing a JSON-encoded string with: | Parameter | Description | Always present? | | ---------------- | ------------------------------- | --------------- | | `product_id` | The product identifier | Yes | | `transaction_id` | The transaction identifier | Yes | | `offer_code` | The promotional offer code used | Only if present | ## Revenue Tracking Revenue is only included for these event types: * `direct_sub_start` * `trial_convert` * `renewal` * `one_time_purchase` All other events (cancellations, expirations, billing issues, refunds) do not carry revenue data. ### Revenue calculation | Setting | Formula | Description | | ------------ | ----------------------------------------------- | --------------------------------------- | | `"Revenue"` | `priceInPurchasedCurrency` | Gross revenue in the purchased currency | | `"Proceeds"` | `priceInPurchasedCurrency * takehomePercentage` | Net revenue after store fees | Revenue is omitted when the calculated amount is below 0.001. The currency code from the transaction is sent alongside the revenue value. ## Device Identification Adjust uses device-level identifiers for attribution. The integration reads these from `userAttributes` on the Superwall event: | User attribute | Adjust parameter | Platform | | ----------------------------- | ------------------- | -------- | | `adjustAdid` or `adjustId` | `adid` | All | | `idfa` | `idfa` | iOS | | `idfv` | `idfv` | iOS | | `advertisingId` or `gps_adid` | `gps_adid` | Android | | `google_app_set_id` | `google_app_set_id` | Android | `adjustAdid` is the preferred attribute name. The legacy `adjustId` name is also accepted for backward compatibility. Any one device identifier is sufficient for the event to be sent. ### Meta AEM parameters When present in `userAttributes`, these additional parameters are forwarded to support Meta's Aggregated Event Measurement: | User attribute | Adjust parameter | Platform | | -------------- | ---------------- | --------------- | | `ip_address` | `ip_address` | All (IPv4 only) | | `device_name` | `device_name` | All | | `os_version` | `os_version` | All | | `attStatus` | `att_status` | iOS only | ## Platform Support The integration determines the platform from the `store` field on each event: | Store | Platform | Supported? | | ------------ | -------- | ---------- | | `APP_STORE` | iOS | Yes | | `PLAY_STORE` | Android | Yes | | `STRIPE` | — | Skipped | | `PADDLE` | — | Skipped | Stripe and Paddle events do not identify the originating app platform, so they cannot be attributed in Adjust and are skipped. ## Sandbox Handling Sandbox events **are** sent to Adjust. The `environment` field on the S2S request is set to `"sandbox"` for sandbox events and `"production"` for production events. Adjust uses this field to distinguish test data from real data. ## Testing the Integration ### 1\. Validate credentials When you save the integration, Superwall sends a test event to verify that your app tokens and S2S tokens are valid. If no event tokens are configured yet, credentials are still accepted. ### 2\. Trigger test events * **iOS**: Use TestFlight with a sandbox Apple ID. Note that StoreKit Configuration files do not generate App Store Server Notifications, so webhooks and downstream integrations will not fire. * **Android**: Use license test accounts to perform purchases. ### 3\. Verify in Adjust Check the Adjust dashboard: 1. **Event Log**: Confirm events are arriving with the correct event token 2. **Callback Data**: Verify callback parameters contain the expected `product_id` and `transaction_id` 3. **Revenue**: Confirm revenue amounts and currency codes for purchase events 4. **Device Attribution**: Ensure events are attributed to the correct device via `adid` ## Best Practices 1. **Set the Adjust device ID early**: Call `setIntegrationAttributes` with the Adjust ID as soon as the Adjust SDK initializes. Events without any device identifier are skipped. 2. **Pass advertising identifiers**: Include `idfa`/`idfv` (iOS) or `advertisingId` (Android) via `setUserAttributes` for richer attribution. 3. **Configure S2S security**: Set the per-platform S2S tokens to authenticate requests and prevent spoofed events. 4. **Only configure tokens you need**: Leave event tokens blank for event types you don't want to track. This keeps your Adjust event log focused. 5. **Be consistent with revenue reporting**: Choose Revenue or Proceeds and keep the same setting across all your analytics integrations. 6. **Monitor for opted-out devices**: Adjust returns HTTP 451 when a device has opted out of tracking. These events are skipped automatically. ## Troubleshooting ### Events not appearing 1. **Check device identifiers**: At least one of `adjustAdid`/`adjustId`, `idfa`, `idfv`, `advertisingId`, `gps_adid`, or `google_app_set_id` must be set in user attributes. 2. **Check event token**: The specific event type must have a token configured (e.g., `event_token_trial_start`). Events without tokens are skipped. 3. **Check app token**: The per-platform app token (`app_token_ios` or `app_token_android`) must be set for the event's platform. 4. **Check store**: Only `APP_STORE` (iOS) and `PLAY_STORE` (Android) events are supported. Stripe and Paddle events are skipped. 5. **Check for 451 responses**: The device may have opted out of tracking. Adjust returns HTTP 451 in this case and the event is skipped. ### Revenue not tracking 1. **Check event type**: Only `direct_sub_start`, `trial_convert`, `renewal`, and `one_time_purchase` carry revenue. 2. **Check amount**: Revenue must be >= 0.001 to be included. 3. **Check sales reporting**: Verify your Revenue vs Proceeds setting. 4. **Refunds**: Refund events (`price < 0`) do not include revenue data in the Adjust request. ### Device attribution issues 1. **Check attribute name**: Use `adjustAdid` (preferred) or `adjustId` in user attributes. 2. **Check platform identifiers**: Verify `idfa`/`idfv` (iOS) or `advertisingId`/`gps_adid` (Android) are being passed. 3. **Check platform detection**: `APP_STORE` maps to iOS, `PLAY_STORE` maps to Android. ### S2S API errors * **App token not found (404)**: The app token is not recognized by Adjust. Verify it matches your app in the Adjust dashboard. * **Authentication failure (401/403)**: If using S2S tokens, verify they match the tokens in Adjust Dashboard > Settings > S2S Security. * **Device opted out (451)**: The device has opted out of tracking. This is not an error — the event is skipped. # 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 # Amplitude The Amplitude integration automatically sends Superwall subscription and payment events to your Amplitude project. Track subscription lifecycle events, analyze revenue metrics, and understand user behavior with automatic event mapping and revenue tracking. In the **Analytics** section within **Integrations**, you can connect your Amplitude account to Superwall: ![](/docs/images/integrations-amplitude.jpeg) ### Required fields Fill out the following fields and **click** the **Enable Amplitude** button at the bottom right to save your changes: ![](/docs/images/integrations-config-amplitude.jpeg) * **Region:** Data residency region for your Amplitude project. * **Api Key:** Your Amplitude API key. * **Sandbox Api Key:** Optional API key for sandbox events (leave blank to opt out). * **Sales Reporting:** Which revenue value to report in Amplitude. Choose between **Proceeds** (after store taxes & fees) or **Revenue**. ### Features * **Automatic Event Mapping**: Converts Superwall events to Amplitude-friendly format * **Revenue Tracking**: Automatic revenue attribution with LTV tracking * **Multi-Region Support**: Works with US and EU data residency * **Sandbox Isolation**: Separate tracking for production and sandbox events * **Human-Readable Events**: Events prefixed with `[Superwall]` for easy identification * **Session Tracking**: Automatic session ID generation * **Platform Attribution**: Tracks which store (App Store, Play Store, Stripe) generated revenue ### Configuration #### Required settings | Field | Description | Example | | ----------------- | ---------------------------- | --------------------------- | | `integration_id` | Must be set to `"amplitude"` | `"amplitude"` | | `region` | Data residency region | `"US (Default)"` or `"EU"` | | `api_key` | Your Amplitude API key | `"abc123def456..."` | | `sales_reporting` | Which value to report | `"Revenue"` or `"Proceeds"` | #### Optional settings | Field | Description | Example | | ----------------- | ------------------------------------------------ | ------------- | | `sandbox_api_key` | API key for sandbox events (leave blank to skip) | `"xyz789..."` | #### Example configuration ```json { "integration_id": "amplitude", "region": "US (Default)", "api_key": "your_production_api_key_here", "sandbox_api_key": "your_sandbox_api_key_here", "sales_reporting": "Revenue" } ``` ### Event mapping Superwall events are transformed into human-readable Amplitude events: #### Event name format All events are prefixed with `[Superwall]` followed by a descriptive name: * Example: `[Superwall] Trial Start` * Example: `[Superwall] Subscription Renewal` #### Complete event mapping | Superwall Event | Amplitude Event | Description | | ---------------------------- | ----------------------------------------- | ------------------------- | | `initial_purchase` + TRIAL | `[Superwall] Trial Start` | Trial begins | | `initial_purchase` + INTRO | `[Superwall] Intro Offer Start` | Intro offer begins | | `initial_purchase` + NORMAL | `[Superwall] Subscription Start` | Paid subscription begins | | `renewal` + trial conversion | `[Superwall] Trial Conversion` | Trial converts to paid | | `renewal` + INTRO | `[Superwall] Intro Offer Conversion` | Intro converts to regular | | `renewal` + NORMAL | `[Superwall] Subscription Renewal` | Regular renewal | | `cancellation` + TRIAL | `[Superwall] Trial Cancellation` | Trial cancelled | | `cancellation` + INTRO | `[Superwall] Intro Offer Cancellation` | Intro cancelled | | `cancellation` + NORMAL | `[Superwall] Subscription Cancellation` | Subscription cancelled | | `uncancellation` + TRIAL | `[Superwall] Trial Uncancellation` | Trial reactivated | | `uncancellation` + INTRO | `[Superwall] Intro Offer Uncancellation` | Intro reactivated | | `uncancellation` + NORMAL | `[Superwall] Subscription Uncancellation` | Subscription reactivated | | `expiration` + TRIAL | `[Superwall] Trial Expiration` | Trial ended | | `expiration` + INTRO | `[Superwall] Intro Offer Expiration` | Intro ended | | `expiration` + NORMAL | `[Superwall] Subscription Expiration` | Subscription ended | | `billing_issue` | `[Superwall] Billing Issue` | Payment failed | | `subscription_paused` | `[Superwall] Subscription Paused` | Subscription paused | | `product_change` | `[Superwall] Product Change` | Plan changed | | `non_renewing_purchase` | `[Superwall] Non-Renewing Purchase` | One-time purchase | | Any with `price < 0` | `[Superwall] Refund` | Refund processed | ### Event properties Every Amplitude event includes comprehensive properties: #### Core Amplitude fields * `user_id`: User identifier (uses `originalAppUserId` or `originalTransactionId`) * `event_type`: Human-readable event name with `[Superwall]` prefix * `time`: Event timestamp (milliseconds) * `session_id`: Same as timestamp (groups related events) * `platform`: Store name (APP\_STORE, PLAY\_STORE, STRIPE) * `insert_id`: Unique event ID prefixed with `sw_` #### Revenue fields (when applicable) * `revenue`: Transaction amount (based on sales\_reporting setting) * `price`: Same as revenue * `quantity`: Always 1 * `productId`: Product identifier * `revenueType`: Same as event type (for revenue categorization) #### Event properties object All Superwall webhook data fields are included: * `id`, `name`, `cancelReason`, `exchangeRate` * `isSmallBusiness`, `periodType`, `countryCode` * `price`, `proceeds`, `priceInPurchasedCurrency` * `taxPercentage`, `commissionPercentage`, `takehomePercentage` * `offerCode`, `isFamilyShare`, `expirationAt` * `transactionId`, `originalTransactionId`, `originalAppUserId` * `store`, `purchasedAt`, `currencyCode`, `productId` * `environment`, `isTrialConversion`, `newProductId` * `bundleId`, `ts` ### Revenue tracking #### Automatic revenue attribution Revenue is automatically tracked for events with non-zero amounts: * **Positive revenue**: Purchases, renewals, conversions * **Negative revenue**: Refunds (automatically deducted) * **Zero revenue**: Cancellations, expirations, billing issues #### Revenue reporting options The `sales_reporting` setting determines which value is used: | Setting | Value Used | Description | | ------------ | ---------- | ------------------------------- | | `"Revenue"` | `price` | Gross revenue before store fees | | `"Proceeds"` | `proceeds` | Net revenue after store fees | #### Revenue examples **Initial Purchase ($9.99):** ```json { "event_type": "[Superwall] Subscription Start", "revenue": 9.99, "price": 9.99, "productId": "com.example.premium", "revenueType": "[Superwall] Subscription Start" } ``` **Refund (-$9.99):** ```json { "event_type": "[Superwall] Refund", "revenue": -9.99, "price": -9.99, "productId": "com.example.premium", "revenueType": "[Superwall] Refund" } ``` ### User identification The integration uses this hierarchy for user identification: 1. **Primary**: `originalAppUserId` (if available) 2. **Fallback**: `originalTransactionId` (always present) This ensures consistent user tracking across: * Multiple devices * App reinstalls * Legacy users without app user IDs #### Platform tracking The `platform` field identifies the payment source: * `APP_STORE`: iOS App Store * `PLAY_STORE`: Google Play Store * `STRIPE`: Stripe web payments This helps analyze: * Revenue by platform * Platform-specific retention * Cross-platform users ### Sandbox handling #### With sandbox API key If `sandbox_api_key` is configured: * Production events → Production project * Sandbox events → Sandbox project #### Without sandbox API key If `sandbox_api_key` is empty: * Production events → Production project * Sandbox events → **Skipped** (not sent) This prevents test data from polluting production analytics. ### Data residency Amplitude supports two data residency regions: | Region | API Endpoint | Use Case | | -------------- | -------------------- | --------------- | | `US (Default)` | api2.amplitude.com | Global, default | | `EU` | api.eu.amplitude.com | GDPR compliance | Choose based on: * Your data privacy requirements * User location * Compliance needs ### Session management Sessions are automatically managed: * `session_id` = Event timestamp * Groups rapid events together * New session for each subscription action * Helps track user journey ### Testing the integration #### 1\. Trigger sandbox events * iOS: Use TestFlight with a sandbox Apple ID. StoreKit Configuration files do not generate App Store Server Notifications, so webhooks and downstream integrations won't fire. * Google Play: Use license test accounts to perform sandbox purchases. * Stripe: Use Stripe Test Mode to create sandbox transactions. #### 2\. Verify in Amplitude Check your Amplitude project: 1. **User Lookup**: Find test user by ID 2. **Event Stream**: Verify events arriving 3. **Revenue Chart**: Confirm revenue tracking 4. **User Properties**: Check LTV calculation #### 3\. Test different scenarios * Purchase event → Positive revenue * Refund event → Negative revenue * Cancellation → No revenue * Trial start → Event without revenue ### Best practices 1. **Consistent User IDs**: Send user IDs to app stores for better tracking 2. **Separate Environments**: Use sandbox API key for testing 3. **Revenue Model**: Choose gross vs net consistently 4. **Event Naming**: Use `[Superwall]` prefix to identify source 5. **Platform Analysis**: Segment by platform for insights 6. **Cohort Analysis**: Use trial conversion events for cohorts ### Common use cases #### Revenue analytics ``` Events: [Superwall] Subscription Start, [Superwall] Subscription Renewal Metric: Sum of revenue Segment by: platform, productId, countryCode ``` #### Conversion funnel ``` 1. [Superwall] Trial Start 2. [Superwall] Trial Conversion Conversion Rate: Step 2 / Step 1 ``` #### Churn analysis ``` Events: [Superwall] Subscription Cancellation Segment by: cancelReason, periodType, price tier ``` #### LTV calculation ``` Revenue Events: All [Superwall] events with revenue > 0 Group by: user_id Calculate: Sum of revenue per user ``` ### Troubleshooting #### Events not appearing 1. **Check API Key**: Verify key is correct for your project 2. **Check Region**: Ensure region matches your Amplitude project 3. **Check Environment**: Sandbox events need sandbox API key 4. **Check User ID**: Must have valid identifier #### Revenue not tracking 1. **Check Amount**: Only non-zero amounts create revenue 2. **Check Event Type**: Revenue fields only for purchase/renewal events 3. **Check Settings**: Verify Revenue vs Proceeds selection 4. **Check Refunds**: Negative amounts should decrease revenue #### Duplicate events The integration uses `insert_id` to prevent duplicates: * Format: `sw_eventId-eventName` * Amplitude automatically deduplicates by `insert_id` #### User attribution issues 1. **Check User ID**: Verify originalAppUserId is being sent 2. **Check Fallback**: originalTransactionId should always exist 3. **Platform Mismatch**: Ensure platform field is correct ### Rate limits Amplitude HTTP API v2 limits: * **Events per batch**: 1000 (we send 1 at a time) * **Request size**: 1MB (well within limit) * **Rate limit**: 1000 events/second per device * **Daily limit**: Based on your plan ### Integration with Amplitude features #### User properties While this integration sends events, consider: * Setting user properties separately * Using Identify API for user traits * Enriching profiles with app data #### Revenue verification Amplitude's revenue verification requires: * Receipt data (not included in webhooks) * Direct integration with app stores * This integration complements but doesn't replace revenue verification #### Predictive analytics Use Superwall events for: * Churn prediction models * LTV forecasting * Conversion probability scoring ### Data privacy * **User IDs**: Pseudonymous by default * **GDPR**: Use EU region for European users * **Data Retention**: Follows Amplitude project settings * **Deletion**: Handle via Amplitude's User Privacy API * **PII**: Avoid sending PII in event properties # 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 # Apple Retention Messaging Configure Apple's Retention Messaging API in Superwall, including the callback URL, messages, default message mappings, and real-time configurations. In the **Retention Messaging** section within **Integrations**, you can configure Apple's Retention Messaging API for subscribers who intend to cancel. > **Warning:** Apple must **first** approve your app for the Retention Messaging API before you can use this integration > in production — Superwall cannot grant this access.After Apple has approved your app, contact > [Superwall Support](https://support.superwall.com) to enable full message configuration in the > dashboard. Until then, only the callback URL is available. ## What the API does Apple's [Retention Messaging API](https://developer.apple.com/documentation/retentionmessaging) lets you choose which message appears on the App Store's cancellation confirmation screen after a customer taps **Cancel Subscription**. You can use it to remind subscribers what they keep with their plan, reinforce product value, or present an alternate product or offer that may reduce churn. Apple supports text-only messages, messages with images, switch-plan messages, and promotional offers. In Superwall, you use this integration to configure the callback URL Apple calls, create retention messages, set default message mappings, and define real-time configurations. Examples of retention messaging in the cancellation flow: ![Retention messaging examples on Apple](/docs/images/retention-messaging-example.png) ## Apple Callback URL The dashboard generates a callback URL using your app's public API key: `https://retention-messaging-api.superwall.com/v1/message/` Use this as the **Retention Messaging URL** in App Store Connect for your app. 1. Copy the callback URL from **Retention Messaging** in Superwall. 2. [Request access from Apple](https://developer.apple.com/contact/request/retention-messaging-api/) for the Retention Messaging API. 3. In App Store Connect, open your app's subscription settings and paste the URL into the **Retention Messaging URL** field. 4. After Apple approves access, contact Superwall support to enable message configuration in the dashboard. The callback URL does **not** change when you switch between Production and Sandbox in the dashboard. The environment selector applies to messages, default mappings, and real-time configurations. ## Messages Use **Messages** to create and manage the retention message payloads that Superwall sends to Apple. These messages are the records referenced by [Default Messages](#default-messages) and [Real-time Configurations](#real-time-configurations). ### Create a message When you create a message, the dashboard asks for: * `Name`: internal label shown in Superwall. * `Environment`: `Production` or `Sandbox`. * `Locale`: for example, `en-US`. * `Header` * `Body` * `Alt Text` (optional) * `Image Identifier` (optional) The messages table shows the Apple review state for each message: `PENDING`, `APPROVED`, `REJECTED`, or `UNKNOWN`. Create the message for the correct environment and locale before adding a default mapping or real-time configuration that references it — the message picker only shows matches for the selected environment and locale. ### Preview a message After a message exists, open the three-dot menu in the messages table and choose the preview action to see a live preview. The preview shows the message payload beside an example cancellation screen, so you can check the header, body, locale, image, and alt text before using the message in a default mapping or real-time configuration. ![Retention message live preview in Superwall](/docs/images/retention_preview_view.jpg) ## Default Messages Use **Default Messages** to define fallback message mappings by product and locale. Use a default mapping when you want Apple to show a specific message whenever there is no matching real-time configuration for that product. ### Create a default mapping To create a default mapping: 1. Choose the environment. 2. Select one or more products. 3. Enter the locale. 4. Choose a message. The picker only shows messages for the same environment and locale. 5. Save the mapping. The dashboard lets you create mappings for multiple products in one action. > **Note:** The UI disables products that already have a default mapping in the selected environment. > If a product is unavailable, delete its existing mapping before creating another one. ## Real-time Configurations Use **Real-time Configurations** to map product and locale combinations to the retention message behavior Apple should use at runtime. ### Supported configuration types Two real-time configuration types are supported: * `Message`: Apple uses the linked retention message. * `Alternate Product`: Apple uses the linked message together with an alternate product. ### Create a real-time configuration To create a configuration: 1. Enter a name. 2. Choose the environment. 3. Select one or more products. 4. Enter the locale. 5. Choose the type. 6. If the type is `Alternate Product`, choose the alternate product. 7. Choose a message. The picker only shows messages for the same environment and locale. 8. Create the configuration. The dashboard lets you create configurations for multiple products in one action. > **Note:** The UI disables products that already have a real-time configuration in the selected > environment. If a product is unavailable, delete its existing configuration before creating > another one. If a real-time configuration exists for a product, Apple uses that behavior instead of the default message mapping. When no real-time configuration applies, Apple falls back to the default message. # 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 # Apple Search Ads Integrate Apple Search Ads with Superwall. View details on users acquired via search ads, visualize conversions from Apple Search Ads in charts, and create powerful campaign filters to target users using search ad data. Search ad integration requires 3.12.0 of the Superwall SDK or higher. In the **Apple Search Ads** section within **Integrations**, you can the enable Apple Search Ads integration with Superwall: ![](/docs/images/integrations-asa.jpeg) > **Note:** Apple offers two different search ad services, "Basic" and "Advanced" tiers. Superwall supports > both of them, though more data is available with the Advanced ads. ### Basic search ads setup If you're only using basic search ads, **click** the toggle next to **Basic Apple Search Ads** to enable the integration: ![](/docs/images/overview-settings-asa-basic.jpeg) That's it, you're all set. With basic Apple Search Ads enabled, you'll be to see users acquired via search ads in the [users page](/docs/dashboard/overview-users). To see what you can do with advanced search ads data, skip down to the [use cases](#use-cases) section. ### Advanced search ads setup Advanced search ads takes a few more steps since it requires the [Campaign Management API](https://searchads.apple.com/help/campaigns/0022-use-the-campaign-management-api). The overview is as follows, with more details about each step below them: * First, you'll need to create a user in Apple Search Ads **using a different Apple Account** than your primary Apple Account. * This new user will need to be set up with either the API Account Manager or API Account Read Only role. * Then, you'll generate three things by pasting in a public key from Superwall: a client ID, team ID and key ID. * Finally, you'll enter those three values into Superwall. **Step One: Invite a new user** 1. Go to [searchads.apple.com](https://searchads.apple.com) and click **Sign In -> Advanced**. ![](/docs/images/overview-settings-asa-advanced-sign-in.jpeg) 2. Locate your account name in the top right corner and click **Account Name -> Settings**. ![](/docs/images/overview-settings-asa-settings.jpeg) 3. Under User Management, click **Invite Users**. ![](/docs/images/overview-settings-asa-invite-user.jpeg) 4. Grant the user appropriate permissions and enter in the rest of the details. The email address here is the one you'll want to use to create a new user in Apple Search Ads: ![](/docs/images/overview-settings-asa-perms.jpeg) **Step Two: Accept the invitation**
Open the email and follow Apple's instructions to set up a new user with Apple Search ads. The email will look similar to this: ![](/docs/images/overview-settings-asa-invite-email.jpeg) Once you've accepted the invitation using the invited Apple Account: 1. Once again, go to [searchads.apple.com](https://searchads.apple.com) and click **Sign In -> Advanced**. ![](/docs/images/overview-settings-asa-advanced-sign-in.jpeg) 2. Locate your account name in the top right corner and click **Account Name -> Settings**. ![](/docs/images/overview-settings-asa-settings.jpeg) 3. Over in Superwall, go to the **Settings -> Apple Search Ads -> click copy** under the public key: ![](/docs/images/overview-settings-asa-paste-sw.jpeg) 4. Back in Apple Search Ads, paste the public key under **Public Key** and click **Generate API Client**: ![](/docs/images/overview-settings-asa-paste-asc.jpeg) **Step Three: Generate the client ID, team ID and key ID**
Now, you should see three values that have been generated by Apple Search Ads, a client ID, team ID and key ID. 1. Copy each generated value. ![](/docs/images/overview-settings-asa-values.jpeg) 2. In Superwall, paste each value in and click "Update ASA Configuration." ![](/docs/images/overview-settings-asa-paste-stuff.jpeg) 3. Finally, click on "Check Configuration" and confirm everything is set up properly. ![](/docs/images/overview-settings-asa-confirm.jpeg) ### Use cases Once you've enabled Apple Search Ads, you can use the data in a few ways. First, users who've been acquired from a search ad will display that information in the users page under "Apple Search Ads." This is available with either the basic or advanced search ads. This can be useful for understanding the quality of users acquired from search ads. If you're using advanced search ads, you get significantly more capabilities: * You can leverage search ad data in your campaigns. This opens up the ability to do things like showing a specific paywall to a user who was acquired via a search ad, tailor messaging from the keyword that was used, and more. * You can view search ads data in charts, breaking down metrics by campaign name and more. #### Viewing users acquired via Apple Search Ads If any user was acquired via a search ad, you'll see that data in the [users page](/docs/dashboard/overview-users). This can be useful for understanding the quality of users acquired from search ads: ![](/docs/images/overivew-settings-asa-user.png) Here's a breakdown of the attributes you'll see: | Attribute | Example | Description | | ----------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | Ad Group Id | 1684936422 | The identifier for the ad group. Use Get Ad Group-Level Reports to correlate your attribution response by adGroupId. | | Ad Group Name | Primary Ad Group | The name of the ad group for organizational and reporting purposes. | | Ad Id | -1 | The identifier representing the assignment relationship between an ad object and an ad group. Applies to devices running iOS 15.2 and later. | | Attribution | true | A Boolean value indicating if the attribution was successful. Returns true if a user clicks an ad up to 30 days before downloading your app. | | Bid Amount | 0.25 | The cost-per-click (CPC) bid amount placed for this ad group. | | Bid Currency | GBP | The currency used for the bid amount. | | Campaign Id | 1633810596 | The unique identifier for the campaign. Use Get Campaign-Level Reports to correlate your attribution response by campaignId. | | Campaign Name | Primary Campaign (US) | The name of the campaign, useful for tracking and organizational purposes. | | Conversion Type | Download | The type of conversion, either Download or Redownload. | | Country Or Region | US | The country or region for the campaign. | | Keyword Id | 1685193881 | The identifier for the keyword. | | Keyword Name | baskeball app | The specific keyword that triggered the ad. | | Match Type | EXACT | The keyword matching type used to trigger the ad (e.g., EXACT, BROAD, or PHRASE). | | Org Id | 3621140 | The identifier of the organization that owns the campaign. This is the same as your account in the Apple Search Ads UI. | #### Using search ad data in campaigns Using the table above, you can turn around and use any of those values to create [campaign filters](/docs/dashboard/dashboard-campaigns/campaigns-audience#filters): ![](/docs/images/overview-settings-asa-filters.png) > **Warning:** There is a delay from the moment a user downloads your app via a search ad to the time that event > is sent to Superwall from Apple's servers. For that reason, using search ad data as a filter on > events like an app's launch is discouraged. #### Charts Use data from Apple Search Ads in our [charts](/docs/dashboard/charts) as a breakdown and filter: ![](/docs/images/asa-chart-breakdowns.png) Apple Search Ads data can be used in the following charts: * **Proceeds** * **Sales** * **Conversions** * **New Subscriptions** * **New Trials** * **Trial Conversions** * **Refund Rate** As far as search ads data, you can create breakdowns using the following: * **Ad Group Name** * **Campaign Name** * **Keywords Match Name** * **Match Type** Some common use cases here are: * Attributing new trials from a search campaign. * Seeing which keywords generate the most revenue. * Understanding the quality of users acquired from a search ad. * etc. # 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 # Appstack The Appstack integration forwards Superwall webhook events directly to Appstack for analytics and attribution. As a pass-through integration, it sends the raw event payload without transformation, giving Appstack full access to your subscription lifecycle data. In the **Analytics** section within **Integrations**, you can connect your Appstack account to Superwall: ### Required Fields Fill out the following fields and **click** the **Enable Appstack** button at the bottom right to save your changes: * **Access Token:** Your Appstack API access token, used to authenticate requests. * **App ID:** Your Appstack application ID, used to route events to the correct app. ### Features * **Pass-Through Delivery**: Raw Superwall webhook events are forwarded directly to Appstack without transformation * **Simple Configuration**: Only an access token and app ID are required * **Credential Validation**: Connection is verified before the integration goes live * **Production Events Only**: Sandbox events are automatically filtered out ## Configuration ### Required Settings | Field | Description | Example | | -------------- | ------------------------------ | ---------------------- | | `access_token` | Your Appstack API access token | `"ask_live_abc123..."` | | `app_id` | Your Appstack application ID | `"app_456def..."` | ### Example Configuration ```json { "access_token": "your_appstack_access_token", "app_id": "your_appstack_app_id" } ``` ## How It Works Appstack is a **pass-through integration**. Unlike analytics integrations that map and transform events into platform-specific formats, the Appstack integration forwards the raw Superwall webhook event payload directly to Appstack. When a subscription event occurs: 1. Superwall generates the webhook event. 2. The integration sends the complete, unmodified event payload to Appstack. 3. Appstack receives and processes the event on its end. ### API Endpoint Events are sent to: ``` POST https://api.event.appstack.tech/superwall/webhook/{app_id} ``` ### Request Headers ``` Content-Type: application/json Authorization: ``` The `access_token` is sent as the `Authorization` header value, and the `app_id` is included in the URL path. ## Sandbox Handling Sandbox events are **automatically filtered out**. Only production events are forwarded to Appstack. There is no option to include sandbox events or to configure a separate sandbox endpoint. ## Testing the Integration ### 1\. Validate Credentials When you save the integration, Superwall sends a test event to the Appstack validation endpoint to confirm your credentials are correct: ``` POST https://api.event.appstack.tech/superwall/validate ``` If validation fails, double-check your access token and app ID. ### 2\. Trigger a Production Event Since sandbox events are filtered out, you will need a production transaction to verify end-to-end delivery: * iOS: Use TestFlight with a sandbox Apple ID. StoreKit Configuration files do not generate App Store Server Notifications, so webhooks and downstream integrations will not fire. * Google Play: Use license test accounts to perform sandbox purchases. * Stripe: Use Stripe Test Mode to create sandbox transactions. > **Note:** Because sandbox events are not forwarded to Appstack, full end-to-end testing requires a production transaction. Use credential validation to confirm the connection is working before going live. ### 3\. Verify in Appstack Check your Appstack dashboard to confirm events are arriving and being processed correctly. ## Troubleshooting ### Events Not Appearing in Appstack **Possible causes:** * Invalid access token or app ID * Events are from a sandbox environment (these are filtered out) * Network or endpoint issues on the Appstack side **Solutions:** 1. Re-save the integration to trigger credential validation 2. Confirm you are generating production (not sandbox) events 3. Verify your access token and app ID match what is shown in your Appstack dashboard 4. Contact Appstack support if credentials are correct but events are still not arriving ### Credential Validation Failing **Possible causes:** * Incorrect access token * Incorrect app ID * Appstack service is temporarily unavailable **Solutions:** 1. Copy the access token and app ID directly from your Appstack dashboard to avoid typos 2. Ensure your Appstack account is active and in good standing 3. Try again after a few minutes if the Appstack service may be experiencing downtime # 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 # AppStance The AppStance integration sends Superwall subscription and payment events to the AppStance platform for Apple Ads ROAS optimization. Automatically forward iOS transaction data with Apple Search Ads attribution for campaign performance analysis. > **Note:** This integration is currently in **Beta**. Features and behavior may change. If you encounter any issues, please reach out to support. In the **Analytics** section within **Integrations**, you can connect your AppStance account to Superwall. AppStance (formerly Search Ads Optimization / SAO) is an Apple Ads ROAS optimization platform. This integration automatically forwards iOS subscription lifecycle events along with Apple Search Ads attribution data to AppStance, enabling you to measure and optimize your Apple Ads campaign performance. ### Required fields Fill out the following fields and **click** the **Enable AppStance** button at the bottom right to save your changes: * **Integration ID:** Must be set to `appstance`. * **Anonymous User Behavior:** Whether to send or skip events for anonymous users (optional). ### Features * **Apple Ads ROAS Optimization**: Connects subscription revenue to Apple Search Ads campaigns * **Automatic ASA Attribution**: Forwards Apple Search Ads attribution fields (org, campaign, ad group, keyword, and more) * **iOS-Only Filtering**: Automatically filters out non-App Store events (Play Store and Stripe events are skipped) * **Production-Only**: Sandbox events are skipped to keep optimization data clean * **No Authentication Required**: The AppStance endpoint is public and does not require API keys * **Device Identifier Forwarding**: Sends IDFV and IDFA when available ### Configuration #### Optional settings | Field | Description | Example | | ------------------------- | ------------------------------------------ | ------------------------ | | `anonymous_user_behavior` | Whether to send events for anonymous users | `"send"` or `"dontSend"` | #### Example configuration ```json { "anonymous_user_behavior": "send" } ``` ### Event mapping AppStance uses its own event name mapping, which differs from the standard Superwall event mapping used by other integrations. | Superwall Event | Period Type | AppStance Event Name | | ----------------------- | ----------- | -------------------- | | `initial_purchase` | `TRIAL` | `free_trial` | | `initial_purchase` | Any other | `initial_purchase` | | `renewal` | Any | `renewal` | | `cancellation` | Any | `cancellation` | | `billing_issue` | Any | `billing_issue` | | `product_change` | Any | `product_change` | | `non_renewing_purchase` | Any | `initial_purchase` | | `test` | Any | `test` | ### Event payload Every event sent to AppStance includes the following fields: #### Core fields | Field | Type | Description | | --------------------- | ------- | --------------------------------------------------------------------- | | `user_id` | string | User identifier (uses `originalAppUserId` or `originalTransactionId`) | | `install_timestamp` | number | Timestamp from `purchasedAt` | | `country_code` | string | Country code of the user | | `store` | string | Always `APP_STORE` (non-iOS events are filtered out) | | `event_unique_id` | string | Unique event identifier | | `event_name` | string | Mapped event name (see table above) | | `event_timestamp` | number | Timestamp of the event | | `period_type` | string | Subscription period type | | `price_usd` | number | Transaction price in USD | | `proceeds_usd` | number | Proceeds after store fees in USD | | `is_trial_conversion` | boolean | Whether this event is a trial-to-paid conversion | #### Revenue fields | Field | Type | Description | | ---------------------- | ------ | ----------------------------------------- | | `revenue_raw_amount` | number | Price in the original purchased currency | | `revenue_raw_currency` | string | Currency code of the original transaction | #### Transaction fields | Field | Type | Description | | ------------------------- | ------- | -------------------------------------------- | | `bundle_id` | string | App bundle identifier | | `transaction_id` | string | Transaction identifier | | `original_transaction_id` | string | Original transaction identifier | | `product_id` | string | Product identifier | | `new_product_id` | string | New product identifier (for product changes) | | `is_small_business` | boolean | Whether the Small Business Program applies | | `environment` | string | Transaction environment | | `project_id` | string | Superwall project identifier | | `application_id` | string | Application identifier (currently null) | ### Apple Search Ads attribution AppStance extracts Apple Search Ads (ASA) attribution data from user attributes and includes it in every event payload. This is the core data that powers Apple Ads ROAS optimization. | Field | Description | | ----------------------- | -------------------------------------------- | | `asa_org_id` | Apple Search Ads organization ID | | `asa_campaign_id` | Campaign identifier | | `asa_adgroup_id` | Ad group identifier | | `asa_keyword_id` | Keyword identifier | | `asa_ad_id` | Ad identifier | | `asa_country_or_region` | Country or region for the campaign | | `asa_conversion_type` | Conversion type (e.g., Download, Redownload) | | `asa_click_date` | Date of the ad click | | `asa_claim_type` | Claim type | | `asa_impression_date` | Date of the ad impression | For ASA attribution data to be available, ensure your app has the [Apple Search Ads integration](/docs/integrations/apple-search-ads) configured and that the Superwall SDK is collecting attribution data on the client side. ### Device identifiers AppStance includes device identifiers from user attributes when available: | Field | Description | | ------ | ------------------------------------------------- | | `idfv` | Identifier for Vendor | | `idfa` | Identifier for Advertisers (requires ATT consent) | These identifiers are extracted from `userAttributes` on the event. If your app collects IDFA (after obtaining App Tracking Transparency consent), it will be forwarded automatically. ### Sandbox handling AppStance is a **production-only** integration. Sandbox events are automatically skipped and are not sent to AppStance. This ensures that test transactions do not affect your ROAS optimization data. Additionally, only App Store (iOS) events are forwarded. Events originating from Google Play Store or Stripe are filtered out, since AppStance is focused exclusively on Apple Ads optimization. ### User identification The integration identifies users using the following hierarchy: 1. **Primary**: `originalAppUserId` (if available) 2. **Fallback**: `originalTransactionId` (always present) If `anonymous_user_behavior` is set to `"dontSend"`, events for users without a resolved identity are skipped. ### Testing the integration #### 1\. Trigger a test event Use the `test` event type to verify connectivity. The integration maps `test` events to the `test` event name in AppStance. #### 2\. Trigger production events Since AppStance skips sandbox events, you will need to test with production transactions: * iOS: Use TestFlight with a sandbox Apple ID. Note that StoreKit Configuration files do not generate App Store Server Notifications, so webhooks and downstream integrations will not fire. * Verify that events appear in your AppStance dashboard. #### 3\. Verify ASA attribution Confirm that Apple Search Ads attribution fields are populated: 1. Ensure the Apple Search Ads integration is enabled in Superwall. 2. Install your app via an Apple Search Ad (or use a test campaign). 3. Make a purchase and check that `asa_campaign_id` and related fields are present in AppStance. ### Best practices 1. **Enable Apple Search Ads**: Ensure the [Apple Search Ads integration](/docs/integrations/apple-search-ads) is configured so that ASA attribution data flows through to AppStance. 2. **Collect IDFA**: Request App Tracking Transparency consent to maximize device identifier coverage for attribution matching. 3. **Set User IDs Early**: Send `originalAppUserId` to Superwall as early as possible so that AppStance can consistently identify users across events. 4. **Use Anonymous User Filtering**: Set `anonymous_user_behavior` to `"dontSend"` if you want to exclude events that lack a resolved user identity. 5. **Monitor Production Data**: Since sandbox events are filtered, verify your integration using production transactions or the `test` event type. ### Troubleshooting #### Events not appearing in AppStance 1. **Check Platform**: Only iOS (App Store) events are sent. Play Store and Stripe events are automatically filtered out. 2. **Check Environment**: Sandbox events are skipped. Ensure you are testing with production transactions. 3. **Check Anonymous Users**: If `anonymous_user_behavior` is `"dontSend"`, events for anonymous users are skipped. #### Missing ASA attribution data 1. **Check Apple Search Ads Integration**: Ensure the Apple Search Ads integration is enabled in Superwall settings. 2. **Check SDK Version**: The Superwall SDK must be collecting ASA attribution data on the client side. 3. **Check Timing**: There is a delay between app install via a search ad and when Apple sends attribution data. Events that fire before attribution data arrives will have empty ASA fields. #### Events not mapping correctly 1. **Check Event Type**: Verify the Superwall event name matches one of the supported types in the event mapping table. 2. **Check Period Type**: For `initial_purchase` events, the period type determines whether the event maps to `free_trial` or `initial_purchase`. 3. **Non-renewing purchases**: These are mapped to `initial_purchase` in AppStance regardless of period type. # 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 # Customer.io The Customer.io integration sends subscription lifecycle events from Superwall to Customer.io's Data Pipelines API. This enables you to trigger targeted messaging campaigns, build user segments based on subscription behavior, and track the complete customer journey from trial to paid subscriber. In the **Communication** section within **Integrations**, you can connect your Customer.io account to Superwall: ![](/docs/images/integrations-customer-io.jpeg) ## Features * **Real-time Event Tracking**: Subscription events are sent immediately to Customer.io as they occur * **Multi-Region Support**: Choose between US and EU data residency to comply with data regulations * **Flexible Revenue Reporting**: Report either gross revenue or net proceeds after store fees * **Sandbox Environment Support**: Separate API key for testing without polluting production data * **Anonymous User Handling**: Configurable behavior for users without an identified app user ID * **Custom Event Names**: Remap default event names to match your existing Customer.io conventions * **Automatic User Identification**: Smart routing between `userId` and `anonymousId` based on user state ## Configuration ### Required Settings | Field | Description | Example | | ----------------- | ---------------------------------------------------- | --------------------------- | | `integration_id` | Must be set to `"customerio"` | `"customerio"` | | `region` | Data residency region for your Customer.io workspace | `"US"` or `"EU"` | | `api_key` | Pipelines API key from your HTTP source | `"abc123def456..."` | | `sales_reporting` | Whether to report gross Revenue or net Proceeds | `"Revenue"` or `"Proceeds"` | ### Optional Settings | Field | Description | Default | | ------------------------- | ------------------------------------------------------ | ----------------------------- | | `sandbox_api_key` | Separate Pipelines API key for sandbox/test events | None (sandbox events skipped) | | `anonymous_user_behavior` | How to handle events from users without an app user ID | `"send"` | | `eventNameMappings` | Custom mapping to rename default event names | None | ### Example Configuration ```json { "integration_id": "customerio", "region": "US", "api_key": "your-pipelines-api-key", "sales_reporting": "Revenue", "sandbox_api_key": "your-sandbox-pipelines-api-key", "anonymous_user_behavior": "send", "eventNameMappings": { "sw_trial_start": "trial_started", "sw_subscription_start": "subscription_started", "sw_renewal": "subscription_renewed" } } ``` ## Getting Your API Key The Customer.io integration uses the **Pipelines API** (part of Customer.io Data Pipelines), not the Track API. To get your API key: 1. Log in to your Customer.io account 2. Navigate to **Data Pipelines** in the left sidebar 3. Go to **Sources** 4. Click **Add Source** and select **HTTP** 5. Name your source (e.g., "Superwall Events") 6. Copy the **API Key** displayed after creation **Important**: The Pipelines API key is different from the Track API credentials (Site ID + API Key). Make sure you're using the correct key from Data Pipelines. ## Event Mapping Superwall subscription events are transformed into Customer.io events based on the event type and subscription period. All events are prefixed with `sw_` by default. ### Trial Events | Superwall Event | Condition | Customer.io Event | | ------------------ | -------------------- | ---------------------- | | `INITIAL_PURCHASE` | `periodType = Trial` | `sw_trial_start` | | `CANCELLATION` | `periodType = Trial` | `sw_trial_cancelled` | | `UNCANCELLATION` | `periodType = Trial` | `sw_trial_uncancelled` | | `EXPIRATION` | `periodType = Trial` | `sw_trial_expired` | | `RENEWAL` | `periodType = Trial` | `sw_trial_converted` | ### Intro Offer Events | Superwall Event | Condition | Customer.io Event | | ------------------ | -------------------- | ---------------------------- | | `INITIAL_PURCHASE` | `periodType = Intro` | `sw_intro_offer_start` | | `CANCELLATION` | `periodType = Intro` | `sw_intro_offer_cancelled` | | `UNCANCELLATION` | `periodType = Intro` | `sw_intro_offer_uncancelled` | | `EXPIRATION` | `periodType = Intro` | `sw_intro_offer_expired` | | `RENEWAL` | `periodType = Intro` | `sw_intro_offer_converted` | ### Subscription Events | Superwall Event | Condition | Customer.io Event | | ------------------ | -------------------------- | ----------------------------- | | `INITIAL_PURCHASE` | `periodType = Normal` | `sw_subscription_start` | | `RENEWAL` | `periodType = Normal` | `sw_renewal` | | `RENEWAL` | `isTrialConversion = true` | `sw_trial_converted` | | `CANCELLATION` | `periodType = Normal` | `sw_subscription_cancelled` | | `UNCANCELLATION` | `periodType = Normal` | `sw_subscription_uncancelled` | | `EXPIRATION` | `periodType = Normal` | `sw_subscription_expired` | ### Other Events | Superwall Event | Customer.io Event | | -------------------------- | -------------------------- | | `PRODUCT_CHANGE` | `sw_product_change` | | `BILLING_ISSUE` | `sw_billing_issue` | | `SUBSCRIPTION_PAUSED` | `sw_subscription_paused` | | `NON_RENEWING_PURCHASE` | `sw_non_renewing_purchase` | | Any event with `price < 0` | `sw_refund` | ## Event Properties Each event sent to Customer.io includes comprehensive properties from the original Superwall event, plus additional formatted fields for revenue tracking. ### Standard Properties All events include the complete Superwall event data: | Property | Description | Example | | ----------------------- | ----------------------------------- | --------------------------- | | `id` | Unique event identifier | `"evt_abc123"` | | `productId` | The subscription product ID | `"com.app.premium.monthly"` | | `store` | App store (APP\_STORE, PLAY\_STORE) | `"APP_STORE"` | | `environment` | Production or Sandbox | `"Production"` | | `countryCode` | User's country code | `"US"` | | `currencyCode` | Transaction currency | `"USD"` | | `originalAppUserId` | Your app's user identifier | `"user_12345"` | | `originalTransactionId` | Store's original transaction ID | `"1000000123456789"` | | `transactionId` | Current transaction ID | `"1000000987654321"` | | `purchasedAt` | Purchase timestamp (ms) | `1705312200000` | | `expirationAt` | Subscription expiration (ms) | `1707990600000` | | `periodType` | Trial, Intro, or Normal | `"Normal"` | | `isTrialConversion` | Whether this converts a trial | `true` | | `isFamilyShare` | Family sharing purchase | `false` | | `bundleId` | App bundle identifier | `"com.example.app"` | ### Revenue Properties When the event has a non-zero price, these additional properties are included: | Property | Description | Example | | ----------------- | ----------------------------------------- | --------------------------- | | `price` | Amount based on `sales_reporting` setting | `9.99` | | `currency` | Currency code | `"USD"` | | `product_id` | Product identifier | `"com.app.premium.monthly"` | | `subscription_id` | Original transaction ID | `"1000000123456789"` | | `offer_code` | Promotional offer code (if present) | `"SUMMER2024"` | ### Revenue vs Proceeds The `sales_reporting` setting controls which amount is sent: * **Revenue**: The full price charged to the customer (e.g., $9.99) * **Proceeds**: The amount after store fees are deducted (e.g., $8.49 after Apple's 15-30% commission) ## User Identification Customer.io uses either `userId` or `anonymousId` to identify users. The integration automatically selects the appropriate identifier based on user state. ### Known Users For users with an `originalAppUserId` set in Superwall: ```json { "userId": "user_12345", "event": "sw_subscription_start", "properties": { ... }, "timestamp": "2024-01-15T10:30:00.000Z" } ``` ### Anonymous Users For users without an `originalAppUserId`, the behavior depends on `anonymous_user_behavior`: **When set to `"send"` (default)**: * Events are sent with an `anonymousId` constructed from the store and transaction ID * Format: `$STORE_NAME:originalTransactionId` ```json { "anonymousId": "$APP_STORE:1000000123456789", "event": "sw_subscription_start", "properties": { ... }, "timestamp": "2024-01-15T10:30:00.000Z" } ``` **When set to `"dontSend"`**: * Events from anonymous users are skipped entirely * Useful if you only want to track identified users ## Sandbox Handling The integration supports separate handling for sandbox (test) events: ### With Sandbox API Key Configured When `sandbox_api_key` is provided: * Production events use the main `api_key` * Sandbox events use the `sandbox_api_key` * Both are sent to Customer.io but can be routed to different destinations ### Without Sandbox API Key When `sandbox_api_key` is not provided: * Production events are sent normally * Sandbox events are **skipped entirely** * This prevents test data from polluting your production Customer.io workspace ## Data Residency Customer.io offers data residency in two regions. The integration automatically routes to the correct endpoint: | Region | API Endpoint | | ------ | ------------------------------------- | | US | `https://cdp.customer.io/v1/track` | | EU | `https://cdp-eu.customer.io/v1/track` | Choose the region that matches your Customer.io workspace configuration. Using the wrong region will result in authentication errors. ## Custom Event Names Use `eventNameMappings` to rename default event names to match your existing Customer.io conventions: ```json { "eventNameMappings": { "sw_trial_start": "Started Free Trial", "sw_subscription_start": "Subscribed", "sw_renewal": "Subscription Renewed", "sw_subscription_cancelled": "Subscription Cancelled", "sw_refund": "Refund Processed" } } ``` Only events you specify in the mapping are renamed. All other events keep their default `sw_` prefixed names. ## Testing the Integration ### 1\. Validate Credentials The integration validates credentials by sending a test event to Customer.io. If the API key is invalid or the region is incorrect, you'll receive an authentication error. ### 2\. Verify in Customer.io After sending test events: 1. Go to **Data Pipelines** → **Sources** → your HTTP source 2. Click on **Events** to see incoming events 3. Verify event names and properties match expectations ### 3\. Test Scenarios Verify these scenarios work correctly: * [ ] Production event with known user (should use `userId`) * [ ] Production event with anonymous user (should use `anonymousId` or skip) * [ ] Sandbox event with sandbox API key (should send to Customer.io) * [ ] Sandbox event without sandbox API key (should be skipped) * [ ] Event with custom name mapping (should use remapped name) * [ ] Revenue event (should include `price`, `currency`, `product_id`) * [ ] Non-revenue event like cancellation (should not include revenue properties) ## Best Practices 1. **Use separate sandbox credentials**: Configure a `sandbox_api_key` to keep test data separate from production, or leave it blank to skip sandbox events entirely. 2. **Choose the right sales reporting**: Use "Revenue" for customer-facing metrics and "Proceeds" for financial reporting that accounts for store fees. 3. **Handle anonymous users thoughtfully**: If your app requires login, use `"dontSend"` to avoid cluttering Customer.io with unidentifiable users. 4. **Keep event names consistent**: If you have existing events in Customer.io, use `eventNameMappings` to maintain naming consistency across your data. 5. **Verify your region**: Ensure your `region` setting matches your Customer.io workspace location to avoid authentication failures. 6. **Test with sandbox first**: Always test your integration configuration with sandbox events before going live with production data. ## Common Use Cases ### Win-Back Campaigns Trigger automated campaigns when users cancel: 1. Listen for `sw_subscription_cancelled` events 2. Create a segment of recently cancelled users 3. Send a series of win-back emails with special offers ### Trial Conversion Optimization Improve trial-to-paid conversion: 1. Track `sw_trial_start` to begin a nurture sequence 2. Send educational content about premium features 3. Trigger a special offer before trial expiration 4. Track `sw_trial_converted` to measure success ### Churn Prevention Identify and engage at-risk subscribers: 1. Monitor `sw_billing_issue` events 2. Send immediate notification to update payment method 3. Follow up with helpful support content 4. Track resolution with subsequent `sw_renewal` events ### Revenue Analytics Build comprehensive revenue reporting: 1. Segment users by subscription status 2. Track lifetime value using revenue properties 3. Analyze conversion rates by cohort 4. Measure impact of promotional offers via `offer_code` ## Troubleshooting ### Events Not Appearing in Customer.io **Possible causes:** * Incorrect API key (make sure you're using Pipelines API key, not Track API) * Wrong region selected (US vs EU mismatch) * Sandbox events without sandbox API key configured (events are skipped) * Anonymous users with `dontSend` behavior (events are skipped) **Solution:** Verify your API key is from Data Pipelines → Sources → HTTP, and check that your region matches your workspace. ### Authentication Errors **Possible causes:** * Using Track API credentials instead of Pipelines API key * Region mismatch between configuration and Customer.io workspace * API key has been revoked or regenerated **Solution:** Generate a new HTTP source in Data Pipelines and use the fresh API key. ### Missing Revenue Properties **Possible causes:** * Event has zero price (cancellations, expirations) * Refund events (price is negative, still included but as negative value) **Solution:** Revenue properties (`price`, `currency`, `product_id`, `subscription_id`) are only added when the price is non-zero. This is expected behavior. ### Wrong Event Names **Possible causes:** * Event name mappings not configured * Typo in mapping configuration **Solution:** Check your `eventNameMappings` configuration. Keys should be the default event names (e.g., `sw_trial_start`), and values should be your desired custom names. ## Rate Limits Customer.io's Pipelines API has generous rate limits suitable for high-volume event ingestion: * **Requests**: 500 requests per second per source * **Payload size**: 32KB per request The integration sends one event per webhook, well within these limits. If you experience rate limiting, contact Customer.io support to increase your limits. ## API Reference ### Endpoint ``` POST https://cdp.customer.io/v1/track (US region) POST https://cdp-eu.customer.io/v1/track (EU region) ``` ### Authentication Basic Authentication with the Pipelines API key as username and empty password: ``` Authorization: Basic base64(api_key:) ``` ### Request Format ```json { "userId": "user_12345", "event": "sw_subscription_start", "timestamp": "2024-01-15T10:30:00.000Z", "properties": { "productId": "com.app.premium.monthly", "price": 9.99, "currency": "USD", "store": "APP_STORE", "environment": "Production", ... } } ``` ### Response Success: `200 OK` with empty body or acknowledgment Errors: * `401 Unauthorized`: Invalid API key or wrong region * `400 Bad Request`: Malformed request body * `429 Too Many Requests`: Rate limit exceeded # 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 # Discord The Discord integration sends real-time subscription notifications to your Discord channels via webhooks. Get instant visibility into subscription activity with beautifully formatted, color-coded embed messages that make it easy to monitor revenue, track trials, and respond to billing issues as they happen. In the **Communication** section within **Integrations**, you can connect your Discord account to Superwall: ![](/docs/images/integrations-discord.jpeg) ## Features * **Rich Embed Messages**: Beautiful, color-coded notifications with emoji indicators for quick visual parsing * **Event Type Filtering**: Choose between revenue-only events or all subscription lifecycle events * **Real-Time Notifications**: Instant alerts when subscription events occur * **Smart Formatting**: Currency formatting, country names, and human-readable descriptions * **Flexible Revenue Reporting**: Report either gross revenue or net proceeds after store fees * **Anonymous User Handling**: Configurable behavior for users without an identified app user ID * **Custom Event Names**: Override default event titles to match your team's terminology * **Sandbox Indicators**: Clear visual badge when events come from test environments ## Configuration ### Required Settings | Field | Description | Example | | ----------------- | ----------------------------------------------- | ----------------------------------------------- | | `integration_id` | Must be set to `"discord"` | `"discord"` | | `webhook_url` | Discord webhook URL from your server | `"https://discord.com/api/webhooks/123/abc..."` | | `sales_reporting` | Whether to report gross Revenue or net Proceeds | `"Revenue"` or `"Proceeds"` | ### Optional Settings | Field | Description | Default | | ------------------------- | ------------------------------------------------------ | --------------------------- | | `event_type` | Filter which events to send | `"All Subscription Events"` | | `anonymous_user_behavior` | How to handle events from users without an app user ID | `"send"` | | `eventNameMappings` | Custom mapping to rename default event titles | None | ### Example Configuration ```json { "integration_id": "discord", "webhook_url": "https://discord.com/api/webhooks/1234567890/abcdefghijklmnop", "sales_reporting": "Revenue", "event_type": "All Subscription Events", "anonymous_user_behavior": "send", "eventNameMappings": { "sw_subscription_start": "New Premium Member!", "sw_trial_start": "New Trial Started" } } ``` ## Creating a Discord Webhook 1. Open your Discord server 2. Go to **Server Settings** (click the server name → Settings) 3. Navigate to **Integrations** → **Webhooks** 4. Click **New Webhook** 5. Configure the webhook: * **Name**: Choose a name (e.g., "Superwall Events") * **Channel**: Select the channel where notifications will appear * **Avatar**: Optionally customize the webhook's avatar 6. Click **Copy Webhook URL** 7. Paste the URL into your integration configuration **Tip**: Create a dedicated channel like `#subscription-events` or `#revenue-alerts` to keep notifications organized. ## Event Filtering The `event_type` setting controls which events are sent to Discord: ### All Subscription Events (Default) Sends every subscription lifecycle event: * Trial starts and conversions * New subscriptions * Renewals * Cancellations and expirations * Billing issues * Product changes * Refunds **Best for**: Teams that want complete visibility into all subscription activity. ### Revenue Events Only Only sends events with non-zero revenue: * New paid subscriptions * Renewals * Trial conversions * One-time purchases * Refunds (negative revenue) **Skips**: Trial starts, cancellations, expirations, billing issues (unless they have revenue attached). **Best for**: Teams focused on revenue notifications without the noise of non-revenue events. ## Message Format Discord messages are sent as rich embeds with the following structure: ### Embed Structure ``` ┌─────────────────────────────────────────┐ │ [Superwall Logo] Superwall │ ← Author ├─────────────────────────────────────────┤ │ 💰 New Subscriber │ ← Title (with emoji) │ │ │ $9.99 subscription started from │ ← Description │ United States │ ├─────────────────────────────────────────┤ │ 👤 User 🎯 Product 📱 Store │ ← Fields (inline) │ user_123 com.app.pro App Store • │ │ United States │ │ │ │ 💰 Revenue │ ← Revenue field │ $9.99 │ ├─────────────────────────────────────────┤ │ Powered by Superwall Jan 15, 2024 │ ← Footer + Timestamp └─────────────────────────────────────────┘ ``` ### Embed Fields | Field | Description | When Shown | | ------------------------ | -------------------------- | ----------------------- | | 👤 User | User ID or "Anonymous" | Always | | 🎯 Product | Product identifier | Always | | 📱 Store | Store name and country | Always | | 💰 Revenue / 💵 Proceeds | Formatted amount | When price ≠ 0 | | ⚙️ Sandbox | Test environment indicator | Sandbox events only | | 🎁 Offer | Promotional offer code | When offer code present | | 🔄 Product Change | Old → New product | Product change events | ## Event Titles and Colors Each event type has a distinct emoji, title, and color for quick visual identification. ### Color Coding | Color | Hex Code | Meaning | | ------ | --------- | ----------------------------------------------------- | | Green | `#36A64F` | Revenue events (purchases, renewals, conversions) | | Blue | `#3498DB` | Trial events (non-revenue) | | Red | `#FA6A6A` | Negative events (cancellations, refunds, expirations) | | Orange | `#FF9500` | Billing issues | | Purple | `#9B59B6` | Product changes | | Gray | `#666666` | Other events | ### Event Title Reference #### Trial Events | Event | Title | Color | | ----------------- | -------------------- | ----- | | Trial Start | 🤩 Trial Start | Blue | | Trial Conversion | 💰 Trial Conversion | Green | | Trial Cancelled | 😞 Cancelled Trial | Red | | Trial Refunded | 🤬 Refunded Trial | Red | | Trial Expired | 😞 Expired Trial | Red | | Trial Uncancelled | 🤩 Trial Uncancelled | Blue | #### Intro Offer Events | Event | Title | Color | | ------------------ | ------------------------- | ----- | | Intro Start (free) | 🤩 Intro Offer Start | Blue | | Intro Start (paid) | 💰 Intro Offer Start | Green | | Intro Conversion | 💰 Intro Offer Conversion | Green | | Intro Cancelled | 😞 Cancelled Intro Offer | Red | | Intro Refunded | 🤬 Refunded Intro Offer | Red | #### Subscription Events | Event | Title | Color | | ---------------- | --------------------------- | ----- | | New Subscription | 💰 New Subscriber | Green | | Renewal | 💰 Renewal | Green | | Cancellation | 😞 Cancelled Subscription | Red | | Refund | 🤬 Refunded Subscription | Red | | Expiration | 😞 Expired Subscription | Red | | Uncancellation | 🤩 Subscription Uncancelled | Green | #### Other Events | Event | Title | Color | | ------------------- | ---------------------- | ------ | | One-Time Purchase | 💰 One-Time Purchase | Green | | Product Change | 😵‍💫 Product Change | Purple | | Billing Issue | 🫠 Billing Issue | Orange | | Subscription Paused | ⏸️ Subscription Paused | Gray | ## Revenue Display ### Revenue vs Proceeds The `sales_reporting` setting controls which amount is displayed: * **Revenue**: The full price charged to the customer (e.g., $9.99) * **Proceeds**: The amount after store fees (e.g., $8.49 after Apple's 15-30% commission) The field label changes based on your setting: * Revenue mode: "💰 Revenue" * Proceeds mode: "💵 Proceeds" ### Currency Formatting Amounts are automatically formatted with the correct currency symbol and locale: * `$9.99` for USD * `€9.99` for EUR * `£9.99` for GBP * `¥999` for JPY ### Zero-Value Events Events without revenue (trial starts, cancellations, expirations) do not show a revenue field, keeping the message compact. ### Refunds Refunds display negative amounts: * "💰 Revenue: -$9.99" ## Anonymous User Handling The `anonymous_user_behavior` setting controls how events from unidentified users are handled: ### Send (Default) * Events from anonymous users are sent to Discord * User field displays "Anonymous" * Useful for complete visibility into all subscription activity ### Don't Send * Events from anonymous users are skipped * No notification is sent to Discord * Useful if you only want to track identified users ## Sandbox Events Events from sandbox/test environments are clearly marked: * A "⚙️ Sandbox" field is added with value "Test Environment" * Helps distinguish test events from production activity * Production events do not show any environment indicator ## Custom Event Names Use `eventNameMappings` to customize event titles: ```json { "eventNameMappings": { "sw_trial_start": "🎉 New Trial User!", "sw_subscription_start": "💎 VIP Member Joined", "sw_renewal": "🔄 Subscription Renewed", "sw_subscription_cancelled": "👋 Member Churned" } } ``` ### Available Event Keys | Key | Default Title | | --------------------------- | ------------------------- | | `sw_trial_start` | 🤩 Trial Start | | `sw_trial_converted` | 💰 Trial Conversion | | `sw_trial_cancelled` | 😞 Cancelled Trial | | `sw_subscription_start` | 💰 New Subscriber | | `sw_renewal` | 💰 Renewal | | `sw_subscription_cancelled` | 😞 Cancelled Subscription | | `sw_subscription_expired` | 😞 Expired Subscription | | `sw_refund` | 🤬 Refunded Subscription | | `sw_billing_issue` | 🫠 Billing Issue | | `sw_product_change` | 😵‍💫 Product Change | | `sw_non_renewing_purchase` | 💰 One-Time Purchase | ## Testing the Integration ### 1\. Validate Credentials The integration validates your webhook URL by sending a test event. If the URL is invalid or the webhook has been deleted, validation will fail. ### 2\. Send a Test Event Trigger a subscription event from your app (or use sandbox mode) to verify messages appear correctly. ### 3\. Verify in Discord Check your configured channel for the notification: * Confirm the embed appears with correct formatting * Verify colors match the event type * Check that fields display correct information ### 4\. Test Scenarios * [ ] New subscription shows green with 💰 emoji * [ ] Trial start shows blue with 🤩 emoji * [ ] Cancellation shows red with 😞 emoji * [ ] Revenue field shows correct amount * [ ] Sandbox events show "⚙️ Sandbox" field * [ ] Revenue-only filter skips zero-price events * [ ] Anonymous users show "Anonymous" or are skipped per setting * [ ] Custom event names appear in title ## Best Practices 1. **Create a dedicated channel**: Keep subscription notifications separate from general chat to avoid noise and make monitoring easier. 2. **Use Revenue Events Only for busy apps**: If you have high volume, filtering to revenue-only events reduces noise while keeping you informed of important transactions. 3. **Set up channel notifications**: Configure Discord channel notification settings (e.g., only notify for @mentions) to avoid constant pings. 4. **Consider multiple webhooks**: Create separate webhooks for different event types (e.g., one for revenue in `#sales`, one for all events in `#subscription-logs`). 5. **Monitor billing issues**: Pay special attention to orange "🫠 Billing Issue" notifications—these represent potential revenue at risk. 6. **Use meaningful custom names**: If you customize event names, make them clear and actionable for your team. ## Common Use Cases ### Sales Celebration Channel Create a `#sales` channel with revenue-only events: ```json { "event_type": "Revenue Events Only", "sales_reporting": "Revenue" } ``` Celebrate new subscribers and renewals with your team! ### Churn Monitoring Create a `#churn-alerts` channel and filter to cancellation events using a separate integration instance: * Monitor cancellation patterns * Quickly identify if something is causing unusual churn * React to billing issues before they become cancellations ### Customer Success Integration Use the user ID field to quickly look up users in your CRM or support system: * Click the dashboard URL in the embed to view user details * Reach out proactively to users who cancelled * Thank high-value subscribers personally ### Team Revenue Dashboard Display the Discord channel on a team dashboard or TV: * Real-time visualization of subscription activity * Color-coded events make it easy to gauge health at a glance * Celebrate wins and identify issues quickly ## Troubleshooting ### Messages Not Appearing **Possible causes:** * Invalid webhook URL * Webhook was deleted in Discord * Channel permissions prevent webhook posting * Event filtered out by `event_type` setting **Solutions:** 1. Verify the webhook still exists in Server Settings → Integrations 2. Check that the webhook has permission to post in the target channel 3. Confirm `event_type` setting includes the event you're expecting 4. Re-create the webhook if it was deleted ### Webhook Rate Limited **Possible causes:** * Discord rate limits webhook requests (30 requests per minute per channel) * High volume of subscription events **Solutions:** 1. Use "Revenue Events Only" to reduce volume 2. Consider using a less busy channel 3. Events will be queued and retried automatically ### Wrong Event Names or Emojis **Possible causes:** * Custom `eventNameMappings` overriding defaults * Unexpected event type mapping **Solutions:** 1. Review your `eventNameMappings` configuration 2. Check the event title reference table above 3. Remove custom mappings to restore defaults ### Missing Revenue Field **Possible causes:** * Event has zero price (normal for trial starts, cancellations) * This is expected behavior **Solutions:** * Revenue field only appears when price ≠ 0 * Trial starts, cancellations, and expirations typically have no revenue ### Sandbox Badge Appearing **Possible causes:** * Event came from sandbox/test environment * This is expected behavior **Solutions:** * The "⚙️ Sandbox" field only appears for sandbox events * Verify you're testing in the correct environment ## Rate Limits Discord enforces rate limits on webhooks: | Limit | Value | | --------------------- | ---------------------------- | | Requests per minute | 30 per channel | | Embed limit | 10 embeds per message | | Total character limit | 6,000 characters per message | The integration sends one embed per event, which is well within these limits. For extremely high-volume applications, consider using the "Revenue Events Only" filter. ## API Reference ### Endpoint Events are sent directly to your Discord webhook URL: ``` POST https://discord.com/api/webhooks/{webhook_id}/{webhook_token} ``` ### Request Headers ``` Content-Type: application/json ``` ### Request Body ```json { "embeds": [ { "author": { "name": "Superwall", "icon_url": "https://superwall.com/favicon.ico" }, "title": "💰 New Subscriber", "description": "$9.99 subscription started from United States", "url": "https://superwall.com/applications/{app_id}", "color": 3582031, "thumbnail": { "url": "https://superwall.com/favicon.ico" }, "fields": [ { "name": "👤 User", "value": "user_123", "inline": true }, { "name": "🎯 Product", "value": "com.app.premium", "inline": true }, { "name": "📱 Store", "value": "App Store • United States", "inline": true }, { "name": "💰 Revenue", "value": "$9.99", "inline": true } ], "timestamp": "2024-01-15T10:30:00.000Z", "footer": { "text": "Powered by Superwall", "icon_url": "https://superwall.com/favicon.ico" } } ] } ``` ### Response **Success**: `204 No Content` (Discord returns no body on success) **Error**: * `400 Bad Request`: Invalid embed structure * `401 Unauthorized`: Invalid webhook token * `404 Not Found`: Webhook was deleted * `429 Too Many Requests`: Rate limited # 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 # Facebook Pixel Track browser-side paywall and checkout events from Superwall web paywalls with Facebook Pixel. This integration injects Meta's client-side Pixel script and maps Web2App events to Pixel events. Use the Facebook Pixel integration to send browser-side events from Superwall web paywalls to Meta. This integration injects the standard Pixel script into the page and maps Web2App events to Meta Pixel events such as `ViewContent`, `InitiateCheckout`, and `Purchase`. > **Note:** If you need server-side subscription lifecycle events such as > renewals, cancellations, expirations, or refunds, use > [Meta Conversion API](/docs/integrations/meta-conversion-api) instead. That > integration is separate from Facebook Pixel. In the **Web2App** integrations area, you can connect Facebook Pixel in Superwall: ![](/docs/images/integrations-facebook-pixel.jpeg) ## How this integration works Superwall exposes Facebook Pixel as a Web2App browser integration. When the integration is enabled: * Superwall injects Meta's `fbq` bootstrap script into the page * The Pixel is initialized with your `pixelId` * A `PageView` event is sent when the script loads * Supported paywall and checkout events are forwarded to `fbq` This is browser-side tracking for web paywalls. It does not forward subscription lifecycle events from webhooks, and it does not use Meta's Conversion API. ## Set up in Superwall Set this up from the dashboard UI rather than by editing a config object. 1. Open your app in Superwall. 2. Go to **Integrations**. 3. Open the **Web2App** integrations area. 4. Add or open **Facebook Pixel**. 5. Enter your **Pixel ID**. 6. Leave **Enabled** on if you want the integration to start sending events immediately. 7. Click **Save Integration**. If the integration already exists, the same screen is used in **Edit Integration** mode. ### Fields shown in the dashboard | UI field | Required | What to enter | | ---------- | -------- | ----------------------------------------------- | | `Pixel ID` | Yes | Your Facebook Pixel ID from Meta Events Manager | | `Enabled` | No | Turn the integration on or off | Superwall stores the underlying Web2App integration config for you. You do not need to manually write `integrationId` or `config.pixelId` in the dashboard. ## Getting your Pixel ID You only need the Pixel ID for this integration. 1. Go to [Meta Events Manager](https://business.facebook.com/events_manager). 2. Select your Pixel from **Data Sources**. 3. Copy the Pixel ID shown at the top of the page. ## Script bootstrap When the integration loads, Superwall injects Meta's standard client-side script into the page: ```html ``` Superwall also adds Meta's `noscript` image fallback for the same Pixel ID. ## Event mapping The browser integration maps Web2App events to Pixel events as follows: | Superwall browser event | Meta Pixel event | Notes | | -------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------- | | `paywall_open` | `ViewContent` | Includes `content_name`, `content_id`, and `content_type: "paywall"` | | `transaction_start` | `InitiateCheckout` | Includes `content_ids` and `content_type: "product"` | | `transaction_complete` | `Purchase` | Includes `transaction_id`, `content_ids`, and optional `value` and `currency` | | `paywall_close` | `trackCustom("PaywallClosed")` | Custom event with `paywall_id` | | `manageLink_click` | `trackCustom("ManageLinkClick")` | Custom event with subscription fields | | `activateDeviceLink_click` | `trackCustom("ActivateDeviceLinkClick")` | Custom event with subscription fields | ## Event payload details Each mapped event sends a small payload based on the browser event data. ### `paywall_open` -> `ViewContent` ```json { "content_name": "Main paywall", "content_id": "paywall_123", "content_type": "paywall" } ``` ### `transaction_start` -> `InitiateCheckout` ```json { "content_ids": ["com.app.premium.monthly"], "content_type": "product" } ``` ### `transaction_complete` -> `Purchase` ```json { "transaction_id": "txn_123", "content_ids": ["com.app.premium.monthly"], "content_type": "product", "value": 9.99, "currency": "USD" } ``` The `value` and `currency` fields are only included when they are present in the browser event payload. ### `paywall_close` -> `PaywallClosed` ```json { "paywall_id": "paywall_123" } ``` ### `manageLink_click` -> `ManageLinkClick` ```json { "subscription_name": "Premium Monthly", "subscription_status": "active", "redemption_code": "ABC123", "provider": "stripe" } ``` ### `activateDeviceLink_click` -> `ActivateDeviceLinkClick` ```json { "subscription_name": "Premium Monthly", "redemption_code": "ABC123" } ``` ## Facebook Pixel vs. Meta Conversion API These integrations are related, but they solve different problems. | Integration | Tracking mode | Best for | Does it send renewals, cancellations, and refunds? | | ------------------- | ------------- | ------------------------------------------------- | -------------------------------------------------- | | Facebook Pixel | Browser-side | Web paywall interactions and checkout flow events | No | | Meta Conversion API | Server-side | Subscription lifecycle and revenue webhook events | Yes | Use Facebook Pixel when you want client-side behavioral signals from web paywalls. Use [Meta Conversion API](/docs/integrations/meta-conversion-api) when you need server-side revenue and subscription lifecycle events. ## Testing the integration Validate the browser-side integration before relying on it in campaigns. 1. Enable the Facebook Pixel integration with your Pixel ID. 2. Open a web paywall that uses the integration. 3. Confirm `PageView` appears in Meta Events Manager. 4. Trigger paywall and checkout events. 5. Verify `ViewContent`, `InitiateCheckout`, `Purchase`, and any custom events appear as expected. You can also confirm that `fbq` is loaded in the browser and inspect network requests to Meta while exercising the paywall flow. ## Common use cases ### Track paywall impressions Use `ViewContent` from `paywall_open` to measure paywall views and build remarketing audiences around paywall engagement. ### Track checkout starts Use `InitiateCheckout` from `transaction_start` to see where users begin the purchase flow but do not complete it. ### Track completed purchases in the browser Use `Purchase` from `transaction_complete` to capture client-side checkout completion on web paywalls. ### Track manage-subscription interactions Use `ManageLinkClick` and `ActivateDeviceLinkClick` to understand how users interact with account and device-link flows. ## Troubleshooting ### Events are not appearing in Meta **Possible causes:** * The Pixel ID is incorrect * The browser integration is not enabled * The page did not load the injected `fbq` script * The paywall flow did not emit the expected browser event **Solutions:** 1. Verify the Pixel ID in Meta Events Manager. 2. Confirm the integration is enabled for the web paywall flow. 3. Check that `fbq` is available in the page. 4. Inspect the browser console and network requests during the flow. ### `Purchase` is missing value or currency **Possible causes:** * The `transaction_complete` event did not include those fields **Solutions:** * Verify the browser event payload includes `value` and `currency`. * If you need more complete revenue lifecycle reporting, use [Meta Conversion API](/docs/integrations/meta-conversion-api). ### You need renewals, cancellations, or refunds Facebook Pixel does not send those lifecycle events in this integration. Use [Meta Conversion API](/docs/integrations/meta-conversion-api) for that server-side workflow. ## Additional resources * [Meta Events Manager](https://business.facebook.com/events_manager) * [Meta Pixel documentation](https://developers.facebook.com/docs/meta-pixel/) * [Meta Conversion API](/docs/integrations/meta-conversion-api) for server-side subscription lifecycle tracking # 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 # Figma Plugin The Superwall Figma Plugin allows designers to convert Figma designs into fully functional paywalls with one click. The Superwall Figma Import plugin can automatically import Figma designs into the paywall editor. Each component is imported individually, preserving your design structure. > **Note:** Auto Layout is required for the **entire frame** in your Figma files for the import to work. To see it in action, check out the video demo: