@appsynk/react-native-sdk
v2.0.3
Published
AppSynk Mobile Attribution SDK for React Native
Maintainers
Readme
@appsynk/react-native-sdk
Official React Native SDK for AppSynk — the Mobile Attribution Platform.
This package is a thin bridge 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 JS layer only forwards your calls.
- iOS 14+
- Android 5+ (API 21+)
- React Native ≥ 0.72 (classic bridge —
NativeModules+NativeEventEmitter)
Installation
Bare React Native
npm install @appsynk/react-native-sdk
cd ios && pod install # CocoaPods resolves the native 'AppSynk' pod automatically
# Android: nothing to do — autolinking + Maven Central resolve 'io.appsynk:sdk'That's it — two commands. There are no extra peer dependencies to install: the native SDKs own storage, device info, advertising identifiers, the Play Install Referrer, SKAdNetwork and fraud detection.
Expo
⚠️ Expo Go is not supported. Like Adjust / AppsFlyer / LinkRunner, this SDK ships custom native modules, which Expo Go cannot load. You need a Dev Client.
npx expo install @appsynk/react-native-sdkAdd the config plugin to app.json / app.config.js:
{
"expo": {
"plugins": [
["@appsynk/react-native-sdk", {
"userTrackingUsageDescription": "We use this data to measure our campaigns.",
"facebookAppId": "1234567890",
"deepLinkHost": "go.appsynk.io"
}]
]
}
}Then generate the native project and build a Dev Client:
npx expo prebuild
# then: eas build --profile development (or: npx expo run:ios / run:android)expo prebuild injects everything the native SDKs require — no manual native
edits:
- iOS —
NSUserTrackingUsageDescription(ATT),SKAdNetworkItems,CFBundleURLTypes(appsynk://), and theapplinks:Associated Domain. - Android —
INTERNET/ACCESS_NETWORK_STATE/AD_IDpermissions, the Meta<queries>, the Facebook App ID<meta-data>, the backup rules, and the deep-link intent-filters (App Link +appsynk://).
Plugin options
| Option | Platform | Description |
| --- | --- | --- |
| userTrackingUsageDescription | iOS | ATT prompt copy. Omit it if you never call requestTrackingAuthorization(). |
| facebookAppId | Android | Facebook App ID for Meta view-through (Install Referrer). |
| deepLinkHost | both | App Link / Associated Domain host. Default go.appsynk.io. |
| skAdNetworkIdentifiers | iOS | Override the default SKAdNetwork ID list. |
Quickstart
import AppSynk from '@appsynk/react-native-sdk';
import { Linking } from 'react-native';
// 1. Init — one line, in your entry point (index.js / App.tsx)
AppSynk.configure('ak_live_xxxxxxxxxxxxxxxxxxxxxxxx');
// 2. Deep links
Linking.getInitialURL().then((url) => url && AppSynk.handleDeepLink(url));
const sub = Linking.addEventListener('url', (e) => AppSynk.handleDeepLink(e.url));
// 3. Events
AppSynk.trackEvent('level_complete', { level: 5 });
AppSynk.trackRevenue(9.99, 'EUR', 'premium_monthly', 'ORDER_123');
// 4. Attribution + ATT (iOS)
const attribution = await AppSynk.getAttributionData();
const status = await AppSynk.requestTrackingAuthorization();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_).
Configuration
import AppSynk, { AppSynkOptions } from '@appsynk/react-native-sdk';
const options: AppSynkOptions = {
logLevel: 'debug', // 'none' | 'error' | 'debug' | 'verbose'
// environment: 'sandbox', // optional — auto-detected from the key prefix
// sessionTimeout: 1800, // seconds (default 1800)
// batchSize: 10, // events per flush (default 10)
// flushInterval: 30, // seconds (default 30)
// anonymizeUserByDefault: true, // start anonymized until consent (see GDPR)
};
AppSynk.configure('ak_sandbox_xxxxxxxxxxxxxxxxxxxxxxxx', options);Options mirror the native AppSynkOptions 1:1 (durations are 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 | 'production' \| 'sandbox' | auto | Detected from the key prefix when omitted. |
| logLevel | 'none' \| 'error' \| 'debug' \| 'verbose' | 'none' | |
| customApiUrl | string | — | Data residency / self-hosted gateway. |
| sessionTimeout | number (s) | 1800 | Inactivity before a new session. |
| batchSize | number | 10 | Events buffered before a flush. |
| flushInterval | number (s) | 30 | Auto-flush interval. |
| attWaitTimeout | number (s) | 60 | iOS — wait for the ATT decision before the gated install. |
| referrerWaitTimeout | number (s) | 10 | Android — wait for Play Referrer + GAID before the install. |
| disableIdfa / disableIdfv / disableAdServices / disableSKAdNetwork | boolean | false | iOS privacy toggles. |
| disableGaid / disableAndroidId / disableMetaReferrer | boolean | false | Android privacy toggles. |
| anonymizeUserByDefault | boolean | false | Start anonymized until consent. |
| hmacSecretKey / hmacKeyId | string | — | Optional HMAC-SHA256 request signing. |
Events
import AppSynk, { STANDARD_EVENTS } from '@appsynk/react-native-sdk';
// Simple event
AppSynk.trackEvent('tutorial_completed');
// Event with properties
AppSynk.trackEvent('level_completed', {
level: 5,
score: 1250,
difficulty: 'hard',
});STANDARD_EVENTS exposes the canonical event names to avoid typos.
Revenue
trackRevenue records a one-time purchase. orderId is required and
de-duplicated for 24 h natively.
AppSynk.trackRevenue(9.99, 'EUR', 'premium_monthly', 'ORDER_123');
// Ad impression revenue
AppSynk.trackAdRevenue({
network: 'admob',
amount: 0.0042,
currency: 'USD',
adUnit: 'banner_main',
adType: 'banner', // 'banner' | 'interstitial' | 'rewarded'
});For subscriptions, renewals, trials and refunds, use trackEvent with the
standard event name (the convention the backend understands):
import AppSynk, { STANDARD_EVENTS } from '@appsynk/react-native-sdk';
AppSynk.trackEvent(STANDARD_EVENTS.SUBSCRIPTION_STARTED, {
amount: 9.99,
currency: 'EUR',
product_id: 'premium_monthly',
order_id: 'SUB_001',
period: 'monthly',
});
AppSynk.trackEvent(STANDARD_EVENTS.REFUND, {
amount: 9.99,
currency: 'EUR',
product_id: 'premium_monthly',
order_id: 'SUB_001',
});User identification
Call this after login so you don't pollute the install event.
AppSynk.setUserId('user_internal_12345');
AppSynk.setUserProperties({
plan: 'premium',
country: 'FR',
});
// On logout — clears userId + user properties, keeps the stable device_id
AppSynk.reset();Deep linking
import { Linking } from 'react-native';
import AppSynk from '@appsynk/react-native-sdk';
useEffect(() => {
// Cold start
Linking.getInitialURL().then((url) => {
if (url) AppSynk.handleDeepLink(url);
});
// Warm start
const sub = Linking.addEventListener('url', ({ url }) => {
AppSynk.handleDeepLink(url);
});
// Deferred deep links (resolved after install attribution)
const dl = AppSynk.addDeepLinkListener((data) => {
if (data.deepLink) navigation.navigate(data.deepLink);
});
return () => {
sub.remove();
dl.remove();
};
}, []);handleDeepLink(url) returns the resolved attribution; addDeepLinkListener
fires for deferred deep links (resolved by the backend after install).
addAttributionListener fires when attribution becomes available.
const attribution = await AppSynk.getAttributionData();
if (attribution && !attribution.isOrganic) {
console.log('Channel:', attribution.channel); // e.g. "tiktok_ads"
console.log('Campaign:', attribution.campaignName);
console.log('Deep link:', attribution.deepLink);
}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 the first launch.
// after onboarding
const status = await AppSynk.requestTrackingAuthorization();
// 'authorized' | 'denied' | 'restricted' | 'not_determined' (iOS)
// 'not_applicable' (Android)Set userTrackingUsageDescription (Expo plugin) or NSUserTrackingUsageDescription
(bare) so the prompt can be shown. Without ATT, AppSynk attributes using the IDFV
plus probabilistic matching, and SKAdNetwork keeps working independently.
Privacy & GDPR
// Collect nothing identifying until consent: start anonymized
AppSynk.configure('ak_live_xxx', { anonymizeUserByDefault: true });
// When the user accepts your consent banner
AppSynk.setConsent({
isUserSubjectToGDPR: true,
hasConsentForDataUsage: true,
hasConsentForAdsPersonalization: true,
});
AppSynk.anonymizeUser(false); // resume sending identifiers
// If the user refuses ad personalization
AppSynk.setConsent({
isUserSubjectToGDPR: true,
hasConsentForDataUsage: true, // analytics OK
hasConsentForAdsPersonalization: false, // no ad targeting
});
AppSynk.anonymizeUser(true);anonymizeUser(true)strips advertising identifiers from future events and marks them anonymized. Data already sent is unaffected.- When
hasConsentForAdsPersonalizationisfalse, the backend skips Meta / TikTok postbacks that carry personal identifiers.
API reference
| Method | Returns | Notes |
| --- | --- | --- |
| configure(apiKey, options?) | void | Call once at startup. |
| trackEvent(name, properties?) | void | |
| trackRevenue(amount, currency, productId, orderId) | void | 24 h dedup on orderId. |
| trackAdRevenue(params) | void | { network, amount, currency, adUnit?, adType? } |
| setUserId(userId) | void | |
| setUserProperties(properties) | void | |
| setConsent(consent) | void | { isUserSubjectToGDPR, hasConsentForDataUsage, hasConsentForAdsPersonalization } |
| anonymizeUser(enabled) | void | |
| reset() | void | Clears identity, keeps device_id. |
| flush() | void | No-op on iOS (auto-flush). |
| getAttributionData() | Promise<AttributionData \| null> | |
| getDeepLinkData() | Promise<AttributionData \| null> | |
| handleDeepLink(url) | Promise<AttributionData \| null> | |
| requestTrackingAuthorization() | Promise<string> | iOS ATT status; 'not_applicable' on Android. |
| addDeepLinkListener(cb) | EmitterSubscription | .remove() to unsubscribe. |
| addAttributionListener(cb) | EmitterSubscription | .remove() to unsubscribe. |
Troubleshooting
[AppSynk] Native module 'AppSynkBridge' not found
→ The native module isn't linked. Bare: re-run cd ios && pod install and
rebuild; on Android, rebuild the app. Expo: you must run npx expo prebuild and
launch a Dev Client — the SDK does not run in Expo Go. Every call no-ops
(without crashing) until the native module 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: '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.
GAID is null on Android → The user opted out of ad personalization. This is expected — AppSynk falls back to probabilistic attribution.
