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.
- Download
CloudXSdk-4.7.2.unitypackagefrom the CloudX Unity SDK 4.7.2 release - In Unity, go to Assets > Import Package > Custom Package
- Select the downloaded
.unitypackagefile - 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:
Banner & MREC ads
Display ads at a fixed screen position with optional refresh control.
Interstitial ads
Load and show full-screen interstitial placements.
Rewarded ads
Reward users after completed rewarded ad views.
App Open ads
Show full-screen ads when users open or return to your app.
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;| Value | Behavior | Use 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. |
true | Every callback runs on the Unity main thread, fullscreen OnAdRevenuePaid included. | Your revenue callback also updates the game (UI, GameObjects, coroutines). |
false | Every 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
| Symptom | Cause and fix |
|---|---|
A fullscreen callback (OnAdShowSuccess, OnAdRevenuePaid) arrives only after the ad closes | Expected: 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 log | Your 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 log | CloudX’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.plistif 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.
| Key | Pass this |
|---|---|
uidapi.com | UID2 advertising token, not the refresh token. Do not decrypt it. UID2 docs |
euid.eu | EUID advertising token, not the refresh token. Do not decrypt it. EUID docs |
liveramp.com | LiveRamp ATS envelope, not a RampID. LiveRamp docs |
id5-sync.com | ID5 universal UID. Do not pass 0. ID5 docs |
intentiq.com | Intent 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:
| Field | Required | Description |
|---|---|---|
Platform | Yes | CloudXRevenuePlatform.AdMob, CloudXRevenuePlatform.Gam, CloudXRevenuePlatform.InMobi, CloudXRevenuePlatform.TopOn, or CloudXRevenuePlatform.Custom("MyProvider") |
Revenue | Yes | Revenue for one impression in the supplied currency, not CPM/eCPM |
AdFormat | Yes | Ad format string such as banner, mrec, interstitial, or rewarded |
CurrencyCode | No | ISO 4217 currency code, if known |
Precision | No | CloudXRevenuePrecision.Exact, Estimated, PublisherDefined, or Undefined |
NetworkName | No | Winning ad network name, if known |
AdUnitId | No | Mediation-platform ad unit ID |
ThirdPartyAdPlacementId | No | Network-side ad unit or placement ID |
CreativeId | No | Creative ID from the ad network |
NetworkPlacement | No | Network placement identifier |
CountryCode | No | User country code, if known |
UserSegment | No | User 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);
};Google Ad Manager paid events
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,
));
}