appsynk 1.0.0
appsynk: ^1.0.0 copied to clipboard
AppSynk Mobile Attribution SDK — bridge over the native iOS/Android SDKs.
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
AppSynkpod is resolved by CocoaPods on the nextflutter run(Flutter runspod installfor you). You don't evencd ios. - Android — autolinking + Maven Central resolve
io.appsynk:sdk. Nothing to do.
Prerequisite. The Flutter plugin depends on the published native SDKs: the
AppSynkpod on the CocoaPods trunk andio.appsynk:sdkon 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>
4. Associated Domains (universal / App Links) #
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.entitlementsis included inexample/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>
2. Deep-link intent filters #
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