Overview

Overview of CloudX Unity SDK setup and core features

The CloudX Unity SDK enables monetization of your Unity games with banner, MREC, interstitial, rewarded, and app open ads across iOS and Android.

Installation

The CloudX Unity SDK is distributed as a .unitypackage file.

  1. Download CloudXSdk-4.7.2.unitypackage from the CloudX Unity SDK 4.7.2 release
  2. In Unity, go to Assets > Import Package > Custom Package
  3. Select the downloaded .unitypackage file
  4. Import all assets when prompted

Sample App

The cloudx-io/cloudx-unity repository is also a runnable Unity demo project showing a working integration for banner, MREC, interstitial, and rewarded ads. It ships with CloudX demo dashboard IDs, so it runs without an account.

To point it at your own CloudX app, replace the app key and ad unit IDs in Assets/Scripts/DemoConfig.cs, then set the bundle identifier registered for that app under Project Settings > Player > Identification. Bid requests are authorized per app key and bundle identifier, so both must match your dashboard app or the SDK gets no fill.

Ad Network Adapters

The CloudX SDK requires ad network adapters to serve ads. Enable them by uncommenting the relevant lines in Assets/CloudXSdk/Editor/CloudXDependencies.xml. See the Adapters section for per-adapter setup instructions, including iOS Info.plist requirements.

Initialization

Initialize the SDK before loading any ads. You can optionally configure user and app properties before initialization.

using CloudX;
using UnityEngine;

public class MyGameManager : MonoBehaviour
{
    void Start()
    {
        InitializeCloudX();
    }

    void InitializeCloudX()
    {
        // Pre-initialization configuration (optional)
        CloudXSdk.SetHashedUserId("hashed-user-id");
        CloudXSdk.SetUserKeyValue("user_level", "premium");
        CloudXSdk.SetAppKeyValue("app_version", "1.0.0");

        // Subscribe to initialization callbacks before initializing
        CloudXInitializationCallbacks.OnSdkInitializedEvent += OnSdkInitialized;
        CloudXInitializationCallbacks.OnSdkInitializationFailedEvent += OnSdkInitializationFailed;

        // Initialize SDK
        var config = CloudXInitializationConfiguration.Create("YOUR_APP_KEY").Build();
        CloudXSdk.Initialize(config);
    }

    private void OnSdkInitialized(CloudXSdkConfiguration config)
    {
        Debug.Log("CloudX SDK initialized successfully");
        // Now you can load ads
    }

    private void OnSdkInitializationFailed(CloudXError error)
    {
        Debug.LogError($"SDK initialization failed: {error}");
    }
}

Ad Formats

CloudX Unity SDK supports banner, MREC, interstitial, rewarded, and app open ad integrations. Use the format-specific guides for implementation details:

Advanced Features

Callback Threading

CloudX raises every callback — initialization, ad lifecycle, Trusted Arbiter and OnAdRevenuePaid — from a native thread. CloudXSdk.InvokeEventsOnUnityMainThread decides whether CloudX hands that callback to the Unity main thread first or invokes it where it was raised.

// Optional; set before Initialize() to also cover initialization callbacks
CloudXSdk.InvokeEventsOnUnityMainThread = true;
ValueBehaviorUse it when
unset (default)Every callback runs on the Unity main thread, except OnAdRevenuePaid for the fullscreen formats (interstitial, app open, rewarded), which is delivered on a background thread. Banner and MREC revenue is main-thread.You want your callbacks to be able to touch Unity APIs, and revenue reporting to fire at impression time.
trueEvery callback runs on the Unity main thread, fullscreen OnAdRevenuePaid included.Your revenue callback also updates the game (UI, GameObjects, coroutines).
falseEvery callback is invoked inline on the native callback thread. Your callbacks must not use Unity APIs.You need every event at the moment it happens and you marshal to the main thread yourself.
  • The property is read on each callback, so it can be changed at any time. Set it before Initialize() to cover initialization callbacks.
  • A callback that throws is caught and logged at ERROR; the other subscribers of that event, and the rest of the ad lifecycle, are unaffected.

Why fullscreen revenue defaults to a background thread

Main-thread callbacks are delivered in Unity’s Update(). While a fullscreen ad is in front the Unity player may be paused, so Update() does not run and anything queued for the main thread may wait until the ad closes. Delivering fullscreen OnAdRevenuePaid on the native thread instead is what hands your revenue tracking (analytics, MMP forwarding) the signal at impression time rather than after the ad is dismissed.

This is a Unity limitation, not something specific to CloudX: any callback you ask to run on the Unity main thread while a fullscreen ad is in front may wait for the player to resume. OnAdShowSuccess arriving only after the ad closes is the same effect, and is expected.

Getting every callback immediately

Set false and queue the work yourself. Only the queued lambda runs on the main thread; the callback itself returns to CloudX immediately.

using System;
using System.Collections.Concurrent;
using CloudX;
using UnityEngine;
using UnityEngine.UI;

public class AdEventPump : MonoBehaviour
{
    [SerializeField] private Text revenueLabel;

    private readonly ConcurrentQueue<Action> _pending = new ConcurrentQueue<Action>();

    private void Awake()
    {
        DontDestroyOnLoad(gameObject);
        CloudXSdk.InvokeEventsOnUnityMainThread = false;

        CloudXAdsCallbacks.Interstitial.OnAdRevenuePaid += ad =>
        {
            // Runs on the native thread, at impression time. No Unity APIs here.
            MyAnalytics.TrackRevenue(ad.AdUnitId, ad.NetworkName, ad.Revenue);
            _pending.Enqueue(() => revenueLabel.text = $"Revenue {ad.Revenue:F4}");
        };
    }

    private void Update()
    {
        while (_pending.TryDequeue(out var action))
        {
            action();
        }
    }
}

Troubleshooting

SymptomCause and fix
A fullscreen callback (OnAdShowSuccess, OnAdRevenuePaid) arrives only after the ad closesExpected: the Unity player may be paused behind the ad, so main-thread delivery waits. Set false and marshal yourself if you need it sooner.
Caught exception in publisher event: <event> in the logYour callback threw. The log line names the event, the subscribing method, and whether it was delivered on the Unity main thread. A callback that touches a Unity API off the main thread throws here — set true, or move that work into an Update() pump.
UnityMainThreadDispatcher does not exist in the logCloudX’s dispatcher GameObject was destroyed by the app. Callbacks are then delivered on the native thread, so any callback touching a Unity API will throw. Do not destroy CloudX’s DontDestroyOnLoad objects.

An event is never silently discarded: it is either delivered on the thread you asked for, or delivered on the native thread with an ERROR explaining why. The one exception is destroying the dispatcher GameObject, which discards whatever it had already queued for the main thread.

Privacy Controls

You can override consent state before initialization when your app is not using a CMP.

// Optional manual privacy overrides before Initialize()
CloudXSdk.SetHasUserConsent(true);
CloudXSdk.SetDoNotSell(false);

// Pass null to clear the manual override and defer back to CMP or IAB signals
CloudXSdk.SetHasUserConsent(null);
CloudXSdk.SetDoNotSell(null);
  • SetHasUserConsent(bool?) sets the GDPR consent override.
  • SetDoNotSell(bool?) sets the CCPA do-not-sell override.
  • IAB consent and privacy signals take precedence over these manual overrides when available.

iOS ATT Usage Description

Starting with Unity SDK 2.2.4, the iOS post-process step adds NSUserTrackingUsageDescription automatically if your app has not already defined it in Info.plist.

  • Default value: This uses device info for more personalized ads and content
  • If you already provide NSUserTrackingUsageDescription, CloudX leaves your existing value unchanged.
  • Set your own copy in Info.plist if you want custom ATT prompt wording.

User Targeting

Configure user and app properties for better ad targeting. Call these methods before Initialize.

// Set hashed user ID
CloudXSdk.SetHashedUserId("hashed-user-id-12345");

// Set user-level key-value pairs
CloudXSdk.SetUserKeyValue("user_level", "premium");
CloudXSdk.SetUserKeyValue("age_group", "25-34");

// Set app-level key-value pairs
CloudXSdk.SetAppKeyValue("app_version", "1.0.0");
CloudXSdk.SetAppKeyValue("build_number", "123");

// Clear all custom key-values
CloudXSdk.ClearAllKeyValues();

The hashed user ID is a publisher-provided pseudonymous identifier. When applicable privacy signals allow it, CloudX captures the value at auction time and returns it as hashed_user_id in the request and impression activity exports. Use auction_id to join the exports. Use the ID to join CloudX activity to cohorts in your own user data. The export value is empty when no ID was set, persistence was suppressed by privacy signals, or the value exceeded 128 characters. Do not pass raw personal data.

Identity passthrough

Pass UID 2.0, EUID, LiveRamp, ID5, and Intent IQ with SetUserKeyValue. Set each value when you have it, and again when it refreshes.

KeyPass this
uidapi.comUID2 advertising token, not the refresh token. Do not decrypt it. UID2 docs
euid.euEUID advertising token, not the refresh token. Do not decrypt it. EUID docs
liveramp.comLiveRamp ATS envelope, not a RampID. LiveRamp docs
id5-sync.comID5 universal UID. Do not pass 0. ID5 docs
intentiq.comIntent IQ ID (IIQ ID) from your Intent IQ integration. Intent IQ docs

CloudX does not generate these IDs. Create them with UID2, EUID, LiveRamp ATS, ID5, or Intent IQ, then pass the string. This is separate from hashed user ID.

CloudXSdk.SetUserKeyValue("uidapi.com", uid2Token);
CloudXSdk.SetUserKeyValue("euid.eu", euidToken);
CloudXSdk.SetUserKeyValue("liveramp.com", liveRampEnvelope);
CloudXSdk.SetUserKeyValue("id5-sync.com", id5Id);
CloudXSdk.SetUserKeyValue("intentiq.com", intentIQId);

Revenue Tracking

All ad formats provide revenue callbacks through the OnAdRevenuePaid event. The CloudXAd object contains revenue information:

CloudXAdsCallbacks.Banner.OnAdRevenuePaid += (ad) =>
{
    Debug.Log($"Revenue: ${ad.Revenue:F4}");
    Debug.Log($"Network: {ad.NetworkName}");
    Debug.Log($"Ad Unit: {ad.AdUnitId}");
    Debug.Log($"Ad Format: {ad.AdFormat}");
    Debug.Log($"Placement: {ad.Placement}");
    Debug.Log($"Network Placement: {ad.NetworkPlacement}");

    // Track revenue in your analytics
    TrackRevenue(ad.Revenue, ad.NetworkName);
};

MMP Ad-Revenue Connectors

If your app uses Adjust, AppsFlyer, or Singular, you can forward CloudX-won impression revenue to your mobile measurement partner without writing Unity C# glue code. Uncomment the connector dependencies in Assets/CloudXSdk/Editor/CloudXDependencies.xml, then run EDM4U dependency resolution.

Use either a connector or your own OnAdRevenuePaid forwarding code, not both.

Publisher-Reported Revenue Data

If your app receives impression-level revenue callbacks or bid metadata from AdMob, Google Ad Manager, InMobi, TopOn, or another mediation platform outside the CloudX ad flow, forward those events to CloudX after initialization:

FieldRequiredDescription
PlatformYesCloudXRevenuePlatform.AdMob, CloudXRevenuePlatform.Gam, CloudXRevenuePlatform.InMobi, CloudXRevenuePlatform.TopOn, or CloudXRevenuePlatform.Custom("MyProvider")
RevenueYesRevenue for one impression in the supplied currency, not CPM/eCPM
AdFormatYesAd format string such as banner, mrec, interstitial, or rewarded
CurrencyCodeNoISO 4217 currency code, if known
PrecisionNoCloudXRevenuePrecision.Exact, Estimated, PublisherDefined, or Undefined
NetworkNameNoWinning ad network name, if known
AdUnitIdNoMediation-platform ad unit ID
ThirdPartyAdPlacementIdNoNetwork-side ad unit or placement ID
CreativeIdNoCreative ID from the ad network
NetworkPlacementNoNetwork placement identifier
CountryCodeNoUser country code, if known
UserSegmentNoUser segment, if known

CloudXSdk.ReportRevenueData() returns true when the event is accepted into the CloudX revenue pipeline. It returns false when the payload is invalid, the SDK is not initialized, or revenue tracking is unavailable.

AdMob paid events

With the Google Mobile Ads Unity plugin, each ad object exposes OnAdPaid. The callback AdValue.Value is reported in micro-units, so divide it by 1_000_000.0 before passing it to CloudX.

private static CloudXRevenuePrecision ToCloudXRevenuePrecision(AdValue.PrecisionType precision) => precision switch
{
    AdValue.PrecisionType.Precise => CloudXRevenuePrecision.Exact,
    AdValue.PrecisionType.Estimated => CloudXRevenuePrecision.Estimated,
    AdValue.PrecisionType.PublisherProvided => CloudXRevenuePrecision.PublisherDefined,
    _ => CloudXRevenuePrecision.Undefined,
};

private static bool ReportAdMobPaidEvent(AdValue adValue, string adFormat, string adUnitId)
{
    return CloudXSdk.ReportRevenueData(new CloudXRevenueData(
        Platform: CloudXRevenuePlatform.AdMob,
        Revenue: adValue.Value / 1_000_000.0,
        AdFormat: adFormat,
        CurrencyCode: adValue.CurrencyCode,
        Precision: ToCloudXRevenuePrecision(adValue.Precision),
        AdUnitId: adUnitId,
    ));
}

bannerView.OnAdPaid += adValue =>
{
    ReportAdMobPaidEvent(adValue, "banner", ADMOB_BANNER_UNIT_ID);
};

Report Google Ad Manager paid events the same way as AdMob paid events, but use CloudXRevenuePlatform.Gam and the Ad Manager ad unit id.

private static bool ReportGamPaidEvent(AdValue adValue, string adFormat, string adUnitId)
{
    return CloudXSdk.ReportRevenueData(new CloudXRevenueData(
        Platform: CloudXRevenuePlatform.Gam,
        Revenue: adValue.Value / 1_000_000.0,
        AdFormat: adFormat,
        CurrencyCode: adValue.CurrencyCode,
        Precision: ToCloudXRevenuePrecision(adValue.Precision),
        AdUnitId: adUnitId,
    ));
}

adManagerInterstitial.OnAdPaid += adValue =>
{
    ReportGamPaidEvent(adValue, "interstitial", GAM_INTERSTITIAL_UNIT_ID);
};

InMobi impression events

For InMobi, save args.AdMetaInfo in OnAdFetchSuccessful. When OnAdImpression fires, send the saved metaInfo.Bid to CloudX, then clear the saved value.

private AdMetaInfo bannerMetaInfo;

private static bool ReportInMobiImpression(AdMetaInfo metaInfo, string adFormat, string placementId)
{
    return CloudXSdk.ReportRevenueData(new CloudXRevenueData(
        Platform: CloudXRevenuePlatform.InMobi,
        Revenue: metaInfo.Bid,
        AdFormat: adFormat,
        Precision: CloudXRevenuePrecision.Estimated,
        ThirdPartyAdPlacementId: placementId,
        CreativeId: metaInfo.CreativeID,
    ));
}

bannerAd.OnAdFetchSuccessful += (_, args) =>
{
    bannerMetaInfo = args.AdMetaInfo;
};

bannerAd.OnAdImpression += (_, _) =>
{
    ReportInMobiImpression(bannerMetaInfo, "banner", INMOBI_BANNER_PLACEMENT_ID);
    bannerMetaInfo = null;
};

TopOn revenue events

With the TopOn Unity plugin v2.1.8 or later, set an IATAdRevenueListener on the ad object. Use adInfo.publisher_revenue for the single-impression revenue; do not use adInfo.adsource_price, because TopOn reports that value as CPM/eCPM.

using AnyThinkAds.Api;
using CloudX;

private static CloudXRevenuePrecision ToTopOnRevenuePrecision(string precision)
{
    return precision switch
    {
        "exact" => CloudXRevenuePrecision.Exact,
        "estimated" => CloudXRevenuePrecision.Estimated,
        "publisher_defined" => CloudXRevenuePrecision.PublisherDefined,
        _ => CloudXRevenuePrecision.Undefined,
    };
}

private sealed class TopOnRevenueListener : IATAdRevenueListener
{
    public void onAdRevenuePaid(string placementId, ATCallbackInfo adInfo)
    {
        ReportTopOnRevenue(adInfo, "banner", placementId);
    }
}

private static bool ReportTopOnRevenue(ATCallbackInfo adInfo, string adFormat, string placementId)
{
    return CloudXSdk.ReportRevenueData(new CloudXRevenueData(
        Platform: CloudXRevenuePlatform.TopOn,
        Revenue: adInfo.publisher_revenue,
        AdFormat: adFormat,
        CurrencyCode: adInfo.currency,
        Precision: ToTopOnRevenuePrecision(adInfo.precision),
        NetworkName: adInfo.network_name,
        AdUnitId: placementId,
        ThirdPartyAdPlacementId: adInfo.network_placement_id,
        NetworkPlacement: adInfo.adsource_id,
        CountryCode: adInfo.country,
    ));
}

ATBannerAd.Instance.setAdRevenueListener(TOPON_BANNER_PLACEMENT_ID, new TopOnRevenueListener());

Custom platform events

Use Custom for providers that do not have a CloudX SDK constant like AdMob, Google Ad Manager, InMobi, and TopOn do. The value is only the provider name, such as CloudXRevenuePlatform.Custom("TradPlus") or CloudXRevenuePlatform.Custom("Nimbus"). Keep the name stable so CloudX can group that provider’s revenue consistently.

Do not include the amount or currency in the provider name. For a TradPlus impression worth USD 0.01, send Platform: CloudXRevenuePlatform.Custom("TradPlus"), Revenue: 0.01, and CurrencyCode: "USD". If your source reports CPM/eCPM, divide by 1_000.0 first.

private static bool ReportCustomRevenueEvent(
    string providerName,
    double revenue,
    string adFormat,
    string currencyCode,
    string adUnitId,
    string placementId)
{
    return CloudXSdk.ReportRevenueData(new CloudXRevenueData(
        Platform: CloudXRevenuePlatform.Custom(providerName),
        Revenue: revenue,
        AdFormat: adFormat,
        CurrencyCode: currencyCode,
        Precision: CloudXRevenuePrecision.PublisherDefined,
        AdUnitId: adUnitId,
        ThirdPartyAdPlacementId: placementId,
    ));
}