Adjust

This guide describes how to add and configure Adjust integration.

Adjust is the industry leader in mobile measurement and fraud prevention.

How does Integration Work?

This integration works in two ways.

1. Receive Attribution Data from Adjust

Once you successfully configured Adjust integration, Apphud will receive attribution data from it. You can view this data on the user's page:

Adjust - Raw Attribution on User page

2. Send Subscription Events to Adjust

Apphud can also send all subscription events to Adjust. So you could view these events in Adjust dashboard and Adjust could pass this data to their partners. This will help to measure the efficiency of your ad campaigns.

How to Add Integration?

Step 1 - Install the SDKs

Step 2 - Preparations in Adjust

Authentication

Open Adjust and sign in. Copy App token.
Select your app and click here:

Apphud - Adjust dashboard authentication (step 1)

Copy App token:

Apphud - Adjust App token (step 2)

If Adjust also issues an S2S token for your account, copy it so it can be pasted later on into the corresponding field in Apphud.

Define events in Adjust

As a preparation, you need to configure the desired events and their tokens in Adjust first. Adjust requires a unique Event token per event. For example, "trial_started", "trial_converted", "subscription_renewed" etc. You can view the list of all available events here.
You can create event tokens only for events that you need, there is no need to add all events.

In Adjust open app's "All Settings", go to the "Events" section and create necessary events. For each event, Adjust will generate a unique Event token.

As a result, you will have something like this:

Apphud - Adjust events configuration (step 3)

Step 3 - Authentication

At Apphud go to Connections → Integrations section. Find Adjust on All or Attribution tabs.
Click Add connection and choose an appropriate source.

Paste the previously copied tokens:

  • Adjust App token
  • Adjust S2S token (optional)
Adjust - Authentication

Step 4 - Additional Settings

Environment and Revenue

Configure generic Environment and Revenue settings as described on Integrations overview → Configuration tab.

Set up filters (optional)

Navigate to Filters tab. Set up filters if needed as described on Integrations overview → Filters tab.

Step 5 - Set up events list

Open the Events tab of the configuration. For each Apphud event you want to forward, paste the corresponding Adjust Event token from Step 2 into the Partner event name field and toggle Enabled on.

Adjust - Events list configuration

Step 6 - Test connection (optional)

Save the connection.

After the configuration is saved, You may send test event to Adjust to check if integration is set up correctly before enabling it to react to all events. Check general guide here.

To test Adjust integration from scratch for iOS source you should do the following:

  • Reset IDFA (Settings > Privacy > Advertising > Reset Advertising Identifier).
  • Uninstall the app.
  • Make sure you initialized Adjust SDK with the ADJEnvironmentSandbox environment. Don't forget to change back to ADJEnvironmentProduction before release!
  • Make sure adjustAttributionChanged(_ attribution: ADJAttribution?) delegate method is called.
  • If the Adjust Attribution Data block exists in your user's page in Apphud, then integration is successful.
  • When viewing events in Adjust Dashboard, make sure you enabled Sandbox Mode in the filter pane.

Step 7 - Enable and save

Once you confirmed the connection works — Enable integration and Save.

Pass Attribution Data to Apphud (required)

Send attribution data to Apphud (or at least Adjust ID):

// only for Apphud SDK v.3.5.7+

// set delegate
adjustConfig?.delegate = self
...

func adjustAttributionChanged(_ attribution: ADJAttribution?) {
    Task {
      if var data = attribution?.dictionary() {
        let adid: String? = await Adjust.adid()
        Apphud.setAttribution(data: ApphudAttributionData(rawData: data), from: .adjust, identifer:adid) { (result) in }
      }
    }
 }
  
 func adjustSessionTrackingSucceeded(_ sessionSuccessResponseData: ADJSessionSuccess?) {
    Task {
      if var data = await Adjust.attribution()?.dictionary() {
        let adid: String? = await Adjust.adid()
        Apphud.setAttribution(data: data, from: .adjust, identifer:adid) { (result) in }
      }
    }
 }
// set delegate
adjustConfig?.delegate = self
...

func adjustAttributionChanged(_ attribution: ADJAttribution?) {
    if let data = attribution?.dictionary() {
        Apphud.addAttribution(data: data, from: .adjust) { (result) in }
    } else if let adid = Adjust.adid() {
        Apphud.addAttribution(data: ["adid" : adid], from: .adjust) { (result) in }
    }
}

func adjustSessionTrackingSucceeded(_ sessionSuccessResponseData: ADJSessionSuccess?) {
    if let data = Adjust.attribution()?.dictionary() {
        Apphud.addAttribution(data: data, from: .adjust) { (result) in }
    } else if let adid = Adjust.adid() {
        Apphud.addAttribution(data: ["adid" : adid], from: .adjust) { (result) in }
    }
}
// set delegate to ADJConfig
config.delegate = self;
...

- (void)adjustAttributionChanged:(ADJAttribution *)attribution {
    [self sendAdjustAttribution:attribution];
}

- (void)adjustSessionTrackingSucceeded:(ADJSessionSuccess *)sessionSuccessResponseData {
    [self sendAdjustAttribution:Adjust.attribution];
}

- (void)sendAdjustAttribution:(ADJAttribution*)attribution {
    if (attribution != nil && attribution.adid != nil) {
            [Apphud addAttributionWithData:attribution.dictionary from:ApphudAttributionProviderAdjust identifer:nil callback:^(BOOL result) {
            }];
    } else if (Adjust.adid != nil) {
        [Apphud addAttributionWithData:@{@"adid" : Adjust.adid} from:ApphudAttributionProviderAdjust identifer:nil callback:^(BOOL result) {
            }];
    }
}
void _initAdjust() {
    final config = AdjustConfig('YourAppToken', AdjustEnvironment.production);
    config.attributionCallback = (adjustData) async {
      final apphudData = <String, dynamic>{};
      if (adjustData.trackerToken != null) {
        apphudData['trackerToken'] = adjustData.trackerToken!;
      }
      if (adjustData.trackerName != null) {
        apphudData['trackerName'] = adjustData.trackerName!;
      }
      if (adjustData.network != null) {
        apphudData['network'] = adjustData.network!;
      }
      if (adjustData.adgroup != null) {
        apphudData['adgroup'] = adjustData.adgroup!;
      }
      if (adjustData.creative != null) {
        apphudData['creative'] = adjustData.creative!;
      }
      if (adjustData.clickLabel != null) {
        apphudData['clickLabel'] = adjustData.clickLabel!;
      }
      if (adjustData.adid != null) {
        apphudData['adid'] = adjustData.adid!;
      }
      if (adjustData.fbInstallReferrer != null) {
        apphudData['fbInstallReferrer'] = adjustData.fbInstallReferrer!;
      }

      await Apphud.addAttribution(
        data: apphudData,
        provider: ApphudAttributionProvider.adjust,
      );
    };
    Adjust.start(config);
  }
fun setupAdjust(context: Context) {
        val env = AdjustConfig.ENVIRONMENT_PRODUCTION
        val config = AdjustConfig(context, "YOUR_ADJUST_TOKEN", env)
        config.setOnAttributionChangedListener {
            Adjust.getAdid { adid -> 
                Apphud.addAttribution(ApphudAttributionProvider.adjust, it.convertToMap(adid), adid)
            }
        }
        config.setOnSessionTrackingSucceededListener {
            Apphud.addAttribution(ApphudAttributionProvider.adjust, null, it.adid)
        }
        Adjust.initSdk(config)
    }

    fun AdjustAttribution.convertToMap(adid: String) = mapOf<String, Any>(
        "trackerToken" to trackerToken,
        "trackerName" to trackerName,
        "network" to network,
        "campaign" to campaign,
        "adgroup" to adgroup,
        "creative" to creative,
        "clickLabel" to clickLabel,
        "adid" to adid
    )
Adjust.GetAttribution( attribution =>
{
   Adjust.GetAdid( adid =>
   {
      	var dict = new Dictionary<string, object>
        	{
            { "trackerToken", attribution.trackerToken },
            { "trackerName",  attribution.trackerName },
            { "network",      attribution.network },
            { "campaign",     attribution.campaign },
            { "adgroup",      attribution.adgroup },
            { "creative",     attribution.creative },
            { "clickLabel",   attribution.clickLabel },
        };

     ApphudSDK.AddAttribution(ApphudAttributionProvider.adjust, dict, adid);
   });
});
// Step 1: Create AdjustConfig instance
const adjustConfig = new AdjustConfig(
  '{YourAppToken}', // Replace with your actual app token
  AdjustConfig.EnvironmentProduction // Change to AdjustConfig.EnvironmentSandbox for testing
);

// Step 2: Assign the attribution callback on the AdjustConfig instance
adjustConfig.setAttributionCallback(function (attribution) {
  console.log("Adjust Attribution Data:", attribution);

  // Fetch ADID separately
  Adjust.getAdid(function (adid) {
    if (!adid) {
      console.log("No ADID found, skipping Apphud attribution submission.");
      return;
    }

    console.log("ADID for attribution:", adid);

    const attributionData = {
      trackerName: attribution.trackerName,
      network: attribution.network,
      campaign: attribution.campaign,
      adgroup: attribution.adgroup,
      creative: attribution.creative,
      clickLabel: attribution.clickLabel,
      adid: adid
    };

    ApphudSdk.addAttribution({
      data: attributionData,
      identifier: adid,
      attributionProviderId: ApphudAttributionProvider.Adjust
    });
  });
});

Adjust.initSdk(adjustConfig);
📌

❗️ Important Note

In order to receive Adjust attribution data from Facebook (Meta), you should accept Facebook's "Advanced Mobile Measurement Agreement" using this link.

Collect Device Identifiers (required)

iOS: Call setDeviceIdentifiers(idfa: String?, idfv: String?) method immediately after the SDK initialization. If the advertising identifier (IDFA) is not available, pass only the IDFV.
When IDFA becomes available, you can call setDeviceIdentifiers(idfa: String?, idfv: String?) again.

Android: Call Apphud.collectDeviceIdentifiers() method after the SDK initialization.
When targeting Android 13 and above, you must also declare AD_ID permission in the manifest file.

For more details, refer to Device Identifiers guide.

Request IDFA Consent (required)

Starting iOS 14.5 access to IDFA requires user consent. You should request IDFA manually using AppTrackingTransparency framework and pass it to Apphud. Read more here.

Events Cheat Sheet

This is a list of all possible events and their parameters that can be sent to Adjust.

📘

Note

You can read more about subscription events here and parameters here.

Trial

Trial period started parameters

  • partner_params.product_id: String

Successful conversion from trial period to regular subscription parameters

  • partner_params.product_id: String
  • revenue: Float
  • currency: String

Failed conversion from trial period to regular subscription parameters

  • partner_params.product_id: String
  • partner_params.reason: String

Cancellations

Trial Canceled parameters

  • partner_params.product_id: String

Subscription Canceled parameters

  • partner_params.product_id: String

Autorenew disabled parameters (Deprecated)

  • partner_params.product_id: String

Autorenew enabled parameters

  • partner_params.product_id: String

Introductory Offer

Introductory offer started parameters

  • partner_params.product_id: String
  • partner_params.offer_type: String
  • revenue: Float
  • currency: String

Introductory offer renewed parameters

  • partner_params.product_id: String
  • partner_params.offer_type: String
  • revenue: Float
  • currency: String

Successful conversion from introductory offer to regular subscription parameters

  • partner_params.product_id: String
  • partner_params.offer_type: String
  • revenue: Float
  • currency: String

Failed conversion from introductory offer to regular subscription or failed renewal parameters

  • partner_params.product_id: String
  • partner_params.reason: String
  • partner_params.offer_type: String

Refund during introductory offer parameters

  • partner_params.product_id: String
  • partner_params.offer_type: String
  • partner_params.reason: String

Regular

Subscription started parameters

  • partner_params.product_id: String
  • revenue: Float
  • currency: String

Subscription renewed parameters

  • partner_params.product_id: String
  • revenue: Float
  • currency: String

Subscription expired parameters

  • partner_params.product_id: String
  • partner_params.reason: String

Subscription refunded parameters

  • partner_params.product_id: String
  • partner_params.reason: String
📘

Note - Refund Events and Revenue in Adjust

We don't send revenue properties with refund events because Adjust does not support negative revenues.
According to Adjust S2S API documentation, the revenue parameter only accepts positive numbers (minimum 0.001).

Promo Offer

Promotional offer started parameters

  • partner_params.product_id: String
  • partner_params.offer_id: String
  • partner_params.offer_type: String
  • revenue: Float
  • currency: String

Promotional offer renewed parameters

  • partner_params.product_id: String
  • partner_params.offer_id: String
  • partner_params.offer_type: String
  • revenue: Float
  • currency: String

Successful conversion from promotional offer to regular subscription parameters

  • partner_params.product_id: String
  • partner_params.offer_id: String
  • partner_params.offer_type: String
  • revenue: Float
  • currency: String

Failed conversion from promotional offer to regular subscription or failed renewal parameters

  • partner_params.product_id: String
  • partner_params.offer_id: String
  • partner_params.offer_type: String
  • partner_params.reason: String

Refund during promotional offer parameters

  • partner_params.product_id: String
  • partner_params.offer_id: String
  • partner_params.offer_type: String
  • partner_params.reason: String

Other Events

Non-renewing purchase parameters

  • partner_params.product_id: String
  • revenue: Float
  • currency: String

Non-renewing purchase refunded parameters

  • partner_params.product_id: String
  • partner_params.reason: String

Billing issue parameters

  • partner_params.product_id: String

Billing issue resolved parameters

  • partner_params.product_id: String
📘

Note

For each event, Apphud includes theenvironmentparameter depending on the environment of the subscription.


FAQ

Q: Why are events in Pending status, or why does the test connection return HTTP status 0 with an empty request body?

A: This indicates that Apphud has not received Adjust attribution data (adid) for this user yet. Apphud builds the S2S request to Adjust from the attribution record stored on the user's device. If addAttribution / setAttribution was never called, or was called without including adid, there is no data to send — the request body will be empty and the event will remain pending until attribution data is received.

Make sure the Adjust attribution callback fires and passes attribution data including adid to Apphud (see Pass Attribution Data above). On Android: call Apphud.collectDeviceIdentifiers() after SDK initialization, and for Android 13+ declare the AD_ID permission in the manifest. Once attribution is stored, future events will be sent correctly; already pending events may be retried automatically.

Q: Why are events skipped with "event disabled"?

A: The event type is not enabled in your Adjust integration settings. Go to Connections → Integrations → Adjust → Events tab, add the Adjust Event token for that event type and toggle Enabled on.

Q: Why are events skipped with "missing idfa, adid, idfv and android_id"?

A: Apphud has no device identifier to include in the S2S request to Adjust. This typically happens on iOS when setDeviceIdentifiers(idfa:idfv:) is called only if ATT is authorized — so users who deny consent have no IDFV on record. Make sure IDFV is always passed regardless of ATT status (see Collect Device Identifiers above).

Q: The user has Adjust attribution data visible on their page, but events are still skipped with "missing identifiers" — why?

A: Apphud reads adid from the device-level attribution record, not from the user page display. If setAttribution was called without adid in the data — for example, organic installs where Adjust does not return an adid — the field is blank at the device level. Combined with missing IDFV and IDFA, there are no identifiers to send. Make sure adid is explicitly included when passing attribution data (see Pass Attribution Data above).

Q: Why are events for anonymous users not sent to Adjust?

A: By default, Apphud skips integration events for anonymous customers — users who opened the app before the Apphud SDK was installed, or whose sessions were never tied to a user identifier. This also affects users migrated from older SDK versions. There is no workaround for historical events; only events after the user is identified will be forwarded.

Q: What is an Adjust Event token and why is it required?

A: Adjust requires a unique Event token for each event type you want to track. You create these in the Adjust dashboard under All Settings → Events. Without a token, Apphud cannot forward the event — the Partner event name field in the Apphud Events tab must contain the corresponding Adjust token, and the event must be toggled Enabled.

Q: What does "No User Consent" mean in Adjust raw attribution data?

A: This appears when the user denied ATT (App Tracking Transparency) permission on iOS. Adjust cannot reveal user-level attribution (campaign, network, creative) for this user due to privacy restrictions, and shows "No User Consent" instead. This is expected behavior — it is not a bug in the integration. Note that if adid is also absent and IDFV was not collected, Apphud will not be able to send S2S subscription events for this user.

Q: Why aren't sandbox events visible in Adjust?

A: Make sure Sandbox Mode is enabled in the Adjust dashboard filter pane when viewing events. Also verify that your Adjust SDK is initialized with ADJEnvironmentSandbox — events sent with the production environment flag will not appear under sandbox filters.

Q: Why is Adjust attribution data missing from Daily Data Exports?

A: Daily Data Exports are designed to export Apphud transaction data, such as purchases, renewals, refunds, revenue, products, store, environment, and user identifiers. Adjust attribution fields — such as adid, campaign, ad group, creative, network, tracker name, or tracker token — are not included in the standard export by default.

If you need attribution data in your data warehouse, you can:

  • send events to your own backend using Server-to-Server Webhooks;
  • retrieve customer data via the Customers API;
  • contact Apphud support to discuss whether a custom export with additional attribution fields is available for your plan.

Note that attribution data availability also depends on what Adjust shares with Apphud for each user. Some users may have limited attribution data due to privacy restrictions or missing device identifiers.

Q: How can I avoid duplicate Facebook events when using Apphud together with Adjust or other MMPs?

A: Duplicates happen when the same subscription event is sent to Facebook through more than one path — for example, directly from Apphud to Facebook, and also forwarded via Adjust or another MMP.

To avoid duplicates, keep only one active event stream per event type:

  1. Use Apphud → Facebook directly — send subscription events from Apphud to Facebook and disable forwarding of the same events from Adjust or other MMPs.
  2. Use Apphud → Adjust → Facebook — let Apphud send events to Adjust, and let Adjust forward them to Facebook. In this case, do not send the same events directly from Apphud to Facebook.
  3. Disable automatic purchase tracking in other SDKs — if the Facebook SDK or another SDK tracks purchases automatically, make sure it does not duplicate events that Apphud already sends.

Apphud does not currently send a Facebook event_id through Adjust for deduplication, so the safest setup is to route each event through one path only.

Q: Why do my Adjust numbers differ from Apphud, Google Play, App Store, or Facebook?

A: Numbers may differ because each system measures events differently. Common reasons:

  1. Different data sources — Apphud tracks subscription events based on store receipts and server notifications. Adjust tracks events received through SDK or S2S. Store reports are based on store-side transaction data.
  2. Different event timing — each system may record events at a different moment (purchase processed vs. event received vs. attributed).
  3. Time zone differences — always compare the same period and time zone across all systems.
  4. Cohort-based vs event-based reporting — some reports group users by install date, others count events by calendar period.
  5. Missing or delayed server notifications — if Apphud does not receive App Store Server Notifications or Google Real-time Developer Notifications on time, some events may be delayed.
  6. Missing attribution or device identifiers — Adjust events may stay Pending or become Skipped if Apphud does not have the required identifiers (adid, IDFV, IDFA, or Android ID).
  7. Sandbox vs production data — make sure both systems show the same environment; sandbox events are often hidden in partner dashboards unless sandbox mode is enabled.
  8. Historical or anonymous users — events for users imported, migrated, or created before the SDK was integrated may not be forwarded to integrations.

For the most accurate comparison, use the same event types, period, time zone, environment, and user cohort across all systems, and check the event delivery status in Apphud for specific transactions.


Did this page help you?