- AppsFlyerSdk
- AFMediationNetwork
- AFLogLevel
- AFPurchaseDetails
- AFAndroidPurchaseDetails
- AFIOSPurchaseDetails
- AFPurchaseType
- AppsFlyerInviteLinkParams
- DeepLinkResult
- DeepLinkStatus
- DeepLinkFailure
- DeepLink
- AppsFlyerException
Call these methods from the main isolate. From a background isolate that has not
been initialized with
BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken), the call
throws AppsFlyerException — see
Getting started → Background isolates.
- init
- enableDebug
- setLogLevel
- start
- registerConversionListener
- unregisterConversionListener
- registerDeepLinkListener
- unregisterDeepLinkListener
- registerSessionReadyListener
- unregisterSessionReadyListener
- isSessionReady
- logEvent
- logLocation
- logSession
- collectDataFromLauncherActivity
- anonymizeUser
- setMinTimeBetweenSessions
- stop
- setCurrencyCode
- setIsUpdate
- setCustomerUserId
- setAdditionalData
- setCollectAndroidID
- setImeiData
- setOaidData
- setAndroidIdData
- setHost
- getHostName
- getHostPrefix
- updateServerUninstallToken
- Validate Purchase
- validateAndLogInAppPurchase
- setUseReceiptValidationSandbox
- setUseUninstallSandbox
- sendPushNotificationData
- handlePushNotification
- addPushNotificationDeepLinkPath
- User Invite
- setAppInviteOneLink
- generateInviteLink
- enableFacebookDeferredApplinks
- setFacebookDeferredAppLink
- enableTCFDataCollection
- setConsentData
- setDisableSKAdNetwork
- setDisableAppleAdsAttribution
- setDisableIDFVCollection
- setShouldCollectDeviceName
- isStopped
- getAppsFlyerUID
- isPreInstalledApp
- getAttributionId
- setCurrentDeviceLanguage
- setInstallId
- setPreinstallAttribution
- setAppId
- setSharingFilterForPartners
- setOneLinkCustomDomain
- setDisableAdvertisingIdentifiers
- setDisableCollectASA
- setPartnerData
- setResolveDeepLinkURLs
- setOutOfStore
- getOutOfStore
- setDisableNetworkData
- disableAppSetId
- performDeepLinking
- appendParametersToDeepLinkingURL
- setDeepLinkTimeout
- Hashed PII setters
- logInvite
- Cross promotion
- logCrossPromoteImpression
- logAndOpenStore
- logAdRevenue
- getSdkVersion
- pluginVersion
Some methods below are marked (Android only) or (iOS only) because only one native SDK implements them.
Calling one on the other platform throws an
AppsFlyerException. The plugin does not keep its own
list of which platform supports what — every call is forwarded and the native
layer answers, so the API surface stays correct as the native SDKs change.
The exception's code therefore comes from the native layer, which reports
404 for a method it does not implement on both platforms. Match on the
platform-only marker in this document rather than on the code.
Guard these calls with Platform.isAndroid / Platform.isIOS, or let the
exception propagate if the call is not essential on that platform.
The native AppsFlyer SDK is process-scoped. The Flutter plugin keeps one native bridge, one event delivery subscription, and one native listener reference per event type for the whole process — the same contract as the native SDKs themselves.
Each FlutterEngine loads its own plugin instance and Dart isolate.
AppsFlyerSdk.instance is a singleton within that isolate only.
Sequential engine lifecycle (one engine destroyed, another created later) is
supported: re-run register*Listener() after the new engine attaches. Native
configuration survives; you are reconnecting Dart callbacks to the existing
bridge. See Getting started → Add-to-app and multiple Flutter engines.
Concurrent multi-engine hosts (two or more engines alive at once — add-to-app
with overlapping Flutter routes, FlutterEngineGroup, multi-scene) are not
supported for event delivery:
- The engine whose EventChannel subscription attached most recently is the only one that receives conversion, deep-link, and session-ready callbacks.
- The last
register*Listener()call from any engine wins at the native SDK and replaces earlier registrations. init()andstart()must be driven from one primary engine; all engines share the same native SDK instance.
Integrate AppsFlyer from a single Flutter entry point. Do not call init() or
register listeners from secondary engines that may run in parallel.
AppsFlyerSdk is the cross-platform SDK entry point. Use its shared
instance; configuration is exposed through explicit methods.
Example:
import 'package:appsflyer_sdk/appsflyer_sdk.dart';
final AppsFlyerSdk appsflyerSdk = AppsFlyerSdk.instance;Once the instance is obtained, call init.
an enumeration that includes the supported mediation networks by AppsFlyer.
| networks |
|---|
| ironSource |
| applovinMax |
| googleAdMob |
| fyber |
| appodeal |
| admost |
| topon |
| tradplus |
| yandex |
| chartboost |
| unity |
| toponPte |
| customMediation |
| directMonetizationNetwork |
An enumeration of the Android SDK logging levels: none, error, warning,
info, debug, and verbose.
Contains a DeepLinkStatus, optional DeepLink, and optional
DeepLinkFailure. Android supplies a stable failure type; iOS supplies a
message.
The deep-link resolution status: found, notFound, error, or unknown.
Contains an optional Android error type or optional iOS error message.
Provides the full clickEvent map, getStringValue(String key), and typed
getters for common Unified Deep Linking values such as deepLinkValue,
mediaSource, campaign, campaignId, afSub1 through afSub5, and
isDeferred. isDeferred is populated on both Android and iOS: it is true
for a link resolved through deferred deep linking (first launch after install)
and false for a direct click into an installed app.
Contains an optional numeric error code and message. Native failures can use
HTTP-style codes (400, 404, 422, 500, …). When the platform supplies a
non-numeric code, code is null and [message] carries the failure text.
Plugin transport failures such as SERIALIZATION_ERROR and malformed native
response payloads use non-numeric codes, so [code] is null. On iOS,
SERIALIZATION_ERROR is raised when a request payload cannot be encoded as JSON
(for example non-finite double values in eventValues). Android does not use
that code today — the same payload may surface as UNEXPECTED_ERROR or a
numeric native error instead.
Calling a platform-only API on the wrong platform throws
AppsFlyerException. The plugin forwards every call to
the native layer, which reports 404 for an unimplemented method on both
platforms. 422 means the method exists but rejected its parameters.
Match on the platform-only marker in this document rather than on the code.
See Platform-only APIs.
Initializes the native SDK without sending a session. appId is the Apple App
ID required by the native iOS SDK. It is optional and is not sent to the native SDK on Android.
Invalid devKey or appId values are validated by the native layer and
reported as AppsFlyerException (typically code 422) when the native layer
rejects the request.
Example:
import 'package:appsflyer_sdk/appsflyer_sdk.dart';
//..
final appsflyerSdk = AppsFlyerSdk.instance;
await appsflyerSdk.init(
devKey: '<DEV_KEY>',
appId: '<APP_ID>',
);Register conversion, deep-link, and session-ready listeners explicitly with their corresponding registration methods.
Future<void> enableDebug(bool enabled)
Enables or disables debug logging, in the native SDK and in the plugin alike.
Enable it only for development and troubleshooting. May be called before
init; call before start so the first session uses the
selected setting.
In release builds the plugin's Dart-side diagnostics are printed only after
enableDebug(true). Debug builds always print them. The setting applies to the
isolate that calls it. It does not gate the plugin's native error logging, which
is always written to logcat on Android and to the system log on iOS.
await appsflyerSdk.enableDebug(true); Future<void> setLogLevel(AFLogLevel logLevel) — Android only
Sets the Android SDK logging level. On iOS the call throws an
AppsFlyerException — see Platform-only APIs. Use
enableDebug for a cross-platform debug toggle.
await appsflyerSdk.setLogLevel(AFLogLevel.debug);Registers the native conversion-data listener and the callbacks that receive its results.
| Parameter | Type | Description |
|---|---|---|
onConversionDataSuccess |
void Function(Map<String, dynamic> data)? |
Optional. Receives the conversion data (GCD). |
onConversionDataFail |
void Function(Map<String, dynamic> error)? |
Optional. Receives conversion-data retrieval failures. |
await appsflyerSdk.registerConversionListener(
onConversionDataSuccess: (data) {
print("conversion data: $data");
},
onConversionDataFail: (error) {
print("conversion data failed: $error");
},
);The plugin holds one callback per event and replaces it when
registerConversionListener() is called again, matching the native SDKs. There
is no stream to subscribe to, so one native event can never be delivered to two
places in your app.
Every register*Listener() and unregister*Listener() call is atomic: if it
throws, the callbacks you had before are still installed and still receiving
events, so a failed re-registration never leaves your app without a listener.
onConversionDataFail is independent of the registration result: this call can succeed
while the native SDK still fails to retrieve conversion data. The failure payload
shape differs by platform — Android reports {"error": String} with no error
code; iOS reports {"error": String, "code": int}. Android also exposes
unregisterConversionListener() to remove its
native listener; iOS has no corresponding unregister operation.
Future<void> unregisterConversionListener() — Android only
Unregisters the native Android conversion-data listener and drops the callbacks
passed to registerConversionListener(). Call registerConversionListener()
again to resume receiving conversion-data events. On iOS it throws an
AppsFlyerException — see Platform-only APIs. A failed
call leaves the callbacks in place, so nothing changes.
await appsflyerSdk.unregisterConversionListener(); Future<void> registerDeepLinkListener({OnDeepLinkReceived? onDeepLinking})
Registers the native Unified Deep Linking listener and the callback that receives its results.
| Parameter | Type | Description |
|---|---|---|
onDeepLinking |
void Function(DeepLinkResult result)? |
Optional. Receives every resolved deep link, deferred or direct. Omit it to register the native listener without a handler. |
await appsflyerSdk.registerDeepLinkListener(onDeepLinking: (result) {
print("result: $result");
});Call this before init. On Android, init() hands the launch
intent to the native SDK, which decides once per install whether to send the
deferred deep-link resolution request; registering afterwards means that request
is never sent for that install, and the skipped state persists across launches.
Direct links are unaffected. Registration before init() is supported on both
platforms.
Calling this again replaces the callback.
Future<void> unregisterDeepLinkListener() — Android only
Requests that Android stop forwarding Unified Deep Linking events and drops the
callback passed to registerDeepLinkListener(). In the
current Android integration, subsequent events may still be delivered; do not
rely on this method to disable deep-link handling. On iOS it throws an
AppsFlyerException — see Platform-only APIs. A failed
call leaves the callback in place, so nothing changes.
await appsflyerSdk.unregisterDeepLinkListener();In SDK 7, init(...) only initializes the SDK; it does not send a session
(Launch). Call start() to report one — initialization never triggers a session
automatically.
When awaitResponse is true, the Future completes when the native request
succeeds and throws AppsFlyerException when it fails. Android times out after
5 seconds, iOS after 10. A timeout does not cancel the native request, which may
still succeed later.
When awaitResponse is false (default), the Future completes when the
native SDK accepts the request. Delivery success or failure is not reported.
| parameter | type | description |
|---|---|---|
awaitResponse |
bool |
Optional. Defaults to false. When true, wait for the native request callback. When false, return when the native SDK accepts the request. |
start() must be called once per foreground cycle. The native SDK resets its "started" state every time the app is backgrounded, so a single start() at launch reports only the first session — subsequent foregrounds send nothing. Register the session-ready listener, whose callback fires once per foreground cycle when the native SDK's session-readiness conditions are satisfied:
// Recommended SDK 7 pattern: start on every session-ready signal.
await appsflyerSdk.registerSessionReadyListener(() async {
await appsflyerSdk.start();
});Gate the first session by deferring the start() call inside the callback.
Future<String?> getAttributionId() — returns the Facebook (Katana) attribution ID the SDK reads from the installed Facebook app's on-device content provider (also attached to attribution payloads automatically). Most apps never need it directly; exposed for parity with the native SDK. Android only — calling it on iOS throws AppsFlyerException when the native layer reports the method as unavailable.
Example:
appsFlyerSdk.getAttributionId().then((id) {
print("Facebook attribution ID: $id");
}); Future<void> logEvent(String eventName, {Map<String, dynamic>? eventValues, bool awaitResponse = false})
- These in-app events help you to understand how loyal users discover your app, and attribute them to specific campaigns/media-sources. Please take the time define the event/s you want to measure to allow you to send ROI (Return on Investment) and LTV (Lifetime Value).
- The
logEventmethod allows you to send in-app events to AppsFlyer analytics. This method allows you to add events dynamically by adding them directly to the application code. - Result reporting mirrors
start; both acceptawaitResponseand default to fire-and-forget acceptance by the native SDK. WithawaitResponse: true, Android times out after 5 seconds, iOS after 10.
| parameter | type | description |
|---|---|---|
eventName |
String |
Use descriptive, action-based names (e.g., "purchase", "add_to_cart", "level_completed"), keep names concise but meaningful, use lowercase with underscores for consistency and avoid special characters and spaces. See the recommended event list by business. |
eventValues |
Map<String, dynamic>? |
Optional named event details. Values must be JSON-serializable. Non-finite numbers (double.nan, double.infinity) are rejected on iOS with AppsFlyerException (SERIALIZATION_ERROR, code is null). On Android the same payload may fail with a different code (UNEXPECTED_ERROR or a numeric native error). Validate with value.isFinite before sending. |
awaitResponse |
bool |
Optional named parameter. Defaults to false. When true, wait for the native request callback. When false, return when the native SDK accepts the request. |
Example:
try {
await appsflyerSdk.logEvent(
eventName,
eventValues: eventValues,
awaitResponse: true,
);
print("logEvent success");
} on AppsFlyerException catch (error) {
print("logEvent error: $error");
}
// Fire-and-forget:
await appsflyerSdk.logEvent(
eventName,
eventValues: eventValues,
awaitResponse: false,
);Manually logs the device location for the current user. The Future completes
when the native SDK accepts the fire-and-forget call. Supported on Android
and iOS. latitude must be between -90 and 90, and longitude must be between
-180 and 180.
await appsflyerSdk.logLocation(
latitude: 32.0853,
longitude: 34.7818,
);Manually logs a session on Android. For typical Flutter apps, call
start from the
registerSessionReadyListener callback instead.
Android only; on iOS the call throws an AppsFlyerException — see
Platform-only APIs.
await appsflyerSdk.logSession();Collects the open/web referrer from the launcher activity's intent. Call it
before start so the referrer reaches the first session.
Android only; on iOS the call throws an AppsFlyerException — see
Platform-only APIs.
Requires an attached activity. When the plugin has none — for example in a
headless or background engine — the native layer rejects the call with
AppsFlyerException (code 422).
await appsflyerSdk.collectDataFromLauncherActivity();In SDK 7 the plugin initializes on init; a session is sent when
the app calls start. Because the native SDK requires start() once
per foreground cycle, register the session-ready listener and call start() from
its callback.
Future<void> registerSessionReadyListener(OnSessionReady onReady)
Enables the native readiness event and registers its callback.
| Parameter | Type | Description |
|---|---|---|
onReady |
void Function() |
Required. Called once per foreground cycle when the native session-readiness conditions are satisfied. |
These conditions can include bounded launch deep-link processing. Call start()
from the callback so every foreground reports a session.
await appsflyerSdk.registerSessionReadyListener(() async {
await appsflyerSdk.start();
});Calling this again replaces the callback, so start() is never issued twice for
one readiness event.
Platform support: Android ✓ · iOS ✓.
Future<void> unregisterSessionReadyListener() — removes the native readiness listener and drops its callback (Android ✓ · iOS ✓).
Future<bool> isSessionReady() — returns whether all
session-readiness conditions are currently satisfied. Supported on both platforms.
await appsFlyerSdk.registerSessionReadyListener(() => print("session ready"));
final ready = await appsFlyerSdk.isSessionReady();The runtime/session configuration setters listed below are not a substitute for
persistent application configuration. Re-apply them when the application
process starts. Individual APIs can have different storage behavior; for
example, Android persists the custom value supplied through setInstallId.
Re-apply your configuration setters on every cold start, before
start, so they attach to that launch event:
// Runs on every cold start (e.g. from initState), before the first start().
await appsFlyerSdk.setCustomerUserId("user-42");
await appsFlyerSdk.setCurrencyCode("EUR");
await appsFlyerSdk.setAdditionalData({"tenant": "eu"});
await appsFlyerSdk.setConsentData(
isUserSubjectToGDPR: false,
hasConsentForDataUsage: false,
hasConsentForAdsPersonalization: false,
hasConsentForAdStorage: false,
);Within a running process the values persist across background→foreground, so you
only re-apply them once per cold start — not on every start(). Setters
that follow this rule include setCustomerUserId,
setCurrencyCode, setAdditionalData,
setConsentData, anonymizeUser,
setSharingFilterForPartners,
setHost, and the hashed-PII setters.
The email, phone, first-name, and last-name setters normalize and hash (SHA-256) their values on-device before sending them to AppsFlyer. The Facebook App-Scoped ID is numeric and is not hashed. These APIs are supported on Android and iOS.
await appsFlyerSdk.setUserEmail("a@a.com");
await appsFlyerSdk.setUserPhone("1", "5551234567");
await appsFlyerSdk.clearUserPii(); Future<void> anonymizeUser(bool shouldAnonymize)
It is possible to anonymize specific user identifiers within AppsFlyer analytics.
This complies with both the latest privacy requirements (GDPR, COPPA) and Facebook's data and privacy policies. To anonymize an app user.
| parameter | type | description |
|---|---|---|
| shouldAnonymize | boolean | True if want Anonymize user Data (default value is false). |
Example:
await appsFlyerSdk.anonymizeUser(true); Future<void> setMinTimeBetweenSessions(int seconds)
You can set the minimum time between session (the default is 5 seconds)
await appsFlyerSdk.setMinTimeBetweenSessions(3); Future<void> stop(bool shouldStop)
You can stop sending events to Appsflyer by using this method.
Example:
await widget.appsFlyerSdk.stop(true);Returns whether the SDK is currently stopped (see stop).
Example:
final stopped = await appsFlyerSdk.isStopped(); Future<void> setCurrencyCode(String currencyCode)
Example:
await appsFlyerSdk.setCurrencyCode("USD"); Future<void> setIsUpdate(bool isUpdate) — Android only
Example:
await appsFlyerSdk.setIsUpdate(true); Future<void> enableTCFDataCollection(bool shouldCollect)
The enableTCFDataCollection method is employed to control the automatic collection of the Transparency and Consent Framework (TCF) data. By setting this flag to true, the system is instructed to automatically collect TCF data. Conversely, setting it to false prevents such data collection.
Example:
await appsFlyerSdk.enableTCFDataCollection(true);
Future<void> setConsentData({required bool isUserSubjectToGDPR, bool? hasConsentForDataUsage, bool? hasConsentForAdsPersonalization, bool? hasConsentForAdStorage})
The named parameters provide manual GDPR and DMA consent data. For a complete workflow, see the DMA compliance documentation.
- Users subjected to GDPR:
await appsflyerSdk.setConsentData(
isUserSubjectToGDPR: true,
hasConsentForDataUsage: true,
hasConsentForAdsPersonalization: true,
hasConsentForAdStorage: true,
);- Users not subject to GDPR:
await appsflyerSdk.setConsentData(
isUserSubjectToGDPR: false,
);The three consent fields may be omitted or null when GDPR does not apply.
When GDPR applies, supply hasConsentForDataUsage and
hasConsentForAdsPersonalization before the first start().
To reflect consent in the conversion payload, configure either
enableTCFDataCollection or setConsentData after initialization and
before the first start():
final appsflyerSdk = AppsFlyerSdk.instance;
await appsflyerSdk.init(
devKey: '<DEV_KEY>',
appId: '<APP_ID>',
);
await appsflyerSdk.setConsentData(
isUserSubjectToGDPR: true,
hasConsentForDataUsage: true,
hasConsentForAdsPersonalization: true,
hasConsentForAdStorage: true,
);
await appsflyerSdk.registerSessionReadyListener(() async {
await appsflyerSdk.start();
});If both TCF collection and explicit consent are used, AppsFlyer backend
prioritizes the explicit data supplied through setConsentData.
Future<void> setCustomerUserId(String customerId)
Example:
await appsFlyerSdk.setCustomerUserId("id"); Future<void> setAdditionalData(Map<String, dynamic> customData)
customData must be non-null; pass an empty map to clear the data.
Example:
var data = {"key1": "value1", "key2": "value2"};
await appsFlyerSdk.setAdditionalData(data);
Future<void> setCollectAndroidID(bool isCollect) — Android only
Example:
await appsFlyerSdk.setCollectAndroidID(true); Future<void> setImeiData(String imei) — Android only
Supplies a pre-collected IMEI value to the SDK.
Example:
await appsFlyerSdk.setImeiData('your-imei-value'); Future<void> setOaidData(String oaid) — Android only
Supplies a pre-collected OAID value to the SDK.
Example:
await appsFlyerSdk.setOaidData('your-oaid-value'); Future<void> setAndroidIdData(String androidId) — Android only
Supplies a pre-collected Android ID value to the SDK.
Example:
await appsFlyerSdk.setAndroidIdData('your-android-id-value'); Future<void> setHost(String hostPrefixName, String hostName)
Changes the default AppsFlyer host. Use this method only when instructed by AppsFlyer Support.
- Android:
hostNamemust be non-empty.hostPrefixNamemay be empty. - iOS: both
hostPrefixNameandhostNamemust be non-empty.
Invalid arguments are reported as AppsFlyerException.
Example:
await appsFlyerSdk.setHost("pref", "my-host");Returns an empty string on iOS when setHost has not been called.
Example:
appsFlyerSdk.getHostName().then((name) {
print("Host name: ${name}");
}); Future<String> getHostPrefix()
Returns an empty string on iOS when setHost has not been called.
Example:
appsFlyerSdk.getHostPrefix().then((name) {
print("Host prefix: ${name}");
}); Future<void> updateServerUninstallToken(String token)
Registers a token for uninstall measurement. Pass an FCM registration token on Android or a hexadecimal APNs device token on iOS.
Token format differs per platform: on Android pass the FCM/GCM registration
token as-is; on iOS pass the APNs device token hex-encoded as an
even-length string (a non-hex string is rejected natively). On iOS,
getAPNSToken() already returns the token in hex form.
Example:
await appsFlyerSdk.updateServerUninstallToken(token);Cross-platform API:
The unified purchase validation API works across Android and iOS. Use the purchase-details implementation for the current platform.
| Parameter | Type | Description |
|---|---|---|
purchase |
AFPurchaseDetails |
An AFAndroidPurchaseDetails or AFIOSPurchaseDetails instance |
additionalParameters |
Map<String, String>? |
Optional additional parameters |
Both platforms always wait for the native validation result, so the Future
completes with that result or throws AppsFlyerException when validation fails.
Android times out after 5 seconds, iOS after 30. When the native reply carries no
payload, the Future completes with an empty map.
| Property | Type | Description |
|---|---|---|
purchaseType |
AFPurchaseType |
Type of purchase (oneTimePurchase or subscription) |
productId |
String |
Product identifier |
| Property | Type | Description |
|---|---|---|
purchaseType |
AFPurchaseType |
Type of Google Play purchase |
purchaseToken |
String |
Google Play purchase token |
productId |
String |
Product identifier |
| Property | Type | Description |
|---|---|---|
purchaseType |
AFPurchaseType |
Type of App Store purchase |
transactionId |
String |
App Store transaction ID |
productId |
String |
Product identifier |
AFPurchaseType.oneTimePurchase- For one-time in-app purchasesAFPurchaseType.subscription- For subscription purchases
Example:
final AFPurchaseDetails purchaseDetails = Platform.isAndroid
? const AFAndroidPurchaseDetails(
purchaseType: AFPurchaseType.oneTimePurchase,
purchaseToken: "your_purchase_token",
productId: "your_product_id",
)
: const AFIOSPurchaseDetails(
purchaseType: AFPurchaseType.oneTimePurchase,
transactionId: "your_transaction_id",
productId: "your_product_id",
);
// Validate purchase
try {
Map<String, dynamic> result = await appsFlyerSdk.validateAndLogInAppPurchase(
purchaseDetails,
additionalParameters: {"custom_param": "value"},
);
print("Validation successful: $result");
} on AppsFlyerException catch (error) {
print("Validation failed: $error");
} on ArgumentError catch (error) {
// A purchase-details object was used on the wrong platform.
print("Invalid purchase details: $error");
}Key Benefits:
- Cross-platform compatibility: Works on both Android and iOS with the same API
- Type safety: Uses structured data classes instead of platform-specific parameters
- Enhanced error handling: Provides detailed error information in structured format (including
NSErrordetails on iOS) - Future-proof: Built on AppsFlyer's latest V2 validation infrastructure
- Platform mapping: Each purchase-details implementation uses the corresponding Android or iOS purchase-validation model
Purchase validation sandbox mode for iOS:
Future<void> setUseReceiptValidationSandbox(bool sandbox) — iOS only
Enables sandbox mode for App Store receipt validation.
Example:
await appsFlyerSdk.setUseReceiptValidationSandbox(true);
Future<void> setUseUninstallSandbox(bool sandbox) — iOS only
Enables sandbox mode for uninstall-measurement validation (companion of setUseReceiptValidationSandbox).
Example:
await appsFlyerSdk.setUseUninstallSandbox(true);See Validate Purchase above for the full validateAndLogInAppPurchase reference — signature, AFPurchaseDetails / AFPurchaseType, example, key benefits, and the iOS sandbox toggles. This anchor is kept for existing links.
Future<void> sendPushNotificationData({required String campaign, required String pid, bool isRetargeting = false, Map<String, dynamic>? additionalParameters}) (Android only)
Push-notification campaigns are used to create re-engagements with existing users → Learn more here
The Android API maps directly to the native AFPushData fields.
Calling it triggers a new Android Launch request even when the SDK already sent
a Launch in the current session.
Passes the push-notification payload to the iOS SDK. Preserve the AppsFlyer
custom-data structure from the notification; native integrations should pass
the complete APNs userInfo dictionary.
🟩 Android:
Call sendPushNotificationData with campaign and pid, plus optional
isRetargeting and additionalParameters values.
🍎 iOS:
Call handlePushNotification with the complete notification payload.
When using firebase_messaging, RemoteMessage.data contains only the custom
data fields, not the complete APNs userInfo dictionary. Ensure that every
AppsFlyer attribution field is present in that custom data. If your provider
encodes a nested object as a JSON string, decode it before reading its fields.
AppsFlyer supports two approaches for measuring push notification campaigns:
Use this approach when your push payload contains a custom af object with attribution parameters.
Required parameters: pid, is_retargeting, c
📦 Example Push Payload with af Object:
{
"af": {
"c": "test_campaign",
"is_retargeting": true,
"pid": "push_provider_int"
},
"aps": {
"alert": "Get 5000 Coins",
"badge": "37",
"sound": "default"
}
}Implementation:
Future<void> passPushToAppsFlyer(Map<String, dynamic> data) async {
if (Platform.isAndroid) {
final rawAf = data['af'];
final decodedAf = rawAf is String ? jsonDecode(rawAf) : rawAf;
final af = Map<String, dynamic>.from(decodedAf as Map);
await appsFlyerSdk.sendPushNotificationData(
campaign: af['c'] as String,
pid: af['pid'] as String,
isRetargeting: af['is_retargeting'] == true,
);
} else if (Platform.isIOS) {
await appsFlyerSdk.handlePushNotification(data);
}
}
// 1️⃣ Handle Foreground Messages
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
await passPushToAppsFlyer(message.data);
});
// 2️⃣ Handle Notification Taps (App in Background)
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
await passPushToAppsFlyer(message.data);
});
// 3️⃣ Handle App Launch from Push (Terminated State)
Future<void> handleInitialPush() async {
final message = await FirebaseMessaging.instance.getInitialMessage();
if (message != null) {
await passPushToAppsFlyer(message.data);
}
}Call handleInitialPush() once during app startup, after Flutter is initialized
and the AppsFlyer SDK setup has completed. getInitialMessage() returns the
notification that opened an app from the terminated state. A Firebase
background-message handler is a separate flow and is not required to handle a
notification tap that launches the app.
Use this approach when your push payload contains a OneLink URL for deep linking. This method provides a unified deep linking experience.
⚠️ Important: This approach requires calling two different methods depending on the platform!
Call addPushNotificationDeepLinkPath before init() to tell AppsFlyer where
to find the OneLink URL in your push payload.
await appsFlyerSdk.addPushNotificationDeepLinkPath(
["deeply", "nested", "deep_link"],
);
await appsFlyerSdk.registerDeepLinkListener(onDeepLinking: (result) {
// Handle deep-link navigation here.
});
await appsFlyerSdk.init(
devKey: '<DEV_KEY>',
appId: '<APP_ID>',
);Push configuration does not replace the SDK 7 session lifecycle. Complete the
normal session-ready → start() setup
so the initial Launch and every later foreground session are reported.
🟩 Android:
On Android, calling addPushNotificationDeepLinkPath is sufficient. The SDK automatically extracts and processes the OneLink URL.
🍎 iOS:
On iOS, you MUST also call handlePushNotification(pushPayload) to pass the push payload to the SDK so it can extract and process the OneLink URL.
📦 Example Push Payload with OneLink URL:
{
"deeply": {
"nested": {
"deep_link": "https://yourapp.onelink.me/ABC/campaign123"
}
},
"aps": {
"alert": "Check out our new feature!",
"badge": "1",
"sound": "default"
}
}Complete Implementation Example:
// ========================================
// 1. Configure SDK (in main.dart or app initialization)
// ========================================
Future<void> initializeAppsFlyer() async {
// STEP 1: Configure the deep-link path before init().
await appsFlyerSdk.addPushNotificationDeepLinkPath(
["deeply", "nested", "deep_link"],
);
// STEP 2: Enable native deep-link events and handle them. Also before init().
await appsFlyerSdk.registerDeepLinkListener(
onDeepLinking: (DeepLinkResult result) {
if (result.status == DeepLinkStatus.found) {
print("Deep link found: ${result.deepLink?.deepLinkValue}");
// Handle deep-link navigation here.
}
});
// STEP 3: Initialize the SDK.
await appsFlyerSdk.init(
devKey: '<DEV_KEY>',
appId: '<APP_ID>',
);
// STEP 4: Register last because this can invoke the callback immediately.
// Required: report a session on every foreground cycle.
await appsFlyerSdk.registerSessionReadyListener(() async {
try {
await appsFlyerSdk.start(awaitResponse: true);
print("AppsFlyer session reported.");
} on AppsFlyerException catch (error) {
print("AppsFlyer start error: $error");
}
});
}
// ========================================
// 2. Handle Push Notifications
// ========================================
// 🍎 iOS: MUST call handlePushNotification
// 🟩 Android: addPushNotificationDeepLinkPath is sufficient
// 1️⃣ Foreground Messages
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
// iOS: Required to process OneLink URL
if (Platform.isIOS) {
await appsFlyerSdk.handlePushNotification(message.data);
}
});
// 2️⃣ Background Notification Taps (App in Background)
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
// iOS: Required to process OneLink URL
if (Platform.isIOS) {
await appsFlyerSdk.handlePushNotification(message.data);
}
});
// 3️⃣ App Launch from Push (Terminated State)
Future<void> handleInitialPush() async {
final message = await FirebaseMessaging.instance.getInitialMessage();
if (message != null) {
// iOS: Required to process OneLink URL from terminated state
if (Platform.isIOS) {
await appsFlyerSdk.handlePushNotification(message.data);
}
}
}Call handleInitialPush() once after initializeAppsFlyer() completes. On
Android, addPushNotificationDeepLinkPath() handles the configured OneLink path;
the explicit terminated-state forwarding above is required only on iOS.
Traditional af Object |
OneLink URL (Recommended) | |
|---|---|---|
| Android | sendPushNotificationData(...) |
addPushNotificationDeepLinkPath() (auto-handles) |
| iOS | handlePushNotification(pushPayload) |
addPushNotificationDeepLinkPath() + handlePushNotification(pushPayload) |
| Deep Linking | Basic attribution only | Full deep linking with the registerDeepLinkListener callback |
| Use Case | Simple re-engagement | Re-engagement + in-app navigation |
- Traditional approach: Call Android
sendPushNotificationData(...)or iOShandlePushNotification(pushPayload) - OneLink approach (Recommended):
- ✅ Both platforms: Call
addPushNotificationDeepLinkPath()andregisterDeepLinkListener()before SDK init - ✅ iOS only: Also call
handlePushNotification(pushPayload)when push is received - ✅ Both platforms: Handle deep links in the
registerDeepLinkListenercallback
- ✅ Both platforms: Call
Registers a custom key path for resolving deep links inside custom JSON payloads in push notifications.
This is the recommended method of integrating AppsFlyer with push notifications. Learn more here.
⚠️ Call this method beforeinit().deepLinkPathmust not be empty.⚠️
Example:
await appsFlyerSdk.addPushNotificationDeepLinkPath(
["deeply", "nested", "deep_link"],
);With this configuration, the SDK will extract the URL from the following push payload:
{
"deeply": {
"nested": {
"deep_link": "https://yourdeeplink2.onelink.me"
}
}
}- First define the Onelink ID (find it in the AppsFlyer dashboard in the onelink section:
Future<void> setAppInviteOneLink(String oneLinkId)
- Set the
AppsFlyerInviteLinkParamsclass to set the query params in the user invite link:
class AppsFlyerInviteLinkParams {
final String? channel;
final String? campaign;
final String? referrerName;
final String? referrerImageUrl;
final String? referrerCustomerId;
final String? baseDeepLink;
final String? brandDomain;
final Map<String, String>? userParams;
}- Call the generateInviteLink API to generate the user invite link.
The Future completes with the generated URL. Native generation failures are
reported as AppsFlyerException. On Android, awaitResponse: true waits for
asynchronous short-link generation and false returns the synchronously
generated long link. On iOS, link generation always waits for the asynchronous
result. Both platforms time out after 10 seconds.
| Parameter | Type | Description |
|---|---|---|
parameters |
AppsFlyerInviteLinkParams? |
Optional OneLink generation parameters |
awaitResponse |
bool |
Optional. Defaults to true. Android honors this value; iOS always waits for completion. |
Example:
await appsFlyerSdk.setAppInviteOneLink('OnelinkID');
const AppsFlyerInviteLinkParams inviteLinkParams = AppsFlyerInviteLinkParams(
channel: "",
referrerName: "",
baseDeepLink: "",
brandDomain: "",
referrerCustomerId: "",
referrerImageUrl: "",
campaign: "",
userParams: {"key":"value"}
);
final url = await appsFlyerSdk.generateInviteLink(
parameters: inviteLinkParams,
awaitResponse: true,
);
print(url);- Log the
af_inviteevent when the user actually shares the invite:
Future<void> logInvite(String channel, [Map<String, String>? eventParameters])
Logs the af_invite in-app event so AppsFlyer can attribute the invite and any
downstream installs to the referring user. The returned Future<void> completes
after the native SDK accepts the logging call. The native API does not
provide a network-completion callback. Supported on Android and iOS.
Example:
await appsFlyerSdk.logInvite("facebook", {"referrerId": "user-123"});Records an impression for a promoted app:
await appsFlyerSdk.logCrossPromoteImpression(
"promoted.app.id",
campaign: "summer",
userParams: {"source": "banner"},
);Records the promotion and asks the native SDK to open the promoted app's store page.
The returned Future means different things per platform. On Android it
completes as soon as the native SDK has been invoked, without waiting for the
store to open. On iOS the plugin must first obtain the click URL from the native
SDK and then open it, so the Future completes only after the store has been
asked to open; that click-URL step times out after 10 seconds and raises
AppsFlyerException. A timeout does not cancel the native request. If iOS
returns no click URL, the Future still completes successfully but nothing
opens. The click URL itself is never returned to Dart.
await appsFlyerSdk.logAndOpenStore(
"promoted.app.id",
campaign: "summer",
userParams: {"source": "banner"},
);Both APIs are supported on Android and iOS.
Future<void> enableFacebookDeferredApplinks(bool isEnabled)
Please make sure the relevant Facebook dependecies are added to the project!
Call this method before init().
For more information check the following article: https://support.appsflyer.com/hc/en-us/articles/207033826-Facebook-Ads-setup-guide#advanced-using-facebook-ads-appsflyer-sdks-for-deferred-deep-linking
Example:
await appsFlyerSdk.enableFacebookDeferredApplinks(true); Future<void> setFacebookDeferredAppLink(String? url) (iOS only)
Manually sets — or, with null, clears — the Facebook deferred app-link URL.
On Android the call throws an AppsFlyerException — see
Platform-only APIs.
Use this only when you already hold the deferred link and want to skip the Facebook SDK lookup; otherwise prefer enableFacebookDeferredApplinks(true).
Example:
await appsFlyerSdk.setFacebookDeferredAppLink(
"https://myapp.onelink.me/abc123",
);
Future<void> setDisableSKAdNetwork(bool disable) — iOS only
Use this API in order to disable the SK Ad network (request will be sent but
the rules won't be returned). On Android the call throws an AppsFlyerException
— see Platform-only APIs.
Example:
await appsFlyerSdk.setDisableSKAdNetwork(true);
Future<void> setDisableAppleAdsAttribution(bool disable) — iOS only
Disables Apple Ads (Apple Search Ads) attribution via the AdServices framework
— pass true to stop the SDK from calling
AAAttribution.attributionToken (iOS 14.3+). On Android the call throws an
AppsFlyerException — see Platform-only APIs.
Example:
if (Platform.isIOS) {
await appsFlyerSdk.setDisableAppleAdsAttribution(true);
}
Future<void> setDisableIDFVCollection(bool disable) — iOS only
Disables collection of the IDFV (Identifier for Vendor) — pass true to stop
the SDK from collecting it. Set it before start(). On Android the call throws
an AppsFlyerException — see Platform-only APIs.
Example:
if (Platform.isIOS) {
await appsFlyerSdk.setDisableIDFVCollection(true);
} Future<void> setShouldCollectDeviceName(bool collect) — iOS only
Enables collection of the device name (e.g. "John's iPhone"). This is an
opt-in — collection is off by default and the device name is personal
data (PII), so only enable it if your privacy policy covers it. Pass true to
start collecting it. On Android the call throws an AppsFlyerException — see
Platform-only APIs.
Example:
if (Platform.isIOS) {
await appsFlyerSdk.setShouldCollectDeviceName(true);
} Future<String?> getAppsFlyerUID()
Use this API in order to get the AppsFlyer ID.
Example:
appsFlyerSdk.getAppsFlyerUID().then((AppsFlyerId) {
print("AppsFlyer ID: ${AppsFlyerId}");
}); Future<String> getSdkVersion()
Returns the native AppsFlyer SDK version.
final sdkVersion = await appsFlyerSdk.getSdkVersion();Returns the Flutter plugin version without invoking a native method.
print(appsFlyerSdk.pluginVersion); Future<bool> isPreInstalledApp() — Android only
Returns whether the app install was a device preinstall (OEM/manufacturer). On
iOS the native layer reports the method as unavailable and the call throws
AppsFlyerException. See also setPreinstallAttribution.
Example:
final bool preinstalled = await appsFlyerSdk.isPreInstalledApp(); Future<void> setCurrentDeviceLanguage(String language) — iOS only
Use this API in order to set the language
Example:
await appsFlyerSdk.setCurrentDeviceLanguage("en"); Future<void> setInstallId(String installId)
Sets a unique install id for the app installation, letting you correlate the AppsFlyer install with an id you generate yourself (e.g. for server-side reconciliation). Supported on both platforms, but the call order and setup requirements differ:
- iOS: call this before
init()(before the dev key is set). RequiresAppsFlyerAllowCustomInstallIdset toYESinInfo.plist. - Android: call this after
init(). Requires the<meta-data>flagAPPSFLYER_ALLOW_CUSTOM_INSTALL_IDset totrueinAndroidManifest.xml.
On both platforms, the call is silently ignored — no error is returned — if the corresponding manifest/plist flag is missing.
Example:
await appsFlyerSdk.setInstallId("install-123");Attributes the install to a device preinstall (OEM / manufacturer) deal, declaring that the app shipped preinstalled and attributing the install to the given mediaSource, campaign, and siteId. Call it before start().
Android only — the iOS SDK does not provide this programmatic
preinstall-attribution API. On iOS the call throws an AppsFlyerException — see
Platform-only APIs.
Example:
await appsFlyerSdk.setPreinstallAttribution(
"media_source",
campaign: "campaign",
siteId: "site_id",
); Future<void> setAppId(String appId)
Overrides the app ID reported to AppsFlyer. Call it before start(). The
Android SDK rejects an empty appId, and the returned Future throws
AppsFlyerException.
Android only — on iOS the app ID is provided through init() and the
iOS SDK has no setAppId, so the call throws an AppsFlyerException — see
Platform-only APIs.
Example:
await appsFlyerSdk.setAppId("com.example.app"); Future<void> setSharingFilterForPartners(List<String>? partners)
Used by advertisers to exclude specified networks/integrated partners from getting data. Learn more here
Example:
await appsFlyerSdk.setSharingFilterForPartners(['facebook_int']);
await appsFlyerSdk.setSharingFilterForPartners(
['facebook_int', 'googleadwords_int'],
);Passing null or an empty list clears the filter; the plugin normalizes
both to the same native request, so the two are interchangeable and clear the
filter on Android and iOS alike.
Future<void> setOneLinkCustomDomain(List<String> domains)
Use this API in order to set branded domains. domains must not be empty.
Find more information in the following article on branded domains.
Example:
await appsFlyerSdk.setOneLinkCustomDomain(
["promotion.greatapp.com", "click.greatapp.com", "deals.greatapp.com"],
); Future<void> setDisableAdvertisingIdentifiers(bool disable)
Disables collection of advertising identifiers (GAID / IDFA / OAID). Pass true to disable collection (enabled by default).
On iOS, this is a runtime setting only. It does not select the Strict vs Main
native SDK variant or change the embedded App Store privacy manifest. Kids apps
and other ad-ID-less integrations that require the Strict binary must set
$AppsFlyerStrictMode = true in the app Podfile before pod install — see
Installation — Strict mode.
Example:
await appsFlyerSdk.setDisableAdvertisingIdentifiers(true); Future<void> setDisableCollectASA(bool disable) — iOS only
Controls collection of Apple Search Ads attribution data. On Android the call
throws an AppsFlyerException — see Platform-only APIs.
if (Platform.isIOS) {
await appsFlyerSdk.setDisableCollectASA(true);
} Future<void> setPartnerData(String partnerId, Map<String, dynamic> data)
Allows sending custom data for partner integration purposes.
Example:
final partnerData = <String, dynamic>{"puid": "1234", "region": "eu"};
await appsFlyerSdk.setPartnerData("partnerId", partnerData); Future<void> setResolveDeepLinkURLs(List<String> urls)
Advertisers can wrap an AppsFlyer OneLink within another Universal Link. This Universal Link will invoke the app but any deep linking data will not propagate to AppsFlyer.
setResolveDeepLinkURLs enables you to configure the SDK to resolve the wrapped OneLink URLs, so that deep linking can occur correctly.
urls must not be empty.
Example:
await appsFlyerSdk.setResolveDeepLinkURLs(
["clickdomain.com", "myclickdomain.com", "anotherclickdomain.com"],
); Future<void> setOutOfStore(String sourceName)
Android Only!
Specify the alternative app store that the app is downloaded from (out-of-store attribution). Re-apply on every cold start — SDK 7 does not persist setter values.
This API does not register manifest receivers. See Advanced features — Android Out of Store.
Example:
if (Platform.isAndroid) {
await appsFlyerSdk.setOutOfStore("amazon");
} Future<String?> getOutOfStore()
Android Only!
Gets the configured alternative app-store value. If no runtime value was set,
the Android SDK may return the AF_STORE manifest value.
Example:
if (Platform.isAndroid) {
final store = await appsFlyerSdk.getOutOfStore();
print(store);
} Future<void> setDisableNetworkData(bool isDisable)
Android Only!
Use to opt-out of collecting the network operator name (carrier) and sim operator name from the device.
Example:
if (Platform.isAndroid) {
await appsFlyerSdk.setDisableNetworkData(true);
} Future<void> disableAppSetId()
Android Only!
Disables the native Android SDK's automatic AppSet ID collection. Use this method to opt out of AppSet ID collection for privacy compliance.
Example:
if (Platform.isAndroid) {
await appsFlyerSdk.disableAppSetId();
} Future<void> performDeepLinking(String url, {bool shouldTriggerSession = false})
Manually triggers deep link resolution for a given url (full URL, OneLink, or intent-data string). Use it to resolve a deep link before the SDK starts (e.g. when delaying start()), or for links that don't arrive through the standard intent / Universal Link flow (e.g. Firebase Messaging).
The resolved link is delivered to the
registerDeepLinkListener callback on both
platforms. shouldTriggerSession defaults to false, so a
bare performDeepLinking(url) resolves the link without an extra Launch and
behaves identically on Android and iOS. The flag is Android-only: pass true to
also enqueue a Launch for re-engagement; on iOS it has no effect because the
link is always resolved without an extra managed session.
Future<void> configureAppsFlyer() async {
final appsflyerSdk = AppsFlyerSdk.instance;
await appsflyerSdk.registerDeepLinkListener(
onDeepLinking: (DeepLinkResult result) {
switch (result.status) {
case DeepLinkStatus.found:
print(result.deepLink);
print("deep link value: ${result.deepLink?.deepLinkValue}");
break;
case DeepLinkStatus.notFound:
print("deep link not found");
break;
case DeepLinkStatus.error:
print("deep link error: ${result.error}");
break;
case DeepLinkStatus.unknown:
print("unknown deep link status");
break;
}
});
await appsflyerSdk.init(
devKey: '<DEV_KEY>',
appId: '<APP_ID>',
);
await appsflyerSdk.registerConversionListener(
onConversionDataSuccess: (data) {
print("conversion data: $data");
},
);
// Resolve a deep link manually.
await appsflyerSdk.performDeepLinking(
"https://yourapp.onelink.me/abc123",
);
} Future<void> appendParametersToDeepLinkingURL(String contains, Map<String, String> parameters)
Appends parameters to any deep-link URL that contains the contains substring, before the SDK resolves / attributes it. Useful for enriching wrapped OneLinks with extra query parameters. Implemented on both Android and iOS.
Pass a non-empty contains and at least one entry in parameters. An empty
contains is invalid on both platforms, and an empty parameters map is
invalid on iOS.
await appsFlyerSdk.appendParametersToDeepLinkingURL(
"deeplink",
{"deep_link_sub1": "cat123", "deep_link_value": "shoes"},
); Future<void> setDeepLinkTimeout(int timeout)
Sets the deep-link resolution timeout, in milliseconds. Configure it before
init(). Use a positive value for a cross-platform configuration. The default
when unset differs by platform: 3000 ms on Android, 60000 ms on iOS.
await appsFlyerSdk.setDeepLinkTimeout(3000); Future<void> logAdRevenue({required String monetizationNetwork, required AFMediationNetwork mediationNetwork, required String currencyIso4217Code, required double revenue, Map<String, dynamic>? additionalParameters})
The logAdRevenue API is designed to simplify the process of logging ad revenue events to AppsFlyer from your Flutter application. This API tracks revenue generated from advertisements, enriching your monetization analytics. Below you will find instructions on how to use this API correctly, along with detailed descriptions and examples for various input scenarios.
To use the logAdRevenue method, you must:
- Prepare the required information about the ad revenue event.
- Pass the values to
logAdRevenue.
Parameters The method accepts the following ad revenue values:
monetizationNetwork: The source network from which the revenue was generated (e.g., AdMob, Unity Ads).mediationNetwork: The mediation platform managing the ad (use AFMediationNetwork enum for supported networks).currencyIso4217Code: The ISO 4217 currency code representing the currency of the revenue amount (e.g., "USD", "EUR").revenue: The amount of revenue generated from the ad.additionalParameters: Additional parameters related to the ad revenue event (optional).
AFMediationNetwork Enum AFMediationNetwork is an enumeration that includes the supported mediation networks by AppsFlyer. It's important to use this enum to ensure you provide a valid network identifier to the logAdRevenue API.
Note (behavior): The returned Future completes after the plugin validates the request and invokes the native logging API. Validation and native call failures are surfaced as
AppsFlyerException. The native API has no delivery callback, so completion does not confirm that the event was uploaded.Cross-platform note: The plugin maps
AFMediationNetwork.customMediationandAFMediationNetwork.directMonetizationNetworkto the correct platform value automatically. All public enum values work on both platforms; no caller action is needed.
// Log the ad revenue event.
await appsFlyerSdk.logAdRevenue(
monetizationNetwork: "GoogleAdMob", // Replace with your actual monetization network.
mediationNetwork: AFMediationNetwork.applovinMax, // Use the value from the enum.
currencyIso4217Code: "USD",
revenue: 1.23,
additionalParameters: {
// Optional additional parameters can be added here. This is an example, can be discard if not needed.
'adUnitId': 'ca-app-pub-XXXX/YYYY',
'ad_network_click_id': '12345'
}
);Additional Points
- Mediation network input must be from the provided AFMediationNetwork
enum to ensure proper processing by AppsFlyer. For instance, use
AFMediationNetwork.googleAdMobto denote Google AdMob as the Mediation Network. - The
additionalParametersmap is optional. Use it to pass any extra information you have regarding the ad revenue event; this information could be useful for more refined analytics. - Make sure the
currencyIso4217Codeadheres to the appropriate standard. Misconfigured currency code may result in incorrect revenue tracking.
