AppsFlyer

Adapty exchanges data with AppsFlyer in both directions. AppsFlyer tells Adapty which campaign brought each user. Adapty tells AppsFlyer what that user paid — purchases, renewals, trials, and refunds, with revenue and product details attached.

  • View the whole subscription lifecycle, not just the first purchase. Renewals and trial conversions are store events, with no app session for AppsFlyer’s client-side SDK to report. Adapty receives subscription events server-side and forwards them to AppsFlyer, so campaign figures keep updating long after the initial install. Refunds travel the same way, and AppsFlyer deducts them from campaign revenue.
  • Optimize your ad campaigns with subscription event data. AppsFlyer relays in-app events from Adapty onward to your ad networks as postbacks. The services that manage your ad budget can optimize on what users actually paid.
  • Filter Adapty analytics by campaign. Adapty saves AppsFlyer’s attribution on each profile, down to the ad set and creative, and subscription charts can filter by it.
  • Show a different paywall per campaign. Adapty segments filter on the same attribution fields — campaign, ad set, and creative. Use a segment as an audience to match the paywall to the ad that brought the user.
Tip

Your Adapty account already includes two tools for paid campaigns. Adapty Ads Manager runs your Apple Ads campaigns; Adapty Attribution covers Meta Ads and TikTok. Both report ROAS and LTV straight from your Adapty purchase data, and both are free to start — see pricing.

AppsFlyer attribution data on an Adapty user profile

How the integration works

Adapty receives attribution data from AppsFlyer, and sends subscription events back to it. Both depend on one value: the AppsFlyer ID, a string AppsFlyer generates when your app first launches.

  1. When the user installs your app, the AppsFlyer SDK assigns them a unique ID.
  2. Your app passes that ID to Adapty, which stores it on the user’s profile as appsflyer_id.
  3. Your app also passes AppsFlyer’s attribution data to Adapty, which saves it on the same profile.
  4. Later, when the user triggers a subscription event — for example, starts a trial or buys a product, Adapty’s servers post the event to AppsFlyer’s S2S API with the same appsflyer_id.
  5. AppsFlyer matches the ID to the install it already attributed, so the purchase inherits that install’s campaign and media source.

Setup instructions

Before you start:

  • Confirm your AppsFlyer plan accepts S2S in-app events. AppsFlyer’s entry-level Zero package doesn’t: its API rejects every event Adapty sends with 403 Forbidden.
  • Ship the AppsFlyer SDK in your app. The AppsFlyer ID that the integration depends on exists only after that SDK initializes. A server-side integration alone won’t produce the ID.
  • Turn off every other attribution integration. Adapty accepts one attribution source per profile and can’t overwrite an existing value. On iOS, non-organic Apple Ads attribution always takes priority — see Select a single attribution source.

Create an S2S token in AppsFlyer

Adapty authenticates against AppsFlyer’s S2S API with a token you create. Only AppsFlyer admins can open the Tokens page, so ask one to create the token if your account isn’t an admin. Skip to Configure Adapty if you already have one.

  1. Sign in to AppsFlyer.

  2. Click your account name in the top-right corner, and open the Security center.

    The Security center entry under the account menu in AppsFlyer
  3. On the Manage your account security page, find the AppsFlyer API and S2S tokens card and click Manage your AppsFlyer tokens. The Tokens page opens.

  4. Click New token.

    The New token dialog in AppsFlyer with the Name field and the Choose type list set to S2S
  5. Enter a Name for the token. The name is for your reference only, and you can change it later.

  6. Select the S2S token type. Any other type breaks the integration.

  7. Click Create new token.

    Note

    AppsFlyer allows two tokens per type. If your account already has two S2S tokens, reuse one of them.

  8. Find your new token in the list. AppsFlyer masks the value, so click the copy icon in the Token column to retrieve it.

    The Tokens page in AppsFlyer showing an S2S token with its copy icon, type, and status

Configure Adapty

  1. Open Integrations > AppsFlyer in the Adapty Dashboard.

  2. Enable the AppsFlyer toggle.

  3. If you already connected your app to the App Store, the iOS App ID field is automatically populated with your app’s numerical Apple ID. If it’s empty, connect your App Store account first. Android requires no equivalent field.

  4. Paste the S2S token into the Production field of S2S key for iOS, S2S key for Android, or both.

  5. Fill in the Sandbox fields to keep test purchases out of your production numbers — see Keep sandbox data out of production.

    The AppsFlyer integration page in Adapty with the iOS app ID and the Production and Sandbox S2S key fields
  6. Under How the revenue data should be send, choose which revenue figure Adapty sends as af_revenue. The three options match the Adapty Analytics revenue views, so your choice also names the view your AppsFlyer numbers should agree with.

    OptionWhat Adapty sends
    Gross revenueThe full amount the buyer paid, before commission and tax. The default.
    Proceeds after store commissionThe amount minus the store’s commission, with tax still included.
    Proceeds after store commission and taxesThe amount minus both.
  7. Set the remaining options:

    ToggleWhen onDefault
    Report user’s currencyAdapty reports each sale in the currency the buyer paid in, rather than USD.Off
    Send trial priceTrial starts carry no revenue otherwise. Turn this on to give each one a placeholder price, and a Trial price percentage field appears — set it to the share of the subscription price a trial should report. At 60%, a $10 subscription sends $6.Off
    Exclude historical eventsAdapty skips events that happened before the user installed a build containing the Adapty SDK.On
    Delay events with a future datetimeApple reports renewals and trial conversions in advance, so these events carry a future date. AppsFlyer usually replaces that date with the day the event arrives. Turn this on to hold each event until its date — see Renewals land on the wrong day.Off
  8. Rename or disable individual events in the Events names section — see Event names.

    The Events names section of the Adapty AppsFlyer integration page
  9. Click Save.

Keep sandbox data out of production

To keep test purchases out of your real numbers, send them to a separate AppsFlyer app. Register a second app for your development builds, then paste its token into the Sandbox field of each platform section.

Adapty routes every transaction by environment — real purchases to the app whose token is in Production, test purchases to the app in Sandbox. To report everything in one app instead, paste the same token into both fields.

App Store review and TestFlight purchases are sandbox transactions, even though they run on a production build. Adapty sends these purchases with the Sandbox key.

Note

Sandbox transactions are excluded from all analytics charts. They still appear on individual profile pages and in the event feed.

Configure your app code

  1. Register a conversion callback with the AppsFlyer SDK. On iOS, implement the AppsFlyerLibDelegate protocol; on Android, the AppsFlyerConversionListener interface; on Unity, the IAppsFlyerConversionData interface. On React Native and Flutter, pass a handler to the onInstallConversionData method instead.
  2. Wait for AppsFlyer to invoke that callback. AppsFlyer attributes each install on its own servers, so the result reaches your app asynchronously rather than at launch. The SDK invokes the callback again on every later session.
  3. Inside the callback, read the user’s AppsFlyer ID with getAppsFlyerUID and pass it to Adapty with setIntegrationIdentifier(). Adapty’s events reach the right AppsFlyer user only with this value.
  4. Inside the same callback, pass AppsFlyer’s attribution data to Adapty with updateAttribution(). This tells Adapty which campaign produced the install. On iOS and Android SDK 4.1 and later, the method is named updateExternalAttribution().
  5. Await Adapty.identify() instead of running it alongside steps 3 and 4. Adapty creates an anonymous profile at activation, then switches to the identified profile once identify() resolves. An appsflyer_id set during that switch doesn’t always survive it.

For the full sequence, see call order in the iOS, Android, React Native, Flutter, Unity, Capacitor, and Kotlin Multiplatform SDKs.

Note

Third-party SDKs generate user IDs asynchronously. The ID may not be ready when Adapty.activate() runs. If your Customer User ID comes from one of these SDKs, call Adapty.activate() without it. Once the ID arrives, call setIntegrationIdentifier(), then identify() with the CUID.

Verify the integration

  1. Trigger a sandbox purchase and open your app’s Event Feed. Every delivery attempt appears there. To read AppsFlyer’s response to a failed attempt, hover its row.
  2. In AppsFlyer, open Settings > SDK Integration Tests > Live Events and select your test device. Live Events lists S2S events as they arrive, well before they reach AppFlyer’s Activity dashboard.
  3. Open your app’s Activity dashboard and confirm the event, its revenue, and its media source. Allow about an hour — S2S events don’t reach this dashboard immediately.
Note

Adapty’s events never appear in your app’s AppsFlyer SDK debug log. Adapty posts them from its own servers, so they never pass through your app. An empty local log says nothing about the integration.

AppsFlyer event structure

Adapty sends one POST request per event to https://api3.appsflyer.com/inappevent/{app_id}, with the S2S token in the authentication header. API 2 uses https://api2.appsflyer.com/inappevent/{app_id}.

{
  "appsflyer_id": "1699887556000-6192770",
  "eventName": "af_subscribe",
  "eventTime": "2026-03-01 12:00:00",
  "eventValue": "{\"af_content_id\":\"yearly.premium.6999\",\"af_order_id\":\"GPA.3383-4699-1373-07113\",\"store_country\":\"US\",\"profile_country\":\"US\",\"af_content_type\":\"in_app\",\"af_revenue\":\"9.9900\",\"af_currency\":\"USD\",\"af_quantity\":\"1\"}",
  "os": "17.0.1",
  "bundleIdentifier": "com.example.app",
  "customer_user_id": "user_12345",
  "eventCurrency": "USD",
  "ip": "192.168.100.1",
  "advertising_id": "00000000-0000-0000-0000-000000000000",
  "idfa": "00000000-0000-0000-0000-000000000000",
  "idfv": "00000000-0000-0000-0000-000000000000",
  "att": "3"
}
ParameterTypeDescription
appsflyer_idStringThe AppsFlyer ID your app passed to setIntegrationIdentifier. AppsFlyer matches the event to an install by this value.
eventNameStringThe name from the Events names section — see Event names.
eventTimeStringWhen the event occurred (UTC, YYYY-MM-DD HH:MM:SS). Adapty replaces it with the current time for events older than 26 hours — see Old events arrive with today’s date.
eventValueStringA JSON-encoded string of the fields in the table below.
osStringThe OS version of the user’s device.
bundleIdentifierStringThe app’s bundle ID on iOS, or package name on Android.
customer_user_idStringThe user’s Customer User ID.
eventCurrencyStringThe ISO 4217 currency code, for example USD.
ipStringThe user’s IP address.
advertising_idStringAndroid only. The Google Advertising ID.
idfaStringiOS only. The ID for Advertisers.
idfvStringiOS only. The ID for Vendors.
attStringiOS only. The App Tracking Transparency status, 0 to 3. Adapty sends 0 when it has no value.

eventValue carries the purchase itself. The last four parameters appear only on events that carry revenue:

ParameterTypeDescription
af_content_idStringThe product ID from the store.
af_order_idStringThe original transaction ID.
store_countryStringThe country of the user’s store account.
profile_countryStringThe country Adapty derived from the user’s IP address.
af_content_typeStringAlways in_app.
af_revenueStringThe revenue amount, to 4 decimal places. Negative on refunds.
af_currencyStringThe currency of af_revenue.
af_quantityStringAlways 1.

Event names

Adapty maps its revenue-carrying events to AppsFlyer’s standard event names by default, rather than sending its own names as custom events. This matters if you forward events to ad networks: a network acts on the standard names it already recognizes, so you skip inventing a mapping for each one.

Adapty eventDefault AppsFlyer name
Subscription startedaf_subscribe
Subscription renewedaf_subscribe
Trial convertedaf_subscribe
Trial startedaf_start_trial
Non-subscription purchaseaf_purchase

Every other Adapty event keeps its own name, for example subscription_refunded. In the Events names section of the AppsFlyer integration page, rename any event or turn off the ones you don’t need. For the full list of what Adapty can send, see Events.

Limitations

  • A profile without appsflyer_id produces no event. AppsFlyer matches an event to the install that generated the ID, and reads the campaign from that install. Adapty sends nothing without it, and the Event Feed flags these profiles — see Events don’t reach AppsFlyer.
  • No historical backfill. Adapty forwards events from the moment you enable the integration. Past purchases never reach AppsFlyer.
  • Device details stay empty in AppsFlyer raw data. An S2S event carries only what the request contains. Device Model, Device Category, Language, Operator, WIFI, App Version, and App Name have no S2S parameter, so nothing sent this way can populate them.

Troubleshooting

Events don’t reach AppsFlyer

Open the Event Feed first. A failed delivery shows the error AppsFlyer returned. These causes account for most of them:

  • The profile has no appsflyer_id. Confirm your app calls getAppsFlyerUID and passes the result to setIntegrationIdentifier on every platform you ship on — see Configure your app code.
  • The app ID for that platform is missing. AppsFlyer addresses each app by ID, so Adapty sends nothing without one. iOS events need iOS App ID on the integration page; Android events need the package name from App settings > Android SDK — see Configure Adapty.
  • The purchase is a sandbox transaction and the Sandbox key is empty. See Keep sandbox data out of production.
  • The event is turned off in the Events names section.

A successful delivery doesn’t guarantee AppsFlyer keeps the event. AppsFlyer returns 200 OK for any well-formed request, then discards events whose appsflyer_id matches no real install.

Purchases show up as organic

Each appsflyer_id identifies one install, and every event carrying that ID inherits the install’s attribution — the campaign that produced it, or none if the install was organic. AppsFlyer needs 20–30 seconds or more to attribute a new install. An event that arrives before then has no attribution to inherit, so AppsFlyer marks it unattributed organic.

Expect first-launch purchases to report as organic — trials that start within seconds of app launch. To avoid it, delay your paywall long enough for AppsFlyer to finish processing the install.

If every event looks organic, not just the occasional first-launch purchase, the cause is attribution rather than timing: another source claimed the profile first. See Select a single attribution source.

Old events arrive with today’s date

Events may reach Adapty late for two reasons:

  • When App Store Server Notifications show Delayed in App Store Connect, Apple queues its notifications. A renewal then reaches Adapty long after it happened — see App Store Server Notifications show “Delayed”.
  • Back-dated events pass through whenever Exclude historical events is off.

AppsFlyer won’t accept old timestamps such as these. To ensure consistent event handling, Adapty replaces eventTime with the current time for any event older than 26 hours. You can’t disable the rewrite.

Renewals land on the wrong day

Apple notifies Adapty of renewals and trial conversions before they happen, and Adapty forwards them immediately with the future eventTime unchanged. AppsFlyer keeps a future timestamp only when it falls on the arrival day — a renewal dated tomorrow gets stamped with today’s arrival time instead.

Turn on Delay events with a future datetime in the integration settings. Adapty then holds each event until its time arrives, so AppsFlyer records the date Apple announced. See Event timestamps with future dates.

Revenue in AppsFlyer doesn’t match Adapty Analytics

Adapty and AppsFlyer count the same purchases differently. These differences account for almost every mismatch.

  • AppsFlyer’s Overview dashboard groups revenue by install date; Adapty’s charts group it by event date. In AppsFlyer, a July renewal from a January install counts toward January. Compare that dashboard against Adapty’s cohort analysis, which also groups revenue by install month. AppsFlyer’s raw data reports group by event date, so compare those against Adapty’s charts instead.
  • Purchases from profiles without appsflyer_id never reach AppsFlyer. They stay in Adapty Analytics. See Events don’t reach AppsFlyer.
  • AppsFlyer shows only the revenue figure you selected in Adapty. The How the revenue data should be send setting decides whether Adapty sends gross revenue, proceeds, or net revenue. Comparing that against another Adapty Analytics view produces a gap equal to the commission, the tax, or both.
  • Adapty uses your app’s reporting timezone; AppsFlyer receives UTC. Integrations always get UTC timestamps, whatever you set in App Settings. A purchase at 23:30 UTC on Jul 1 lands on Jul 2 in Adapty if your reporting timezone is +02:00.
  • AppsFlyer is missing historical events. There are two probable causes. With Exclude historical events on, a user’s AppsFlyer history starts at their first launch of an Adapty build. Adapty also never backfills events it processed before you enabled the integration.
  • Sandbox purchases reach the AppsFlyer app named by the Sandbox key. When that’s the production app, test purchases inflate its revenue. See Keep sandbox data out of production.
  • Events turned off in the integration never reach AppsFlyer. Adapty Analytics still counts them. Review the Events names section — turning off subscription_renewed removes most of the revenue for any established app.

Failed to authenticate in the Event Feed

AppsFlyer rejects the credentials when they don’t match the API version. API 2 expects a Dev key; API 3 expects an S2S token. Switching the version without replacing the key produces this error on every event.

Create a fresh token in the AppsFlyer Security Center and paste it into the S2S key fields, or follow Switch from AppsFlyer S2S API 2 to 3.

access_level_updated shows as failed in the Event Feed

access_level_updated is a webhook-only event. Adapty never sends it to this integration. But Adapty records an outcome for every enabled integration, and an unsupported event is displayed as a failure.