# 3rd Party Analytics Source: https://superwall.com/docs/3rd-party-analytics Superwall can easily be integrated with 3rd party analytics tools. ### Hooking up Superwall events to 3rd party tools SuperwallKit automatically tracks some internal events. You can [view the list of events here](/tracking-analytics). We encourage you to also track them in your own analytics by implementing the [Superwall delegate](/using-superwall-delegate). Using the `handleSuperwallEvent(withInfo:)` function, you can forward events to your analytics service: ```Swift Swift extension SuperwallService: SuperwallDelegate { func handleSuperwallEvent(withInfo eventInfo: SuperwallEventInfo) { print("analytics event called", eventInfo.event.description) MyAnalyticsService.shared.track( event: eventInfo.event.description, params: eventInfo.params ) } } ``` ```Swift Objective-C - (void)handleSuperwallEventWithInfo:(SWKSuperwallEventInfo *)info { NSLog(@"Analytics event called %@", info.event.description)); [[MyAnalyticsService shared] trackEvent:info.event.description params:info.params]; } ``` ```kotlin Kotlin override fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) { println("analytics event: ${eventInfo.event.rawName}") MyAnalytics.shared.track(eventInfo.event.rawName, eventInfo.params) } ``` ```dart Flutter @override void handleSuperwallEvent(SuperwallEventInfo eventInfo) async { print("handleSuperwallEvent: $eventInfo"); // Example usage... switch (eventInfo.event.type) { case EventType.appOpen: print("appOpen event"); case EventType.deviceAttributes: print("deviceAttributes event: ${eventInfo.event.deviceAttributes} "); case EventType.paywallOpen: final paywallInfo = eventInfo.event.paywallInfo; print("paywallOpen event: ${paywallInfo} "); if (paywallInfo != null) { final identifier = await paywallInfo.identifier; print("paywallInfo.identifier: ${identifier} "); final productIds = await paywallInfo.productIds; print("paywallInfo.productIds: ${productIds} "); } default: break; } } ``` ```typescript React Native handleSuperwallEvent(eventInfo: SuperwallEventInfo) { console.log(`handleSuperwallEvent: ${eventInfo}`); switch (eventInfo.event.type) { case EventType.appOpen: console.log("appOpen event"); break; case EventType.deviceAttributes: console.log(`deviceAttributes event: ${eventInfo.event.deviceAttributes}`); break; case EventType.paywallOpen: const paywallInfo = eventInfo.event.paywallInfo; console.log(`paywallOpen event: ${paywallInfo}`); if (paywallInfo !== null) { paywallInfo.identifier().then((identifier: string) => { console.log(`paywallInfo.identifier: ${identifier}`); }); paywallInfo.productIds().then((productIds: string[]) => { console.log(`paywallInfo.productIds: ${productIds}`); }); } break; default: break; } } ```
You might also want to set user attribute to allow for [Cohorting in 3rd Party Tools](/cohorting-in-3rd-party-tools) Alternatively, if you want typed versions of all these events with associated values, you can access them via `eventInfo.event`: ```swift Swift func handleSuperwallEvent(withInfo eventInfo: SuperwallEventInfo) { switch eventInfo.event { case .firstSeen: break case .appOpen: break case .appLaunch: break case .identityAlias: break case .appInstall: break case .sessionStart: break case .deviceAttributes(let attributes): break case .subscriptionStatusDidChange: break case .appClose: break case .deepLink(let url): break case .triggerFire(let placementName, let result): break case .paywallOpen(let paywallInfo): break case .paywallClose(let paywallInfo): break case .paywallDecline(let paywallInfo): break case .transactionStart(let product, let paywallInfo): break case .transactionFail(let error, let paywallInfo): break case .transactionAbandon(let product, let paywallInfo): break case .transactionComplete(let transaction, let product, let type, let paywallInfo): break case .subscriptionStart(let product, let paywallInfo): break case .freeTrialStart(let product, let paywallInfo): break case .transactionRestore(let restoreType, let paywallInfo): break case .transactionTimeout(let paywallInfo): break case .userAttributes(let atts): break case .nonRecurringProductPurchase(let product, let paywallInfo): break case .paywallResponseLoadStart(let triggeredPlacementName): break case .paywallResponseLoadNotFound(let triggeredPlacementName): break case .paywallResponseLoadFail(let triggeredPlacementName): break case .paywallResponseLoadComplete(let triggeredPlacementName, let paywallInfo): break case .paywallWebviewLoadStart(let paywallInfo): break case .paywallWebviewLoadFail(let paywallInfo): break case .paywallWebviewLoadComplete(let paywallInfo): break case .paywallWebviewLoadTimeout(let paywallInfo): break case .paywallWebviewLoadFallback(let paywallInfo): break case .paywallProductsLoadStart(let triggeredPlacementName, let paywallInfo): break case .paywallProductsLoadFail(let triggeredPlacementName, let paywallInfo): break case .paywallProductsLoadComplete(let triggeredPlacementName): break case .paywallProductsLoadRetry(let triggeredPlacementName, let paywallInfo, let attempt): break case .surveyResponse(let survey, let selectedOption, let customResponse, let paywallInfo): break case .paywallPresentationRequest(let status, let reason): break case .touchesBegan: break case .surveyClose: break case .reset: break case .restoreStart: break case .restoreFail(let message): break case .restoreComplete: break case .configRefresh: break case .customPlacement(let name, let params, let paywallInfo): break case .configAttributes: break case .confirmAllAssignments: break case .configFail: break case .adServicesTokenRequestStart: break case .adServicesTokenRequestFail(let error): break case .adServicesTokenRequestComplete(let token): break case .shimmerViewStart: break case .shimmerViewComplete: break } } ``` ```kotlin Kotlin override fun handleSuperwallEvent(eventInfo: SuperwallEventInfo) { when(eventInfo.event) { is SuperwallPlacement.AppClose -> TODO() is SuperwallPlacement.AppInstall -> TODO() is SuperwallPlacement.AppLaunch -> TODO() is SuperwallPlacement.AppOpen -> TODO() is SuperwallPlacement.DeepLink -> TODO() is SuperwallPlacement.FirstSeen -> TODO() is SuperwallPlacement.FreeTrialStart -> TODO() is SuperwallPlacement.NonRecurringProductPurchase -> TODO() is SuperwallPlacement.PaywallClose -> TODO() is SuperwallPlacement.PaywallDecline -> TODO() is SuperwallPlacement.PaywallOpen -> TODO() is SuperwallPlacement.PaywallPresentationRequest -> TODO() is SuperwallPlacement.PaywallProductsLoadComplete -> TODO() is SuperwallPlacement.PaywallProductsLoadFail -> TODO() is SuperwallPlacement.PaywallProductsLoadStart -> TODO() is SuperwallPlacement.PaywallResponseLoadComplete -> TODO() is SuperwallPlacement.PaywallResponseLoadFail -> TODO() is SuperwallPlacement.PaywallResponseLoadNotFound -> TODO() is SuperwallPlacement.PaywallResponseLoadStart -> TODO() is SuperwallPlacement.PaywallWebviewLoadComplete -> TODO() is SuperwallPlacement.PaywallWebviewLoadFail -> TODO() is SuperwallPlacement.PaywallWebviewLoadStart -> TODO() is SuperwallPlacement.PaywallWebviewLoadTimeout -> TODO() is SuperwallPlacement.SessionStart -> TODO() is SuperwallPlacement.SubscriptionStart -> TODO() is SuperwallPlacement.SubscriptionStatusDidChange -> TODO() is SuperwallPlacement.SurveyClose -> TODO() is SuperwallPlacement.SurveyResponse -> TODO() is SuperwallPlacement.TransactionAbandon -> TODO() is SuperwallPlacement.TransactionComplete -> TODO() is SuperwallPlacement.TransactionFail -> TODO() is SuperwallPlacement.TransactionRestore -> TODO() is SuperwallPlacement.TransactionStart -> TODO() is SuperwallPlacement.TransactionTimeout -> TODO() is SuperwallPlacement.TriggerFire -> TODO() is SuperwallPlacement.UserAttributes -> TODO() } } ``` ```dart Flutter @override void handleSuperwallEvent(SuperwallEventInfo eventInfo) async { // Example usage... switch (eventInfo.event.type) { case PlacementType.appOpen: print("appOpen event"); case PlacementType.deviceAttributes: print("deviceAttributes event: ${eventInfo.event.deviceAttributes} "); case PlacementType.paywallOpen: final paywallInfo = eventInfo.event.paywallInfo; print("paywallOpen event: ${paywallInfo} "); if (paywallInfo != null) { final identifier = await paywallInfo.identifier; print("paywallInfo.identifier: ${identifier} "); final productIds = await paywallInfo.productIds; print("paywallInfo.productIds: ${productIds} "); } default: break; } } ``` ```typescript React Native handleSuperwallEvent(eventInfo: SuperwallEventInfo) { console.log(`handleSuperwallEvent: ${eventInfo}`); switch (eventInfo.event.type) { case EventType.appOpen: console.log("appOpen event"); break; case EventType.deviceAttributes: console.log(`deviceAttributes event: ${eventInfo.event.deviceAttributes}`); break; case EventType.paywallOpen: const paywallInfo = eventInfo.event.paywallInfo; console.log(`paywallOpen event: ${paywallInfo}`); if (paywallInfo !== null) { paywallInfo.identifier().then((identifier: string) => { console.log(`paywallInfo.identifier: ${identifier}`); }); paywallInfo.productIds().then((productIds: string[]) => { console.log(`paywallInfo.productIds: ${productIds}`); }); } break; default: break; } } ``` Wanting to use events to see which product was purchased on a paywall? Check out this [doc](/viewing-purchased-products). # Manually Handling Purchases and Subscription Status Source: https://superwall.com/docs/advanced-configuration If you need fine-grain control over the purchasing pipeline, use a purchase controller. Using a `PurchaseController` is only recommended for advanced use cases. Superwall handles all subscription-related logic and purchasing operations for you out of the box. By default, Superwall handles basic subscription-related logic for you: 1. **Purchasing**: When the user initiates a checkout on a paywall. 2. **Restoring**: When the user restores previously purchased products. 3. **Subscription Status**: When the user's subscription status changes to active or expired (by checking the local receipt). However, if you want more control, you can pass in a `PurchaseController` when configuring the SDK via `configure(apiKey:purchaseController:options:)` and manually set `Superwall.shared.subscriptionStatus` to take over this responsibility. ### Step 1: Creating a `PurchaseController` A `PurchaseController` handles purchasing and restoring via protocol methods that you implement. You pass in your purchase controller when configuring the SDK: ```swift Swift // MyPurchaseController.swift import SuperwallKit import StoreKit final class MyPurchaseController: PurchaseController { static let shared = MyPurchaseController() // 1 func purchase(product: StoreProduct) async -> PurchaseResult { // Use StoreKit or some other SDK to purchase... // Send Superwall the result. return .purchased // .cancelled, .pending, .failed(Error) } func restorePurchases() async -> RestorationResult { // Use StoreKit or some other SDK to restore... // Send Superwall the result. return .restored // Or failed(error) } } ``` ```swift Objective-C @import SuperwallKit; @import StoreKit; // MyPurchaseController @interface MyPurchaseController: NSObject + (instancetype)sharedInstance; @end @implementation MyPurchaseController + (instancetype)sharedInstance { static MyPurchaseController *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [MyPurchaseController new]; }); return sharedInstance; } // 1 - (void)purchaseWithProduct:(SWKStoreProduct * _Nonnull)product completion:(void (^ _Nonnull)(enum SWKPurchaseResult, NSError * _Nullable))completion { // TODO // ---- // Purchase via StoreKit, RevenueCat, Qonversion or however // you like and return a valid SWKPurchaseResult completion(SWKPurchaseResultPurchased, nil); } // 2 - (void)restorePurchasesWithCompletion:(void (^ _Nonnull)(enum SWKRestorationResult, NSError * _Nullable))completion { // TODO // ---- // Restore purchases and return `SWKRestorationResultRestored` if successful. // Return an `NSError` if not. completion(SWKRestorationResultRestored, nil); } @end ``` ```kotlin Kotlin // MyPurchaseController.kt class MyPurchaseController(val context: Context): PurchaseController { // 1 override suspend fun purchase( activity: Activity, productDetails: ProductDetails, basePlanId: String?, offerId: String? ): PurchaseResult { // TODO // ---- // Purchase via GoogleBilling, RevenueCat, Qonversion or however // you like and return a valid PurchaseResult return PurchaseResult.Purchased() } // 2 override suspend fun restorePurchases(): RestorationResult { // TODO // ---- // Restore purchases and return true if successful. return RestorationResult.Success() } } ``` ```dart Flutter // MyPurchaseController.dart class MyPurchaseController extends PurchaseController { // 1 @override Future purchaseFromAppStore(String productId) async { // TODO // ---- // Purchase via StoreKit, RevenueCat, Qonversion or however // you like and return a valid PurchaseResult return PurchaseResult.purchased; } @override Future purchaseFromGooglePlay( String productId, String? basePlanId, String? offerId ) async { // TODO // ---- // Purchase via Google Billing, RevenueCat, Qonversion or however // you like and return a valid PurchaseResult return PurchaseResult.purchased; } // 2 @override Future restorePurchases() async { // TODO // ---- // Restore purchases and return true if successful. return RestorationResult.restored; } } ``` ```typescript React Native export class MyPurchaseController extends PurchaseController { // 1 async purchaseFromAppStore(productId: string): Promise { // TODO // ---- // Purchase via StoreKit, RevenueCat, Qonversion or however // you like and return a valid PurchaseResult } async purchaseFromGooglePlay( productId: string, basePlanId?: string, offerId?: string ): Promise { // TODO // ---- // Purchase via Google Billing, RevenueCat, Qonversion or however // you like and return a valid PurchaseResult } // 2 async restorePurchases(): Promise { // TODO // ---- // Restore purchases and return true if successful. } } ```
```swift import StoreKit import SuperwallKit final class SWPurchaseController: PurchaseController { // MARK: Sync Subscription Status /// Makes sure that Superwall knows the customer's subscription status by /// changing `Superwall.shared.subscriptionStatus` func syncSubscriptionStatus() async { var products: Set = [] for await verificationResult in Transaction.currentEntitlements { switch verificationResult { case .verified(let transaction): products.insert(transaction.productID) case .unverified: break } } let storeProducts = await Superwall.shared.products(for: products) let entitlements = Set(storeProducts.flatMap { $0.entitlements }) await MainActor.run { Superwall.shared.subscriptionStatus = .active(entitlements) } } // MARK: Handle Purchases /// Makes a purchase with Superwall and returns its result after syncing subscription status. This gets called when /// someone tries to purchase a product on one of your paywalls. func purchase(product: StoreProduct) async -> PurchaseResult { let result = await Superwall.shared.purchase(product) await syncSubscriptionStatus() return result } // MARK: Handle Restores /// Makes a restore with Superwall and returns its result after syncing subscription status. /// This gets called when someone tries to restore purchases on one of your paywalls. func restorePurchases() async -> RestorationResult { let result = await Superwall.shared.restorePurchases() await syncSubscriptionStatus() return result } } ``` Here’s what each method is responsible for: 1. Purchasing a given product. In here, enter your code that you use to purchase a product. Then, return the result of the purchase as a `PurchaseResult`. For Flutter, this is separated into purchasing from the App Store and Google Play. This is an enum that contains the following cases, all of which must be handled: 1. `.cancelled`: The purchase was cancelled. 2. `.purchased`: The product was purchased. 3. `.pending`: The purchase is pending/deferred and requires action from the developer. 4. `.failed(Error)`: The purchase failed for a reason other than the user cancelling or the payment pending. 2. Restoring purchases. Here, you restore purchases and return a `RestorationResult` indicating whether the restoration was successful or not. If it was, return `.restore`, or `failed` along with the error reason. ### Step 2: Configuring the SDK With Your `PurchaseController` Pass your purchase controller to the `configure(apiKey:purchaseController:options:)` method: ```swift UIKit // AppDelegate.swift import UIKit import SuperwallKit @main class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { Superwall.configure( apiKey: "MY_API_KEY", purchaseController: MyPurchaseController.shared // <- Handle purchases on your own ) return true } } ``` ```swift SwiftUI @main struct MyApp: App { init() { Superwall.configure( apiKey: "MY_API_KEY", purchaseController: MyPurchaseController.shared // <- Handle purchases on your own ) } var body: some Scene { WindowGroup { ContentView() } } } ``` ```swift Objective-C // AppDelegate.m @import SuperwallKit; @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. [Superwall configureWithApiKey:@"MY_API_KEY" purchaseController:[MyPurchaseController sharedInstance] options:nil completion:nil]; return YES; } ``` ```kotlin Kotlin // MainApplication.kt class MainApplication : android.app.Application(), SuperwallDelegate { override fun onCreate() { super.onCreate() Superwall.configure(this, "MY_API_KEY", MyPurchaseController(this)) // OR using the DSL configureSuperwall("MY_API_KEY") { purchaseController = MyPurchaseController(this@MainApplication) } } } ``` ```dart Flutter // main.dart void initState() { // Determine Superwall API Key for platform String apiKey = Platform.isIOS ? "MY_IOS_API_KEY" : "MY_ANDROID_API_KEY"; // Create the purchase controller MyPurchaseController purchaseController = MyPurchaseController(); Superwall.configure(apiKey, purchaseController); } ``` ```typescript React Native export default function App() { React.useEffect(() => { const apiKey = Platform.OS === "ios" ? "MY_IOS_API_KEY" : "MY_ANDROID_API_KEY" const purchaseController = new MyPurchaseController() Superwall.configure({ apiKey: apiKey, purchaseController: purchaseController, }) }, []) } ``` ### Step 3: Keeping `subscriptionStatus` Up-To-Date You **must** set `Superwall.shared.subscriptionStatus` every time the user's subscription status changes, otherwise the SDK won't know who to show a paywall to. This is an enum that has three possible cases: 1. **`.unknown`**: This is the default value. In this state, paywalls will not show and their presentation will be ***automatically delayed*** until `subscriptionStatus` changes to a different value. 2. **`.active(let entitlements)`**: Indicates that the user has an active entitlement. Paywalls will not show in this state unless you remotely set the paywall to ignore subscription status. A user can have one or more active entitlement. 3. **`.inactive`**: Indicates that the user doesn't have an active entitlement. Paywalls can show in this state. Here's how you might do this: ```swift Swift import SuperwallKit func syncSubscriptionStatus() async { var purchasedProductIds: Set = [] // get all purchased product ids for await verificationResult in Transaction.currentEntitlements { switch verificationResult { case .verified(let transaction): purchasedProductIds.insert(transaction.productID) case .unverified: break } } // get store products for purchased product ids from Superwall let storeProducts = await Superwall.shared.products(for: purchasedProductIds) // get entitlements from purchased store products let entitlements = Set(storeProducts.flatMap { $0.entitlements }) // set subscription status await MainActor.run { Superwall.shared.subscriptionStatus = .active(entitlements) } } ``` ```swift Objective-C @import SuperwallKit; // when a subscription is purchased, restored, validated, expired, etc... [myService setSubscriptionStatusDidChange:^{ if (user.hasActiveSubscription) { [Superwall sharedInstance] setActiveSubscriptionStatusWith:[NSSet setWithArray:@[myEntitlements]]]; } else { [[Superwall sharedInstance] setInactiveSubscriptionStatus]; } }]; ``` ```kotlin Kotlin // When a subscription is purchased, restored, validated, expired, etc... myService.subscriptionStatusDidChange { if (it.hasActiveSubscription) { Superwall.instance.setSubscriptionStatus(SubscriptionStatus.Active(entitlements)) } else { Superwall.instance.setSubscriptionStatus(SubscriptionStatus.Inactive(entitlements)) } } ``` ```dart Flutter // When a subscription is purchased, restored, validated, expired, etc... myService.addSubscriptionStatusListener((subscriptionInfo) { var entitlements = subscriptionInfo.entitlements.active.keys .map((id) => Entitlement(id: id)) .toSet(); var hasActiveSubscription = subscriptionInfo.isActive; if (hasActiveSubscription) { Superwall.shared.setSubscriptionStatus(SubscriptionStatusActive(entitlements: entitlements)); } else { Superwall.shared.setSubscriptionStatus(SubscriptionStatusInactive()); } }); ``` ```typescript React Native // When a subscription is purchased, restored, validated, expired, etc... myService.addSubscriptionStatusListener((subscriptionInfo: SubscriptionInfo) => { const entitlements = Object.keys(subscriptionInfo.entitlements.active).map((id) => ({ id, })) if (entitlements.length === 0) { Superwall.shared.setSubscriptionStatus(SubscriptionStatus.Inactive()) } else { Superwall.shared.setSubscriptionStatus( SubscriptionStatus.Active(entitlements.map((id) => new Entitlement(id))) ) } }) ```
`subscriptionStatus` is cached between app launches ### Listening for subscription status changes If you need a simple way to observe when a user's subscription status changes, on iOS you can use the `Publisher` for it. Here's an example: ```swift iOS subscribedCancellable = Superwall.shared.$subscriptionStatus .receive(on: DispatchQueue.main) .sink { [weak self] status in switch status { case .unknown: self?.subscriptionLabel.text = "Loading subscription status." case .active(let entitlements): self?.subscriptionLabel.text = "You currently have an active subscription: \(entitlements.map { $0.id }). Therefore, the paywall will not show unless feature gating is disabled." case .inactive: self?.subscriptionLabel.text = "You do not have an active subscription so the paywall will show when clicking the button." } } ``` ```kotlin Kotlin Superwall.instance.subscriptionStatus.collect { status: SubscriptionStatus -> // React to changes } ``` ```dart Flutter Superwall.shared.subscriptionStatus.listen((status) { // React to changes } //Or use SuperwallBuilder widget which triggers the builder closure when subscription status changes SuperwallBuilder( builder: (context, status) => Center( child: Text('Subscription Status: ${status}'), ) ) ``` ```typescript React Native Superwall.shared.subscriptionStatusEmitter.addListener("change", (status) => { switch (status.status) { case "ACTIVE": break default: break } }) ``` You can do similar tasks with the `SuperwallDelegate`, such as [viewing which product was purchased from a paywall](/3rd-party-analytics#using-events-to-see-purchased-products). # Android Source: https://superwall.com/docs/android The long-awaited Superwall for Android is now available! Here's how to get started! ### Quickstart Follow [this guide](/creating-applications) to create a new android app. Follow [this guide](/installation-via-gradle) to install the SDK. Follow [this guide](/configuring-the-sdk) to configure the SDK You're all set! 🎉 Reach out to us on Intercom or by [email](mailto:team@superwall.com) For any feedback, email [team@superwall.com](mailto:team@superwall.com) *** #### Known Issues * [Presentation Style](/paywall-editor-settings#presentation-style) (right now only full screen) #### Next Up! Once we're done fixing up any initial issues we'll move on to improving on the version we've delivered. Including supporting Google Upgrade & Downgrade flow. # App Store Privacy Labels Source: https://superwall.com/docs/app-privacy-nutrition-labels (iOS only) When submitting your app for review, you'll need to fill out an App Store Privacy label. When using the Superwall SDK, there are a few choices you may need to consider. ### App Store Privacy Labels Privacy disclosures in regards to how data is processed or otherwise used are required when submitting an app for review on the App Store. When using the Superwall SDK, there are a few options you'll need to select to comply with this requirement. **At a minimum, you'll need to select "Purchases":** ![](https://mintlify.s3.us-west-1.amazonaws.com/superwall-docs/images/app-privacy-purchases.png "App Store Privacy - Purchases") When you select "Purchases", you'll need to scroll down finish setup. When you do, there are two options you'll need to select: 1. Analytics 2. App Functionality ![](https://mintlify.s3.us-west-1.amazonaws.com/superwall-docs/images/app-privacy-purchase-history.png "App Store Privacy - Purchase History") ### Identifying Users How you proceed with the next prompt depends on how you are identifying users. If you *are* identifying users via their email or any other means, disclose that here. Note that the Superwall SDK does not do this. Finally, Superwall does not track purchase history of users for advertising purposes — so you can choose "No" here (unless you're using other SDKs which do this, or you're performing any purchase history tracking for advertising purposes on your own ): ![](https://mintlify.s3.us-west-1.amazonaws.com/superwall-docs/images/app-privacy-tagging-users.png "App Store Privacy - User Identification") In terms of the Superwall SDK, that's all you need to choose. But again, remember that your privacy label could look different depending on how you process data, how other SDKs are used and more. ### Collected Data Here is a detailed list of anything that might be collected in the Superwall SDK: | Property | Description | | ----------------------------- | --------------------------------------------------------------- | | `publicApiKey` | The API key for accessing the public API. | | `platform` | The operating system of the device (e.g., iOS, Android). | | `appUserId` | A unique identifier for the app user. | | `aliases` | List of aliases associated with the app user. | | `vendorId` | The vendor ID of the device. | | `appVersion` | The version of the app. | | `osVersion` | The operating system version running on the device. | | `deviceModel` | The model of the device (e.g., iPhone or Android device model). | | `deviceLocale` | The current locale set on the device. | | `preferredLocale` | The preferred locale of the user. | | `deviceLanguageCode` | The language code of the device's system language. | | `preferredLanguageCode` | The preferred language code set by the user. | | `regionCode` | The region code set on the device. | | `preferredRegionCode` | The preferred region code of the user. | | `deviceCurrencyCode` | The currency code for transactions on the device. | | `deviceCurrencySymbol` | The currency symbol based on the device’s settings. | | `interfaceType` | The type of user interface (e.g., vision, ipad, etc). | | `timezoneOffset` | The device’s current timezone offset in minutes. | | `radioType` | The network radio type (e.g., WiFi, Cellular). | | `interfaceStyle` | The interface style (e.g., light or dark mode). | | `isLowPowerModeEnabled` | Indicates whether low power mode is enabled. | | `bundleId` | The bundle identifier of the app. | | `appInstallDate` | The date the app was installed. | | `isMac` | A boolean indicating if the device is a Mac. | | `daysSinceInstall` | The number of days since the app was installed. | | `minutesSinceInstall` | The number of minutes since the app was installed. | | `daysSinceLastPaywallView` | The number of days since the last paywall view. | | `minutesSinceLastPaywallView` | The number of minutes since the last paywall view. | | `totalPaywallViews` | The total number of paywall views. | | `utcDate` | The current UTC date. | | `localDate` | The local date of the device. | | `utcTime` | The current UTC time. | | `localTime` | The local time on the device. | | `utcDateTime` | The UTC date and time combined. | | `localDateTime` | The local date and time combined. | | `isSandbox` | Indicates if the app is running in a sandbox environment. | | `subscriptionStatus` | The subscription status of the app user. | | `isFirstAppOpen` | Boolean indicating if it is the user’s first app open. | | `sdkVersion` | The current version of the SDK. | | `sdkVersionPadded` | The padded version of the SDK (e.g. 001.002.003-beta.001). | | `appBuildString` | The app’s build string identifier. | | `appBuildStringNumber` | The numeric value of the app’s build number. | | `interfaceStyleMode` | The current interface style mode (e.g., dark, light). | | `ipRegion` | The region derived from the device's IP address. | | `ipRegionCode` | The region code derived from the device's IP. | | `ipCountry` | The country derived from the device's IP address. | | `ipCity` | The city derived from the device's IP address. | | `ipContinent` | The continent derived from the device's IP address. | | `ipTimezone` | The timezone derived from the device's IP address. | | `capabilities` | A string indicating any Superwall-SDK specific capabilities. | | `capabilitiesConfig` | A JSON configuration of the above capabilities. | | `platformWrapper` | The platform wrapper (e.g., React Native). | | `platformWrapperVersion` | The version of the platform wrapper. | # Campaigns Source: https://superwall.com/docs/campaigns Campaigns are logical groupings of paywalls to show when certain _events_ are registered and _conditions_ are met. They are an incredibly powerful tool for creating experiments and managing best-in-class monetization flows. View **Campaigns** by clicking them over on the left-hand **sidebar**: ![](https://mintlify.s3.us-west-1.amazonaws.com/superwall-docs/images/campaign-sidebar.png) Campaigns consist of three main concepts: 1. [Placements](/feature-gating) 2. [Audiences](/campaigns-audience) 3. [Paywalls](/paywall-editor-overview) Campaigns are the centerpiece of Superwall. You can use one, or several, campaigns that can run concurrently. To understand campaigns, think of them like this: * In a campaign, you add **placements** — which are actions you want to result in a paywall, or might someday want to result in a paywall(i.e. `loggedCaffeine`, `addedEntry`, etc). * Then, as users take actions in your app, those placements are **registered** in the Superwall SDK. * When a placement is registered, it's then evaluated by Superwall. Superwall looks at your campaign **[filters](/campaigns-audience#configuring-an-audience)**, and may or may not show a matching **paywall**. With this setup, you can be incredibly simple or make in-depth, complex filters which determine when (and what) paywall is shown. You can set the percentage of new users that see each paywall, or even configure no paywall (a.k.a. a holdout) to be shown for certain placements. ### Toggling campaigns by status You can toggle between campaigns by their status using the tabs at the top (above the campaigns): ![](https://mintlify.s3.us-west-1.amazonaws.com/superwall-docs/images/campaigns-tabs.png) * **All:** The default view. This shows all of your campaigns, regardless of their status. * **Active:** Campaigns being used in production and serving up paywalls. * **Inactive:** Campaigns that are not serving paywalls in production, but can be quickly re-enabled. * **Archived:** Campaigns that have been archived, and not attached to any campaign. These can be restored. ### Viewing campaign top-level metrics Each campaign will also display its active placements and top-level metrics (if any are available). In this example, the campaign at the top has data, while the one below it doesn't: ![](https://mintlify.s3.us-west-1.amazonaws.com/superwall-docs/images/campaigns-inline-metrics.png) Metrics shown include: * **Opens:** The amount of time any of the campaign's placements resulted in a paywall being presented. * **Conversions:** The number of conversions produced from any paywall attached to the campaign. * **Conversion Rate:** The conversion rate of the current campaign. If the campaign isn't currently serving any paywalls because none have been attached to it, you'll see a wanting indicating that in this view. In the image above, that's the case for the campaign at the bottom, called "Survey". ### Toggling campaigns by date To toggle the date range that the metrics previously mentioned should display within, use the date toggle at the top-right side: ![](https://mintlify.s3.us-west-1.amazonaws.com/superwall-docs/images/campaigns-date-toggle.png) ### Viewing campaign details To view more about any campaign, to set up filters, edit placements, paywalls and more — simply **click** on any campaign listed in the table. Then, campaigns details will be presented. More on that in the next [page](/campaigns-structure). # Audiences Source: https://superwall.com/docs/campaigns-audience Audiences allow you to set up simple or complex filtering rules to match certain users and show a paywall to them. For a user to see a paywall, they must be matched to an audience. An audience can show one or more paywalls based on a percentage you set (i.e. show paywall A to 70% of users, and paywall B to 30%). **Another way to think of them is this: If you're wanting to create conditions, filters or certain rules or flows that must happen to show a paywall — then you create an audience for it.** If creating filters to show a paywall under certain conditions doesn't apply to you, then you can simply leave the default audience on — it'll match everyone who hits a [placement](/campaigns-placements). In the audience view, you can set up filtering rules, check results of experiments and recent transactions resulting from them. All of your current audiences will show in the left-hand side of the campaign details screen: ![](https://mintlify.s3.us-west-1.amazonaws.com/superwall-docs/images/campaigns-audiences.png) The audience section lets you [edit the order](#reordering-audiences) in which audiences are evaluated. **Superwall evaluates audiences top-to-bottom.** For example, consider you had three audiences for a caffeine tracking app: * An audience for users who tried to set a custom app icon. * An audience for users who've logged caffeine late at night. * And, everyone else. If a user logged caffeine in the morning, Superwall would first check if they matched the custom app icon audience, and then the audience for logging caffeine late at night. Since neither of those match (since they are logging caffeine in the morning, and not setting a custom icon), they'd land in the "everyone else" audience bucket. ### Adding a new audience To create a new audience, **click** the **+** button in the audiences section, located at the left-hand side of the campaign details view: ![](https://mintlify.s3.us-west-1.amazonaws.com/superwall-docs/images/campaigns-add-audience.png) Superwall will create a new audience, and place it at the bottom of your current audiences by default. ### Renaming Audiences To rename an audience, **click** the **pencil icon**, located at the top of a selected audience: ![](https://mintlify.s3.us-west-1.amazonaws.com/superwall-docs/images/campaigns-audiences-set-name.png) ### Configuring an audience To use an audience to filter for a particular set of events, rules or any other condition — you use **filters**, specify if an **entitlement** should be evaluated, along with an optional **limit**. #### Creating filters You can add filters (i.e. rules or conditions to match against) by **clicking** on an audience, and then clicking the **+ Add Filter** button: ![](https://mintlify.s3.us-west-1.amazonaws.com/superwall-docs/images/campaigns-audience-add-filter.png) From there, select any of the events to create a filter with. For example, if you want to use a placement you've made to match against: 1. Click "+ Add Filter". 2. Type in "event\_name". 3. For the evaluation operator, choose "is". 4. And then, type in the placement's name. For example, if we wanted to show a certain paywall for users who tried to set a custom icon, it might look like this: ![](https://mintlify.s3.us-west-1.amazonaws.com/superwall-docs/images/campaigns-audience-filter-by-event.png) When you have a condition setup, **click** the **Save** button towards the bottom to apply it: ![](https://mintlify.s3.us-west-1.amazonaws.com/superwall-docs/images/campaign-audience-filter-save.png) If you don't want to save any filter you're working on, **click** the **Discard** button by the save button. You can combine rules together, too. In the following example, if we only wanted the paywall to show on iOS, and not Android, you can simply click "+Add Filter" once more, and add the condition: ![](https://mintlify.s3.us-west-1.amazonaws.com/superwall-docs/images/campaigns-audience-combo-condition.png) For a hands on tutorial of creating multiple filters to show different paywalls, check out this video: