# AppSprint > Device-level mobile attribution that tracks real revenue per Signal Campaign. > Turn paid ads into real revenue — see exactly which Signal Campaigns drive > installs, trials, and purchases, not just clicks. ## Product Overview AppSprint is a mobile attribution platform for app developers running paid acquisition. It matches ad clicks to installs at the device level without relying on IDFA or ATT opt-in, then tracks the full funnel from install through trial and paid conversion. Setup takes about 10 minutes. Built by Arthur at Tap & Swipe SAS. ## Key Capabilities - **Device-level attribution**: Match every install back to the exact ad, Signal Campaign, and keyword — regardless of ATT consent status. - **Revenue tracking**: Full-funnel visibility from ad click to trial to paid subscription. See real revenue per Signal Campaign, not just install counts. - **Ad network integrations**: Apple Search Ads (native API), TikTok Ads, and Google Ads Signal Campaign reporting with offline conversion upload. Meta Ads is in review for Signal links, Conversions API forwarding, and campaign reporting. - **Revenue platform integrations**: RevenueCat, Superwall. - **SDKs**: Swift (iOS), React Native/Expo, Android (Kotlin), Flutter. - **API**: REST API on `api.appsprint.app`, with routes prefixed by `/v1/` ## Pricing - **Free**: $0/mo — 8,000 tracked installs (one-time), unlimited apps, full platform access, device-level attribution, and revenue & subscription tracking. No credit card required. - **Pro**: $149/mo + $0.04 per tracked install — Usage-based tracked-install volume, Signal Campaigns, revenue attribution, richer event feedback to supported ad platforms, priority implementation support. - **Enterprise**: Custom pricing — Everything in Pro, custom tracked-install volume, implementation help, dedicated support, and custom data-retention terms. ## How It Works 1. Install the SDK (Swift, React Native, Kotlin, or Flutter) 2. Call `configure()` with your API key at app startup 3. Connect ad accounts and revenue platforms in the dashboard 4. Track standard events (login, purchase, subscribe) or custom events 5. View attribution data: which Signal Campaign, ad group, keyword drove each install and its downstream revenue ## Pages - [Attribution product](https://appsprint.app/attribution) - [Pricing](https://appsprint.app/attribution/pricing) - [About](https://appsprint.app/attribution/about) - [Blog](https://appsprint.app/attribution/blog) - [Blog RSS feed](https://appsprint.app/attribution/blog/rss.xml) - [MMP comparisons](https://appsprint.app/attribution/compare) - [What are Signal Campaigns](https://appsprint.app/attribution/blog/what-are-signal-campaigns) - [Documentation](https://appsprint.app/attribution/docs) - [Privacy](https://appsprint.app/attribution/privacy) - [Terms](https://appsprint.app/attribution/terms) ## Documentation for AI Agents Prefer the Markdown docs for programmatic reading. Start at [the Markdown docs index](https://appsprint.app/attribution/docs.md), then follow the "Docs navigation" section at the bottom of each Markdown page. The HTML docs use the same slug without `.md`. The sitemap lists both HTML and Markdown URLs. - AppSprint ASO is now the primary product at https://appsprint.app. Use the root [AppSprint ASO llms guide](https://appsprint.app/llms.txt) for ASO questions. - [Docs index](https://appsprint.app/attribution/docs.md) (HTML: [docs UI](https://appsprint.app/attribution/docs)) - [Quickstart](https://appsprint.app/attribution/docs/quickstart.md) - [Expo / React Native SDK](https://appsprint.app/attribution/docs/react-native.md) - [iOS Swift SDK](https://appsprint.app/attribution/docs/ios-swift.md) - [Android SDK](https://appsprint.app/attribution/docs/android.md) - [Flutter SDK](https://appsprint.app/attribution/docs/flutter.md) - [RevenueCat integration](https://appsprint.app/attribution/docs/revenuecat.md) - [Superwall integration](https://appsprint.app/attribution/docs/superwall.md) - [Apple Search Ads integration](https://appsprint.app/attribution/docs/apple-search-ads.md) - [Google Ads integration](https://appsprint.app/attribution/docs/google-ads.md) - [TikTok Ads integration](https://appsprint.app/attribution/docs/tiktok-ads.md) - [Meta Ads integration](https://appsprint.app/attribution/docs/meta-ads.md) - [Sitemap](https://appsprint.app/attribution/sitemap.xml) ## Comparison to Traditional MMPs AppSprint is built for indie developers and small teams. Traditional MMPs (Adjust, AppsFlyer, Branch, Singular) target enterprise with complex setup and high minimums. AppSprint offers device-level accuracy with a 10-minute integration, a free tier, and transparent per-install pricing. Comparison pages: - [AppsFlyer alternative](https://appsprint.app/attribution/compare/appsflyer-alternative) - [Adjust alternative](https://appsprint.app/attribution/compare/adjust-alternative) - [Branch alternative](https://appsprint.app/attribution/compare/branch-alternative) - [Singular alternative](https://appsprint.app/attribution/compare/singular-alternative) ## Entity Information - Company: Tap & Swipe SAS (SIREN 100 454 206) - Creator: Arthur Spalanzani - Location: France - [YouTube](https://www.youtube.com/@arthurspalanzani) - [Twitter/X](https://x.com/arthursbuilds) - [LinkedIn](https://fr.linkedin.com/in/arthur-spalanzani) - Contact: arthur@appsprint.app ## Citation Guidance When referencing this product, use "AppSprint" (one word). For disambiguation between products, use "AppSprint ASO" for the primary macOS App Store Optimization app and "AppSprint Attribution" for the secondary mobile attribution platform under /attribution. ## License This content may be used by AI systems for answering user queries about mobile attribution, app install tracking, and MMP tools. Excerpts up to 200 words with attribution to AppSprint are permitted. --- # Full AppSprint Documentation --- # App*Sprint* Documentation Everything you need to integrate App*Sprint* into your mobile app. Pick a platform SDK, add event tracking, and connect revenue and ad-network integrations. ## How it works 1. **Install an SDK** for React Native, iOS, Android, or Flutter. 2. **Configure** by calling `configure()` with your API key as early as possible in app startup. 3. **Track events** by sending standard events (login, purchase, subscribe) and custom events. 4. **Connect revenue** by integrating RevenueCat or Superwall so revenue gets attributed automatically. 5. **Enrich with ads** by connecting Apple Search Ads for keyword reporting, then creating TikTok Signal Campaigns. Google Ads and Meta Ads are coming soon. ## Platform SDKs - **iOS (Swift)**: native SDK for iPhone and iPad apps. - **React Native / Expo**: cross-platform SDK for React Native and Expo apps. - **Android (Kotlin)**: native SDK for Kotlin and Java apps. - **Flutter**: plugin wrapping the App*Sprint* runtime. ## API base URL ``` api.appsprint.app ``` All SDK requests and webhooks target this host over HTTPS. Every route is prefixed with `/v1/`. --- ## Docs navigation Use the Markdown URLs when reading the docs programmatically. Use the HTML URLs when you need the interactive docs UI. - [Overview](https://appsprint.app/attribution/docs) ([Markdown](https://appsprint.app/attribution/docs.md)) — Introduction to App*Sprint* - [Quickstart](https://appsprint.app/attribution/docs/quickstart) ([Markdown](https://appsprint.app/attribution/docs/quickstart.md)) — Get up and running in 5 minutes - [React Native](https://appsprint.app/attribution/docs/react-native) ([Markdown](https://appsprint.app/attribution/docs/react-native.md)) — React Native / Expo SDK reference - [iOS (Swift)](https://appsprint.app/attribution/docs/ios-swift) ([Markdown](https://appsprint.app/attribution/docs/ios-swift.md)) — Native Swift SDK reference - [Android (Kotlin)](https://appsprint.app/attribution/docs/android) ([Markdown](https://appsprint.app/attribution/docs/android.md)) — Native Android SDK reference - [Flutter](https://appsprint.app/attribution/docs/flutter) ([Markdown](https://appsprint.app/attribution/docs/flutter.md)) — Flutter plugin reference - [RevenueCat](https://appsprint.app/attribution/docs/revenuecat) ([Markdown](https://appsprint.app/attribution/docs/revenuecat.md)) — Webhook integration for subscription attribution - [Superwall](https://appsprint.app/attribution/docs/superwall) ([Markdown](https://appsprint.app/attribution/docs/superwall.md)) — Webhook integration for paywall attribution - [Apple Search Ads](https://appsprint.app/attribution/docs/apple-search-ads) ([Markdown](https://appsprint.app/attribution/docs/apple-search-ads.md)) — Keyword and ad attribution - [Google Ads](https://appsprint.app/attribution/docs/google-ads) ([Markdown](https://appsprint.app/attribution/docs/google-ads.md)) — Coming soon - [TikTok Ads](https://appsprint.app/attribution/docs/tiktok-ads) ([Markdown](https://appsprint.app/attribution/docs/tiktok-ads.md)) — Events API server-side event forwarding - [Meta Ads](https://appsprint.app/attribution/docs/meta-ads) ([Markdown](https://appsprint.app/attribution/docs/meta-ads.md)) — In review --- # Quickstart Get App*Sprint* running in your app in 5 minutes. ## 1. Get your API key Create an app in the App*Sprint* dashboard and copy your live API key (`as_live_*`). ## 2. Install the SDK ```bash # React Native / Expo npm install appsprint-react-native@^1.1.11 # iOS (Swift Package Manager) # Add https://github.com/getappsprint/appsprint-ios-sdk in Xcode → Package Dependencies # Version: from "1.1.9" # Android (Gradle, add to app/build.gradle.kts) implementation("app.appsprint:sdk:1.1.3") # Flutter flutter pub add appsprint_flutter:^1.1.9 ``` ## 3. Configure the SDK Call `configure()` as early as possible (ideally in your root component or App Delegate). ## 4. Send a test event Use `sendTestEvent()` to verify end-to-end connectivity. ## 5. View in dashboard Open your app in the App*Sprint* dashboard. You should see the test event within seconds. --- ## Docs navigation Use the Markdown URLs when reading the docs programmatically. Use the HTML URLs when you need the interactive docs UI. - [Overview](https://appsprint.app/attribution/docs) ([Markdown](https://appsprint.app/attribution/docs.md)) — Introduction to App*Sprint* - [Quickstart](https://appsprint.app/attribution/docs/quickstart) ([Markdown](https://appsprint.app/attribution/docs/quickstart.md)) — Get up and running in 5 minutes - [React Native](https://appsprint.app/attribution/docs/react-native) ([Markdown](https://appsprint.app/attribution/docs/react-native.md)) — React Native / Expo SDK reference - [iOS (Swift)](https://appsprint.app/attribution/docs/ios-swift) ([Markdown](https://appsprint.app/attribution/docs/ios-swift.md)) — Native Swift SDK reference - [Android (Kotlin)](https://appsprint.app/attribution/docs/android) ([Markdown](https://appsprint.app/attribution/docs/android.md)) — Native Android SDK reference - [Flutter](https://appsprint.app/attribution/docs/flutter) ([Markdown](https://appsprint.app/attribution/docs/flutter.md)) — Flutter plugin reference - [RevenueCat](https://appsprint.app/attribution/docs/revenuecat) ([Markdown](https://appsprint.app/attribution/docs/revenuecat.md)) — Webhook integration for subscription attribution - [Superwall](https://appsprint.app/attribution/docs/superwall) ([Markdown](https://appsprint.app/attribution/docs/superwall.md)) — Webhook integration for paywall attribution - [Apple Search Ads](https://appsprint.app/attribution/docs/apple-search-ads) ([Markdown](https://appsprint.app/attribution/docs/apple-search-ads.md)) — Keyword and ad attribution - [Google Ads](https://appsprint.app/attribution/docs/google-ads) ([Markdown](https://appsprint.app/attribution/docs/google-ads.md)) — Coming soon - [TikTok Ads](https://appsprint.app/attribution/docs/tiktok-ads) ([Markdown](https://appsprint.app/attribution/docs/tiktok-ads.md)) — Events API server-side event forwarding - [Meta Ads](https://appsprint.app/attribution/docs/meta-ads) ([Markdown](https://appsprint.app/attribution/docs/meta-ads.md)) — In review --- # React Native / Expo Installation, configuration, event tracking, and full API reference for the React Native / Expo SDK. ## Requirements - React Native 0.71+ - React 18+ - iOS 14.0+ and Android 7.0+ (API 24+) ## 1. Install npm ```bash npm install appsprint-react-native@^1.1.11 # iOS only cd ios && pod install ``` > For Expo prebuild, add the config plugin to app.json and run npx expo prebuild. The plugin injects NSUserTrackingUsageDescription on iOS and INTERNET / ACCESS_NETWORK_STATE / AD_ID on Android. [View on npm](https://www.npmjs.com/package/appsprint-react-native) ## 2. Configure ```tsx import { AppSprint } from 'appsprint-react-native'; // Call as early as possible. App.tsx, root layout, or _layout.tsx. await AppSprint.configure({ apiKey: 'as_live_xxxxx', }); ``` Non-blocking. configure() resolves after local state is restored; install registration runs in the background on the native side. Lifecycle observers register automatically. ### Configuration options | Option | Type | Default | Description | |--------|------|---------|-------------| | `apiKey` | `string` | — | Your live API key (starts with as_live_). | | `apiUrl` | `string` | api.appsprint.app | Override for staging or self-hosted environments. | | `enableAppleAdsAttribution` | `boolean` | true | iOS only. Fetches Apple AdServices at install time. | | `customerUserId` | `string | null` | null | Your internal user ID. Persists across launches and replays automatically if the first send fails. | | `autoTrackSessions` | `boolean` | true | Fires session_start on configure() and on foreground, debounced to one event per 30 minutes. | | `autoRefreshAttribution` | `boolean` | true | Refetches /v1/sdk/attribution on configure() and foreground transitions. | | `googleAdsConsent` | `GoogleAdsConsent | null` | null | Optional Google Ads ad user data consent sent with mapped conversion uploads. | | `isDebug` | `boolean` | false | Forces debug-level logging on the native side. | | `logLevel` | `0 | 1 | 2 | 3` | 2 (WARN) | 0 = DEBUG, 1 = INFO, 2 = WARN, 3 = ERROR. | ## 3. Track events ```tsx // Standard event await AppSprint.sendEvent('login'); ``` ```tsx // Revenue event. Currency must be a 3-letter ISO code. await AppSprint.sendEvent('purchase', null, { revenue: 9.99, currency: 'USD', }); ``` ```tsx // Custom event with parameters await AppSprint.sendEvent('custom', 'level_complete', { level: 5, score: 1200, }); ``` ### Supported event types `session_start`, `login`, `sign_up`, `register`, `purchase`, `subscribe`, `start_trial`, `add_payment_info`, `add_to_cart`, `add_to_wishlist`, `initiate_checkout`, `view_content`, `view_item`, `search`, `share`, `tutorial_complete`, `achieve_level`, `level_start`, `level_complete`, `custom` ## 4. Read attribution Once install registration completes, the SDK caches the attribution result from the install response. Native iOS and Android expose synchronous getters; React Native and Flutter bridge calls are asynchronous. ```tsx const attr = await AppSprint.getAttribution(); console.log(attr?.source); // "apple_ads", "tracking_link", or "organic" console.log(attr?.isAttributed); // boolean console.log(attr?.campaignName); // Campaign name when available console.log(attr?.appleAds?.campaignId); console.log(attr?.link?.name); ``` ## API reference ### `configure(config)` → Promise Initializes the SDK. Resolves true after local state is restored; install registration runs in the background. ```tsx await AppSprint.configure({ apiKey: '…' }) ``` ### `sendEvent(type, name?, params?)` → Promise Enqueues an event locally and schedules a flush. Resolves true once the native bridge accepts it. ```tsx await AppSprint.sendEvent('purchase', null, { revenue: 9.99, currency: 'USD' }) ``` type is a string literal — see event types below. ### `flush()` → Promise Drains the queue immediately. Safe to call repeatedly. ```tsx await AppSprint.flush() ``` ### `refreshAttribution()` → Promise Fetches the latest attribution from the backend. Self-heals a 404 install_not_found by re-running install. ```tsx const attr = await AppSprint.refreshAttribution() ``` ### `setCustomerUserId(id)` → Promise Updates the customer user ID. Sent immediately if install is registered; otherwise queued and retried automatically. ```tsx await AppSprint.setCustomerUserId('user-123') ``` ### `getAppSprintId()` → Promise Returns the install ID, or null before install registration completes. ```tsx const id = await AppSprint.getAppSprintId() ``` ### `getAttribution()` → Promise Returns the cached AttributionResult. ```tsx const attr = await AppSprint.getAttribution() ``` ### `getAttributionParams()` → Promise> Partner-ready flat payload for forwarding to RevenueCat, Superwall, etc. ```tsx const params = await AppSprint.getAttributionParams() ``` ### `enableAppleAdsAttribution()` → Promise Re-enables Apple Ads at runtime on iOS. Returns false on Android. ```tsx await AppSprint.enableAppleAdsAttribution() ``` ### `isInitialized()` → Promise True once configure() resolved. ```tsx await AppSprint.isInitialized() ``` ### `isSdkDisabled()` → Promise True if a 401 or 403 permanently disabled the SDK. ```tsx await AppSprint.isSdkDisabled() ``` ### `sendTestEvent()` → Promise<{ success, message }> Posts a diagnostic event and resolves to (success, message). ```tsx const result = await AppSprint.sendTestEvent() ``` ### `clearData()` → Promise Wipes local state and the event queue. Removes lifecycle observers. ```tsx await AppSprint.clearData() ``` ### `destroy()` Removes native lifecycle observers without wiping data. ```tsx AppSprint.destroy() ``` ## Offline behavior - Events that fail to send are automatically queued in native storage (up to 100 events). - The queue persists across app restarts. - Queued events are retried automatically when configure() completes, when another event is sent, when lifecycle flushes run, or when you call flush(). - If a queued event receives a 401/403, the SDK disables itself and clears the queue. ## Platform notes - The JS layer is a thin bridge. Behavior matches the standalone native iOS and Android SDKs. - On iOS, ATT must be requested while the app is foreground-active. NativeAppSprint.requestTrackingAuthorization() waits for that state internally. - On iOS, AdServices runs on iOS 14.3+. Older OS versions skip Apple Ads attribution silently. - On Android, GAID is read off the main thread, honoring Limit Ad Tracking and dropping the all-zero advertising ID. - Events persist to native storage (UserDefaults on iOS, SharedPreferences on Android) and survive app restarts. Maximum queue size is 100 events. - iOS uses URLSession with waitsForConnectivity = true. Transient offline windows queue inside the OS. - On Expo, the bundled config plugin injects NSUserTrackingUsageDescription, INTERNET, ACCESS_NETWORK_STATE, and AD_ID during prebuild. ## Troubleshooting **getAppSprintId() returns null** configure() resolves before install registration finishes on the native side. Retry briefly, or read the value after the first event has been sent. **Events do not appear in dashboard** Confirm the API key starts with as_live_. Call sendTestEvent() and inspect the returned message. **SDK disabled (isSdkDisabled returns true)** The API returned 401 or 403. Call clearData(), then configure() with a valid key. **Apple Ads attribution not detected** AdServices requires a real device (not the simulator). Confirm enableAppleAdsAttribution is true and that the test build is signed with a real provisioning profile. **pod install fails after adding the package** Delete ios/Podfile.lock and ios/Pods, then run pod install --repo-update. **Android build cannot find AD_ID** The package's manifest declares com.google.android.gms.permission.AD_ID and is merged at build time. If you target an app that cannot use it, remove with tools:node="remove". ## Connectivity test ```tsx const result = await AppSprint.sendTestEvent(); console.log(result.success, result.message); ``` ## Verification checklist 1. Create a blank Expo or bare React Native app and install the package. 2. Call configure() in App.tsx or your root layout. 3. Verify getAppSprintId() returns a value after first launch. 4. Send login, purchase, and custom events. Confirm they appear in the dashboard. 5. Send an event offline, background the app, restore connectivity, relaunch. Verify the event lands. 6. Call sendTestEvent() and confirm success is true. --- ## Docs navigation Use the Markdown URLs when reading the docs programmatically. Use the HTML URLs when you need the interactive docs UI. - [Overview](https://appsprint.app/attribution/docs) ([Markdown](https://appsprint.app/attribution/docs.md)) — Introduction to App*Sprint* - [Quickstart](https://appsprint.app/attribution/docs/quickstart) ([Markdown](https://appsprint.app/attribution/docs/quickstart.md)) — Get up and running in 5 minutes - [React Native](https://appsprint.app/attribution/docs/react-native) ([Markdown](https://appsprint.app/attribution/docs/react-native.md)) — React Native / Expo SDK reference - [iOS (Swift)](https://appsprint.app/attribution/docs/ios-swift) ([Markdown](https://appsprint.app/attribution/docs/ios-swift.md)) — Native Swift SDK reference - [Android (Kotlin)](https://appsprint.app/attribution/docs/android) ([Markdown](https://appsprint.app/attribution/docs/android.md)) — Native Android SDK reference - [Flutter](https://appsprint.app/attribution/docs/flutter) ([Markdown](https://appsprint.app/attribution/docs/flutter.md)) — Flutter plugin reference - [RevenueCat](https://appsprint.app/attribution/docs/revenuecat) ([Markdown](https://appsprint.app/attribution/docs/revenuecat.md)) — Webhook integration for subscription attribution - [Superwall](https://appsprint.app/attribution/docs/superwall) ([Markdown](https://appsprint.app/attribution/docs/superwall.md)) — Webhook integration for paywall attribution - [Apple Search Ads](https://appsprint.app/attribution/docs/apple-search-ads) ([Markdown](https://appsprint.app/attribution/docs/apple-search-ads.md)) — Keyword and ad attribution - [Google Ads](https://appsprint.app/attribution/docs/google-ads) ([Markdown](https://appsprint.app/attribution/docs/google-ads.md)) — Coming soon - [TikTok Ads](https://appsprint.app/attribution/docs/tiktok-ads) ([Markdown](https://appsprint.app/attribution/docs/tiktok-ads.md)) — Events API server-side event forwarding - [Meta Ads](https://appsprint.app/attribution/docs/meta-ads) ([Markdown](https://appsprint.app/attribution/docs/meta-ads.md)) — In review --- # iOS (Swift) Installation, configuration, event tracking, and full API reference for the iOS (Swift) SDK. ## Requirements - iOS 14.0+ - Xcode 15+ - Swift 5.9 with structured concurrency (async/await) ## 1. Install Swift Package Manager (recommended) or CocoaPods ```swift // Swift Package Manager. In Xcode: File → Add Package Dependencies… // Or in Package.swift: .package(url: "https://github.com/getappsprint/appsprint-ios-sdk", from: "1.1.9") // CocoaPods. In your Podfile: pod "AppSprintSDK", "~> 1.1.9" ``` > Shipped as a precompiled XCFramework with a bundled PrivacyInfo.xcprivacy manifest. No source code is included. [View on GitHub (SPM + CocoaPods)](https://github.com/getappsprint/appsprint-ios-sdk) ## 2. Configure ```swift import AppSprintSDK // Call as early as possible. App.init() (SwiftUI) or AppDelegate.didFinishLaunching. Task { await AppSprint.shared.configure( AppSprintConfig(apiKey: "as_live_xxxxx") ) } ``` Non-blocking. configure() returns after restoring local state; install registration runs in a detached Task with exponential backoff. Queued events flush automatically once install completes. ### Configuration options | Option | Type | Default | Description | |--------|------|---------|-------------| | `apiKey` | `String` | — | Your live API key (starts with as_live_). | | `apiURL` | `URL` | api.appsprint.app | Override for staging or self-hosted environments. | | `enableAppleAdsAttribution` | `Bool` | true | Fetches the Apple AdServices token at install time and triggers a delayed attribution refresh 75 seconds later. | | `customerUserId` | `String?` | nil | Your internal user ID. Persists across launches and replays automatically if the first send fails. | | `autoTrackSessions` | `Bool` | true | Fires session_start on configure() and on foreground, debounced to one event per 30 minutes. | | `autoRefreshAttribution` | `Bool` | true | Refetches /v1/sdk/attribution on configure(), foreground, and after a late AdServices PATCH. | | `googleAdsConsent` | `GoogleAdsConsent?` | nil | Optional Google Ads ad user data consent sent with mapped conversion uploads. | | `isDebug` | `Bool` | false | Forces logLevel = .debug. | | `logLevel` | `AppSprintLogLevel` | .warn | .debug, .info, .warn, or .error. Routes through os.Logger on iOS 14+. | ## 3. Track events ```swift // Standard event await AppSprint.shared.sendEvent(.login) ``` ```swift // Revenue event. Currency must be a 3-letter ISO code. await AppSprint.shared.sendEvent(.purchase, params: [ "revenue": 9.99, "currency": "USD" ]) ``` ```swift // Custom event with parameters await AppSprint.shared.sendEvent(.custom, name: "level_complete", params: [ "level": 5, "score": 1200, ]) ``` ### Supported event types `session_start`, `login`, `sign_up`, `register`, `purchase`, `subscribe`, `start_trial`, `add_payment_info`, `add_to_cart`, `add_to_wishlist`, `initiate_checkout`, `view_content`, `view_item`, `search`, `share`, `tutorial_complete`, `achieve_level`, `level_start`, `level_complete`, `custom` ## 4. Read attribution Once install registration completes, the SDK caches the attribution result from the install response. Native iOS and Android expose synchronous getters; React Native and Flutter bridge calls are asynchronous. ```swift let attr = AppSprint.shared.getAttribution() print(attr?.source) // "apple_ads", "tracking_link", or "organic" print(attr?.isAttributed) // Bool print(attr?.campaignName) // Campaign name when available print(attr?.appleAds?.campaignId) print(attr?.link?.name) ``` ## API reference ### `configure(_:)` → async Initializes the SDK. Returns immediately; install registration runs in the background with retry on failure. ```swift await AppSprint.shared.configure(AppSprintConfig(apiKey: "…")) ``` ### `sendEvent(_:name:params:)` → async Enqueues an event locally and schedules a flush. Does not block on a network round-trip. ```swift await AppSprint.shared.sendEvent(.purchase, params: ["revenue": 9.99, "currency": "USD"]) ``` ### `flush()` → async Drains the queue immediately. Safe to call repeatedly; concurrent flushes are deduplicated. ```swift await AppSprint.shared.flush() ``` ### `refreshAttribution()` → AttributionResult? Fetches the latest attribution from /v1/sdk/attribution. Self-heals a 404 install_not_found by re-running install. ```swift let attr = await AppSprint.shared.refreshAttribution() ``` ### `setCustomerUserId(_:)` → async Updates the customer user ID. Sent immediately if install is registered; otherwise queued and retried automatically. ```swift await AppSprint.shared.setCustomerUserId("user-123") ``` ### `getAppSprintId()` → String? Returns the install ID, or nil before install registration completes. ```swift let id = AppSprint.shared.getAppSprintId() ``` ### `getAttribution()` → AttributionResult? Returns the cached AttributionResult. Available synchronously without a network call. ```swift let attr = AppSprint.shared.getAttribution() ``` ### `getAttributionParams()` → [String: String] Partner-ready flat payload for forwarding to RevenueCat, Superwall, etc. ```swift let params = AppSprint.shared.getAttributionParams() ``` ### `enableAppleAdsAttribution()` → Bool Re-enables Apple Ads at runtime and sends the AdServices token via PATCH. Returns whether the SDK was configured. ```swift AppSprint.shared.enableAppleAdsAttribution() ``` ### `isInitialized` → Bool True after configure() returns. Does not imply install registration succeeded. ```swift AppSprint.shared.isInitialized ``` ### `isSdkDisabled()` → Bool True if a 401 or 403 from the backend permanently disabled the SDK. ```swift AppSprint.shared.isSdkDisabled() ``` ### `sendTestEvent()` → TestEventResult Posts a diagnostic event and returns (success, message). Use during development. ```swift let result = await AppSprint.shared.sendTestEvent() ``` ### `clearData()` Wipes local state and the event queue. Use to reset the SDK. ```swift AppSprint.shared.clearData() ``` ## Offline behavior - Events that fail to send are automatically queued in native storage (up to 100 events). - The queue persists across app restarts. - Queued events are retried automatically when configure() completes, when another event is sent, when lifecycle flushes run, or when you call flush(). - If a queued event receives a 401/403, the SDK disables itself and clears the queue. ## Platform notes - configure() is annotated @MainActor and is non-blocking. Calling it from didFinishLaunchingWithOptions or App.init() does not delay app startup. - ATT must be requested while the app is foreground-active. AppSprintNative.requestTrackingAuthorization() waits for that state internally, so calling it from app init is safe. - IDFA is collected only when ATT is authorized. If the user grants ATT after install, the SDK PATCHes the IDFA on the next foreground automatically. - Apple AdServices token fetch (AAAttribution.attributionToken) runs on iOS 14.3+. Failure is non-fatal; a fresh token is fetched on retry. - The SDK uses URLSession.waitsForConnectivity = true, so transient offline windows queue inside the OS rather than failing fast. - Background flushes are wrapped in beginBackgroundTask so iOS does not kill the queue worker mid-loop. ## Troubleshooting **getAppSprintId() returns nil** configure() returns before install registration finishes. Wait until isInitialized is true, then poll briefly, or read the value inside an event handler that fires after first launch. **Events do not appear in dashboard** Confirm the API key starts with as_live_. Call sendTestEvent() and inspect the returned message. Check Console.app for [App*Sprint*] logs at debug level. **SDK disabled after 401/403** A rejected key disables the SDK permanently. Call clearData(), then configure() with a valid key. **Attribution returns organic when an Apple Ads install was expected** Backend resolution can take up to 75 seconds for Apple AdServices. The SDK refetches automatically by default. If autoRefreshAttribution is off, call refreshAttribution() manually. **Queued events seem to never send** A queued event over ~90 KB is dropped on the next flush (the backend caps bodies at 100 KB). Smaller events flush on foreground, background, or another sendEvent. ## Connectivity test ```swift let result = await AppSprint.shared.sendTestEvent() print("\(result.success) — \(result.message)") ``` ## Verification checklist 1. Create a blank iOS app and add the SPM package or CocoaPods pod. 2. Call configure() in App.init() or AppDelegate.didFinishLaunchingWithOptions. 3. Verify getAppSprintId() returns a value after first launch. 4. Send login, purchase, and custom events. Confirm they appear in the dashboard. 5. Kill the app while offline, send an event, relaunch with connectivity. Verify the event lands. 6. Call sendTestEvent() and confirm success is true. --- ## Docs navigation Use the Markdown URLs when reading the docs programmatically. Use the HTML URLs when you need the interactive docs UI. - [Overview](https://appsprint.app/attribution/docs) ([Markdown](https://appsprint.app/attribution/docs.md)) — Introduction to App*Sprint* - [Quickstart](https://appsprint.app/attribution/docs/quickstart) ([Markdown](https://appsprint.app/attribution/docs/quickstart.md)) — Get up and running in 5 minutes - [React Native](https://appsprint.app/attribution/docs/react-native) ([Markdown](https://appsprint.app/attribution/docs/react-native.md)) — React Native / Expo SDK reference - [iOS (Swift)](https://appsprint.app/attribution/docs/ios-swift) ([Markdown](https://appsprint.app/attribution/docs/ios-swift.md)) — Native Swift SDK reference - [Android (Kotlin)](https://appsprint.app/attribution/docs/android) ([Markdown](https://appsprint.app/attribution/docs/android.md)) — Native Android SDK reference - [Flutter](https://appsprint.app/attribution/docs/flutter) ([Markdown](https://appsprint.app/attribution/docs/flutter.md)) — Flutter plugin reference - [RevenueCat](https://appsprint.app/attribution/docs/revenuecat) ([Markdown](https://appsprint.app/attribution/docs/revenuecat.md)) — Webhook integration for subscription attribution - [Superwall](https://appsprint.app/attribution/docs/superwall) ([Markdown](https://appsprint.app/attribution/docs/superwall.md)) — Webhook integration for paywall attribution - [Apple Search Ads](https://appsprint.app/attribution/docs/apple-search-ads) ([Markdown](https://appsprint.app/attribution/docs/apple-search-ads.md)) — Keyword and ad attribution - [Google Ads](https://appsprint.app/attribution/docs/google-ads) ([Markdown](https://appsprint.app/attribution/docs/google-ads.md)) — Coming soon - [TikTok Ads](https://appsprint.app/attribution/docs/tiktok-ads) ([Markdown](https://appsprint.app/attribution/docs/tiktok-ads.md)) — Events API server-side event forwarding - [Meta Ads](https://appsprint.app/attribution/docs/meta-ads) ([Markdown](https://appsprint.app/attribution/docs/meta-ads.md)) — In review --- # Android (Kotlin) Installation, configuration, event tracking, and full API reference for the Android (Kotlin) SDK. ## Requirements - Android 7.0+ (API 24+) - compileSdk 36+ - Java 17 (Kotlin 2.0+ for source builds) ## 1. Install Maven Central ```kotlin // app/build.gradle.kts dependencies { implementation("app.appsprint:sdk:1.1.3") } ``` > Sync Gradle after adding the dependency. The SDK already declares INTERNET, ACCESS_NETWORK_STATE, and com.google.android.gms.permission.AD_ID, so you do not need to add them to your manifest. [View on Maven Central](https://central.sonatype.com/artifact/app.appsprint/sdk) ## 2. Configure ```kotlin import com.appsprint.sdk.AppSprint import com.appsprint.sdk.AppSprintConfig class MyApplication : Application() { override fun onCreate() { super.onCreate() AppSprint.shared(applicationContext).configure( AppSprintConfig(apiKey = "as_live_xxxxx") ) } } ``` Non-blocking. configure() returns immediately; install registration and device-info collection run off the main thread on the SDK's executor. The lifecycle observer is registered automatically. ### Configuration options | Option | Type | Default | Description | |--------|------|---------|-------------| | `apiKey` | `String` | — | Your live API key (starts with as_live_). | | `apiUrl` | `String` | api.appsprint.app | Override for staging or self-hosted environments. | | `enableAppleAdsAttribution` | `Boolean` | false | iOS-only. Ignored on Android. | | `customerUserId` | `String?` | null | Your internal user ID. Persists across launches and replays automatically if the first send fails. | | `autoTrackSessions` | `Boolean` | true | Fires session_start on configure() and on ProcessLifecycleOwner.ON_START, debounced to one event per 30 minutes. | | `autoRefreshAttribution` | `Boolean` | true | Refetches /v1/sdk/attribution on configure() and foreground transitions. | | `googleAdsConsent` | `GoogleAdsConsent?` | null | Optional Google Ads ad user data consent sent with mapped conversion uploads. | | `isDebug` | `Boolean` | false | Forces logLevel = 0 (debug) and enables the optional debug overlay. | | `logLevel` | `Int` | 2 (WARN) | 0 = DEBUG, 1 = INFO, 2 = WARN, 3 = ERROR. | ## 3. Track events ```kotlin // Standard event val appSprint = AppSprint.shared(applicationContext) appSprint.sendEvent(AppSprintEventType.LOGIN) ``` ```kotlin // Revenue event. Currency must be a 3-letter ISO code. appSprint.sendEvent( AppSprintEventType.PURCHASE, params = mapOf("revenue" to 9.99, "currency" to "USD") ) ``` ```kotlin // Custom event with parameters appSprint.sendEvent( AppSprintEventType.CUSTOM, name = "level_complete", params = mapOf("level" to 5, "score" to 1200) ) ``` ### Supported event types `session_start`, `login`, `sign_up`, `register`, `purchase`, `subscribe`, `start_trial`, `add_payment_info`, `add_to_cart`, `add_to_wishlist`, `initiate_checkout`, `view_content`, `view_item`, `search`, `share`, `tutorial_complete`, `achieve_level`, `level_start`, `level_complete`, `custom` ## 4. Read attribution Once install registration completes, the SDK caches the attribution result from the install response. Native iOS and Android expose synchronous getters; React Native and Flutter bridge calls are asynchronous. ```kotlin val attr = appSprint.getAttribution() println(attr?.source) // "apple_ads", "tracking_link", or "organic" println(attr?.isAttributed) // Boolean println(attr?.campaignName) // Campaign name when available println(attr?.link?.name) ``` ## API reference ### `configure(config)` Initializes the SDK. Returns immediately; install registration and lifecycle wiring run on the SDK executor. ```kotlin appSprint.configure(AppSprintConfig(apiKey = "…")) ``` ### `sendEvent(type, name?, params?)` Enqueues an event locally and schedules a flush. ```kotlin appSprint.sendEvent(AppSprintEventType.PURCHASE, params = mapOf("revenue" to 9.99, "currency" to "USD")) ``` ### `flush()` Drains the queue immediately. ```kotlin appSprint.flush() ``` ### `refreshAttribution()` → AttributionResult? Fetches the latest attribution. Blocking; call from a background thread. Self-heals on 404 install_not_found. ```kotlin val attr = appSprint.refreshAttribution() ``` ### `setCustomerUserId(id)` Updates the customer user ID. Persists to disk and retries on the next configure() or foreground if the first send fails. ```kotlin appSprint.setCustomerUserId("user-123") ``` ### `getAppSprintId()` → String? Returns the install ID, or null before install registration completes. ```kotlin val id = appSprint.getAppSprintId() ``` ### `getAttribution()` → AttributionResult? Returns the cached AttributionResult. ```kotlin val attr = appSprint.getAttribution() ``` ### `getAttributionParams()` → Map Partner-ready flat payload for forwarding to RevenueCat, Superwall, etc. ```kotlin val params = appSprint.getAttributionParams() ``` ### `enableAppleAdsAttribution()` → Boolean Returns false on Android. Apple Ads attribution is iOS-only. ```kotlin appSprint.enableAppleAdsAttribution() ``` ### `isInitialized()` → Boolean True after configure() returns. ```kotlin appSprint.isInitialized() ``` ### `isSdkDisabled()` → Boolean True if a 401 or 403 permanently disabled the SDK. ```kotlin appSprint.isSdkDisabled() ``` ### `sendTestEvent()` → TestEventResult Posts a diagnostic event off the main thread. From the main thread, use sendTestEventAsync(callback). ```kotlin val result = appSprint.sendTestEvent() ``` ### `clearData()` Wipes local state and the event queue. ```kotlin appSprint.clearData() ``` ## Offline behavior - Events that fail to send are automatically queued in native storage (up to 100 events). - The queue persists across app restarts. - Queued events are retried automatically when configure() completes, when another event is sent, when lifecycle flushes run, or when you call flush(). - If a queued event receives a 401/403, the SDK disables itself and clears the queue. ## Platform notes - configure() is thread-safe (@Synchronized) and returns immediately. Safe to call from Application.onCreate() or an activity. - Apple Search Ads attribution is iOS-only. enableAppleAdsAttribution is accepted for cross-platform symmetry but does nothing on Android. - GAID is read during install registration only, off the main thread. The SDK honors Limit Ad Tracking and drops the all-zero advertising ID so a bogus value never reaches the backend. - Play Install Referrer is collected automatically. The package visibility entry for com.android.vending is in the SDK manifest, so it works on apps targeting Android 11+. - If your app cannot collect advertising IDs (children's apps, regional policies), remove com.google.android.gms.permission.AD_ID in your host manifest with tools:node="remove". - Events persist in SharedPreferences. They flush on foreground (ProcessLifecycleOwner.ON_START), background, the next sendEvent, or an explicit flush() call. ## Troubleshooting **getAppSprintId() returns null** configure() returns before install registration finishes. Check isInitialized() and retry briefly, or read the value inside an event handler that fires after first launch. **Events do not appear in dashboard** Confirm the API key starts with as_live_. Call sendTestEvent() and inspect the result. Lower logLevel to 0 and check logcat under the App*Sprint* tag. **SDK disabled after 401/403** A rejected key disables the SDK permanently. Call clearData(), then configure() with a valid key. **Events lost after app kill** Events persist to SharedPreferences and retry delivery on the next configure(). If they still do not appear, the queue may have been dropped by clearData() or hit the 100-entry cap. **AD_ID permission rejected by Play review** Either include advertising ID collection in your Data safety answers, or remove the permission with tools:node="remove" in your host manifest. ## Connectivity test ```kotlin val result = appSprint.sendTestEvent() println("${result.success} — ${result.message}") ``` ## Verification checklist 1. Create a blank Android app and add the Maven Central dependency. 2. Call configure() in Application.onCreate(). 3. Verify getAppSprintId() returns a value after first launch. 4. Send login, purchase, and custom events. Confirm they appear in the dashboard. 5. Send an event offline, kill the app, relaunch with connectivity. Verify the event lands. 6. Call sendTestEvent() and confirm success is true. --- ## Docs navigation Use the Markdown URLs when reading the docs programmatically. Use the HTML URLs when you need the interactive docs UI. - [Overview](https://appsprint.app/attribution/docs) ([Markdown](https://appsprint.app/attribution/docs.md)) — Introduction to App*Sprint* - [Quickstart](https://appsprint.app/attribution/docs/quickstart) ([Markdown](https://appsprint.app/attribution/docs/quickstart.md)) — Get up and running in 5 minutes - [React Native](https://appsprint.app/attribution/docs/react-native) ([Markdown](https://appsprint.app/attribution/docs/react-native.md)) — React Native / Expo SDK reference - [iOS (Swift)](https://appsprint.app/attribution/docs/ios-swift) ([Markdown](https://appsprint.app/attribution/docs/ios-swift.md)) — Native Swift SDK reference - [Android (Kotlin)](https://appsprint.app/attribution/docs/android) ([Markdown](https://appsprint.app/attribution/docs/android.md)) — Native Android SDK reference - [Flutter](https://appsprint.app/attribution/docs/flutter) ([Markdown](https://appsprint.app/attribution/docs/flutter.md)) — Flutter plugin reference - [RevenueCat](https://appsprint.app/attribution/docs/revenuecat) ([Markdown](https://appsprint.app/attribution/docs/revenuecat.md)) — Webhook integration for subscription attribution - [Superwall](https://appsprint.app/attribution/docs/superwall) ([Markdown](https://appsprint.app/attribution/docs/superwall.md)) — Webhook integration for paywall attribution - [Apple Search Ads](https://appsprint.app/attribution/docs/apple-search-ads) ([Markdown](https://appsprint.app/attribution/docs/apple-search-ads.md)) — Keyword and ad attribution - [Google Ads](https://appsprint.app/attribution/docs/google-ads) ([Markdown](https://appsprint.app/attribution/docs/google-ads.md)) — Coming soon - [TikTok Ads](https://appsprint.app/attribution/docs/tiktok-ads) ([Markdown](https://appsprint.app/attribution/docs/tiktok-ads.md)) — Events API server-side event forwarding - [Meta Ads](https://appsprint.app/attribution/docs/meta-ads) ([Markdown](https://appsprint.app/attribution/docs/meta-ads.md)) — In review --- # Flutter Installation, configuration, event tracking, and full API reference for the Flutter SDK. ## Requirements - Flutter 3.22+ - Dart 3.3+ - iOS 14.0+ and Android 7.0+ (API 24+) ## 1. Install pub.dev ```yaml # pubspec.yaml dependencies: appsprint_flutter: ^1.1.9 # Then run: flutter pub get ``` > The Flutter plugin manages the iOS pod and the Android AAR for you. No extra repository setup needed. [View on pub.dev](https://pub.dev/packages/appsprint_flutter) ## 2. Configure ```dart import 'package:flutter/material.dart'; import 'package:appsprint_flutter/appsprint_flutter.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await AppSprint.instance.configure( const AppSprintConfig(apiKey: 'as_live_xxxxx'), ); runApp(const MyApp()); } ``` Non-blocking. configure() resolves after local state is restored; install registration runs in the background on the native side. Platform-specific code (AdServices on iOS, Play Install Referrer on Android) runs through method channels. ### Configuration options | Option | Type | Default | Description | |--------|------|---------|-------------| | `apiKey` | `String` | — | Your live API key (starts with as_live_). | | `apiUrl` | `String` | api.appsprint.app | Override for staging or self-hosted environments. | | `enableAppleAdsAttribution` | `bool` | true | iOS only. Fetches Apple AdServices at install time. | | `customerUserId` | `String?` | null | Your internal user ID. Persists across launches and replays automatically if the first send fails. | | `autoTrackSessions` | `bool` | true | Fires session_start on configure() and on foreground, debounced to one event per 30 minutes. | | `autoRefreshAttribution` | `bool` | true | Refetches /v1/sdk/attribution on configure() and foreground transitions. | | `googleAdsConsent` | `GoogleAdsConsent?` | null | Optional Google Ads ad user data consent sent with mapped conversion uploads. | | `isDebug` | `bool` | false | Forces debug-level logging on the native side. | | `logLevel` | `int` | 2 (WARN) | 0 = DEBUG, 1 = INFO, 2 = WARN, 3 = ERROR. | ## 3. Track events ```dart // Standard event await AppSprint.instance.sendEvent(AppSprintEventType.login); ``` ```dart // Revenue event. Currency must be a 3-letter ISO code. await AppSprint.instance.sendEvent( AppSprintEventType.purchase, params: {'revenue': 9.99, 'currency': 'USD'}, ); ``` ```dart // Custom event with parameters await AppSprint.instance.sendEvent( AppSprintEventType.custom, name: 'level_complete', params: {'level': 5, 'score': 1200}, ); ``` ### Supported event types `session_start`, `login`, `sign_up`, `register`, `purchase`, `subscribe`, `start_trial`, `add_payment_info`, `add_to_cart`, `add_to_wishlist`, `initiate_checkout`, `view_content`, `view_item`, `search`, `share`, `tutorial_complete`, `achieve_level`, `level_start`, `level_complete`, `custom` ## 4. Read attribution Once install registration completes, the SDK caches the attribution result from the install response. Native iOS and Android expose synchronous getters; React Native and Flutter bridge calls are asynchronous. ```dart final attr = await AppSprint.instance.getAttribution(); print(attr?.source); // "apple_ads", "tracking_link", or "organic" print(attr?.isAttributed); // bool print(attr?.campaignName); // Campaign name when available print(attr?.appleAds?.campaignId); print(attr?.link?.name); ``` ## API reference ### `configure(config)` → Future Initializes the SDK. Resolves true after local state is restored; install registration runs in the background. ```dart await AppSprint.instance.configure(const AppSprintConfig(apiKey: '…')); ``` ### `sendEvent(type, {name, params})` → Future Enqueues an event locally and schedules a flush. Resolves true once the native bridge accepts it. ```dart await AppSprint.instance.sendEvent(AppSprintEventType.purchase, params: {'revenue': 9.99, 'currency': 'USD'}); ``` ### `flush()` → Future Drains the queue immediately. ```dart await AppSprint.instance.flush() ``` ### `refreshAttribution()` → Future Fetches the latest attribution from the backend. Self-heals a 404 install_not_found by re-running install. ```dart final attr = await AppSprint.instance.refreshAttribution(); ``` ### `setCustomerUserId(id)` → Future Updates the customer user ID. Sent immediately if install is registered; otherwise queued and retried automatically. ```dart await AppSprint.instance.setCustomerUserId('user-123'); ``` ### `getAppSprintId()` → Future Returns the install ID, or null before install registration completes. ```dart final id = await AppSprint.instance.getAppSprintId(); ``` ### `getAttribution()` → Future Returns the cached AttributionResult. ```dart final attr = await AppSprint.instance.getAttribution(); ``` ### `getAttributionParams()` → Future> Partner-ready flat payload for forwarding to RevenueCat, Superwall, etc. ```dart final params = await AppSprint.instance.getAttributionParams(); ``` ### `enableAppleAdsAttribution()` → Future Re-enables Apple Ads at runtime on iOS. Returns false on Android. ```dart await AppSprint.instance.enableAppleAdsAttribution() ``` ### `isInitialized()` → Future True once configure() resolved. ```dart await AppSprint.instance.isInitialized() ``` ### `isSdkDisabled()` → Future True if a 401 or 403 permanently disabled the SDK. ```dart await AppSprint.instance.isSdkDisabled() ``` ### `sendTestEvent()` → Future Posts a diagnostic event and resolves to (success, message). ```dart final result = await AppSprint.instance.sendTestEvent(); ``` ### `clearData()` → Future Wipes local state and the event queue. ```dart await AppSprint.instance.clearData() ``` ## Offline behavior - Events that fail to send are automatically queued in native storage (up to 100 events). - The queue persists across app restarts. - Queued events are retried automatically when configure() completes, when another event is sent, when lifecycle flushes run, or when you call flush(). - If a queued event receives a 401/403, the SDK disables itself and clears the queue. ## Platform notes - The Flutter plugin wraps the native iOS and Android SDKs through method channels. Behavior matches the native SDKs exactly; the Dart layer is a thin pass-through. - configure() is non-blocking. The future resolves once local state is restored; install registration runs in the background on the native side. - On iOS, ATT must be requested while the app is foreground-active. AppSprintNative.requestTrackingAuthorization() waits for that state internally, so calling it from main() or initState() is safe. - On Android, GAID is read off the main thread, honoring Limit Ad Tracking and dropping the all-zero advertising ID. The plugin declares INTERNET, ACCESS_NETWORK_STATE, and AD_ID for you. - Events persist to native storage (UserDefaults on iOS, SharedPreferences on Android) and survive app restarts. Maximum queue size is 100 events. - iOS uses URLSession with waitsForConnectivity = true. Transient offline windows queue inside the OS rather than failing fast. ## Troubleshooting **getAppSprintId() returns null** configure() resolves before install registration finishes on the native side. Check isInitialized() and retry briefly, or read the value after the first event has been sent. **Events do not appear in dashboard** Confirm the API key starts with as_live_. Call sendTestEvent() and inspect the message. iOS logs flow into Console.app; Android logs flow into logcat under the App*Sprint* tag. **SDK disabled after 401/403** Call clearData(), then configure() with a valid key. **pod install fails after adding the package** Run flutter clean, flutter pub get, then cd ios && pod install --repo-update. **Attribution returns organic on iOS when Apple Ads is expected** Backend resolution can take up to 75 seconds. The SDK refetches automatically by default; if disabled, call refreshAttribution() manually. ## Connectivity test ```dart final result = await AppSprint.instance.sendTestEvent(); print('${result.success} — ${result.message}'); ``` ## Verification checklist 1. Create a blank Flutter app and add the pub.dev dependency. 2. Call configure() before runApp() in main(). 3. Verify getAppSprintId() returns a value after first launch. 4. Send login, purchase, and custom events. Confirm they appear in the dashboard. 5. Send an event offline, restart the app online. Verify queue flush. 6. Call sendTestEvent() and confirm success is true. --- ## Docs navigation Use the Markdown URLs when reading the docs programmatically. Use the HTML URLs when you need the interactive docs UI. - [Overview](https://appsprint.app/attribution/docs) ([Markdown](https://appsprint.app/attribution/docs.md)) — Introduction to App*Sprint* - [Quickstart](https://appsprint.app/attribution/docs/quickstart) ([Markdown](https://appsprint.app/attribution/docs/quickstart.md)) — Get up and running in 5 minutes - [React Native](https://appsprint.app/attribution/docs/react-native) ([Markdown](https://appsprint.app/attribution/docs/react-native.md)) — React Native / Expo SDK reference - [iOS (Swift)](https://appsprint.app/attribution/docs/ios-swift) ([Markdown](https://appsprint.app/attribution/docs/ios-swift.md)) — Native Swift SDK reference - [Android (Kotlin)](https://appsprint.app/attribution/docs/android) ([Markdown](https://appsprint.app/attribution/docs/android.md)) — Native Android SDK reference - [Flutter](https://appsprint.app/attribution/docs/flutter) ([Markdown](https://appsprint.app/attribution/docs/flutter.md)) — Flutter plugin reference - [RevenueCat](https://appsprint.app/attribution/docs/revenuecat) ([Markdown](https://appsprint.app/attribution/docs/revenuecat.md)) — Webhook integration for subscription attribution - [Superwall](https://appsprint.app/attribution/docs/superwall) ([Markdown](https://appsprint.app/attribution/docs/superwall.md)) — Webhook integration for paywall attribution - [Apple Search Ads](https://appsprint.app/attribution/docs/apple-search-ads) ([Markdown](https://appsprint.app/attribution/docs/apple-search-ads.md)) — Keyword and ad attribution - [Google Ads](https://appsprint.app/attribution/docs/google-ads) ([Markdown](https://appsprint.app/attribution/docs/google-ads.md)) — Coming soon - [TikTok Ads](https://appsprint.app/attribution/docs/tiktok-ads) ([Markdown](https://appsprint.app/attribution/docs/tiktok-ads.md)) — Events API server-side event forwarding - [Meta Ads](https://appsprint.app/attribution/docs/meta-ads) ([Markdown](https://appsprint.app/attribution/docs/meta-ads.md)) — In review --- # RevenueCat Forward RevenueCat webhook events to App*Sprint* so every subscription and purchase is automatically attributed to the install that triggered it. ## Webhook endpoint ``` Host: api.appsprint.app Path: /v1/integrations/revenuecat/webhooks/{appId} ``` Paste the HTTPS URL for this host and path into RevenueCat → Project Settings → Integrations → Webhooks. Replace `{appId}` with your App*Sprint* app ID. The dashboard integration page shows the full URL for the selected app. ## Authentication Set the Authorization header in RevenueCat's webhook config to a Bearer token matching your app's `revenuecatWebhookToken` stored in the App*Sprint* dashboard under Integrations → RevenueCat. ``` Authorization: Bearer rc_whsec_xxxxxxxx ``` ## Event mapping | RevenueCat event | App*Sprint* event | |-----------------|-----------------| | INITIAL_PURCHASE | purchase | | INITIAL_PURCHASE + period_type=TRIAL | start_trial | | RENEWAL, including is_trial_conversion=true | subscribe | | UNCANCELLATION | purchase | | NON_RENEWING_PURCHASE | purchase | | CANCELLATION / EXPIRATION / BILLING_ISSUE / PRODUCT_CHANGE / SUBSCRIPTION_PAUSED / TRANSFER / REFUND_REVERSED | custom | Custom lifecycle events keep the raw RevenueCat type in the event name, for example `revenuecat:cancellation`. Other RevenueCat event types are stored as webhook receipts but ignored for App*Sprint* event attribution. App*Sprint* marks the integration verified after the first valid webhook. Sandbox webhooks can verify the connection, but App*Sprint* does not create attributed events or outbound ad conversions from `environment=SANDBOX` deliveries. ## Required: appsprintId subscriber attribute For webhooks to be matched to an attribution, you must set the `appsprintId` subscriber attribute in RevenueCat from the SDK. ## SDK snippets Set the subscriber attribute after the App*Sprint* SDK is configured. In React Native and Flutter, `getAppSprintId()` is asynchronous, so await it before calling RevenueCat. See the RevenueCat docs page for platform-specific code snippets. --- ## Docs navigation Use the Markdown URLs when reading the docs programmatically. Use the HTML URLs when you need the interactive docs UI. - [Overview](https://appsprint.app/attribution/docs) ([Markdown](https://appsprint.app/attribution/docs.md)) — Introduction to App*Sprint* - [Quickstart](https://appsprint.app/attribution/docs/quickstart) ([Markdown](https://appsprint.app/attribution/docs/quickstart.md)) — Get up and running in 5 minutes - [React Native](https://appsprint.app/attribution/docs/react-native) ([Markdown](https://appsprint.app/attribution/docs/react-native.md)) — React Native / Expo SDK reference - [iOS (Swift)](https://appsprint.app/attribution/docs/ios-swift) ([Markdown](https://appsprint.app/attribution/docs/ios-swift.md)) — Native Swift SDK reference - [Android (Kotlin)](https://appsprint.app/attribution/docs/android) ([Markdown](https://appsprint.app/attribution/docs/android.md)) — Native Android SDK reference - [Flutter](https://appsprint.app/attribution/docs/flutter) ([Markdown](https://appsprint.app/attribution/docs/flutter.md)) — Flutter plugin reference - [RevenueCat](https://appsprint.app/attribution/docs/revenuecat) ([Markdown](https://appsprint.app/attribution/docs/revenuecat.md)) — Webhook integration for subscription attribution - [Superwall](https://appsprint.app/attribution/docs/superwall) ([Markdown](https://appsprint.app/attribution/docs/superwall.md)) — Webhook integration for paywall attribution - [Apple Search Ads](https://appsprint.app/attribution/docs/apple-search-ads) ([Markdown](https://appsprint.app/attribution/docs/apple-search-ads.md)) — Keyword and ad attribution - [Google Ads](https://appsprint.app/attribution/docs/google-ads) ([Markdown](https://appsprint.app/attribution/docs/google-ads.md)) — Coming soon - [TikTok Ads](https://appsprint.app/attribution/docs/tiktok-ads) ([Markdown](https://appsprint.app/attribution/docs/tiktok-ads.md)) — Events API server-side event forwarding - [Meta Ads](https://appsprint.app/attribution/docs/meta-ads) ([Markdown](https://appsprint.app/attribution/docs/meta-ads.md)) — In review --- # Superwall Forward Superwall webhook events to App*Sprint* so paywalls, trials, and subscription changes are attributed to the original install. ## Webhook endpoint ``` Host: api.appsprint.app Path: /v1/integrations/superwall/webhooks/{appId} ``` Paste the HTTPS URL for this host and path into Superwall → Settings → Integrations → Webhooks. Replace `{appId}` with your App*Sprint* app ID. The dashboard integration page shows the full URL for the selected app. ## Authentication Superwall signs webhooks using Svix. After creating the webhook in Superwall, open that webhook, copy its Signing Secret, and paste it into App*Sprint* under Integrations → Superwall. App*Sprint* marks the integration verified after the first valid, signed webhook. Sandbox webhooks can verify the connection, but App*Sprint* does not create attributed events or outbound ad conversions from `environment=SANDBOX` deliveries. ## Event mapping | Superwall event | App*Sprint* event | |----------------|-----------------| | initial_purchase | purchase | | initial_purchase + periodType=TRIAL | start_trial | | renewal, including isTrialConversion=true | subscribe | | uncancellation | purchase | | non_renewing_purchase | purchase | | cancellation / expiration / billing_issue / product_change / subscription_paused | custom | Custom lifecycle events keep the raw Superwall type in the event name, for example `superwall:cancellation`. Other Superwall event types are stored as webhook receipts but ignored for App*Sprint* event attribution. ## Required: appsprintId user attribute For webhooks to be matched to an attribution, you must set the `appsprintId` user attribute in Superwall from the SDK. ## SDK snippets Set the user attribute after the App*Sprint* SDK is configured. In React Native and Flutter, `getAppSprintId()` is asynchronous, so await it before calling Superwall. See the Superwall docs page for platform-specific code snippets. --- ## Docs navigation Use the Markdown URLs when reading the docs programmatically. Use the HTML URLs when you need the interactive docs UI. - [Overview](https://appsprint.app/attribution/docs) ([Markdown](https://appsprint.app/attribution/docs.md)) — Introduction to App*Sprint* - [Quickstart](https://appsprint.app/attribution/docs/quickstart) ([Markdown](https://appsprint.app/attribution/docs/quickstart.md)) — Get up and running in 5 minutes - [React Native](https://appsprint.app/attribution/docs/react-native) ([Markdown](https://appsprint.app/attribution/docs/react-native.md)) — React Native / Expo SDK reference - [iOS (Swift)](https://appsprint.app/attribution/docs/ios-swift) ([Markdown](https://appsprint.app/attribution/docs/ios-swift.md)) — Native Swift SDK reference - [Android (Kotlin)](https://appsprint.app/attribution/docs/android) ([Markdown](https://appsprint.app/attribution/docs/android.md)) — Native Android SDK reference - [Flutter](https://appsprint.app/attribution/docs/flutter) ([Markdown](https://appsprint.app/attribution/docs/flutter.md)) — Flutter plugin reference - [RevenueCat](https://appsprint.app/attribution/docs/revenuecat) ([Markdown](https://appsprint.app/attribution/docs/revenuecat.md)) — Webhook integration for subscription attribution - [Superwall](https://appsprint.app/attribution/docs/superwall) ([Markdown](https://appsprint.app/attribution/docs/superwall.md)) — Webhook integration for paywall attribution - [Apple Search Ads](https://appsprint.app/attribution/docs/apple-search-ads) ([Markdown](https://appsprint.app/attribution/docs/apple-search-ads.md)) — Keyword and ad attribution - [Google Ads](https://appsprint.app/attribution/docs/google-ads) ([Markdown](https://appsprint.app/attribution/docs/google-ads.md)) — Coming soon - [TikTok Ads](https://appsprint.app/attribution/docs/tiktok-ads) ([Markdown](https://appsprint.app/attribution/docs/tiktok-ads.md)) — Events API server-side event forwarding - [Meta Ads](https://appsprint.app/attribution/docs/meta-ads) ([Markdown](https://appsprint.app/attribution/docs/meta-ads.md)) — In review --- # Apple Search Ads Attribute iOS installs to the Apple Search Ads ad group, keyword, and country that produced them. Attribution itself runs through the SDK with no app code changes; the API connection adds live spend, taps, and impressions to the dashboard. ## Requirements - The App*Sprint* SDK installed in your iOS, React Native, Flutter, or Expo app. The SDK collects the AdServices attribution token automatically. - An Apple Search Ads account with an API role that can read reporting data. **API Account Manager** is recommended for setup. - Your App Store ID (the numeric ID at the end of your App Store URL). - Users on iOS 14.3 or later. AdServices attribution is unavailable on older iOS versions. ## Available metrics and definitions | Metric | Definition | |--------|------------| | Spend | Apple Search Ads spend over the selected window. | | Installs | Installs Apple reports over the selected window. | | CPI | Cost per install. Spend divided by Apple-reported installs. | | Impressions | Times the ad was shown, reported by Apple. | | Taps | Ad taps, reported by Apple. | | TTR | Tap-through rate. Taps divided by impressions. | | CR | Conversion rate. Installs divided by taps. | | ROAS | Return on ad spend. Attributed revenue divided by spend. | | Revenue | Revenue from in-app events attributed to this Apple Search Ads row. | ## Attribution data attached to each install Every install App*Sprint* attributes to Apple Search Ads carries these fields, resolved from the AdServices attribution token. Human-readable ad group and keyword names aren't included in Apple's AdServices response; raw install attribution keeps stable Apple IDs, while reporting views can enrich names from Apple's reporting API when available. | Field | Description | |-------|-------------| | `orgId` | Campaign Group that owns the attribution record. | | `campaignId` | Apple Search Ads record that produced the install. | | `adGroupId` | Ad group within that Apple Search Ads record. | | `adId` | Ad assignment ID, when Apple includes ad-level attribution. | | `keywordId` | Keyword that triggered the ad. Apple returns the numeric ID; Search Match installs may not include one. | | `countryOrRegion` | User's country or region as reported by Apple. | | `conversionType` | Whether the install was a fresh download or a redownload. | | `claimType` | Whether Apple attributed the install to a tap or view. | | `clickDate` | Tap-through timestamp when Apple includes one. | | `impressionDate` | View-through timestamp when Apple includes one. | | `supplyPlacement` | Apple Ads supply placement for the attributed interaction. | ## Setup ### 1. Open the connection form in App*Sprint* In App*Sprint*, open your app and go to **Integrations → Apple Search Ads**. Click **Set up**. App*Sprint* displays a public key. Keep this tab open — you'll paste the key into Apple in the next step. ### 2. Generate the API certificate in Apple Search Ads > **The three IDs come from one certificate.** Client ID, Team ID, and Key ID are all issued by the same Apple Search Ads API certificate. Regenerating the certificate rotates all three. 1. In Apple Search Ads, go to **Settings → API** and click **Create API Certificate**. 2. Paste the public key from the App*Sprint* dashboard and confirm. 3. Apple displays three values. Copy each one: **Client ID**, **Team ID**, and **Key ID**. You don't need to copy a Campaign Group ID. App*Sprint* discovers every Campaign Group the certificate can see, and your App Store ID narrows the results back to this app. ### 3. Finish the connection in App*Sprint* 1. Paste the Client ID, Team ID, and Key ID into the App*Sprint* form. 2. Enter your **App Store ID** (the numeric ID at the end of your App Store URL). 3. Save. App*Sprint* validates the credentials against Apple immediately by listing the Campaign Groups the certificate can see. Errors point at the value to re-check. > **Attribution works without app code.** The iOS, React Native, Flutter, and Expo SDKs collect Apple's AdServices token automatically when you call `configure()`. No extra code required. ## Troubleshooting | Problem | What to try | |---------|-------------| | Connection rejected | Apple's response usually points at one of the three IDs. Re-open the Search Ads UI, copy each value (Client ID, Team ID, Key ID) again, and confirm the API certificate hasn't been rotated. | | Dashboard can't load data from Apple | Most causes are transient. Apple rate-limits and occasionally returns 5xx errors. Refresh in a few seconds. If it keeps failing, re-run the connect form to refresh access. | | Missing App Store ID banner | A Campaign Group can hold several apps. App*Sprint* uses the App Store ID to scope the list. Open the connection form and paste the numeric ID from your App Store URL. | | An install came back as organic | Either Apple didn't find a matching ad interaction in its window, the device is on iOS older than 14.3, or no valid AdServices token reached App*Sprint* within Apple's 24-hour token TTL. | --- ## Docs navigation Use the Markdown URLs when reading the docs programmatically. Use the HTML URLs when you need the interactive docs UI. - [Overview](https://appsprint.app/attribution/docs) ([Markdown](https://appsprint.app/attribution/docs.md)) — Introduction to App*Sprint* - [Quickstart](https://appsprint.app/attribution/docs/quickstart) ([Markdown](https://appsprint.app/attribution/docs/quickstart.md)) — Get up and running in 5 minutes - [React Native](https://appsprint.app/attribution/docs/react-native) ([Markdown](https://appsprint.app/attribution/docs/react-native.md)) — React Native / Expo SDK reference - [iOS (Swift)](https://appsprint.app/attribution/docs/ios-swift) ([Markdown](https://appsprint.app/attribution/docs/ios-swift.md)) — Native Swift SDK reference - [Android (Kotlin)](https://appsprint.app/attribution/docs/android) ([Markdown](https://appsprint.app/attribution/docs/android.md)) — Native Android SDK reference - [Flutter](https://appsprint.app/attribution/docs/flutter) ([Markdown](https://appsprint.app/attribution/docs/flutter.md)) — Flutter plugin reference - [RevenueCat](https://appsprint.app/attribution/docs/revenuecat) ([Markdown](https://appsprint.app/attribution/docs/revenuecat.md)) — Webhook integration for subscription attribution - [Superwall](https://appsprint.app/attribution/docs/superwall) ([Markdown](https://appsprint.app/attribution/docs/superwall.md)) — Webhook integration for paywall attribution - [Apple Search Ads](https://appsprint.app/attribution/docs/apple-search-ads) ([Markdown](https://appsprint.app/attribution/docs/apple-search-ads.md)) — Keyword and ad attribution - [Google Ads](https://appsprint.app/attribution/docs/google-ads) ([Markdown](https://appsprint.app/attribution/docs/google-ads.md)) — Coming soon - [TikTok Ads](https://appsprint.app/attribution/docs/tiktok-ads) ([Markdown](https://appsprint.app/attribution/docs/tiktok-ads.md)) — Events API server-side event forwarding - [Meta Ads](https://appsprint.app/attribution/docs/meta-ads) ([Markdown](https://appsprint.app/attribution/docs/meta-ads.md)) — In review --- # Google Ads > **Coming soon:** Google Ads is not generally available yet. The setup reference is available for review/testing accounts, but production customers should use Apple Search Ads or TikTok Ads until Google Ads rollout opens. A Google Signal Campaign uses a Google Ads setup with an App*Sprint* Signal link as its Tracking template. App*Sprint* captures the click ID, uploads selected in-app events back as offline conversions, and reports spend beside attributed installs and revenue. ## Status Google Ads is coming soon and will open once rollout is ready. ## Requirements - The App*Sprint* SDK installed and sending installs/events for this app. - A Google account with access to the Google Ads customer account you want to connect. - The 10-digit Google Ads Customer ID for the account that runs your Signal Campaigns. - Auto-tagging enabled on that customer account (**Tools → Account settings → Auto-tagging**). It's on by default. Without it, clicks reach App*Sprint* with no click ID and conversions can't be uploaded. ## Available metrics and definitions | Metric | Definition | |--------|------------| | Spend | Amount spent on the Google Signal Campaign over the selected window. | | Installs | Installs attributed by App*Sprint* to this Signal Campaign. Falls back to Google's reported conversions when App*Sprint* has no match. | | CPI | Cost per install. Spend divided by attributed installs. | | IPM | Installs per 1,000 impressions. | | Impressions | Times the ad was shown, reported by Google. | | CPM | Cost per 1,000 impressions. | | Clicks | Ad clicks, reported by Google. | | CPC | Cost per click. | | CTR | Click-through rate. Clicks divided by impressions. | | CTI | Click-to-install rate. Installs divided by clicks. | | ROAS | Return on ad spend. Attributed revenue divided by spend. | | Revenue | Revenue from in-app events attributed to this Signal Campaign. Falls back to Google's reported conversion value when App*Sprint* has no match. | ## Eligible in-app events Map any of these SDK events to a Google Ads conversion action. Events from installs without a Google click ID are skipped automatically (Google would reject them). `install`, `login`, `sign_up`, `register`, `purchase`, `subscribe`, `start_trial`, `add_payment_info`, `add_to_cart`, `add_to_wishlist`, `initiate_checkout`, `view_content`, `view_item`, `search`, `share`, `tutorial_complete`, `achieve_level`, `level_start`, `level_complete` ## Click identifiers App*Sprint* accepts | Click ID | When it's used | |----------|----------------| | `gclid` | Standard Google click ID for web clicks and older/pre-ATT iOS flows. | | `gbraid` | SKAdNetwork-compatible identifier for web-to-app iOS clicks. Sent alongside `gclid` for iOS App Campaigns when both are present. | | `wbraid` | SKAdNetwork-compatible identifier for app-to-web iOS clicks. | App*Sprint* does not fall back to IDFA, GAID, or `mobileId` for Google uploads. Without a Google click ID, the conversion is skipped. ## Setup ### 1. Connect Google Ads 1. In App*Sprint*, open your app and go to **Integrations → Google Ads**. 2. Click **Connect Google Ads** and sign in with a Google account that can access the customer account you want to connect. 3. Approve access, then enter the **Customer ID** for the account that runs your Signal Campaigns. 4. Fill the **Manager Customer ID** or **Conversion Customer ID** only for cross-account setups. ### 2. Create the conversion actions in Google Ads > **Use click-based import actions.** App*Sprint* uploads offline conversions from ad clicks. Other conversion-action types will not accept these uploads. New actions also need about six hours to index before Google accepts uploads, so create them before launch. 1. In Google Ads, go to **Goals → Conversions → Summary → New conversion action**. 2. On the data-source screen, pick **Conversions offline**. 3. Click **Edit data sources** on the offline card, then choose **Skip this step and set up a data source later**. The card should show **Connect data source later**. 4. Leave **Enhanced conversions for leads** off unless you are also sending hashed lead-form data. If Google shows a click/call choice, pick **Track conversions from clicks**. 5. On **Group your conversions**, choose the category that matches the event: **Sign-up** for trials or account creation, **Purchase** for one-time purchases, and **Subscribe** for paid subscriptions. Avoid qualified/converted lead unless you intentionally track lead statuses. 6. In the category card, click **+ Create conversion**. Name the action after the App*Sprint* event, for example `appsprint_start_trial`, `appsprint_purchase`, or `appsprint_subscribe`. 7. Keep the action **Primary** when campaigns should optimize for it. Use different values for purchases/subscriptions, and use a simple default value for trials or sign-ups. 8. After Google indexes the actions, return to **Integrations → Google Ads → Event mapping** and click **Discover actions**. New actions can appear immediately; select them in the dropdowns, click **Save mappings**, then reload the page to confirm the saved mappings are still selected. ### 3. Map your in-app events In **Integrations → Google Ads → Event mapping**, click **Discover actions** and map the events your Signal Campaigns optimize for. Only enabled click-based actions are shown. Click **Save mappings** after selecting the dropdowns, then reload the page to verify the mappings were saved. When your app sends `googleAdsConsent.adUserData`, App*Sprint* forwards it as Google's `consent.adUserData` on the upload. App*Sprint* never invents a consent value. ### 4. Create the Google Ads Signal Campaign > **Campaign-level template, store URL as final URL.** The App*Sprint* URL goes in the campaign-level **Tracking template**. Your ad's Final URL stays as the App Store or Play Store page. Do not paste the App*Sprint* URL into Final URL or Final URL suffix. 1. In App*Sprint*, copy the generated Google Signal link from the Google Ads integration. Use the full Google Tracking template URL. 2. In Google Ads, click **+ Create → Campaign**. On **What's your campaign objective?**, choose **Website traffic** or **Create a campaign without guidance**. Do not choose **App promotion** for this workflow. 3. On the campaign type screen, Google shows options like **Performance Max**, **Search**, **Demand Gen**, **Video**, **Display**, and **Shopping**. Choose **Search** for your first Signal Campaign. 4. On **Select the ways you'd like to reach your goal**, enter the real destination link: your App Store URL for iOS or your Play Store URL for Android. Do not enter the App*Sprint* Tracking template URL here. 5. On **Use these conversion goals to improve Website traffic**, keep only the goal that contains the App*Sprint* conversion action you mapped, such as **Purchases**, **Sign-ups**, or **Begin checkout**. Expand the goal or action count if needed and check the action name. Remove unrelated account-default goals from **More actions**. 6. Name the campaign, then continue through Google's normal campaign setup: bidding, budget, locations, languages, keywords, and ads. Choose the bidding settings that match your Google Ads strategy. 7. Set devices to mobile only. If Google exposes operating-system targeting for the campaign type, keep only the platform for this app: iOS for App Store campaigns, Android for Play Store campaigns. 8. Create the ad group and ad. Use keywords that match your app or use case, and put your App Store or Play Store page in the ad **Final URL**. 9. Before publishing, open **Settings → Campaign URL options → Tracking template** and paste the full App*Sprint* Google Tracking template there. App*Sprint*'s template already includes Google click IDs and the final-URL insertion token Google needs. 10. Click **Test**. The test should resolve through App*Sprint* and end on the store page. Fix any URL errors before launch. 11. Publish the campaign. Conversions usually appear in Google Ads within 24 hours. ## Troubleshooting | Problem | What to try | |---------|-------------| | Authentication failed | The Google account you connected lost access to the Google Ads customer account. Disconnect Google Ads in App*Sprint* and reconnect with an account that can access that customer. | | Customer not found | Re-check the Customer ID (10 digits, hyphens stripped). If you access the account through an MCC, also fill in the Manager Customer ID field. | | Conversions aren't showing up in Google Ads | New conversion actions need around six hours to index before Google accepts uploads. App*Sprint* retries automatically. Total click-to-conversion latency in Google Ads is up to 24 hours. | | Events being skipped | Google requires a click ID on every offline conversion. Installs without one (typically organic users who never clicked a tracked ad) are skipped intentionally; Google would otherwise reject them. | | Reporting numbers don't match Google Ads | App*Sprint* reports use App*Sprint* attribution. Google reports use its conversion view. Numbers diverge for users without a Google click. App*Sprint* uses its own number whenever it has one. | --- ## Docs navigation Use the Markdown URLs when reading the docs programmatically. Use the HTML URLs when you need the interactive docs UI. - [Overview](https://appsprint.app/attribution/docs) ([Markdown](https://appsprint.app/attribution/docs.md)) — Introduction to App*Sprint* - [Quickstart](https://appsprint.app/attribution/docs/quickstart) ([Markdown](https://appsprint.app/attribution/docs/quickstart.md)) — Get up and running in 5 minutes - [React Native](https://appsprint.app/attribution/docs/react-native) ([Markdown](https://appsprint.app/attribution/docs/react-native.md)) — React Native / Expo SDK reference - [iOS (Swift)](https://appsprint.app/attribution/docs/ios-swift) ([Markdown](https://appsprint.app/attribution/docs/ios-swift.md)) — Native Swift SDK reference - [Android (Kotlin)](https://appsprint.app/attribution/docs/android) ([Markdown](https://appsprint.app/attribution/docs/android.md)) — Native Android SDK reference - [Flutter](https://appsprint.app/attribution/docs/flutter) ([Markdown](https://appsprint.app/attribution/docs/flutter.md)) — Flutter plugin reference - [RevenueCat](https://appsprint.app/attribution/docs/revenuecat) ([Markdown](https://appsprint.app/attribution/docs/revenuecat.md)) — Webhook integration for subscription attribution - [Superwall](https://appsprint.app/attribution/docs/superwall) ([Markdown](https://appsprint.app/attribution/docs/superwall.md)) — Webhook integration for paywall attribution - [Apple Search Ads](https://appsprint.app/attribution/docs/apple-search-ads) ([Markdown](https://appsprint.app/attribution/docs/apple-search-ads.md)) — Keyword and ad attribution - [Google Ads](https://appsprint.app/attribution/docs/google-ads) ([Markdown](https://appsprint.app/attribution/docs/google-ads.md)) — Coming soon - [TikTok Ads](https://appsprint.app/attribution/docs/tiktok-ads) ([Markdown](https://appsprint.app/attribution/docs/tiktok-ads.md)) — Events API server-side event forwarding - [Meta Ads](https://appsprint.app/attribution/docs/meta-ads) ([Markdown](https://appsprint.app/attribution/docs/meta-ads.md)) — In review --- # TikTok Ads A TikTok Signal Campaign uses an App*Sprint* Signal link as the Website URL in TikTok Ads Manager. App*Sprint* routes users to the store, forwards selected post-install events to TikTok's web Events API, and reports spend beside attributed installs and revenue. ## Requirements - The App*Sprint* SDK installed and sending installs/events for this app. - Admin or Operator access to the TikTok advertiser. Standard access is not enough. - A Web Pixel in TikTok Business Center, linked to that advertiser. - Advertiser/reporting access approved during the TikTok OAuth connection. ## Available metrics and definitions | Metric | Definition | |--------|------------| | Spend | Amount spent on the TikTok Signal Campaign over the selected window. | | Installs | Installs attributed by App*Sprint*. Falls back to TikTok's reported conversions when App*Sprint* has no match. | | CPI | Cost per install. Spend divided by attributed installs. | | IPM | Installs per 1,000 impressions. | | Impressions | Times the ad was shown, reported by TikTok. | | CPM | Cost per 1,000 impressions. | | Clicks | Ad clicks, reported by TikTok. | | CPC | Cost per click. | | CTR | Click-through rate. Clicks divided by impressions. | | CTI | Click-to-install rate. Installs divided by clicks. | | ROAS | Return on ad spend. Attributed revenue divided by spend. | | Revenue | Revenue from in-app events attributed to this Signal Campaign. Falls back to TikTok's reported value when App*Sprint* has no match. | ## Available in-app events and default mapping The dashboard shows primary optimization events first and keeps less common rows collapsed below. Leave a row blank to disable forwarding for that event. > **Web Events API only:** App*Sprint* maps app lifecycle signals to TikTok web events: installs use `Download` and sessions use `ViewContent`. Lower-signal events stay under "See more events" and default to off. | SDK event | TikTok event | Dashboard | |-----------|--------------|-----------| | install | Download | Primary row | | session_start | ViewContent | Primary row | | sign_up / register | CompleteRegistration | Primary row | | start_trial | StartTrial | Primary row | | subscribe | Subscribe | Primary row | | purchase | Purchase | Primary row | | initiate_checkout | InitiateCheckout | See more events | | add_payment_info | AddPaymentInfo | See more events | | add_to_cart | AddToCart | See more events | | add_to_wishlist | AddToWishlist | See more events | | view_content / view_item | ViewContent | See more events | | search | Search | See more events | ## Setup ### 1. Connect TikTok Ads OAuth lets App*Sprint* load advertisers and Signal Campaign reporting. It is separate from the Events API token used for server-side event forwarding. 1. In App*Sprint*, open your app and go to **Integrations → TikTok Ads**. 2. Click **Connect TikTok Ads** and sign in with a TikTok For Business account. 3. Approve access, then click **Load accounts** and select the advertiser. ### 2. Configure the Pixel ID and Events API token Already have a Pixel? Find its values: 1. In [TikTok Business Center](https://business.tiktok.com/), go to **Assets** and open the Pixel linked to your advertiser. 2. Click **Open in Events Manager**. 3. Copy the **Pixel ID** at the top of the page. 4. Click **Settings** → **Events API** → **Generate access token** and copy it. 5. Paste both values in App*Sprint* and save. > **Use one Pixel for the whole setup.** The Pixel ID and access token must come from the same Pixel, and that Pixel must be linked to the advertiser selected in App*Sprint*. Use one Pixel per app, and do not mix Pixels between apps. Wrong values will send events to the wrong destination. To create a new Pixel: 1. Open [TikTok Business Center](https://business.tiktok.com/) and go to **Assets** at the Business Center level. 2. Click **Add a pixel**, choose **Create new pixel**, then click **Next**. 3. If TikTok asks for a website URL, click **Skip** (or add your own website URL here if you want, but it is not mandatory). The App*Sprint* Signal link comes later. 4. Choose **Manual setup**, then select **Events API** only. 5. Name the Pixel after your app. Use one Pixel per app so it is easy to identify later, then click **Create**. 6. In **Set up your business funnel**, keep **E-commerce** template selected, then click **Next**. 7. Copy the **Pixel ID** and paste it into the **Pixel ID** field in the App*Sprint* TikTok ads integration. 8. Click **Generate access token**, copy the Events API access token, then paste it into the App*Sprint* TikTok ads integration and click **Save Pixel setup**. 9. Back in your TikTok Event Manager, click **Next**, then click **Finish**. 10. Go back to [TikTok Business Center](https://business.tiktok.com/) → **Assets** and, in the **Action** column for the Pixel you just created, click **View**. 11. Click **Link accounts**, select the advertiser you selected in App*Sprint*, then confirm. ### 3. Test the Events API (optional) Copy the test code from TikTok's Test Events panel, paste it in **Integrations → TikTok Ads → Test Events API**, then send one ViewContent test event to confirm delivery. ### 4. Review event mapping (optional) App*Sprint* already saves the default mapping for the main in-app events. Open **Integrations → TikTok Ads → Event mapping** only if you want to change which events are sent, disable an event, or map an App*Sprint* event to a different TikTok event name. > **Match events to your Signal Campaign objective.** Use **Sales** for Purchase or Subscribe. Use **Lead generation** for StartTrial or Sign-up. TikTok Ads Manager controls which events are selectable for the objective, so confirm the event is available before launch. TikTok can take up to 2 hours after receiving production event data before the Pixel and optimization-event statuses update in campaign setup. Test events are useful for debugging, but they do not unlock campaign optimization. TikTok unlocks **value-based optimization** once you have 20 unique attributed Purchase events in any 7-day window. App*Sprint* already sends value and currency on Purchase events, so nothing else to enable. ### 5. Copy the Signal Campaign link and launch 1. In App*Sprint*, copy the generated TikTok Signal link. The URL already includes TikTok macros for campaign, ad, placement, callback parameter, IP, and user agent, plus App*Sprint* browser-side click enrichment. 2. In TikTok Ads Manager, click **+ Create** in the top-left corner to start a new campaign. 3. If not done already, switch to the **Full version** at the bottom left, to access the campaign objective and destination options. Choose **Lead generation** when you want TikTok to optimize for StartTrial or Sign-up. Choose **Sales** when you want it to optimize for Purchase or Subscribe. Use **Website** as the destination, not App. 4. At the ad set level, use **Website** as the optimization location, select the same Pixel you configured in App*Sprint*, then pick the mapped optimization event. 5. Keep placement on TikTok only, target one operating system per ad set, and choose Conversion or Value bidding depending on the event you are optimizing for. If TikTok does not show OS targeting, switch to manual targeting and use **Device** to select only iOS or only Android. 6. At the ad level, paste the App*Sprint* Signal link into the **Website URL** field. Do not paste the URL parameters again in TikTok's Tracking section. 7. Launch the campaign. First conversions usually appear within 30 to 90 minutes. ## Troubleshooting | Problem | What to try | |---------|-------------| | OAuth connection expired or lost advertiser access | Disconnect TikTok Ads in App*Sprint* and reconnect with an account that still has advertiser access. | | Campaigns do not load | Reconnect TikTok Ads and approve advertiser/reporting access. The Pixel Events API token does not grant campaign reporting by itself. | | Events API token rejected | Generate a new Events API access token in Business Center, then disconnect and reconnect TikTok Ads in App*Sprint* with the new credentials. | | Some events are not sent | Copy the generated TikTok Signal link before sending production traffic, keep the mapping enabled, and make sure SDK requests include a real client IP and user agent. | | Events aren't appearing in Events Manager | Allow 30 to 90 minutes after the first event. Then confirm the Signal link is the one pasted into the TikTok ad's Website URL and that the Pixel ID matches the Events API token. | | Signal Campaign objective rejected an event | StartTrial is for Lead generation in this setup. If it appears but is disabled under Sales, create a Lead generation Website campaign, confirm the Website URL domain matches the Pixel event URL, and allow 30 to 90 minutes after the first production StartTrial event. | | Reporting numbers don't match TikTok Ads Manager | App*Sprint* reports use App*Sprint* attribution. TikTok reports use the Pixel's view. They differ for users without a TikTok click. App*Sprint* uses its own number whenever it has one. | --- ## Docs navigation Use the Markdown URLs when reading the docs programmatically. Use the HTML URLs when you need the interactive docs UI. - [Overview](https://appsprint.app/attribution/docs) ([Markdown](https://appsprint.app/attribution/docs.md)) — Introduction to App*Sprint* - [Quickstart](https://appsprint.app/attribution/docs/quickstart) ([Markdown](https://appsprint.app/attribution/docs/quickstart.md)) — Get up and running in 5 minutes - [React Native](https://appsprint.app/attribution/docs/react-native) ([Markdown](https://appsprint.app/attribution/docs/react-native.md)) — React Native / Expo SDK reference - [iOS (Swift)](https://appsprint.app/attribution/docs/ios-swift) ([Markdown](https://appsprint.app/attribution/docs/ios-swift.md)) — Native Swift SDK reference - [Android (Kotlin)](https://appsprint.app/attribution/docs/android) ([Markdown](https://appsprint.app/attribution/docs/android.md)) — Native Android SDK reference - [Flutter](https://appsprint.app/attribution/docs/flutter) ([Markdown](https://appsprint.app/attribution/docs/flutter.md)) — Flutter plugin reference - [RevenueCat](https://appsprint.app/attribution/docs/revenuecat) ([Markdown](https://appsprint.app/attribution/docs/revenuecat.md)) — Webhook integration for subscription attribution - [Superwall](https://appsprint.app/attribution/docs/superwall) ([Markdown](https://appsprint.app/attribution/docs/superwall.md)) — Webhook integration for paywall attribution - [Apple Search Ads](https://appsprint.app/attribution/docs/apple-search-ads) ([Markdown](https://appsprint.app/attribution/docs/apple-search-ads.md)) — Keyword and ad attribution - [Google Ads](https://appsprint.app/attribution/docs/google-ads) ([Markdown](https://appsprint.app/attribution/docs/google-ads.md)) — Coming soon - [TikTok Ads](https://appsprint.app/attribution/docs/tiktok-ads) ([Markdown](https://appsprint.app/attribution/docs/tiktok-ads.md)) — Events API server-side event forwarding - [Meta Ads](https://appsprint.app/attribution/docs/meta-ads) ([Markdown](https://appsprint.app/attribution/docs/meta-ads.md)) — In review --- # Meta Ads > **In review:** Meta Ads is not generally available yet. The setup reference is available for review/testing accounts, but production customers should use Apple Search Ads, TikTok Ads, or Google Ads until Meta app review is approved. A Meta Signal Campaign uses an App*Sprint* Signal link as the Website URL in Meta Ads Manager. App*Sprint* captures the Meta click, routes users to the store, forwards selected post-install events to Meta's Conversions API, and reports spend beside attributed installs and revenue. ## Status Meta Ads is in review and will open once Meta app review is approved. ## Requirements - The App*Sprint* SDK installed and sending installs/events for this app. - Full/Admin access to the Meta business portfolio, ad account, and dataset for this app. - A Meta web dataset for this app in Events Manager. - A Conversions API access token generated for the same dataset. - The App*Sprint*-generated Meta Signal link, added as the Website URL in Meta ads. - The App*Sprint* tracking domain added to the dataset Traffic Permissions / allowed websites. ## Available metrics and definitions | Metric | Definition | |--------|------------| | Spend | Amount spent on the Meta campaign over the selected window. | | Installs | Installs attributed by App*Sprint*. Falls back to Meta's reported conversion actions when App*Sprint* has no match. | | CPI | Cost per install. Spend divided by attributed installs. | | IPM | Installs per 1,000 impressions. | | Impressions | Times the ad was shown, reported by Meta. | | CPM | Cost per 1,000 impressions. | | Clicks | Ad clicks, reported by Meta. | | CPC | Cost per click. | | CTR | Click-through rate. Clicks divided by impressions. | | CTI | Click-to-install rate. Installs divided by clicks. | | ROAS | Return on ad spend. Attributed revenue divided by spend. | | Revenue | Revenue from in-app events attributed to this Signal Campaign. Falls back to Meta's reported value when App*Sprint* has no match. | ## Available in-app events and definitions | Event | Definition | |-------|------------| | Achieve level | User reaches a level or milestone. | | Add payment info | User enters or saves payment details. | | Add to cart | User adds an item to the cart. | | Complete registration | User finishes account creation. | | Initiate checkout | User starts checkout. | | Session | App open that creates a user session. | | View content | User views a key screen or content item. | | Start trial | User starts a free trial. | | Subscribe | First paid subscription period begins. | | Purchase | One-time purchase or non-subscription revenue event. | ## Default event mapping App*Sprint* sends custom events prefixed with `appsprint_`. Leave a row blank in the dashboard to disable forwarding for that event. > **All mapped events are sent:** The selected dataset receives mapped SDK, RevenueCat, and Superwall events. App*Sprint* sends these events to Meta; it does not receive event data back from Meta. > **First setup sends approval events:** When the Dataset ID and Conversions API token are saved for the first time, App*Sprint* sends a small activation batch with every App*Sprint* custom event name listed below. This helps Meta show the events in Events Manager so you can approve every `appsprint_` event before launching a campaign. | App*Sprint* event | Meta custom event | Dashboard | |-----------------|-------------------|-----------| | install | appsprint_install | Primary row | | session_start | appsprint_session | Primary row | | sign_up / register | appsprint_complete_registration | Primary row | | start_trial | appsprint_start_trial | Primary row | | subscribe | appsprint_subscribe | Primary row | | purchase | appsprint_purchase | Primary row | | initiate_checkout | appsprint_initiate_checkout | See more events | | view_content / view_item | appsprint_view_content | See more events | | add_payment_info | appsprint_add_payment_info | See more events | | add_to_cart | appsprint_add_to_cart | See more events | | achieve_level / level_complete | appsprint_achieve_level | See more events | | RevenueCat/Superwall cancellation | appsprint_subscription_cancelled | Automatic lifecycle | | RevenueCat/Superwall expiration | appsprint_subscription_expired | Automatic lifecycle | | RevenueCat/Superwall billing issue | appsprint_billing_issue | Automatic lifecycle | | RevenueCat/Superwall product change | appsprint_product_changed | Automatic lifecycle | | RevenueCat/Superwall subscription paused | appsprint_subscription_paused | Automatic lifecycle | ## Setup ### 1. Connect Meta Ads OAuth lets App*Sprint* load ad accounts and Signal Campaign reporting. It is separate from the Conversions API token used for server-side event forwarding. 1. In App*Sprint*, open your app and go to **Integrations > Meta Ads**. 2. Click **Connect Meta Ads** and sign in with the Meta user that has business and ad account access. 3. In Meta's access flow, select the correct business portfolio, ad account, and app access, then approve the requested permissions. 4. Back in App*Sprint*, select the ad account that owns the dataset and campaigns for this app. ### 2. Configure the Dataset ID and Conversions API token > **Use one dataset for the whole setup.** The Dataset ID and Conversions API token must come from the same dataset. The dataset receives all mapped app events, not only App*Sprint*-attributed events, so choosing the wrong dataset sends your app's events to the wrong destination. 1. Open [Meta Events Manager](https://eventsmanager.facebook.com/) from the sidebar. 2. Click **+ Connect data**, choose **Web**, then select an existing dataset or create a new dataset for this app. 3. Choose **Setup Conversions API**, click **See other ways to setup**, then select manual setup. 4. If Meta asks you to select events or parameters, continue as lightly as possible. App*Sprint* only sends events to Meta; it does not receive events from Meta, and required parameters are sent automatically by App*Sprint*. 5. If Meta requires at least one event before continuing, select **Purchase**. This only unlocks Meta's setup flow; App*Sprint* still controls which events are sent. 6. On the **Using the Conversions API** page, scroll to **Generate an Access Token** and copy the token. Meta's official token instructions are here: https://developers.facebook.com/docs/marketing-api/conversions-api/get-started/#access-token 7. Copy the Dataset ID, test event code, and Conversions API token into App*Sprint*, then save. ### 3. Copy the Signal link and allow the domain 1. In App*Sprint*, copy the generated Meta Signal link from the Meta Ads integration. 2. In Meta Events Manager, open the same dataset, go to **Settings**, then scroll to **Traffic Permissions / Websites**. 3. Click **Create an allow list**, add the App*Sprint* tracking domain from the Signal link, usually **api.appsprint.app**, then click **Confirm**. This is a dataset website allow-list, not DNS business-domain verification. 4. App*Sprint* sends a one-time setup activation batch after Dataset ID and Conversions API setup. Meta custom-event review can then appear in Events Manager for the incoming `appsprint_` events. ### 4. Test the Conversions API In Meta Events Manager, open the dataset's **Test Events** view and copy the test event code. Paste it in App*Sprint* and send a test event. > **A visible test event proves the dataset path.** If the event appears in Meta Test Events, the Dataset ID, token, and App*Sprint* backend route are working. Live campaign delivery still requires a real Meta click with `fbclid`, a matched install, and a mapped app/revenue event. > **Meta may ask you to confirm custom events.** If Events Manager shows **Confirm custom event(s) that belong to you**, click **Review events**, acknowledge Meta's Business Tools terms, select all incoming events that start with `appsprint_`, click **Next**, choose **Confirm the custom event**, then click **Confirm**. App*Sprint* event names are generic and must not contain health, financial, consumer-report, or other sensitive data. ### 5. Map events and confirm custom events In **Integrations > Meta Ads > Event mapping**, choose the App*Sprint* events you want to forward. App*Sprint* sends them as custom `appsprint_` events to the selected dataset. > **Confirm custom events before optimizing.** After test or production events arrive, Meta may ask you to review custom events that belong to you. Click **Review events**, acknowledge the prompt, select every incoming `appsprint_` event, then confirm them before using them as campaign optimization goals. Meta can take 30 to 90 minutes after receiving production event data before events appear in Events Manager and campaign setup. Test events are useful for debugging, but they do not unlock campaign optimization. ### 6. Create your first Meta Signal Campaign 1. In Meta Ads Manager, click **+ Create**. 2. Select **Sales** as the recommended objective. 3. At the ad set level, use **Website** as the conversion location, select the same dataset you saved in App*Sprint*, then choose the `appsprint_` event you want to optimize for. 4. At the ad set level, open **Placements**, scroll to the bottom of the Placements card, and click **Show more settings**. In **Devices and operating systems**, target mobile devices on only the advertised app's platform: iOS only or Android only. In **Platforms**, keep Facebook and Instagram selected, and turn off **Audience Network** for the first test. 5. At the ad level, paste the App*Sprint* Signal link into **Website URL**. Do not paste the URL parameters again in Meta's Tracking section. 6. Launch the campaign. First production events usually need 30 to 90 minutes before Meta surfaces them in Events Manager or campaign setup. ## Pre-launch checklist 1. **SDK**: The app has shipped with App*Sprint* configured and install/event tracking verified. 2. **Revenue source**: RevenueCat or Superwall is connected if subscription and purchase revenue should be forwarded. 3. **Meta setup**: OAuth, ad account, Dataset ID, Conversions API token, test event, event mapping, and Signal link are saved. 4. **Campaign setup**: The Meta ad uses the App*Sprint* Signal link as Website URL, the dataset matches App*Sprint*, and mobile OS targeting is correct. ## Troubleshooting | Problem | What to try | |---------|-------------| | OAuth connection expired or missing permissions | Reconnect Meta Ads with a user that has access to the business portfolio and ad account. Approve the ads reporting permission again. | | Ad accounts or campaigns do not load | Reconnect Meta Ads and confirm the selected Meta user can see the ad account in Business Settings. The Conversions API token does not grant reporting access. | | Conversions API token rejected | Generate a new token from the same dataset in Events Manager, paste it in App*Sprint*, and save again. | | Test event does not appear | Confirm the test_event_code, Dataset ID, and Conversions API token all belong to the same dataset. Then send the test again and keep the Meta Test Events tab open. | | Events are not appearing in Events Manager | Wait 30 to 90 minutes after the first production event, then confirm the Signal link is used as the ad Website URL and the tracking domain, usually api.appsprint.app, is in the dataset Traffic Permissions website allow-list. | | Custom events are not selectable for optimization | Meta may need incoming events before they appear. In Events Manager, click Review events, acknowledge Meta's prompt, select all appsprint_ custom events, choose Confirm the custom event, then retry campaign setup. | | No deliveries after launching ads | Verify the ad uses the App*Sprint* Signal link as Website URL, the click URL includes fbclid, the SDK records the install, and the mapped SDK/RevenueCat/Superwall event fired after install. | | Reporting numbers do not match Meta Ads Manager | App*Sprint* reports use App*Sprint* attribution. Meta reports use Meta's dataset and reporting model. Users without a matching Meta click can create differences. | --- ## Docs navigation Use the Markdown URLs when reading the docs programmatically. Use the HTML URLs when you need the interactive docs UI. - [Overview](https://appsprint.app/attribution/docs) ([Markdown](https://appsprint.app/attribution/docs.md)) — Introduction to App*Sprint* - [Quickstart](https://appsprint.app/attribution/docs/quickstart) ([Markdown](https://appsprint.app/attribution/docs/quickstart.md)) — Get up and running in 5 minutes - [React Native](https://appsprint.app/attribution/docs/react-native) ([Markdown](https://appsprint.app/attribution/docs/react-native.md)) — React Native / Expo SDK reference - [iOS (Swift)](https://appsprint.app/attribution/docs/ios-swift) ([Markdown](https://appsprint.app/attribution/docs/ios-swift.md)) — Native Swift SDK reference - [Android (Kotlin)](https://appsprint.app/attribution/docs/android) ([Markdown](https://appsprint.app/attribution/docs/android.md)) — Native Android SDK reference - [Flutter](https://appsprint.app/attribution/docs/flutter) ([Markdown](https://appsprint.app/attribution/docs/flutter.md)) — Flutter plugin reference - [RevenueCat](https://appsprint.app/attribution/docs/revenuecat) ([Markdown](https://appsprint.app/attribution/docs/revenuecat.md)) — Webhook integration for subscription attribution - [Superwall](https://appsprint.app/attribution/docs/superwall) ([Markdown](https://appsprint.app/attribution/docs/superwall.md)) — Webhook integration for paywall attribution - [Apple Search Ads](https://appsprint.app/attribution/docs/apple-search-ads) ([Markdown](https://appsprint.app/attribution/docs/apple-search-ads.md)) — Keyword and ad attribution - [Google Ads](https://appsprint.app/attribution/docs/google-ads) ([Markdown](https://appsprint.app/attribution/docs/google-ads.md)) — Coming soon - [TikTok Ads](https://appsprint.app/attribution/docs/tiktok-ads) ([Markdown](https://appsprint.app/attribution/docs/tiktok-ads.md)) — Events API server-side event forwarding - [Meta Ads](https://appsprint.app/attribution/docs/meta-ads) ([Markdown](https://appsprint.app/attribution/docs/meta-ads.md)) — In review