appsynk

Official Flutter SDK for AppSynk — the Mobile Attribution Platform.

This package is a thin wrapper over the native AppSynk SDKs (the AppSynk CocoaPod on iOS, io.appsynk:sdk on Android). Installs, sessions, app updates, revenue, attribution, deep links, SKAdNetwork and fraud signals are all handled natively — the Dart layer only forwards your calls.

  • iOS 14+
  • Android 5+ (API 21+)
  • Flutter ≥ 3.19 / Dart ≥ 3.3

Installation

flutter pub add appsynk

That's it. Flutter resolves the native dependencies automatically at build time:

  • iOS — the AppSynk pod is resolved by CocoaPods on the next flutter run (Flutter runs pod install for you). You don't even cd ios.
  • Android — autolinking + Maven Central resolve io.appsynk:sdk. Nothing to do.

Prerequisite. The Flutter plugin depends on the published native SDKs: the AppSynk pod on the CocoaPods trunk and io.appsynk:sdk on Maven Central. These must be published before you build an app that uses this package (they share the exact same artifacts as the React Native SDK).

After installing, complete the native setup below (the manual equivalent of an Expo config plugin — Flutter plugins can't edit the host Info.plist / AndroidManifest.xml for you). The example app under example/ ships this exact configuration as a working reference.


Quickstart

import 'package:appsynk/appsynk.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await AppSynk.configure('ak_live_xxxxxxxxxxxxxxxxxxxxxxxx'); // one line

  // Deep links are auto-captured (universal/App Links + appsynk:// scheme).
  AppSynk.onDeepLink.listen((attribution) {
    final destination = attribution.deepLink;
    if (destination != null) {
      // ... route the user to `destination`
    }
  });

  runApp(const MyApp());
}

// Later, anywhere in your app:
await AppSynk.trackEvent('level_complete', properties: {'level': 5});
await AppSynk.trackRevenue(9.99, 'EUR', 'premium_monthly', 'ORDER_123');
final attribution = await AppSynk.getAttributionData();
await AppSynk.requestTrackingAuthorization(); // iOS ATT prompt

configure() automatically tracks, natively: first-install, reinstall, app_open, sessions, app_update, device info, advertising IDs, the Play Install Referrer (Android), and registers SKAdNetwork (iOS). The environment (production vs sandbox) is auto-detected from the key prefix (ak_live_ / ak_sandbox_).


iOS setup

Add the following to your app's ios/Runner/Info.plist. The native pod is resolved automatically — these keys are the only manual iOS step.

1. App Tracking Transparency (ATT)

Required only if you call requestTrackingAuthorization().

<key>NSUserTrackingUsageDescription</key>
<string>We use this data to measure our advertising campaigns.</string>

2. SKAdNetwork

The conversion network IDs for the channels AppSynk supports (TikTok, Meta, Google, Apple Search Ads + known variants):

<key>SKAdNetworkItems</key>
<array>
  <dict><key>SKAdNetworkIdentifier</key><string>2fnua5tdw4.skadnetwork</string></dict><!-- TikTok Ads -->
  <dict><key>SKAdNetworkIdentifier</key><string>v9wttpbfk9.skadnetwork</string></dict><!-- Meta (Facebook) Ads -->
  <dict><key>SKAdNetworkIdentifier</key><string>cstr6suwn9.skadnetwork</string></dict><!-- Google Ads -->
  <dict><key>SKAdNetworkIdentifier</key><string>4fzdc2evr5.skadnetwork</string></dict><!-- Apple Search Ads -->
  <dict><key>SKAdNetworkIdentifier</key><string>hs6bdukanm.skadnetwork</string></dict><!-- Meta variant -->
  <dict><key>SKAdNetworkIdentifier</key><string>prcb7njfa6.skadnetwork</string></dict><!-- Meta variant -->
  <dict><key>SKAdNetworkIdentifier</key><string>mlmmfzh3r3.skadnetwork</string></dict><!-- Google variant -->
</array>

3. Custom URL scheme (appsynk://)

Lets the SDK receive custom-scheme deep links from your tracking links:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>appsynk</string>
    </array>
  </dict>
</array>

Required for https://go.appsynk.io/... universal links. Add the capability in Xcode (Runner target → Signing & Capabilities → + Capability → Associated Domains) — this creates/links Runner.entitlements and updates the provisioning profile. The entry to add:

<key>com.apple.developer.associated-domains</key>
<array>
  <string>applinks:go.appsynk.io</string>
</array>

A ready-to-use Runner.entitlements is included in example/ios/Runner/. You still need to enable the capability in Xcode so it's attached to your build.

No pod install step is required: Flutter runs it on flutter run.


Android setup

The native AAR (io.appsynk:sdk) already contributes, via manifest merging: the Meta <queries> (Facebook / Instagram), the INTERNET / ACCESS_NETWORK_STATE / AD_ID permissions, and the backup XML resources. You only add the following to android/app/src/main/AndroidManifest.xml.

1. Backup rules + Facebook App ID

On the <application> tag, reference the backup resources provided by the AAR, and (optionally, for Meta view-through) declare your Facebook App ID:

<application
    android:label="your_app"
    android:name="${applicationName}"
    android:icon="@mipmap/ic_launcher"
    android:dataExtractionRules="@xml/appsynk_backup_rules"
    android:fullBackupContent="@xml/appsynk_full_backup">

    <!-- Optional: enables Meta (Facebook/Instagram) view-through attribution -->
    <meta-data
        android:name="com.appsynk.FacebookApplicationId"
        android:value="YOUR_FACEBOOK_APP_ID" />

    <!-- ... your activities ... -->
</application>

Add these to your launcher <activity> (the one with MAIN / LAUNCHER) so the SDK auto-captures deep links at cold and warm start:

<!-- App Links: https://go.appsynk.io/{linkId} -->
<intent-filter android:autoVerify="true">
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="https" android:host="go.appsynk.io" />
</intent-filter>

<!-- Custom scheme: appsynk://{linkId} -->
<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="appsynk" />
</intent-filter>

Make sure the launcher activity is android:launchMode="singleTop" (the Flutter default) so warm-start deep links arrive via onNewIntent. No code is needed — the plugin captures the intents automatically.


Configuration

import 'package:appsynk/appsynk.dart';

await AppSynk.configure(
  'ak_sandbox_xxxxxxxxxxxxxxxxxxxxxxxx',
  options: const AppSynkOptions(
    logLevel: AppSynkLogLevel.debug, // none | error | debug | verbose
    // environment: AppSynkEnvironment.sandbox, // optional — auto-detected
    // sessionTimeout: 1800, // seconds (default 1800)
    // batchSize: 10,        // events per flush (default 10)
    // flushInterval: 30,    // seconds (default 30)
    // anonymizeUserByDefault: true, // start anonymized until consent
  ),
);

Options mirror the native AppSynkOptions 1:1 (durations in seconds) and are forwarded as-is; each platform ignores the options that don't apply to it (e.g. disableIdfa on Android, disableGaid on iOS).

Option Type Default Notes
environment AppSynkEnvironment auto Detected from the key prefix when omitted.
logLevel AppSynkLogLevel none
customApiUrl String — Data residency / self-hosted gateway.
sessionTimeout int (s) 1800 Inactivity before a new session.
batchSize int 10 Events buffered before a flush.
flushInterval int (s) 30 Auto-flush interval.
attWaitTimeout int (s) 60 iOS — wait for the ATT decision before the gated install.
referrerWaitTimeout int (s) 10 Android — wait for the Play referrer + GAID before the install.
disableIdfa / disableIdfv / disableAdServices / disableSKAdNetwork bool false iOS privacy toggles.
disableGaid / disableAndroidId / disableMetaReferrer bool false Android privacy toggles.
anonymizeUserByDefault bool false Start anonymized until consent.
hmacSecretKey / hmacKeyId String — Optional HMAC-SHA256 request signing.

Events

// Simple event
await AppSynk.trackEvent('tutorial_completed');

// Event with properties
await AppSynk.trackEvent('level_completed', properties: {
  'level': 5,
  'score': 1250,
  'difficulty': 'hard',
});

Revenue

trackRevenue records a one-time purchase. orderId is required and de-duplicated for 24 h natively.

await AppSynk.trackRevenue(9.99, 'EUR', 'premium_monthly', 'ORDER_123');

// Ad impression revenue
await AppSynk.trackAdRevenue(
  network: 'admob',
  amount: 0.0042,
  currency: 'USD',
  adUnit: 'banner_main',
  adType: 'banner', // 'banner' | 'interstitial' | 'rewarded'
);

User identification

Call this after login so you don't pollute the install event.

await AppSynk.setUserId('user_internal_12345');
await AppSynk.setUserProperties({'plan': 'premium', 'country': 'FR'});

// On logout — clears userId + user properties, keeps the stable device_id
await AppSynk.reset();

Deep linking

Deep links are auto-captured on both platforms (no URL forwarding needed): iOS via the application delegate (universal links + appsynk://), Android via ActivityAware + onNewIntent (App Links + appsynk://), for both cold and warm starts.

final sub = AppSynk.onDeepLink.listen((attribution) {
  if (attribution.deepLink != null) {
    // navigate to attribution.deepLink
  }
});
// ... remember to sub.cancel() when no longer needed.

// Best-known attribution at any time:
final attribution = await AppSynk.getAttributionData();
if (attribution != null && !attribution.isOrganic) {
  print('Channel: ${attribution.channel}');       // e.g. "tiktok_ads"
  print('Campaign: ${attribution.campaignName}');
  print('Deep link: ${attribution.deepLink}');
}

onDeepLink is a broadcast stream of resolved / deferred deep links. AttributionData fields: channel, campaignName, adSetName, creativeName, medium, source, clickId, clickTimestamp, isOrganic, attributionModel, confidenceScore, deepLink.

App Tracking Transparency (iOS)

AppSynk never shows the ATT prompt on its own. Call it at the right UX moment — after onboarding, not on first launch.

final status = await AppSynk.requestTrackingAuthorization();
// 'authorized' | 'denied' | 'restricted' | 'not_determined' (iOS)
// 'not_applicable' (Android)

Set NSUserTrackingUsageDescription (see iOS setup) so the prompt can show. Without ATT, AppSynk attributes using the IDFV plus probabilistic matching, and SKAdNetwork keeps working independently.

Privacy & GDPR

// Collect nothing identifying until consent: start anonymized
await AppSynk.configure('ak_live_xxx',
    options: const AppSynkOptions(anonymizeUserByDefault: true));

// When the user accepts your consent banner
await AppSynk.setConsent(
  isUserSubjectToGDPR: true,
  hasConsentForDataUsage: true,
  hasConsentForAdsPersonalization: true,
);
await AppSynk.anonymizeUser(false); // resume sending identifiers

anonymizeUser(true) strips advertising identifiers from future events. When hasConsentForAdsPersonalization is false, the backend skips Meta / TikTok postbacks that carry personal identifiers.


API reference

Method Returns Notes
configure(apiKey, {options}) Future<void> Call once at startup.
trackEvent(name, {properties}) Future<void>
trackRevenue(amount, currency, productId, orderId) Future<void> 24 h dedup on orderId.
trackAdRevenue({network, amount, currency, adUnit?, adType?}) Future<void>
setUserId(userId) Future<void>
setUserProperties(properties) Future<void>
setConsent({isUserSubjectToGDPR, hasConsentForDataUsage, hasConsentForAdsPersonalization}) Future<void>
anonymizeUser(enabled) Future<void>
reset() Future<void> Clears identity, keeps device_id.
flush() Future<void> No-op on iOS (auto-flush).
getAttributionData() Future<AttributionData?>
getDeepLinkData() Future<AttributionData?>
handleDeepLink(url) Future<AttributionData?> Manual fallback; auto-capture is the default.
requestTrackingAuthorization() Future<String> iOS ATT status; not_applicable on Android.
onDeepLink Stream<AttributionData> Broadcast stream of resolved deep links.

Troubleshooting

[AppSynk] Native plugin unavailable → The native plugin isn't linked. iOS: re-run flutter run (Flutter runs pod install). Android: rebuild. Every call no-ops (without crashing) until the plugin is present.

Invalid API key / app not found → Check the key (live vs sandbox) and that your bundle ID matches the one registered in the AppSynk dashboard. Enable logLevel: AppSynkLogLevel.debug to see the native validation log.

Events don't appear → Events are batched (every flushInterval seconds or every batchSize events). Call AppSynk.flush() to force a send. In sandbox, events appear under Sandbox data in the dashboard.

Deep links don't open the app → iOS: confirm the Associated Domains capability is enabled and apple-app-site-association is served on go.appsynk.io. Android: confirm the intent filters are on the launcher activity and (for App Links) that assetlinks.json is served.


License

MIT

Libraries

appsynk
AppSynk — Flutter SDK for the AppSynk mobile attribution platform.