# CloudX Documentation — full text
Source: https://docs.cloudx.io/en/introduction
Chinese documentation is not included here; append `.md` to any /zh/ URL to read it.
---
# Introduction
> Welcome to CloudX Docs - maximize your ad revenue with our unified monetization platform
> **img:**
# CloudX Documentation
CloudX is a unified ad monetization platform that helps mobile developers maximize revenue through intelligent mediation and access to premium demand partners.
## Why CloudX?
* Higher Revenue
Our real-time bidding and mediation technology ensures you get the highest paying ad for every impression.
* Easy Integration
Get up and running with just a few lines of code — or let your AI coding agent do it on any platform via [AI-assisted integration](/en/ai-integration).
* Premium Demand
Access demand from Meta, Vungle, and other top-tier ad networks through a single SDK.
* Privacy Compliant
Built-in support for GDPR, CCPA, and IAB standards (TCF 2.0, GPP, US Privacy).
## Supported Platforms
* [Android](/en/android/integration)
Native Android SDK with Kotlin and Java support.
* [iOS](/en/ios/integration)
Native iOS SDK with Swift and Objective-C support.
* [React Native](/en/react-native/integration)
Cross-platform React Native SDK. Supports iOS and Android.
* [Flutter](/en/flutter/integration)
Cross-platform Flutter SDK. Supports iOS and Android.
* [Unity](/en/unity/integration)
Cross-platform Unity plugin for games. Supports Android and iOS builds.
---
# How the auction works
> How CloudX runs multi-round auctions: line items, priorities, price floors, and round-by-round execution — with interactive simulators.
Most monetization platforms run a single auction per impression opportunity. All bidders compete once, under a single set of conditions.
CloudX takes a different approach: for every impression, it can run **multiple auction rounds**. Bidders compete head-to-head within each round, while you control which bidders participate in each round and on what terms.
## Why it matters
Waterfalls introduced sequencing, but limited competition per impression. Single unified auctions increased competition, but at the expense of publisher control. Multi-round auctions give you the best of both worlds: real-time competition *inside* each round, and publisher-defined sequencing *across* rounds.
A single auction where bid price is all that matters has the benefit of simplicity, but that simplicity has costs. When every $60 bid is treated equally — whether the ad is a 120-second non-skippable with a tricky close button or a 30-second skippable with an obvious one — the incentive structure is clear, and ad quality races to the bottom while retention pays the price.
Multi-round auctions unlock things a single auction can't express:
* **Increased pricing power** — higher-value demand competes at higher floors first; lower-value demand is only accessed if needed.
* **Prioritize preferred partners** — give preferred demand partners preferred access, based on creative quality, strategic value, demand diversity, or any other reason.
* **Work more flexibly with buyers** — structure access on your terms: PMPs, first-party data sharing, custom floors.
The result: more revenue from the same inventory, with the ability to optimize for factors beyond bid price — creative quality, buyer relationships, and user experience.
## Line items define the rules
You don't configure rounds directly. You configure [line items](/en/dashboard/line-items), and CloudX groups them into rounds automatically.
A line item is a targeting rule paired with bidder configuration: *for requests matching these conditions, invite the specified bidders and reject anything below the stated floor.*
| Field | What it does |
| ----------- | --------------------------------------------------------------------------------------------- |
| Priority | Position in the sequence, from 1 (called first) to 12 (default: 8) |
| Targeting | Which requests the line item matches (ad unit, country, OS, connection type, IFA presence, …) |
| Bidders | Which demand sources compete when this line item is called |
| Price floor | The minimum CPM a bid must meet to win |
Every ad request carries attributes — country, OS, language, connection type, session depth, ATT status. A line item participates only when its targeting matches the request:
> **AuctionTargeting:**
## Matching line items become rounds
Matching line items are grouped by priority, and each distinct priority becomes one round, ordered from priority 1 down. Line items sharing a priority share a round, and the effective floor for a round is the highest across all line items in it:
```text round_floor
round_floor = MAX(li.price_floor for each li in round)
// This is the common floor on CloudX's ranking basis.
// Settlement adjustments can change the floor sent to an individual bidder.
```
> **Warning:** An auction executes **at most 3 rounds**. If more than 3 distinct priorities match a request, CloudX prepares the 3 highest-priority rounds and drops eligible line items from later priorities. Keep your matching line items within 3 priority levels so nothing silently falls out of the auction.
> **AuctionRounds:**
## Rounds execute in order
Once rounds are formed, CloudX executes them sequentially:
1. **Enter the highest-priority round.** The round with the lowest priority number (closest to 1) runs first.
2. **Send bid requests to all bidders in parallel.** Bidding happens server-side, so every bidder in the round sees the request simultaneously.
3. **Reject anything below the floor.** CloudX applies any settlement adjustment, then discards bids under the round's effective floor.
4. **Select the highest valid bid.** The highest remaining effective bid wins and the auction ends — later rounds are never called. Ties are random. If no valid bid remains, the next round runs. If every round is exhausted (at most 3), the request ends with no fill.
Multi-round auctions operate within latency limits set with fill rates and user experience in mind — sequencing across rounds never means bidders waiting on each other inside a round.
> **AuctionExecution:**
> **Info:** Within a round, competition is a real-time auction — the best effective price wins. Across rounds, *you* decide the order. A direct deal at priority 1 always gets first look, no matter what the open exchange would have paid later.
## Next steps
* [Line items](/en/dashboard/line-items)
Create line items and configure priorities, floors, targeting, and bidders.
* [A/B testing](/en/dashboard/ab-testing)
Test different auction configurations against each other.
---
# AI-Assisted Integration
> Integrate the CloudX SDK using your AI coding agent — Claude Code, Codex, Cursor, or any agent that can read files and fetch URLs
Point your AI coding agent at the CloudX agent package and it can carry out the
SDK integration for you — on Android, iOS, React Native, Flutter, and Unity.
The package contains no version numbers or API snapshots; your agent fetches
this documentation live while it works, so its guidance is always current with
the latest release.
The package also includes playbooks for real-world situations the reference
docs don't cover: running CloudX first-look or in parallel alongside an
existing mediator (AppLovin MAX, LevelPlay, Google, or another), migrating off
a mediation stack, consent and CMP handling, and platform build-system quirks.
## Claude Code
Install the plugin once:
```text
/plugin marketplace add cloudx-io/cloudx-sdk-agents
/plugin install cloudx@cloudx
```
Then, in your app project:
```text
integrate CloudX with app key YOUR_APP_KEY
```
## Cursor / Codex
Clone [cloudx-io/cloudx-sdk-agents](https://github.com/cloudx-io/cloudx-sdk-agents)
(or vendor its `AGENTS.md`, `.agents/skills/`, and `playbooks/` into your app
repository). `AGENTS.md` is the entry point; both tools pick it up natively.
## Any other agent
No install needed — paste into any agent with web access:
```text
Follow https://raw.githubusercontent.com/cloudx-io/cloudx-sdk-agents/main/.agents/skills/cloudx-integrate/SKILL.md
to integrate the CloudX SDK into this project.
```
## Example prompts
* `integrate CloudX with app key YOUR_APP_KEY` — full integration on the
detected platform
* `we're on AppLovin MAX — integrate CloudX first-look for our banner, MAX
stays as fallback` — coexistence with your existing mediation
* `add a CloudX banner in parallel with our existing setup so we can compare
revenue` — side-by-side comparison
* `audit my CloudX integration` — check an existing integration against the
current docs
> **Info:** Have your CloudX app key and ad unit IDs ready, and tell the agent which
> networks are enabled for your app in the CloudX dashboard — it installs
> exactly those adapters.
## Related
* [CloudX MCP](/en/mcp) — reporting, auction inspection, and documentation
search inside the same agent. Complements the integration package.
---
# Overview
> Overview of CloudX Android SDK setup and core features
[](https://central.sonatype.com/artifact/io.cloudx/sdk)
> **Info:** Prefer AI-assisted setup? Point your coding agent at the CloudX agent package — see [AI-assisted integration](/en/ai-integration).
## Installation
Requires Android API 23+, Java 8+, and Kotlin 2.1 or newer in the host app. The Magnite adapter needs Kotlin 2.3 or newer; see the note below the dependencies.
The CloudX SDK and its adapters are published to Maven Central (`https://repo1.maven.org/maven2/`). Make sure your project resolves from it in `settings.gradle.kts`:
```kotlin
dependencyResolutionManagement {
repositories {
google()
mavenCentral() // https://repo1.maven.org/maven2/
}
}
```
Then add the CloudX SDK to your app's build.gradle:
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:sdk:4.10.0")
// Adapters for ad networks
implementation("io.cloudx:adapter-bigo:6.1.0.0") // BIGO Ads SDK 6.1.0
implementation("io.cloudx:adapter-digitalturbine:8.4.7.2") // Digital Turbine Marketplace SDK 8.4.7
implementation("io.cloudx:adapter-googlewaterfall:25.5.0.0") // Google Mobile Ads SDK 25.5.0
implementation("io.cloudx:adapter-inmobi:11.5.0.1") // InMobi SDK 11.5.0
implementation("io.cloudx:adapter-magnite:1.0.2.1") // Magnite Ads SDK 1.0.2
implementation("io.cloudx:adapter-meta:6.22.0.1") // Meta Audience Network 6.22.0
implementation("io.cloudx:adapter-mintegral:17.1.81.1") // Mintegral SDK 17.1.81
implementation("io.cloudx:adapter-mobilefuse:1.12.0.1") // MobileFuse SDK 1.12.0
implementation("io.cloudx:adapter-moloco:4.13.0.1") // Moloco SDK 4.13.0
implementation("io.cloudx:adapter-pangle:8.3.0.4.2") // Pangle SDK 8.3.0.4
implementation("io.cloudx:adapter-taurusx:1.19.1.1") // TaurusX SDK 1.19.1
implementation("io.cloudx:adapter-unityads:4.21.0.2") // Unity Ads SDK 4.21.0
implementation("io.cloudx:adapter-verve:3.9.3.0") // Verve HyBid SDK 3.9.3
implementation("io.cloudx:adapter-vungle:7.7.9.1") // Vungle SDK 7.7.9
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:sdk:4.10.0'
// Adapters for ad networks
implementation 'io.cloudx:adapter-bigo:6.1.0.0' // BIGO Ads SDK 6.1.0
implementation 'io.cloudx:adapter-digitalturbine:8.4.7.2' // Digital Turbine Marketplace SDK 8.4.7
implementation 'io.cloudx:adapter-googlewaterfall:25.5.0.0' // Google Mobile Ads SDK 25.5.0
implementation 'io.cloudx:adapter-inmobi:11.5.0.1' // InMobi SDK 11.5.0
implementation 'io.cloudx:adapter-magnite:1.0.2.1' // Magnite Ads SDK 1.0.2
implementation 'io.cloudx:adapter-meta:6.22.0.1' // Meta Audience Network 6.22.0
implementation 'io.cloudx:adapter-mintegral:17.1.81.1' // Mintegral SDK 17.1.81
implementation 'io.cloudx:adapter-mobilefuse:1.12.0.1' // MobileFuse SDK 1.12.0
implementation 'io.cloudx:adapter-moloco:4.13.0.1' // Moloco SDK 4.13.0
implementation 'io.cloudx:adapter-pangle:8.3.0.4.2' // Pangle SDK 8.3.0.4
implementation 'io.cloudx:adapter-taurusx:1.19.1.1' // TaurusX SDK 1.19.1
implementation 'io.cloudx:adapter-unityads:4.21.0.2' // Unity Ads SDK 4.21.0
implementation 'io.cloudx:adapter-verve:3.9.3.0' // Verve HyBid SDK 3.9.3
implementation 'io.cloudx:adapter-vungle:7.7.9.1' // Vungle SDK 7.7.9
}
```
> **Note:** Apps that include the Magnite adapter must compile with Kotlin 2.3 or later, because Magnite SDK 1.0.1 and later depend on kotlin-stdlib 2.4.0.
## Initialization
```kotlin Kotlin
// Initialize with app key
CloudX.initialize(
configuration = CloudXInitializationConfiguration.builder("your-app-key-here")
.build(),
listener = object : CloudXInitializationListener {
override fun onInitialized(configuration: CloudXSdkConfiguration) {
Log.d("CloudX", "CloudX SDK initialized successfully")
}
override fun onInitializationFailed(cloudXError: CloudXError) {
Log.e("CloudX", "Failed to initialize CloudX SDK: ${cloudXError.message}")
}
}
)
```
```java Java
// Initialize with app key
CloudXInitializationConfiguration configuration = CloudXInitializationConfiguration.builder("your-app-key-here")
.build();
CloudX.initialize(configuration, new CloudXInitializationListener() {
@Override
public void onInitialized(@NonNull CloudXSdkConfiguration configuration) {
Log.d("CloudX", "CloudX SDK initialized successfully");
}
@Override
public void onInitializationFailed(@NonNull CloudXError cloudXError) {
Log.e("CloudX", "Failed to initialize CloudX SDK: " + cloudXError.getMessage());
}
});
```
### App Startup Warm-Up
Starting with SDK 4.7.0, the SDK includes an [AndroidX App Startup](https://developer.android.com/topic/libraries/app-startup) initializer that prepares some of its work before you call `CloudX.initialize()`. This is a performance optimization: it lets the SDK initialize and load its first ad faster, without slowing your app's startup.
The warm-up does no work on the main thread at process start and waits until your app is in the foreground and idle before doing anything more than lightweight background setup. Processes that never come to the foreground, such as those started for push messages or background jobs, do only that lightweight setup.
This does not initialize the SDK. `CloudX.initialize()` is still required, and nothing about when or how you call it changes. The warm-up is best-effort: if it has not finished, the SDK does the work when it needs it, and it cannot cause your app's startup to fail.
To turn the warm-up off, remove the initializer in your app manifest:
```xml AndroidManifest.xml
```
The `xmlns:tools` declaration on the provider lets you paste the block in as-is. If your `` element already declares the prefix, you can drop it.
Behavior is the same either way. With the initializer removed, the SDK does this preparation when you call `initialize()`, so your first initialization and first ad load are noticeably slower.
## Ad Formats
CloudX supports banner, MREC, interstitial, rewarded, native, and App Open ad integrations. Use the format-specific guides for implementation details:
* [Banner & MREC ads](/en/android/ad-formats/banner-mrec)
Create fixed-size display placements with optional refresh control.
* [Interstitial ads](/en/android/ad-formats/interstitial)
Load and show full-screen interstitial placements.
* [Native ads](/en/android/ad-formats/native)
Render native creatives in custom app layouts.
* [Rewarded ads](/en/android/ad-formats/rewarded)
Reward users after completed rewarded ad views.
* [App Open ads](/en/android/ad-formats/app-open)
Show fullscreen ads at natural app-open or app-resume moments.
### Ad Information (CloudXAd)
The `CloudXAd` object is passed to listener callbacks and contains information about the loaded/displayed ad:
| Property | Type | Description |
| ------------------ | --------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `adFormat` | `CloudXAdFormat` | Ad format (BANNER, MREC, INTERSTITIAL, REWARDED, NATIVE, APP\_OPEN) |
| `adUnitId` | `String` | The ad unit ID |
| `networkName` | `String` | Name of the winning ad network |
| `networkPlacement` | `String?` | Network-specific placement ID |
| `mediatorName` | `String` | Mediator that served the ad; `cloudx` for CloudX-won ads |
| `mediatorAdUnitId` | `String?` | The mediator's ad unit ID; equals `adUnitId` for CloudX-won ads |
| `creativeId` | `String?` | Creative identifier for creative-level issue reporting |
| `placement` | `String?` | Custom placement set via `setPlacement()` |
| `revenue` | `Double` | Revenue in USD: the bid-time estimate for CloudX bids, or the realized amount when the ad source reports impression-level revenue |
| `revenuePrecision` | `String` | Precision of `revenue`: `exact`, `estimated`, `publisher_defined`, or `undefined` |
| `adValues` | `Map` | SDK-provided ad metadata, used by features such as Trusted Arbiter |
`creativeId` is `null` when the demand source does not report a creative id. It is distinct from the `creativeId` you pass in [`CloudXRevenueData`](#publisher-reported-revenue-data), which describes an impression won outside the CloudX ad flow.
```kotlin
override fun onAdLoaded(cloudXAd: CloudXAd) {
Log.d("CloudX", "Ad format: ${cloudXAd.adFormat}")
Log.d("CloudX", "Network: ${cloudXAd.networkName}")
Log.d("CloudX", "Bid-time revenue: ${cloudXAd.revenue}")
}
```
### Error Handling
All SDK errors are returned as `CloudXError` objects in listener callbacks:
| Property | Type | Description |
| ------------------ | ----------------- | ---------------------------------------------------- |
| `code` | `CloudXErrorCode` | Error category |
| `message` | `String` | Human-readable description |
| `cause` | `Throwable?` | Optional underlying exception |
| `formattedMessage` | `String` | Pre-formatted message including code and description |
#### Error Code Categories
| Range | Category | Common Codes |
| ------- | -------------- | -------------------------------------------------------------------------------------------- |
| 0 | General | `INTERNAL_ERROR` |
| 100-199 | Network | `NETWORK_ERROR`, `NETWORK_TIMEOUT`, `NETWORK_SERVER_ERROR`, `NETWORK_NO_CONNECTION` |
| 200-299 | Initialization | `NOT_INITIALIZED`, `SDK_DISABLED`, `NO_ADAPTERS_FOUND`, `INVALID_APP_KEY` |
| 300-399 | Ad Loading | `NO_FILL`, `INVALID_AD_UNIT`, `ADS_DISABLED` |
| 400-499 | Display | `AD_NOT_READY`, `AD_ALREADY_SHOWING` |
| 600-699 | Adapter | `ADAPTER_NO_FILL`, `ADAPTER_TIMEOUT`, `ADAPTER_LOAD_TIMEOUT`, `ADAPTER_INITIALIZATION_ERROR` |
## Advanced Features
### Debug Logging
```kotlin Kotlin
CloudX.setMinLogLevel(CloudXLogLevel.DEBUG) // Enable debug logging
CloudX.setMinLogLevel(CloudXLogLevel.NONE) // Disable all logging
```
```java Java
CloudX.setMinLogLevel(CloudXLogLevel.DEBUG); // Enable debug logging
CloudX.setMinLogLevel(CloudXLogLevel.NONE); // Disable all logging
```
**Log Levels:** `VERBOSE` \< `DEBUG` \< `INFO` \< `WARN` \< `ERROR` \< `NONE`
Filter logcat with tag `CloudX` to see SDK logs.
### Mediation Debugger
Open the Mediation Debugger to check an integration on the device: every installed adapter with its version and status (completed, incomplete, missing, or not configured), the SDK configuration, privacy state, and your ad units. You can also load and show a test ad per ad unit and share the whole report as text.
```kotlin Kotlin
CloudX.showMediationDebugger(context)
```
```java Java
CloudX.showMediationDebugger(context);
```
Call it after the `initialize` callback reports success. Before that, it returns `false` and opens nothing. The screen updates live while open. Pass an Activity context where possible. It is meant for development and QA builds; wire it to a debug menu or a gesture.
**Note:** A test mode request from the debugger takes effect on the next initialization, after you restart the app. The server decides whether that session runs in test mode.
### Publisher-Reported Revenue Data
If your app receives impression-level revenue callbacks or bid metadata from AdMob, 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.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`, `rewarded`, `native`, or `app_open` |
| `currencyCode` | No | ISO 4217 currency code, if known |
| `precision` | No | `CloudXRevenuePrecision.EXACT`, `ESTIMATED`, `PUBLISHER_DEFINED`, 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 |
`reportRevenueData()` returns `true` when the event is accepted into the CloudX revenue pipeline. It returns `false` if the SDK is not initialized, server-side revenue tracking is disabled, or the platform name is blank. Acceptance is not a delivery guarantee.
#### AdMob paid events
AdMob Android `AdValue.valueMicros` is reported in micro-units of the supplied currency, so divide it by `1_000_000.0` before passing it to CloudX.
```kotlin
private fun Int.toCloudXRevenuePrecision(): CloudXRevenuePrecision = when (this) {
AdValue.PrecisionType.PRECISE -> CloudXRevenuePrecision.EXACT
AdValue.PrecisionType.ESTIMATED -> CloudXRevenuePrecision.ESTIMATED
AdValue.PrecisionType.PUBLISHER_PROVIDED -> CloudXRevenuePrecision.PUBLISHER_DEFINED
else -> CloudXRevenuePrecision.UNDEFINED
}
private fun reportAdMobPaidEvent(
adValue: AdValue,
adFormat: String,
adUnitId: String,
responseInfo: ResponseInfo?,
): Boolean {
val servedBy = responseInfo?.loadedAdapterResponseInfo
return CloudX.reportRevenueData(
CloudXRevenueData.builder(
platform = CloudXRevenuePlatform.ADMOB,
revenue = adValue.valueMicros / 1_000_000.0,
adFormat = adFormat,
)
.currencyCode(adValue.currencyCode)
.precision(adValue.precisionType.toCloudXRevenuePrecision())
.networkName(servedBy?.adSourceName)
.adUnitId(adUnitId)
.thirdPartyAdPlacementId(servedBy?.adSourceInstanceName)
.build(),
)
}
bannerView.setOnPaidEventListener { adValue ->
reportAdMobPaidEvent(
adValue = adValue,
adFormat = "banner",
adUnitId = adUnitId,
responseInfo = bannerView.responseInfo,
)
}
```
#### InMobi impression events
For InMobi, save the `AdMetaInfo` object from `onAdFetchSuccessful`. When `onAdImpression` fires, send the saved `metaInfo.bid` to CloudX, then clear the saved value.
```kotlin
private var latestInMobiMetaInfo: AdMetaInfo? = null
override fun onAdFetchSuccessful(ad: InMobiBanner, info: AdMetaInfo) {
latestInMobiMetaInfo = info
}
override fun onAdImpression(ad: InMobiBanner) {
latestInMobiMetaInfo?.let { metaInfo ->
reportInMobiImpression(
metaInfo = metaInfo,
adFormat = "banner",
placementId = inMobiPlacementId.toString(),
)
}
latestInMobiMetaInfo = null
}
private fun reportInMobiImpression(
metaInfo: AdMetaInfo,
adFormat: String,
placementId: String,
): Boolean =
CloudX.reportRevenueData(
CloudXRevenueData.builder(
platform = CloudXRevenuePlatform.INMOBI,
revenue = metaInfo.bid,
adFormat = adFormat,
)
.precision(CloudXRevenuePrecision.ESTIMATED)
.thirdPartyAdPlacementId(placementId)
.creativeId(metaInfo.creativeID)
.build(),
)
```
For interstitial and rewarded InMobi ads, use the same pattern with `InterstitialAdEventListener`: save `AdMetaInfo` in `onAdFetchSuccessful`, then report it in `onAdImpression`.
#### TopOn revenue events
TopOn Android reports revenue in `onAdRevenuePaid(ATAdInfo)`. Set an `ATAdRevenueListener` on the ad object and pass `adInfo.getPublisherRevenue()` directly to CloudX. Do not divide this value like AdMob micros or CPM.
```kotlin
private fun reportTopOnRevenue(
adInfo: ATAdInfo,
adFormat: String,
): Boolean =
CloudX.reportRevenueData(
CloudXRevenueData.builder(
platform = CloudXRevenuePlatform.TOPON,
revenue = adInfo.getPublisherRevenue(),
adFormat = adFormat,
)
.currencyCode(adInfo.getCurrency())
.networkName(adInfo.getNetworkName())
.adUnitId(adInfo.getPlacementId())
.thirdPartyAdPlacementId(adInfo.getNetworkPlacementId())
.networkPlacement(adInfo.getAdsourceId())
.build(),
)
mBannerView.setAdRevenueListener(object : ATAdRevenueListener {
override fun onAdRevenuePaid(adInfo: ATAdInfo) {
reportTopOnRevenue(
adInfo = adInfo,
adFormat = "banner",
)
}
})
```
For other TopOn ad formats, set the same listener on the loaded ad object and pass the matching CloudX `adFormat`.
#### Custom platform events
Use `custom` for providers that do not have a CloudX SDK constant like AdMob, 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.
```kotlin
private fun reportCustomRevenueEvent(
providerName: String,
revenue: Double,
adFormat: String,
currencyCode: String,
adUnitId: String,
placementId: String,
): Boolean =
CloudX.reportRevenueData(
CloudXRevenueData.builder(
platform = CloudXRevenuePlatform.custom(providerName),
revenue = revenue,
adFormat = adFormat,
)
.currencyCode(currencyCode)
.precision(CloudXRevenuePrecision.PUBLISHER_DEFINED)
.adUnitId(adUnitId)
.thirdPartyAdPlacementId(placementId)
.build(),
)
```
### Impression-Level Revenue Tracking
Set a `revenueListener` on any ad format to receive revenue callbacks. `CloudXAd.revenue` contains the bid-time USD estimate.
```kotlin Kotlin
bannerAd.revenueListener = object : CloudXAdRevenueListener {
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
Log.d("CloudX", "Bid-time revenue: ${cloudXAd.revenue} from ${cloudXAd.networkName}")
}
}
```
```java Java
bannerAd.setRevenueListener(new CloudXAdRevenueListener() {
@Override
public void onAdRevenuePaid(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Bid-time revenue: " + cloudXAd.getRevenue() + " from " + cloudXAd.getNetworkName());
}
});
```
Works with all ad formats (banner, MREC, interstitial, rewarded).
> **Info:** Starting with SDK 4.9.0, test-mode sessions deliver per-ad and global revenue callbacks again, including revenue sent through connectors, with `revenue` set to 0 and `revenuePrecision` set to `"undefined"`. SDK 4.7.0 to 4.8.x suppressed them. Use these callbacks to check your revenue wiring, not the amounts.
### Test Mode
Test mode is **server-controlled** via device whitelisting. This provides better security and control over which devices receive test ads.
**To enable test mode:**
1. Initialize the SDK and check logcat for your device advertising ID:
```
[CloudX][AdvertisingIdProvider] Device IFA for test whitelisting: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX (LAT: false)
```
2. Copy the advertising ID and add it to your device whitelist on the CloudX server dashboard
3. The SDK will automatically configure adapters for test mode and include the test flag in bid requests
**Note:** Test mode is determined by the server, so you don't need to change any code between development and production builds.
### Privacy Compliance
The CloudX SDK supports GDPR and CCPA privacy compliance by reading standard IAB privacy strings from SharedPreferences. These values are typically set automatically by your Consent Management Platform (CMP) such as Google UMP, OneTrust, or Sourcepoint.
#### How It Works
The SDK automatically detects user location and reads consent signals:
1. **EU Users (GDPR)**: Checks TCF v2 consent for purposes 1 and 2 per the [IAB Global Vendor List](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework) and vendor consent ([CloudX Vendor ID: 1510](https://vendor-list.consensu.org/v3/vendor-list.json))
2. **US Users (CCPA)**: Checks for sale/sharing opt-out signals
3. **Other Regions**: No restrictions applied
When consent is denied or user opts out, the SDK removes PII from ad requests:
* Advertising ID (GAID) is cleared
* Geo coordinates (lat/lon) are removed
* User key-values are not sent
* Hashed user ID is removed from bidder-bound requests and suppressed from impression reporting
#### Supported Privacy Keys
| Key | Standard | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `IABGPP_HDR_GppString` | [GPP](https://github.com/InteractiveAdvertisingBureau/Global-Privacy-Platform) | Global Privacy Platform string (modern) |
| `IABGPP_GppSID` | GPP | Section IDs (e.g., "2" for EU, "7" for US-National, "8" for US-CA) |
| `IABTCF_TCString` | [TCF v2](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework) | GDPR consent string (legacy) |
| `IABTCF_gdprApplies` | TCF v2 | Whether GDPR applies (1 = yes, 0 = no) |
| `IABUSPrivacy_String` | [US Privacy](https://github.com/InteractiveAdvertisingBureau/USPrivacy) | CCPA privacy string (legacy, e.g., "1YNN") |
> **Note**: The SDK prioritizes GPP (modern standard) over legacy TCF/US Privacy strings when both are available.
#### Manual Privacy API
If you manage user consent yourself (without a CMP), you can set GDPR and CCPA privacy status directly. These must be called prior to SDK initialization.
```kotlin Kotlin
// GDPR consent: true, false, or null to defer to CMP
CloudX.setHasUserConsent(true)
// CCPA do-not-sell: true, false, or null to defer to CMP
CloudX.setDoNotSell(true)
```
```java Java
// GDPR consent: true, false, or null to defer to CMP
CloudX.setHasUserConsent(true);
// CCPA do-not-sell: true, false, or null to defer to CMP
CloudX.setDoNotSell(true);
```
> **Note:** When both manual values and CMP signals are present, CMP signals (GPP/TCF/US Privacy) take priority. Manual values act as a fallback when no CMP is integrated.
### User Targeting
```kotlin Kotlin
// Set hashed user ID for targeting
CloudX.setHashedUserId("hashed-user-id")
// Set custom user key-value pairs
CloudX.setUserKeyValue("age", "25")
CloudX.setUserKeyValue("gender", "male")
CloudX.setUserKeyValue("location", "US")
// Set custom app key-value pairs
CloudX.setAppKeyValue("app_version", "1.0.0")
CloudX.setAppKeyValue("user_level", "premium")
// Clear all custom key-values
CloudX.clearAllKeyValues()
```
```java Java
// Set hashed user ID for targeting
CloudX.setHashedUserId("hashed-user-id");
// Set custom user key-value pairs
CloudX.setUserKeyValue("age", "25");
CloudX.setUserKeyValue("gender", "male");
CloudX.setUserKeyValue("location", "US");
// Set custom app key-value pairs
CloudX.setAppKeyValue("app_version", "1.0.0");
CloudX.setAppKeyValue("user_level", "premium");
// Clear all custom key-values
CloudX.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](/en/cli/exports#cloudx-export-requests) and [impression](/en/cli/exports#cloudx-export-impressions) 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.
User and app key-values are also available in the [request activity export](/en/cli/exports#cloudx-export-requests). Use `setUserKeyValue` for session-wide user attributes and `setAppKeyValue` for session-wide app attributes. The SDK sends current values with each later auction request.
#### 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](https://unifiedid.com/docs/ref-info/ref-tokens) |
| `euid.eu` | EUID advertising token, not the refresh token. Do not decrypt it. [EUID docs](https://euid.eu/docs/ref-info/ref-tokens) |
| `liveramp.com` | LiveRamp ATS envelope, not a RampID. [LiveRamp docs](https://developers.liveramp.com/authenticatedtraffic-api/docs/pass-mobile-envelopes-to-the-bidstream) |
| `id5-sync.com` | ID5 universal UID. Do not pass `0`. [ID5 docs](https://wiki.id5.io/docs/mobile-in-app-integration) |
| `intentiq.com` | Intent IQ ID (IIQ ID) from your Intent IQ integration. [Intent IQ docs](https://docs.prebid.org/dev-docs/modules/userid-submodules/intentiq.html) |
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.
```kotlin Kotlin
CloudX.setUserKeyValue("uidapi.com", uid2Token)
CloudX.setUserKeyValue("euid.eu", euidToken)
CloudX.setUserKeyValue("liveramp.com", liveRampEnvelope)
CloudX.setUserKeyValue("id5-sync.com", id5Id)
CloudX.setUserKeyValue("intentiq.com", intentIQId)
```
```java Java
CloudX.setUserKeyValue("uidapi.com", uid2Token);
CloudX.setUserKeyValue("euid.eu", euidToken);
CloudX.setUserKeyValue("liveramp.com", liveRampEnvelope);
CloudX.setUserKeyValue("id5-sync.com", id5Id);
CloudX.setUserKeyValue("intentiq.com", intentIQId);
```
> **Warning:** Use `setUserKeyValue` only. Do not put these tokens in `setAppKeyValue` or `setExtraParameter`. Extra parameters apply to one ad load. Key names must match exactly.
> **Note:** Do not set a key until you have a value. Setting it again replaces the previous value. If a value expires without a replacement, set that key to an empty string to stop sending its EID. `clearAllKeyValues()` clears all user and app key-values and the hashed user ID.
For metadata that belongs to one ad object or load, call `setExtraParameter` before `load`:
```kotlin
adView.setExtraParameter("requestId", "request-456")
adView.setExtraParameter("impressionKey", "impression-789")
adView.load()
```
Extra parameters remain on that ad object until changed or cleared. The SDK snapshots them for each load. CloudX returns the complete bag in the `extra_parameters` request-export column. The reserved `tags` key also controls [tag-based routing](/en/dashboard/tags); use another key for correlation metadata.
CloudX stores each of the three compact JSON bags only when the entire bag is at most 256 UTF-8 bytes. The limit is independent for each export column and includes all combined keys, values, quotes, separators, and braces—not 256 bytes per K/V pair. When privacy rules prohibit publisher-data persistence, or a bag is malformed or too large, its export cell is empty. Do not include raw personal data, secrets, or consent strings.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Sample App
> Run the public CloudX Android demo app and point it at your own app
The [cloudx-io/cloudx-android-public](https://github.com/cloudx-io/cloudx-android-public) repository is a runnable sample app. It opens on a screen that picks one of three flows. General shows banner, MREC, interstitial, rewarded, app open and native ads, each in one self-contained class under `app/src/main/java/io/cloudx/demo/demoapp/ads/`. First Look loads CloudX first and falls back to AdMob, as described in [First Look](/en/android/integrations/first-look). Arbiter/TPA loads CloudX and AdMob in parallel and lets [Trusted Arbiter](/en/android/trusted-arbiter) pick the one to show. It ships with CloudX demo dashboard IDs and Google's AdMob test IDs, so it runs without an account: `./gradlew :app:installDebug` installs it.
## Use your own CloudX app
To point it at your own CloudX app, replace the app key and ad unit IDs in `DemoConfig.kt`, then set `applicationId` in `app/build.gradle.kts` to the package name registered for that app. Bid requests are authorized per app key and package name, so both must match your dashboard app or the SDK gets no fill.
---
# First Look
> Give CloudX the first chance to fill an ad placement, then fall back to your existing mediation setup
First Look gives CloudX the first chance to fill a placement while preserving your existing mediation setup as the fallback path. Start with one placement, verify load and show behavior, then expand to more placements.
This example uses an AdMob interstitial fallback. The same pattern applies to other fallback mediators: load CloudX first, load the fallback only if CloudX does not load, and keep the app flow moving when neither source is ready.
> **Info:** Initialize CloudX and Google Mobile Ads before creating the controller. Keep your existing pacing, retry, and placement timing rules around this controller.
```kotlin FirstLookInterstitialController.kt
import android.app.Activity
import com.google.android.gms.ads.AdError
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.FullScreenContentCallback
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.interstitial.InterstitialAd
import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback
import io.cloudx.sdk.CloudX
import io.cloudx.sdk.CloudXAd
import io.cloudx.sdk.CloudXError
import io.cloudx.sdk.CloudXInterstitialAd
import io.cloudx.sdk.CloudXInterstitialListener
class FirstLookInterstitialController(
private val activity: Activity,
private val cloudXAdUnitId: String,
private val adMobAdUnitId: String,
) {
enum class Source {
CLOUDX,
ADMOB,
}
interface Listener {
fun onAdLoaded(source: Source) {}
fun onAdLoadFailed(source: Source, message: String?) {}
fun onAdShown(source: Source) {}
fun onAdShowFailed(source: Source, message: String?) {}
fun onAdClosed(source: Source) {}
fun onAdClicked(source: Source) {}
}
var listener: Listener? = null
private val cloudXInterstitial: CloudXInterstitialAd =
CloudX.createInterstitial(activity, cloudXAdUnitId)
private var adMobInterstitial: InterstitialAd? = null
private var isLoadingCloudX = false
private var isLoadingAdMob = false
init {
cloudXInterstitial.listener = createCloudXListener()
}
fun load() {
if (isLoadingCloudX || isLoadingAdMob || cloudXInterstitial.isAdReady || adMobInterstitial != null) {
return
}
isLoadingCloudX = true
cloudXInterstitial.load()
}
fun show(): Boolean {
if (cloudXInterstitial.isAdReady) {
cloudXInterstitial.show(activity)
return true
}
return showAdMobFallback()
}
fun destroy() {
cloudXInterstitial.destroy()
adMobInterstitial = null
listener = null
}
private fun createCloudXListener() = object : CloudXInterstitialListener {
override fun onAdLoaded(cloudXAd: CloudXAd) {
isLoadingCloudX = false
listener?.onAdLoaded(Source.CLOUDX)
}
override fun onAdLoadFailed(adUnitId: String, cloudXError: CloudXError) {
isLoadingCloudX = false
loadAdMobFallback()
}
override fun onAdDisplayed(cloudXAd: CloudXAd) {
listener?.onAdShown(Source.CLOUDX)
}
override fun onAdDisplayFailed(cloudXAd: CloudXAd, cloudXError: CloudXError) {
if (showAdMobFallback()) {
return
}
listener?.onAdShowFailed(Source.CLOUDX, cloudXError.message)
}
override fun onAdHidden(cloudXAd: CloudXAd) {
listener?.onAdClosed(Source.CLOUDX)
}
override fun onAdClicked(cloudXAd: CloudXAd) {
listener?.onAdClicked(Source.CLOUDX)
}
}
private fun loadAdMobFallback() {
if (isLoadingAdMob || adMobInterstitial != null) {
return
}
isLoadingAdMob = true
InterstitialAd.load(
activity,
adMobAdUnitId,
AdRequest.Builder().build(),
object : InterstitialAdLoadCallback() {
override fun onAdLoaded(interstitialAd: InterstitialAd) {
isLoadingAdMob = false
adMobInterstitial = interstitialAd.apply {
fullScreenContentCallback = createAdMobFullScreenCallback()
}
listener?.onAdLoaded(Source.ADMOB)
}
override fun onAdFailedToLoad(loadAdError: LoadAdError) {
isLoadingAdMob = false
adMobInterstitial = null
listener?.onAdLoadFailed(Source.ADMOB, loadAdError.message)
}
},
)
}
private fun showAdMobFallback(): Boolean {
val ad = adMobInterstitial ?: return false
ad.show(activity)
return true
}
private fun createAdMobFullScreenCallback() = object : FullScreenContentCallback() {
override fun onAdShowedFullScreenContent() {
adMobInterstitial = null
listener?.onAdShown(Source.ADMOB)
}
override fun onAdDismissedFullScreenContent() {
adMobInterstitial = null
listener?.onAdClosed(Source.ADMOB)
}
override fun onAdFailedToShowFullScreenContent(adError: AdError) {
adMobInterstitial = null
listener?.onAdShowFailed(Source.ADMOB, adError.message)
}
override fun onAdClicked() {
listener?.onAdClicked(Source.ADMOB)
}
}
}
```
Use `show()` at the placement moment. If it returns `false`, neither CloudX nor the fallback had a ready ad, so continue the app flow without showing an ad.
```kotlin
if (!firstLookInterstitial.show()) {
continueToNextScreen()
}
```
After a show, close, or terminal failure, call `load()` again when your app is ready to prepare the next placement opportunity.
> **Tip:** This sample keeps the fallback lazy: AdMob loads only after CloudX fails to load. Do not parallel-load both sources unless CloudX recommends a different rollout strategy for your app.
## Demo App
The [CloudX Android sample app](https://github.com/cloudx-io/cloudx-android-public) runs this flow on device. It structures the flow as three files, the controller and one source per SDK, and its host screen retries a failed load or show with a 2 to 60 second backoff.
* [FirstLookInterstitialController.kt](https://github.com/cloudx-io/cloudx-android-public/blob/main/app/src/main/java/io/cloudx/demo/demoapp/ads/firstlook/FirstLookInterstitialController.kt)
Gives CloudX the first load attempt. AdMob loads only after CloudX fails or is unavailable.
* [CloudXFirstLookSource.kt](https://github.com/cloudx-io/cloudx-android-public/blob/main/app/src/main/java/io/cloudx/demo/demoapp/ads/firstlook/CloudXFirstLookSource.kt)
CloudX's leg of the flow.
* [AdMobFirstLookSource.kt](https://github.com/cloudx-io/cloudx-android-public/blob/main/app/src/main/java/io/cloudx/demo/demoapp/ads/firstlook/AdMobFirstLookSource.kt)
The lazy AdMob fallback. A loaded ad shows once and expires after one hour.
* [FirstLookActivity.kt](https://github.com/cloudx-io/cloudx-android-public/blob/main/app/src/main/java/io/cloudx/demo/demoapp/ui/FirstLookActivity.kt)
Waits for both SDKs, retries with backoff and reports each step in the status lines. This is demo-only layout; take the rules, not the file.
Copy the first three together. The two sources log through the demo's `DemoLog`; swap in your own logging when you copy them.
---
# Trusted Arbiter
> Compare CloudX bids with supported third-party bids in Android apps
Trusted Arbiter compares a loaded CloudX bid with supported third-party bids and returns the selected platform. CloudX SDK versions 4.1.0 and later support CloudX, Unity LevelPlay, and PubMatic bid inputs. CloudX SDK 4.2.0 and later also supports custom publisher-supplied bid inputs.
## Why use Trusted Arbiter?
When your app loads ads from several platforms, it needs a way to choose which ad to show. Trusted Arbiter provides a shared comparison API for supported bids.
### Less comparison logic to maintain
Submit the supported bids to Trusted Arbiter and use the returned platform to select the ad. Your app continues to manage partner SDK integration, ad loading, and display.
### Use available bid values
Trusted Arbiter can compare actual bid values where available. Some demand uses estimated prices. See the pricing guidance below for each supported input.
> **Info:** Call `CloudX.arbiter()` after `CloudX.initialize()` has completed. Until the arbiter service is available, the SDK falls back to selecting the highest comparable USD bid from the supplied inputs.
## Demo App
The [CloudX Android sample app](https://github.com/cloudx-io/cloudx-android-public) runs this page's cycle for interstitials. CloudX and AdMob load in parallel, the loaded ads become bids, `CloudX.arbiter()` picks the winner, and the winner is shown from a stored result so the show path makes no network call.
* [ArbiterInterstitialController.kt](https://github.com/cloudx-io/cloudx-android-public/blob/main/app/src/main/java/io/cloudx/demo/demoapp/ads/arbiter/ArbiterInterstitialController.kt)
The whole load, arbitrate and show cycle, with both SDKs' calls and the AdMob revenue report in one file. Rewarded follows this file with the rewarded calls substituted.
* [ArbiterActivity.kt](https://github.com/cloudx-io/cloudx-android-public/blob/main/app/src/main/java/io/cloudx/demo/demoapp/ui/ArbiterActivity.kt)
Brings up both SDKs, waits up to 15 seconds for CloudX, retries with a 2 to 60 second backoff and wires the Show button. This is demo-only layout; take the rules, not the file.
* [DemoConfig.kt](https://github.com/cloudx-io/cloudx-android-public/blob/main/app/src/main/java/io/cloudx/demo/demoapp/DemoConfig.kt)
App key and ad unit IDs. This is the first file to edit when you run the demo yourself.
The controller logs through the demo's `DemoLog`; swap in your own logging when you copy it. The demo ships Google's AdMob test units, which report revenue of 0. CloudX does not keep a price of 0, so the AdMob bid has no comparable price while you test. Point the demo at a paying unit to watch prices compete, or give the AdMob bid a manual price for one launch as the demo's README shows.
## When to run the arbiter
Run the arbiter when the candidate ads finish loading — never on the show path. The arbiter call is a network round trip, so a user who taps a button that shows an ad must never be left waiting on it. Arbitrate ahead of the placement and the decision is already made when the placement arrives.
### Fullscreen formats (interstitial, rewarded, app open): prepare ahead
1. Load all candidates in parallel.
2. When every candidate has settled — loaded or failed — run the arbiter and **store** the result.
3. At the placement, show the stored winner immediately. No arbiter call here.
4. Start the cycle again once the ad has been shown and dismissed, fails to display, or a candidate expires.
```kotlin
private var nextWinner: CloudXArbiterResult? = null
// Runs as soon as the candidates have loaded, ahead of the placement.
private fun prepareWinner() {
val configuration = CloudXArbiterConfiguration.builder(bids).build()
CloudX.arbiter(configuration, object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
nextWinner = result
}
})
}
// Runs at the placement. No network call here.
fun showInterstitial(activity: Activity) {
when (nextWinner?.platform) {
CloudXArbiterPlatform.CLOUDX -> cloudXInterstitial.show(activity)
CloudXArbiterPlatform.LEVELPLAY -> levelPlayInterstitial.showAd(activity)
else -> { } // no winner prepared; continue without an ad
}
nextWinner = null
}
```
If the placement arrives before a winner is stored, either continue the app flow without an ad or show the single candidate that did load. Showing a lone candidate is an acceptable degraded path, but it must never be the primary one — an arbitrated winner earns more than an unarbitrated fill.
### View formats (banner, MREC, native): arbitrate, then render
View formats have no user-initiated show, so nothing is waiting on the round trip. Here it is correct to attach or render the winner directly inside the completion callback: the arbiter completing *is* the trigger to show. Only the winner's view or assets may ever be attached — see [Banner and MREC arbitration](#banner-and-mrec-arbitration) for the attachment and refresh rules.
> **Note:** The arbiter call always completes. Its per-call timeout is supplied by the server in your init configuration, and on timeout, error, or an unavailable arbiter service the SDK returns a local fallback: the highest comparable USD bid among the inputs you supplied. A single supplied bid wins immediately without any service call. Do not wrap `CloudX.arbiter()` in a timeout of your own and do not compare revenue yourself — the SDK owns both the timeout and the fallback, and your own timer can only discard a decision that was on its way.
>
> `CloudXArbiterPlatform.NONE` means no winner was selected. With no bids supplied that is the only possible answer; with several bids it means the fallback found no candidate carrying a locally comparable price — for example Google bids without `manualRevenuePerImpressionUSD`.
## Supported ad formats
Trusted Arbiter is format-agnostic: arbiter payloads are attached to CloudX ads of every format, and any loaded CloudX ad can be compared against the supplied third-party bids.
* **Interstitial, rewarded, and app open** (fullscreen formats) prepare a winner ahead of the placement — see [When to run the arbiter](#when-to-run-the-arbiter) — and follow the step-by-step walkthrough and controller pattern shown later on this page. Worked examples: [Interstitial example](#interstitial-example) and [App Open example](#app-open-example). Rewarded follows the interstitial pattern unchanged — only the ad type differs.
* **Banner, MREC, and native** (view formats) arbitrate and then render. They need the additional handling covered in [Banner and MREC arbitration](#banner-and-mrec-arbitration) below, since a losing bid's view must never be attached to the view hierarchy. Worked examples: [Banner example](#banner-example) and [Native example](#native-example).
## Basic API
Create bid candidates from loaded ads, then pass them to `CloudX.arbiter()`.
```kotlin
// cloudXAd is the CloudXAd from a CloudX onAdLoaded callback.
// levelPlayAdInfo is the Unity LevelPlay ad info object.
// pobBid is the PubMatic/OpenWrap bid object.
val bids = listOf(
CloudXArbiterBid.cloudX(cloudXAd),
CloudXArbiterBid.levelPlay(
networkName = levelPlayAdInfo.adNetwork,
revenue = levelPlayAdInfo.revenue,
precision = levelPlayAdInfo.precision,
),
CloudXArbiterBid.pubmatic(
price = pobBid.price,
partnerName = pobBid.partnerName,
)
)
val configuration = CloudXArbiterConfiguration.builder(bids).build()
CloudX.arbiter(configuration, object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
Log.d("CloudX", "Selected platform: ${result.platform.name}")
}
})
```
`CloudXArbiterBid.cloudX()` accepts the `CloudXAd` object from a CloudX load callback. `levelPlay()` accepts Unity LevelPlay ad info values. `pubmatic()` accepts a PubMatic OpenWrap bid price and optional partner name. `partnerName` and the `extras` map are optional on the bid factories. `onCompleted()` runs on the main thread. For fullscreen formats, store the result there and show it at the placement — see [When to run the arbiter](#when-to-run-the-arbiter); view formats may render the winner directly from it.
`result.platform` is `CloudXArbiterPlatform.CLOUDX`, `LEVELPLAY`, or `PUBMATIC` for the selected platform, or `CloudXArbiterPlatform.NONE` when no winner could be selected — either because no bids were supplied, or because the fallback found no candidate carrying a locally comparable price.
> **Tip:** Kotlin callers can use the suspending overload instead of a listener. It returns the same `CloudXArbiterResult`:
>
> ```kotlin
> val result = CloudX.arbiter(configuration)
> Log.d("CloudX", "Selected platform: ${result.platform.name}")
> ```
## AdMob and Google Ad Manager
CloudX compares a loaded CloudX ad with a loaded AdMob or Google Ad Manager ad. AdMob and Ad Manager are separate demand sources, so both may bid in the same arbitration. This applies to publisher-managed mediation setups, including accounts described commercially as AdMob Pro; there is no separate `AdMob Pro` SDK API.
Google demand does not normally reveal the price of a loaded ad before it is shown, so there is no pre-bid price for the arbiter to compare against CloudX's bid. CloudX prices the bid automatically from your historical Google performance, so you do not supply a price and no pre-bid pricing API is required. The revenue reporting below feeds that history. See [how AdMob/GAM estimated pricing works](/en/networks/google-estimated).
If your AdMob account exposes impression-level revenue data pre-bid, you can supply that exact price yourself instead of using the estimate — see [Manual input with pre-bid ILRD](#manual-input-with-pre-bid-ilrd) below.
### Report Google paid events back to CloudX (required)
Forwarding Google's paid events is a required part of the Trusted Arbiter AdMob and Ad Manager integration, not an optional analytics extra. After you show an AdMob or Ad Manager ad that won an arbitration, hand Google's impression-level revenue for that ad to the CloudX SDK. Without it, CloudX never learns what Google demand actually paid, and the estimates it produces for future arbitrations degrade.
Google delivers this revenue per ad through `OnPaidEventListener` — there is no global impression bus — so attach the listener to every arbitrated Google ad and forward each event through `CloudX.reportRevenueData()`. `AdValue.valueMicros` is in micro-units of the reported currency, so divide by `1_000_000.0` to get revenue for one impression. Use `CloudXRevenuePlatform.ADMOB` for AdMob ads and `CloudXRevenuePlatform.GAM` for Ad Manager ads.
```kotlin
private fun Int.toCloudXRevenuePrecision(): CloudXRevenuePrecision = when (this) {
AdValue.PrecisionType.PRECISE -> CloudXRevenuePrecision.EXACT
AdValue.PrecisionType.ESTIMATED -> CloudXRevenuePrecision.ESTIMATED
AdValue.PrecisionType.PUBLISHER_PROVIDED -> CloudXRevenuePrecision.PUBLISHER_DEFINED
else -> CloudXRevenuePrecision.UNDEFINED
}
private fun reportGooglePaidEvent(
platform: CloudXRevenuePlatform,
adValue: AdValue,
adFormat: String,
adUnitId: String,
responseInfo: ResponseInfo?,
): Boolean {
val servedBy = responseInfo?.loadedAdapterResponseInfo
return CloudX.reportRevenueData(
CloudXRevenueData.builder(
platform = platform,
revenue = adValue.valueMicros / 1_000_000.0,
adFormat = adFormat,
)
.currencyCode(adValue.currencyCode)
.precision(adValue.precisionType.toCloudXRevenuePrecision())
.networkName(servedBy?.adSourceName)
.adUnitId(adUnitId)
.thirdPartyAdPlacementId(servedBy?.adSourceInstanceName)
.build(),
)
}
// Attach the listener to the arbitrated Google ad before you show it.
adMobInterstitial.setOnPaidEventListener { adValue ->
reportGooglePaidEvent(
platform = CloudXRevenuePlatform.ADMOB,
adValue = adValue,
adFormat = "interstitial",
adUnitId = adMobAdUnitId,
responseInfo = adMobInterstitial.responseInfo,
)
}
// An Ad Manager ad reports the same way, with the GAM platform.
adManagerInterstitial.setOnPaidEventListener { adValue ->
reportGooglePaidEvent(
platform = CloudXRevenuePlatform.GAM,
adValue = adValue,
adFormat = "interstitial",
adUnitId = adManagerAdUnitId,
responseInfo = adManagerInterstitial.responseInfo,
)
}
```
`reportRevenueData()` returns `true` when the event is accepted into the CloudX revenue pipeline, and `false` if the SDK is not initialized or server-side revenue tracking is disabled. For banner and MREC, attach the listener once to the `AdView` — Google re-fires paid events on the same view — and pass `"banner"` or `"mrec"` as the format. See [Publisher-Reported Revenue Data](/en/android/integration#publisher-reported-revenue-data) for the full field reference and the other supported platforms.
Create the bid from the ad unit id of the ad you loaded:
```kotlin
// networkName is optional; pass the winning ad source when you know it, e.g.
// responseInfo?.loadedAdapterResponseInfo?.adSourceName.
val adMobBid = CloudXArbiterBid.adMob(
adUnitId = adMobAdUnitId,
networkName = adMobNetworkName ?: "admob",
manualRevenuePerImpressionUSD = null,
extras = emptyMap(),
)
// An Ad Manager ad unit id takes the form /NNNNNNN/placement/name.
val adManagerBid = CloudXArbiterBid.gam(
adUnitId = "/21775744923/example/interstitial",
networkName = "gam",
manualRevenuePerImpressionUSD = null,
extras = emptyMap(),
)
val configuration = CloudXArbiterConfiguration.builder(
listOf(CloudXArbiterBid.cloudX(cloudXAd), adMobBid, adManagerBid)
).build()
// Runs once the candidates have loaded, ahead of the placement. Store the winner.
CloudX.arbiter(configuration, object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
nextWinner = result
}
})
// Runs at the placement. No arbiter call here.
fun showInterstitial(activity: Activity) {
when (nextWinner?.platform) {
CloudXArbiterPlatform.CLOUDX -> cloudXInterstitial.show(activity)
CloudXArbiterPlatform.ADMOB -> adMobInterstitial.show(activity)
CloudXArbiterPlatform.GAM -> adManagerInterstitial.show(activity)
else -> { } // no winner prepared; continue without an ad
}
nextWinner = null
}
```
A winning Google bid reports its own platform — `CloudXArbiterPlatform.ADMOB` or `CloudXArbiterPlatform.GAM`. You no longer need to inspect `result.platformName` to tell these sources apart, as you would for a [custom bid](#custom-bid-inputs).
> **Note:** Google's paid-event callback fires after an impression, so it cannot price the same loaded ad before the arbiter runs. Post-impression revenue is exactly what you forward through `CloudX.reportRevenueData()`.
Pass a blank ad unit id and the bid still builds rather than crashing your app, but it carries no usable identity: it is never priced and the server rejects it.
## Manual input with pre-bid ILRD
Some AdMob accounts expose impression-level revenue data pre-bid: the ad value for the loaded ad is available at load time, before the ad is shown. This is a legacy, account-gated capability, so check with your Google account team whether it is enabled for your account. An exact per-impression price that you know before show is the one case where supplying your own price beats CloudX's estimate.
> **Warning:** We recommend manually assigned prices only when they come from pre-bid ILRD. Prices from weaker sources — average eCPMs, guesses, or post-impression paid events — degrade arbitration rather than improving it. Leave `manualRevenuePerImpressionUSD` null or absent in those cases and let CloudX's estimate price the bid instead.
Pass the pre-bid ad value as `manualRevenuePerImpressionUSD` and it overrides the estimate:
```kotlin
val adMobBid = CloudXArbiterBid.adMob(
adUnitId = adMobAdUnitId,
networkName = adMobNetworkName ?: "admob",
// valueMicros is in micros: 1,000,000 micros is one currency unit.
// Do not also divide by 1,000 — this is already a per-impression value, not a CPM.
manualRevenuePerImpressionUSD = preBidAdValue.valueMicros / 1_000_000.0,
extras = emptyMap(),
)
```
How the value is treated:
* `0.0` is a real price. It means this bid is worth nothing — not that the price is missing.
* Negative and non-finite values are not prices, so they are treated as absent and logged.
* A blank ad unit id drops the manual price entirely, because a bid with no identity cannot be validated.
`manualRevenuePerImpressionUSD` is revenue for a single impression, in USD — not CPM. Convert whatever your source reports:
* **A Google ad value** is reported in **micros** on Android, so divide by 1,000,000. A `valueMicros` of `5000` is `0.005` per impression. Do not confuse a micros value with an eCPM and divide by 1,000 as well.
* **A non-USD amount** must be converted to USD first.
## Step-by-step: arbitrate CloudX and LevelPlay
This walkthrough shows exactly which Unity LevelPlay callback to read and which values to pass into `CloudX.arbiter()`. It uses an interstitial, but the same field mapping applies to any format — see [Supported ad formats](#supported-ad-formats) for what changes for Banner and MREC.
1. **Load both candidates.** Create the CloudX and LevelPlay interstitials, attach listeners, and start a load on each platform.
```kotlin
val cloudXInterstitial = CloudX.createInterstitial(context, "YOUR_CLOUDX_AD_UNIT_ID")
cloudXInterstitial.listener = cloudXListener
cloudXInterstitial.load()
val levelPlayInterstitial = LevelPlayInterstitialAd("YOUR_LEVELPLAY_AD_UNIT_ID")
levelPlayInterstitial.setListener(levelPlayListener)
levelPlayInterstitial.loadAd()
```
2. **Capture each platform's loaded ad.** LevelPlay delivers a `LevelPlayAdInfo` in its `onAdLoaded` callback; CloudX delivers a `CloudXAd` in `onAdLoaded`. Hold onto both — you read the arbiter inputs from them in the next step.
```kotlin
private var cloudXAd: CloudXAd? = null
private var levelPlayInfo: LevelPlayAdInfo? = null
// CloudXInterstitialListener
override fun onAdLoaded(cloudXAd: CloudXAd) {
this.cloudXAd = cloudXAd
}
// LevelPlayInterstitialAdListener
override fun onAdLoaded(levelPlayAdInfo: LevelPlayAdInfo) {
levelPlayInfo = levelPlayAdInfo
}
```
3. **Map the values into bids.** Read the LevelPlay fields off `LevelPlayAdInfo` and pass them to `CloudXArbiterBid.levelPlay()`. The CloudX bid takes the `CloudXAd` directly. `listOfNotNull` submits only the platforms that actually loaded.
| `LevelPlayAdInfo` field | Type | `CloudXArbiterBid.levelPlay` parameter |
| ----------------------- | -------- | -------------------------------------- |
| `adNetwork` | `String` | `networkName` |
| `revenue` | `Double` | `revenue` |
| `precision` | `String` | `precision` |
```kotlin
val bids = listOfNotNull(
cloudXAd?.let { CloudXArbiterBid.cloudX(it) },
levelPlayInfo?.let { info ->
CloudXArbiterBid.levelPlay(
networkName = info.adNetwork, // LevelPlayAdInfo.adNetwork
revenue = info.revenue, // LevelPlayAdInfo.revenue
precision = info.precision, // LevelPlayAdInfo.precision
)
},
)
```
4. **Run the arbiter and store the winner.** Once both candidates have settled, wrap the bids in a `CloudXArbiterConfiguration` and pass it to `CloudX.arbiter()` with a listener. Store the result — do not show anything yet. `onCompleted()` runs on the main thread.
```kotlin
private var nextWinner: CloudXArbiterResult? = null
private fun prepareWinner() {
val configuration = CloudXArbiterConfiguration.builder(bids).build()
CloudX.arbiter(configuration, object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
nextWinner = result
}
})
}
```
5. **Show the stored winner at the placement.** When the placement is reached, switch on the stored `platform` and show that platform's ad. There is no arbiter call on this path. A stored `CloudXArbiterPlatform.NONE`, or no stored result at all, means there is no winner to show — continue without showing an ad.
```kotlin
fun showAtPlacement(activity: Activity) {
when (nextWinner?.platform) {
CloudXArbiterPlatform.CLOUDX -> cloudXInterstitial.show(activity)
CloudXArbiterPlatform.LEVELPLAY -> levelPlayInterstitial.showAd(activity)
else -> { } // no winner prepared; continue without an ad
}
nextWinner = null
}
```
The `ArbiterInterstitialController` below packages these same steps into a reusable component.
## Interstitial example
This controller is the reference implementation of the prepare-ahead rule for fullscreen formats. It arbitrates between two platforms — CloudX and Unity LevelPlay — and prepares a winner before the placement is reached:
1. Load CloudX and LevelPlay in parallel.
2. Wait until both platforms have loaded or failed.
3. Submit only loaded candidates to Trusted Arbiter.
4. Cache the selected platform.
5. At the placement, show the cached winner immediately.
If both platforms fail, start another load cycle. If the placement is reached before a winner is prepared, continue the app flow without showing an ad.
```kotlin ArbiterInterstitialController.kt
/**
* Prepares a Trusted Arbiter winner ahead of time so an interstitial can be shown
* instantly when a placement is reached.
*
* Loads [cloudXInterstitial] and [levelPlayInterstitial] in parallel, waits until both
* have finished loading or failing, submits the loaded candidates to [CloudX.arbiter],
* and caches the selected [CloudXArbiterPlatform] in [nextWinner].
*/
class ArbiterInterstitialController(
private val cloudXInterstitial: CloudXInterstitialAd,
private val levelPlayInterstitial: LevelPlayInterstitialAd,
) {
/** Receives the arbitration outcome once a winner has been prepared. */
interface Listener {
/** Called when the arbiter has selected [platform] for the next show. */
fun onWinnerPrepared(platform: CloudXArbiterPlatform)
}
/** Set to observe [Listener.onWinnerPrepared] callbacks. */
var listener: Listener? = null
private var cloudXAd: CloudXAd? = null
private var cloudXLoadDone = false
private var levelPlayAdInfo: LevelPlayAdInfo? = null
private var levelPlayLoadDone = false
private var nextWinner: CloudXArbiterPlatform? = null
init {
cloudXInterstitial.listener = createCloudXListener()
levelPlayInterstitial.setListener(createLevelPlayListener())
}
/** Starts a load for each platform that does not currently hold a cached ad. */
fun loadMissingAds() {
if (cloudXAd == null) cloudXInterstitial.load()
if (levelPlayAdInfo == null) levelPlayInterstitial.loadAd()
}
/**
* Shows the prepared winner on [activity], returning true only when a show call was made.
*
* Returns false when no winner is ready or the cached ad is no longer available, in which
* case a fresh load cycle is started.
*/
fun showAtPlacement(activity: Activity): Boolean {
return when (nextWinner) {
CloudXArbiterPlatform.CLOUDX -> showCloudX(activity)
CloudXArbiterPlatform.LEVELPLAY -> showLevelPlay(activity)
else -> false
}
}
/**
* Runs the arbiter once both platforms have settled, then caches the winning platform.
*
* Returns early until both loads complete. If neither platform loaded, it restarts the
* load cycle; otherwise it submits the loaded candidates to [CloudX.arbiter].
*/
private fun maybePrepareWinner() {
if (!cloudXLoadDone || !levelPlayLoadDone) return
if (cloudXAd == null && levelPlayAdInfo == null) {
cloudXLoadDone = false
levelPlayLoadDone = false
loadMissingAds()
return
}
val bids = listOfNotNull(
cloudXAd?.let { CloudXArbiterBid.cloudX(it) },
levelPlayAdInfo?.let { adInfo ->
CloudXArbiterBid.levelPlay(
networkName = adInfo.adNetwork,
revenue = adInfo.revenue,
precision = adInfo.precision,
)
}
)
CloudX.arbiter(
configuration = CloudXArbiterConfiguration.builder(bids).build(),
listener = object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
nextWinner = result.platform
listener?.onWinnerPrepared(result.platform)
}
}
)
}
private fun showCloudX(activity: Activity): Boolean {
if (cloudXInterstitial.isAdReady) {
cloudXInterstitial.show(activity)
return true
}
clearCloudXAndLoadMissingAds()
return false
}
private fun showLevelPlay(activity: Activity): Boolean {
if (levelPlayInterstitial.isAdReady) {
levelPlayInterstitial.showAd(activity)
return true
}
clearLevelPlayAndLoadMissingAds()
return false
}
private fun clearCloudXAndLoadMissingAds() {
cloudXAd = null
cloudXLoadDone = false
nextWinner = null
loadMissingAds()
}
private fun clearLevelPlayAndLoadMissingAds() {
levelPlayAdInfo = null
levelPlayLoadDone = false
nextWinner = null
loadMissingAds()
}
private fun createCloudXListener() = object : CloudXInterstitialListener {
override fun onAdLoaded(cloudXAd: CloudXAd) {
this@ArbiterInterstitialController.cloudXAd = cloudXAd
cloudXLoadDone = true
maybePrepareWinner()
}
override fun onAdLoadFailed(adUnitId: String, cloudXError: CloudXError) {
cloudXAd = null
cloudXLoadDone = true
maybePrepareWinner()
}
override fun onAdDisplayed(cloudXAd: CloudXAd) = Unit
override fun onAdDisplayFailed(cloudXAd: CloudXAd, cloudXError: CloudXError) {
clearCloudXAndLoadMissingAds()
}
override fun onAdHidden(cloudXAd: CloudXAd) {
clearCloudXAndLoadMissingAds()
}
override fun onAdClicked(cloudXAd: CloudXAd) = Unit
}
private fun createLevelPlayListener() = object : LevelPlayInterstitialAdListener {
override fun onAdLoaded(levelPlayAdInfo: LevelPlayAdInfo) {
this@ArbiterInterstitialController.levelPlayAdInfo = levelPlayAdInfo
levelPlayLoadDone = true
maybePrepareWinner()
}
override fun onAdLoadFailed(levelPlayAdError: LevelPlayAdError) {
levelPlayAdInfo = null
levelPlayLoadDone = true
maybePrepareWinner()
}
override fun onAdInfoChanged(levelPlayAdInfo: LevelPlayAdInfo) {
this@ArbiterInterstitialController.levelPlayAdInfo = levelPlayAdInfo
}
override fun onAdDisplayed(levelPlayAdInfo: LevelPlayAdInfo) = Unit
override fun onAdDisplayFailed(
levelPlayAdError: LevelPlayAdError,
levelPlayAdInfo: LevelPlayAdInfo
) {
clearLevelPlayAndLoadMissingAds()
}
override fun onAdClosed(levelPlayAdInfo: LevelPlayAdInfo) {
clearLevelPlayAndLoadMissingAds()
}
override fun onAdClicked(levelPlayAdInfo: LevelPlayAdInfo) = Unit
}
}
```
`showAtPlacement()` returns `true` only when an ad show call was made. `onAdInfoChanged()` keeps the cached LevelPlay candidate up to date while it remains loaded.
For PubMatic OpenWrap, create a third-party bid with `CloudXArbiterBid.pubmatic(price, partnerName)`. If the arbiter service is unavailable, the SDK falls back to the highest comparable USD bid among the supplied supported bid inputs.
## App Open example
App Open is a fullscreen format, so it prepares a winner ahead of the placement exactly like interstitial and rewarded. What differs is the trigger. Nothing in your UI asks for an App Open ad, so the decision has to be made while the app is in the background — a user returning to the app must never wait on a load or an arbiter round trip.
`ArbiterAppOpenController` arbitrates a CloudX App Open ad against an AdMob App Open ad:
1. Load both platforms at startup, and again after every show.
2. Run the arbiter once both have settled, and store the winner.
3. On the next foreground transition, show the stored winner and start the next cycle.
4. If no winner is stored, return the user to the app without an ad and start a fresh load.
```kotlin ArbiterAppOpenController.kt
/**
* Arbitrates a CloudX App Open ad against an AdMob App Open ad and shows the winner on the
* next foreground transition.
*
* App Open has no user-initiated show, so the winner must already be chosen by the time the
* user comes back. [loadMissingAds] starts both loads, the arbiter runs as soon as both
* platforms settle, and [showOnForeground] only shows a result that was stored earlier.
*/
class ArbiterAppOpenController(
private val context: Context,
cloudXAdUnitId: String,
private val adMobAdUnitId: String,
) {
/** Receives the arbitration outcome once a winner has been prepared. */
interface Listener {
/** Called when the arbiter has selected [platform] for the next foreground show. */
fun onWinnerPrepared(platform: CloudXArbiterPlatform)
}
/** Set to observe [Listener.onWinnerPrepared] callbacks. */
var listener: Listener? = null
private val cloudXAppOpen: CloudXAppOpenAd = CloudX.createAppOpen(context, cloudXAdUnitId)
private var cloudXAd: CloudXAd? = null
private var cloudXLoadDone = false
private var adMobAppOpen: AppOpenAd? = null
/** The ad that is presenting, or presented last. Held so a late paid event still has an ad. */
private var presentedAdMobAd: AppOpenAd? = null
private var adMobLoadDone = false
private var cloudXLoadInFlight = false
private var adMobLoadInFlight = false
private var nextWinner: CloudXArbiterPlatform? = null
private var isShowingAd = false
init {
cloudXAppOpen.listener = createCloudXListener()
}
/**
* Starts a load for each platform that holds neither a cached ad nor a request in flight.
*
* Each load it starts clears that platform's `LoadDone` flag, so a round only arbitrates once
* every request it started has come back. Without that reset, a retry for the platform that
* failed last round lands against a flag still set from the previous round, and the arbiter
* runs on a one-candidate snapshot.
*/
fun loadMissingAds() {
if (cloudXAd == null && !cloudXLoadInFlight) {
cloudXLoadInFlight = true
cloudXLoadDone = false
cloudXAppOpen.load()
}
if (adMobAppOpen == null && !adMobLoadInFlight) {
adMobLoadInFlight = true
adMobLoadDone = false
loadAdMob()
}
}
/**
* Shows the prepared winner on [activity], returning true only when a show call was made.
*
* Call this from a foreground observer. It never holds up the return to the app: with an ad
* already on screen, or no winner prepared, it returns false and starts the next load cycle.
*/
fun showOnForeground(activity: Activity): Boolean {
if (isShowingAd) return false
return when (nextWinner) {
CloudXArbiterPlatform.CLOUDX -> showCloudX(activity)
CloudXArbiterPlatform.ADMOB -> showAdMob(activity)
CloudXArbiterPlatform.NONE -> {
// The arbiter settled on no winner, so both cached ads are unusable. Drop them:
// loadMissingAds() only requests a platform whose slot is empty, so without this
// the same two candidates would sit there and no later round could ever run.
discardCandidatesAndReload()
false
}
// nextWinner is null — a cycle is still in flight. Leave it alone.
else -> false
}
}
/** Releases both platforms' ads. Call this from the owning component's teardown. */
fun destroy() {
cloudXAppOpen.destroy()
adMobAppOpen = null
presentedAdMobAd = null
}
/**
* Runs the arbiter once both platforms have settled, then caches the winning platform.
*
* Returns early until both loads complete. If neither platform loaded, it restarts the
* load cycle; otherwise it submits the loaded candidates to [CloudX.arbiter].
*/
private fun maybePrepareWinner() {
if (!cloudXLoadDone || !adMobLoadDone) return
if (cloudXAd == null && adMobAppOpen == null) {
loadMissingAds()
return
}
val bids = listOfNotNull(
cloudXAd?.let { CloudXArbiterBid.cloudX(it) },
adMobAppOpen?.let { CloudXArbiterBid.adMob(adUnitId = adMobAdUnitId) },
)
CloudX.arbiter(
configuration = CloudXArbiterConfiguration.builder(bids).build(),
listener = object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
nextWinner = result.platform
listener?.onWinnerPrepared(result.platform)
}
}
)
}
private fun showCloudX(activity: Activity): Boolean {
if (cloudXAppOpen.isAdReady) {
isShowingAd = true
cloudXAppOpen.show(activity, "app_foreground")
return true
}
clearCloudXAndLoadMissingAds()
return false
}
private fun showAdMob(activity: Activity): Boolean {
val ad = adMobAppOpen
if (ad == null) {
clearAdMobAndLoadMissingAds()
return false
}
ad.fullScreenContentCallback = object : FullScreenContentCallback() {
override fun onAdDismissedFullScreenContent() {
clearAdMobAndLoadMissingAds()
}
override fun onAdFailedToShowFullScreenContent(adError: AdError) {
Log.e("CloudX", "AdMob App Open failed to show: ${adError.message}")
clearAdMobAndLoadMissingAds()
}
}
// An AppOpenAd is single-use: show() consumes it. Move it aside now, so a load that
// lands mid-show cannot arbitrate a spent ad. It stays alive in presentedAdMobAd
// because AdMob can deliver the paid event after the ad has already been dismissed,
// and that revenue is what prices the next AdMob bid.
presentedAdMobAd = ad
adMobAppOpen = null
isShowingAd = true
ad.show(activity)
return true
}
private fun loadAdMob() {
AppOpenAd.load(
context,
adMobAdUnitId,
AdRequest.Builder().build(),
object : AppOpenAd.AppOpenAdLoadCallback() {
override fun onAdLoaded(ad: AppOpenAd) {
ad.setOnPaidEventListener { adValue -> reportAdMobPaidEvent(ad, adValue) }
adMobAppOpen = ad
adMobLoadInFlight = false
adMobLoadDone = true
maybePrepareWinner()
}
override fun onAdFailedToLoad(error: LoadAdError) {
Log.w("CloudX", "AdMob App Open failed to load: ${error.message}")
adMobAppOpen = null
adMobLoadInFlight = false
adMobLoadDone = true
maybePrepareWinner()
}
},
)
}
/**
* Forwards AdMob's realized revenue to CloudX. This is a required part of the integration,
* not optional analytics: CloudX prices future AdMob bids from what you report back.
*
* toCloudXRevenuePrecision() is the mapping shown in
* "Report Google paid events back to CloudX" above.
*/
private fun reportAdMobPaidEvent(ad: AppOpenAd, adValue: AdValue) {
val servedBy = ad.responseInfo.loadedAdapterResponseInfo
val accepted = CloudX.reportRevenueData(
CloudXRevenueData.builder(
platform = CloudXRevenuePlatform.ADMOB,
revenue = adValue.valueMicros / 1_000_000.0,
adFormat = "app_open",
)
.currencyCode(adValue.currencyCode)
.precision(adValue.precisionType.toCloudXRevenuePrecision())
.networkName(servedBy?.adSourceName)
.adUnitId(adMobAdUnitId)
.thirdPartyAdPlacementId(servedBy?.adSourceInstanceName)
.build(),
)
if (!accepted) {
Log.w("CloudX", "AdMob App Open paid event was not accepted by the CloudX SDK")
}
}
/** Drops both candidates and starts a new cycle. Used when the arbiter reports no winner. */
private fun discardCandidatesAndReload() {
cloudXAd = null
adMobAppOpen = null
cloudXLoadDone = false
adMobLoadDone = false
nextWinner = null
loadMissingAds()
}
private fun clearCloudXAndLoadMissingAds() {
cloudXAd = null
cloudXLoadDone = false
nextWinner = null
isShowingAd = false
loadMissingAds()
}
private fun clearAdMobAndLoadMissingAds() {
adMobAppOpen = null
adMobLoadDone = false
nextWinner = null
isShowingAd = false
loadMissingAds()
}
private fun createCloudXListener() = object : CloudXAppOpenListener {
override fun onAdLoaded(cloudXAd: CloudXAd) {
this@ArbiterAppOpenController.cloudXAd = cloudXAd
cloudXLoadInFlight = false
cloudXLoadDone = true
maybePrepareWinner()
}
override fun onAdLoadFailed(adUnitId: String, cloudXError: CloudXError) {
Log.w("CloudX", "CloudX App Open failed to load: ${cloudXError.message}")
cloudXAd = null
cloudXLoadInFlight = false
cloudXLoadDone = true
maybePrepareWinner()
}
override fun onAdDisplayed(cloudXAd: CloudXAd) = Unit
override fun onAdDisplayFailed(cloudXAd: CloudXAd, cloudXError: CloudXError) {
Log.e("CloudX", "CloudX App Open failed to display: ${cloudXError.message}")
clearCloudXAndLoadMissingAds()
}
override fun onAdHidden(cloudXAd: CloudXAd) {
clearCloudXAndLoadMissingAds()
}
override fun onAdClicked(cloudXAd: CloudXAd) = Unit
}
}
```
`showOnForeground()` returns `true` only when a show call was made. `isShowingAd` keeps the ad's own fullscreen presentation from being read as a second foreground transition — without it, a lifecycle observer can re-enter `showOnForeground()` while an ad is already on screen.
Drive it from the process lifecycle, so the show happens on a real return to the foreground rather than on every activity resume:
```kotlin
// In the class that owns the controller, for example your Application subclass:
private val controller = ArbiterAppOpenController(context, cloudXAdUnitId, adMobAdUnitId)
ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
currentActivity?.let { controller.showOnForeground(it) }
}
})
```
Both platforms report a winning App Open bid the same way as any other format: `CloudXArbiterPlatform.CLOUDX` or `CloudXArbiterPlatform.ADMOB`. Keep the App Open placement rules from [App Open Ads](/en/android/ad-formats/app-open) — arbitration changes which network fills the placement, not when it is appropriate to show one.
## Banner and MREC arbitration
Banner and MREC are view formats: multiple networks can hold a loaded ad at the same time, but only one view can be on screen. Arbitrating between them adds three requirements that fullscreen formats don't have.
> **Warning:** Only attach the winning bid's view to the view hierarchy. Attaching a losing view renders it and fires an impression for a bid that Trusted Arbiter did not select.
### Disable auto-refresh
Auto-refresh must be turned off everywhere so the arbiter — not each network's internal timer — controls when a new ad is shown:
* Turn off auto-refresh for the ad unit in the CloudX dashboard.
* Call `stopAutoRefresh()` on the `CloudXAdView` immediately after creating it.
* Disable auto-refresh on the other arbitrated networks' banner APIs as well.
### View attachment
Load every candidate off-screen. Only the winning bid's banner view may be attached to the view hierarchy or displayed. Keep losing views unattached — do not add them to a parent view, even temporarily — since attaching a view renders it and can fire an impression for a bid the arbiter rejected. Unlike the standard banner integration, which adds the view to the layout at creation time (see [Banner & MREC](/en/android/ad-formats/banner-mrec)), with Trusted Arbiter the view must not be attached at creation — attach it only after arbitration selects it as the winner.
### Refresh cycle
With auto-refresh disabled, the arbiter must drive its own refresh loop:
1. **Run the first round.** Load candidates from every network in parallel, run the arbiter, and attach the winning view.
2. **Start the next load on the winner.** As soon as the winner's impression fires, immediately start loading a new fill from the winning network so it's ready for the next round.
3. **Retain non-winning fills.** Keep the non-winning networks' already-filled ads for the next arbitration round. Re-request loads only from networks that did not fill in the previous round.
4. **Re-arbitrate.** Once the outstanding load responses return, run the arbiter again over the full candidate set.
5. **Swap on an interval.** Refresh the displayed ad on a 20–30 second interval by attaching the new winner's view in place of the old one. Intervals shorter than 20 seconds decrease CPM performance.
### Banner example
This controller is the reference implementation of the arbitrate-then-render rule for view formats. It arbitrates a CloudX banner against a LevelPlay banner and drives the refresh cycle above. The same structure applies to MREC — swap `CloudX.createBanner()` for `CloudX.createMREC()` and the LevelPlay banner type for the LevelPlay MREC type.
```kotlin ArbiterBannerController.kt
/**
* Drives Trusted Arbiter for a banner placement: loads [cloudXBanner] and
* [levelPlayBanner] in parallel, arbitrates between whichever candidates filled,
* attaches the winning view to [container], and refreshes on [refreshIntervalMs].
*
* Auto-refresh must be disabled on both networks (dashboard setting for CloudX,
* `stopAutoRefresh()` here, and the equivalent LevelPlay setting) — this controller
* owns the refresh cycle instead.
*/
class ArbiterBannerController(
private val cloudXBanner: CloudXAdView,
private val levelPlayBanner: LevelPlayBannerAdView,
private val container: ViewGroup,
private val refreshIntervalMs: Long = 25_000L,
) {
private val handler = Handler(Looper.getMainLooper())
private var cloudXAd: CloudXAd? = null
private var cloudXFilled = false
private var cloudXLoadDone = false
private var levelPlayAdInfo: LevelPlayAdInfo? = null
private var levelPlayFilled = false
private var levelPlayLoadDone = false
private var currentWinner: CloudXArbiterPlatform? = null
private val refreshRunnable = Runnable { runArbitrationRound() }
init {
cloudXBanner.stopAutoRefresh()
cloudXBanner.listener = createCloudXListener()
cloudXBanner.revenueListener = createCloudXRevenueListener()
levelPlayBanner.bannerListener = createLevelPlayListener()
}
/** Starts the first round: loads every network that isn't already holding a fill. */
fun start() {
loadMissingAds()
}
fun stop() {
handler.removeCallbacks(refreshRunnable)
}
private fun loadMissingAds() {
if (!cloudXFilled) cloudXBanner.load()
if (!levelPlayFilled) levelPlayBanner.loadAd()
}
/** Runs once both networks have settled (filled or failed); re-arbitrates only pending candidates. */
private fun maybeArbitrate() {
if (!cloudXLoadDone || !levelPlayLoadDone) return
val bids = listOfNotNull(
cloudXAd?.let { CloudXArbiterBid.cloudX(it) },
levelPlayAdInfo?.let { info ->
CloudXArbiterBid.levelPlay(
networkName = info.adNetwork,
revenue = info.revenue,
precision = info.precision,
)
},
)
if (bids.isEmpty()) {
loadMissingAds()
return
}
CloudX.arbiter(
configuration = CloudXArbiterConfiguration.builder(bids).build(),
listener = object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
showWinner(result.platform)
}
}
)
}
private fun runArbitrationRound() {
maybeArbitrate()
}
/** Attaches only the winning view; the losing view is never added to [container]. */
private fun showWinner(platform: CloudXArbiterPlatform) {
currentWinner = platform
container.removeAllViews()
when (platform) {
CloudXArbiterPlatform.CLOUDX -> container.addView(cloudXBanner)
CloudXArbiterPlatform.LEVELPLAY -> container.addView(levelPlayBanner)
else -> return // CloudXArbiterPlatform.NONE — no winner; leave container empty
}
handler.removeCallbacks(refreshRunnable)
handler.postDelayed(refreshRunnable, refreshIntervalMs)
}
/** Starts the winning network's next load immediately after its impression fires. */
private fun onWinnerImpression(platform: CloudXArbiterPlatform) {
when (platform) {
CloudXArbiterPlatform.CLOUDX -> {
cloudXFilled = false
cloudXLoadDone = false
cloudXBanner.load()
}
CloudXArbiterPlatform.LEVELPLAY -> {
levelPlayFilled = false
levelPlayLoadDone = false
levelPlayBanner.loadAd()
}
else -> Unit
}
}
private fun createCloudXListener() = object : CloudXAdViewListener {
override fun onAdLoaded(cloudXAd: CloudXAd) {
this@ArbiterBannerController.cloudXAd = cloudXAd
cloudXFilled = true
cloudXLoadDone = true
maybeArbitrate()
}
override fun onAdLoadFailed(adUnitId: String, cloudXError: CloudXError) {
cloudXAd = null
cloudXFilled = false
cloudXLoadDone = true
maybeArbitrate()
}
override fun onAdClicked(cloudXAd: CloudXAd) = Unit
override fun onAdExpanded(cloudXAd: CloudXAd) = Unit
override fun onAdCollapsed(cloudXAd: CloudXAd) = Unit
}
private fun createCloudXRevenueListener() = object : CloudXAdRevenueListener {
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
// onAdRevenuePaid fires at CloudX impression time; only act on it while CloudX is showing.
if (currentWinner == CloudXArbiterPlatform.CLOUDX) {
onWinnerImpression(CloudXArbiterPlatform.CLOUDX)
}
}
}
private fun createLevelPlayListener() = object : LevelPlayBannerAdViewListener {
override fun onAdLoaded(levelPlayAdInfo: LevelPlayAdInfo) {
this@ArbiterBannerController.levelPlayAdInfo = levelPlayAdInfo
levelPlayFilled = true
levelPlayLoadDone = true
maybeArbitrate()
}
override fun onAdLoadFailed(levelPlayAdError: LevelPlayAdError) {
levelPlayAdInfo = null
levelPlayFilled = false
levelPlayLoadDone = true
maybeArbitrate()
}
override fun onAdDisplayed(levelPlayAdInfo: LevelPlayAdInfo) {
if (currentWinner == CloudXArbiterPlatform.LEVELPLAY) {
onWinnerImpression(CloudXArbiterPlatform.LEVELPLAY)
}
}
override fun onAdClicked(levelPlayAdInfo: LevelPlayAdInfo) = Unit
}
}
```
`onAdRevenuePaid()` (CloudX) and `onAdDisplayed()` (LevelPlay) are where each network's impression fires; that is where the winning network's next load is kicked off, keeping a fresh fill ready for the following round without holding up the current display. Networks that already have a filled, unattached ad skip straight past `loadMissingAds()` until their fill is used or expires.
## Native example
Native is a view format, so it arbitrates and then renders — but unlike banner and MREC, a native ad has no view until you make one. That is what makes native the easiest format to get wrong: `loadAd(adView)` renders the ad at load time, before the arbiter has chosen anything.
> **Warning:** Load native candidates with `loadAd()` and no ad view. Rendering is what fires a native impression, so rendering a bid the arbiter has not selected bills an impression against a loser. Call `render()` only for the winner.
`ArbiterNativeController` arbitrates a CloudX native ad against an AdMob native ad and renders exactly one of them into the container:
1. Load both platforms with no view attached.
2. Run the arbiter once both have settled.
3. Build a view for the winner only, render into it, and attach it.
4. Keep the losing platform's ad loaded and unrendered for the next round.
```kotlin ArbiterNativeController.kt
/**
* Arbitrates a CloudX native ad against an AdMob native ad and renders only the winner
* into [container].
*
* Both platforms load without a view: [CloudXNativeAdLoader.loadAd] is called with no ad view
* so nothing is rendered at load time, and the AdMob ad is held unrendered. The losing bid's
* assets are never rendered or attached, so they never fire an impression.
*/
class ArbiterNativeController(
private val context: Context,
private val container: ViewGroup,
private val binder: CloudXNativeAdViewBinder,
cloudXAdUnitId: String,
private val adMobAdUnitId: String,
) {
private val cloudXLoader: CloudXNativeAdLoader =
CloudX.createNativeAdLoader(context, cloudXAdUnitId)
private var cloudXAd: CloudXAd? = null
private var cloudXLoadDone = false
private var adMobNativeAd: NativeAd? = null
private var adMobLoadDone = false
private var cloudXLoadInFlight = false
private var adMobLoadInFlight = false
private var renderedPlatform: CloudXArbiterPlatform? = null
private var renderedAdView: View? = null
// The ads behind the attached view. They outlive their own candidate state: a rendered ad
// stays on screen after its impression, until its replacement is attached.
private var renderedCloudXAd: CloudXAd? = null
private var renderedAdMobAd: NativeAd? = null
init {
cloudXLoader.nativeAdListener = createCloudXListener()
cloudXLoader.revenueListener = createCloudXRevenueListener()
}
/** Starts the first round: loads every platform that is not already holding a fill. */
fun start() {
loadMissingAds()
}
/** Releases both platforms' ads and detaches the rendered view. */
fun destroy() {
// detachRenderedAd() releases the rendered AdMob ad; only destroy the candidate here
// when it is a different, never-rendered instance.
val unrenderedAdMobAd = adMobNativeAd.takeIf { it !== renderedAdMobAd }
detachRenderedAd()
cloudXLoader.destroy()
unrenderedAdMobAd?.destroy()
adMobNativeAd = null
}
/**
* Starts a load for each platform that holds neither a filled ad nor a request in flight.
*
* Each load it starts clears that platform's `LoadDone` flag, so a round only arbitrates once
* every request it started has come back.
*/
private fun loadMissingAds() {
// loadAd() with no ad view defers rendering. Never use loadAd(adView) here: that
// renders the CloudX ad at load time, before the arbiter has chosen a winner.
if (cloudXAd == null && !cloudXLoadInFlight) {
cloudXLoadInFlight = true
cloudXLoadDone = false
cloudXLoader.loadAd()
}
if (adMobNativeAd == null && !adMobLoadInFlight) {
adMobLoadInFlight = true
adMobLoadDone = false
loadAdMob()
}
}
/** Runs once both platforms have settled (filled or failed), then renders the winner. */
private fun maybeArbitrate() {
if (!cloudXLoadDone || !adMobLoadDone) return
val bids = listOfNotNull(
cloudXAd?.let { CloudXArbiterBid.cloudX(it) },
adMobNativeAd?.let { CloudXArbiterBid.adMob(adUnitId = adMobAdUnitId) },
)
if (bids.isEmpty()) {
loadMissingAds()
return
}
CloudX.arbiter(
configuration = CloudXArbiterConfiguration.builder(bids).build(),
listener = object : CloudXArbiterListener {
override fun onCompleted(result: CloudXArbiterResult) {
renderWinner(result.platform)
}
}
)
}
/** Renders only the winning platform's assets; the loser stays unrendered and unattached. */
private fun renderWinner(platform: CloudXArbiterPlatform) {
// Rendering is what fires the impression, so never render the same ad twice.
// maybeArbitrate() runs again whenever the losing candidate reloads, and the same
// platform winning that round is a normal outcome — but by then the served ad has been
// dropped as a candidate, so a repeat win is always a new ad and does render.
if (isAlreadyRendered(platform)) return
if (platform == CloudXArbiterPlatform.NONE) {
// No winner. Keep whatever is already on screen — detaching it here would drop a
// perfectly good ad — and drop only the candidates that lost, so the next round has
// something new to compare.
discardUnrenderedCandidatesAndReload()
return
}
detachRenderedAd()
when (platform) {
CloudXArbiterPlatform.CLOUDX -> renderCloudX()
CloudXArbiterPlatform.ADMOB -> renderAdMob()
else -> Unit
}
}
/** True when this platform's current candidate is the ad already attached to the container. */
private fun isAlreadyRendered(platform: CloudXArbiterPlatform): Boolean = when (platform) {
CloudXArbiterPlatform.CLOUDX -> cloudXAd != null && cloudXAd === renderedCloudXAd
CloudXArbiterPlatform.ADMOB -> adMobNativeAd != null && adMobNativeAd === renderedAdMobAd
else -> false
}
/** Drops every candidate that is not currently rendered, then starts a new cycle. */
private fun discardUnrenderedCandidatesAndReload() {
if (renderedPlatform != CloudXArbiterPlatform.CLOUDX) {
cloudXAd = null
cloudXLoadDone = false
}
if (renderedPlatform != CloudXArbiterPlatform.ADMOB) {
adMobNativeAd = null
adMobLoadDone = false
}
loadMissingAds()
}
/**
* Removes only the ad view this controller attached, so anything else the publisher keeps in
* the container — a placeholder, a spinner, a label — survives the round.
*
* This is also the only place a rendered ad is released. It stays alive for as long as it is
* on screen, and goes away when its replacement is attached or the controller is destroyed.
*/
private fun detachRenderedAd() {
renderedAdView?.let { container.removeView(it) }
renderedAdView = null
renderedPlatform = null
renderedCloudXAd?.let { cloudXLoader.destroy(it) }
renderedCloudXAd = null
renderedAdMobAd?.destroy()
renderedAdMobAd = null
}
private fun renderCloudX() {
val ad = cloudXAd ?: return
val adView = CloudXNativeAdView(context, binder)
cloudXLoader.render(adView, ad)
container.addView(adView)
renderedAdView = adView
renderedPlatform = CloudXArbiterPlatform.CLOUDX
renderedCloudXAd = ad
}
private fun renderAdMob() {
val ad = adMobNativeAd ?: return
// Inflate your own NativeAdView layout and assign its asset views, exactly as you
// would in a non-arbitrated AdMob native integration.
val adView = LayoutInflater.from(context)
.inflate(R.layout.admob_native_ad_layout, container, false) as NativeAdView
adView.setNativeAd(ad)
container.addView(adView)
renderedAdView = adView
renderedPlatform = CloudXArbiterPlatform.ADMOB
renderedAdMobAd = ad
}
private fun loadAdMob() {
AdLoader.Builder(context, adMobAdUnitId)
.forNativeAd { nativeAd ->
nativeAd.setOnPaidEventListener { adValue ->
reportAdMobPaidEvent(nativeAd, adValue)
}
adMobNativeAd = nativeAd
adMobLoadInFlight = false
adMobLoadDone = true
maybeArbitrate()
}
.withAdListener(object : AdListener() {
override fun onAdFailedToLoad(error: LoadAdError) {
Log.w("CloudX", "AdMob native ad failed to load: ${error.message}")
adMobNativeAd = null
adMobLoadInFlight = false
adMobLoadDone = true
maybeArbitrate()
}
// The winning AdMob ad is consumed the moment its impression fires, so request
// the next one here. Without this the controller keeps bidding a spent ad for
// the rest of the session.
override fun onAdImpression() {
if (renderedPlatform == CloudXArbiterPlatform.ADMOB) {
onWinnerImpression(CloudXArbiterPlatform.ADMOB)
}
}
})
.build()
.loadAd(AdRequest.Builder().build())
}
/**
* Forwards AdMob's realized revenue to CloudX. This is a required part of the integration,
* not optional analytics: CloudX prices future AdMob bids from what you report back.
*
* toCloudXRevenuePrecision() is the mapping shown in
* "Report Google paid events back to CloudX" above.
*/
private fun reportAdMobPaidEvent(ad: NativeAd, adValue: AdValue) {
val servedBy = ad.responseInfo?.loadedAdapterResponseInfo
val accepted = CloudX.reportRevenueData(
CloudXRevenueData.builder(
platform = CloudXRevenuePlatform.ADMOB,
revenue = adValue.valueMicros / 1_000_000.0,
adFormat = "native",
)
.currencyCode(adValue.currencyCode)
.precision(adValue.precisionType.toCloudXRevenuePrecision())
.networkName(servedBy?.adSourceName)
.adUnitId(adMobAdUnitId)
.thirdPartyAdPlacementId(servedBy?.adSourceInstanceName)
.build(),
)
if (!accepted) {
Log.w("CloudX", "AdMob native paid event was not accepted by the CloudX SDK")
}
}
private fun createCloudXListener() = object : CloudXNativeAdListener {
// adView is null here because loadAd() was called without one. The ad is rendered
// later, and only if it wins.
override fun onNativeAdLoaded(adView: CloudXNativeAdView?, ad: CloudXAd) {
cloudXAd = ad
cloudXLoadInFlight = false
cloudXLoadDone = true
maybeArbitrate()
}
override fun onNativeAdLoadFailed(adUnitId: String, error: CloudXError) {
Log.w("CloudX", "CloudX native ad failed to load: ${error.message}")
cloudXAd = null
cloudXLoadInFlight = false
cloudXLoadDone = true
maybeArbitrate()
}
override fun onNativeAdClicked(ad: CloudXAd) = Unit
override fun onNativeAdExpired(ad: CloudXAd) {
// detachRenderedAd() releases the rendered ad, so hand it this one instead of
// destroying it twice.
if (renderedCloudXAd !== ad) cloudXLoader.destroy(ad)
clearCloudXAndReload()
}
override fun onNativeAdClosed(ad: CloudXAd) {
if (renderedCloudXAd !== ad) cloudXLoader.destroy(ad)
clearCloudXAndReload()
}
}
private fun createCloudXRevenueListener() = object : CloudXAdRevenueListener {
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
// onAdRevenuePaid fires at CloudX impression time; only act on it while CloudX is
// the platform on screen.
if (renderedPlatform == CloudXArbiterPlatform.CLOUDX) {
onWinnerImpression(CloudXArbiterPlatform.CLOUDX)
}
}
}
/**
* Starts the next cycle once the winner's impression has fired.
*
* The rendered view stays on screen and the ad behind it stays alive. A native slot has
* nothing to fall back to, so detaching the ad the moment it is billed would leave the slot
* blank for as long as the next round takes. Only the candidate state is dropped, so the
* spent ad is never bid or rendered again; the ad itself is released in detachRenderedAd(),
* when its replacement goes up. This is the same rule the banner controller follows.
*/
private fun onWinnerImpression(platform: CloudXArbiterPlatform) {
when (platform) {
CloudXArbiterPlatform.CLOUDX -> {
cloudXAd = null
cloudXLoadDone = false
cloudXLoadInFlight = false
}
CloudXArbiterPlatform.ADMOB -> {
adMobNativeAd = null
adMobLoadDone = false
adMobLoadInFlight = false
}
else -> return
}
loadMissingAds()
}
/** Drops the CloudX candidate, detaching it first if it is the one on screen. */
private fun clearCloudXAndReload() {
if (renderedPlatform == CloudXArbiterPlatform.CLOUDX) {
detachRenderedAd()
}
cloudXAd = null
cloudXLoadDone = false
cloudXLoadInFlight = false
loadMissingAds()
}
}
```
`onNativeAdLoaded()` delivers a null `adView` because the load was started without one — that null is the signal that rendering is still yours to trigger. Build the `CloudXNativeAdView` in `renderCloudX()` and pass it to `render()` only after the arbiter has named CloudX the winner.
The losing platform keeps its filled ad for the next round, so only the platform that was consumed or expired is re-requested. A rendered ad is spent, so each platform drops it as a candidate as soon as the impression fires — `onAdRevenuePaid` for CloudX, `onAdImpression()` for AdMob — and requests a new one.
Neither handler detaches the view. A native slot has nothing to fall back to, so removing the served ad at its impression would leave the slot blank for as long as the next round takes. The rendered ad stays up and is released in `detachRenderedAd()`, at the moment its replacement is attached or the controller is destroyed — the same rule the banner controller follows. An ad that expires or is closed while it is on screen is the one case that does detach early, which is why `renderedPlatform` is tracked.
`renderWinner()` returns early when the arbiter picks the platform already on screen, because rendering is what fires the impression and that round did not change the winner.
To refresh a native slot on a timer, use the same cycle as [Banner and MREC arbitration](#banner-and-mrec-arbitration): re-arbitrate on a 20–30 second interval and swap the rendered view for the new winner's. For a Reels-style feed, create one controller per slot — see [Native Ads](/en/android/ad-formats/native).
## Custom Bid Inputs
Use `CloudXArbiterBid.custom()` when you want Trusted Arbiter to compare CloudX with a third-party platform that does not have a dedicated bid helper.
```kotlin
val customBid = CloudXArbiterBid.custom(
platformName = "my_mediation_platform",
networkName = "winning_demand_source",
revenuePerImpressionUSD = 0.00125,
precision = CloudXArbiterPrecision.EXACT,
extras = mapOf("ad_unit" to "third-party-ad-unit-id"),
)
val configuration = CloudXArbiterConfiguration.builder(
listOf(CloudXArbiterBid.cloudX(cloudXAd), customBid)
).build()
```
When a custom bid wins, `result.platform` is `CloudXArbiterPlatform.CUSTOM` and `result.platformName` contains the `platformName` supplied on the bid. Pass `revenuePerImpressionUSD` as revenue for one impression in USD, not CPM. Use `CloudXArbiterPrecision.EXACT`, `ESTIMATED`, `PUBLISHER_DEFINED`, or `UNDEFINED` to describe that revenue value.
---
# Banner & MREC Ads
> Integrate CloudX banner and MREC ads in Android apps
#### CloudX-rendered creatives
CloudX-served creatives are rendered by the built-in renderer, which ships inside the core SDK — no separate renderer adapter module is required. The creative type is selected automatically from the bid response, so no publisher integration is needed to render either format. The renderer supports MRAID 3.0 (HTML) and VAST creatives across the ad formats:
| Ad format | HTML (MRAID 3.0) | VAST |
| ------------ | ---------------- | ---- |
| Banner | ✓ | — |
| MREC | ✓ | ✓ |
| Interstitial | ✓ | ✓ |
| Rewarded | ✓ | ✓ |
With SDK 4.7.0 or later, CloudX-rendered MREC VAST video starts muted and pauses off-screen. Auto-refresh pauses during MREC VAST playback and resumes after playback ends. Standard 320x50 banners do not support VAST creatives.
HTML/MRAID video in CloudX-rendered banner and MREC ads also starts muted. Unmuting requires a user gesture on that video’s controls; a tap elsewhere in the ad does not enable audio.
## Banner Ads (320x50)
```kotlin Kotlin
class MainActivity : AppCompatActivity(), CloudXAdViewListener, CloudXAdRevenueListener {
private lateinit var bannerAd: CloudXAdView
private fun createBannerAd() {
bannerAd = CloudX.createBanner(this, "your-banner-ad-unit-id")
bannerAd.listener = this
bannerAd.revenueListener = this
val layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
layoutParams.gravity = Gravity.CENTER_HORIZONTAL
findViewById(R.id.banner_container).addView(bannerAd, layoutParams)
}
// CloudXAdViewListener callbacks
override fun onAdLoaded(cloudXAd: CloudXAd) {
Log.d("CloudX", "Banner ad loaded from ${cloudXAd.networkName}")
}
override fun onAdLoadFailed(adUnitId: String, cloudXError: CloudXError) {
Log.e("CloudX", "Banner ad failed to load: ${cloudXError.message}")
}
override fun onAdClicked(cloudXAd: CloudXAd) {
Log.d("CloudX", "Banner ad clicked")
}
override fun onAdExpanded(cloudXAd: CloudXAd) {
Log.d("CloudX", "Banner ad expanded")
}
override fun onAdCollapsed(cloudXAd: CloudXAd) {
Log.d("CloudX", "Banner ad collapsed")
}
// CloudXAdRevenueListener callback
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
Log.d("CloudX", "Banner bid-time revenue: ${cloudXAd.revenue} from ${cloudXAd.networkName}")
}
override fun onDestroy() {
super.onDestroy()
bannerAd.destroy()
}
}
```
```java Java
public class MainActivity extends AppCompatActivity implements CloudXAdViewListener, CloudXAdRevenueListener {
private CloudXAdView bannerAd;
private void createBannerAd() {
bannerAd = CloudX.createBanner(this, "your-banner-ad-unit-id");
bannerAd.setListener(this);
bannerAd.setRevenueListener(this);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
layoutParams.gravity = Gravity.CENTER_HORIZONTAL;
((LinearLayout) findViewById(R.id.banner_container)).addView(bannerAd, layoutParams);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (bannerAd != null) {
bannerAd.destroy();
}
}
// CloudXAdViewListener callbacks
@Override
public void onAdLoaded(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Banner ad loaded from " + cloudXAd.getNetworkName());
}
@Override
public void onAdLoadFailed(@NonNull String adUnitId, @NonNull CloudXError cloudXError) {
Log.e("CloudX", "Banner ad failed to load: " + cloudXError.getMessage());
}
@Override
public void onAdClicked(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Banner ad clicked");
}
@Override
public void onAdExpanded(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Banner ad expanded");
}
@Override
public void onAdCollapsed(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Banner ad collapsed");
}
// CloudXAdRevenueListener callback
@Override
public void onAdRevenuePaid(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Banner bid-time revenue: " + cloudXAd.getRevenue() + " from " + cloudXAd.getNetworkName());
}
}
```
Banner ads auto-refresh by default unless disabled in CloudX configuration. With SDK 4.7.0 or later, `startAutoRefresh()` cannot override that setting. Use `load()` for manual requests on a server-disabled ad unit. A pause made with `stopAutoRefresh()` can still be resumed.
To control refresh manually:
```kotlin Kotlin
bannerAd.stopAutoRefresh() // Stop auto-refresh
bannerAd.load() // Manually load a new ad
bannerAd.startAutoRefresh() // Re-enable auto-refresh
```
```java Java
bannerAd.stopAutoRefresh(); // Stop auto-refresh
bannerAd.load(); // Manually load a new ad
bannerAd.startAutoRefresh(); // Re-enable auto-refresh
```
Optional placement and custom data for tracking:
```kotlin Kotlin
bannerAd.setPlacement("home_screen")
bannerAd.setCustomData("level:5,coins:100")
```
```java Java
bannerAd.setPlacement("home_screen");
bannerAd.setCustomData("level:5,coins:100");
```
## MREC Ads (300x250)
```kotlin Kotlin
class MainActivity : AppCompatActivity(), CloudXAdViewListener, CloudXAdRevenueListener {
private lateinit var mrecAd: CloudXAdView
private fun createMrecAd() {
mrecAd = CloudX.createMREC(this, "your-mrec-ad-unit-id")
mrecAd.listener = this
mrecAd.revenueListener = this
val layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
layoutParams.gravity = Gravity.CENTER_HORIZONTAL
findViewById(R.id.mrec_container).addView(mrecAd, layoutParams)
}
// CloudXAdViewListener callbacks (same as Banner)
override fun onAdLoaded(cloudXAd: CloudXAd) {
Log.d("CloudX", "MREC ad loaded from ${cloudXAd.networkName}")
}
override fun onAdLoadFailed(adUnitId: String, cloudXError: CloudXError) {
Log.e("CloudX", "MREC ad failed to load: ${cloudXError.message}")
}
override fun onAdClicked(cloudXAd: CloudXAd) {
Log.d("CloudX", "MREC ad clicked")
}
override fun onAdExpanded(cloudXAd: CloudXAd) {
Log.d("CloudX", "MREC ad expanded")
}
override fun onAdCollapsed(cloudXAd: CloudXAd) {
Log.d("CloudX", "MREC ad collapsed")
}
// CloudXAdRevenueListener callback
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
Log.d("CloudX", "MREC bid-time revenue: ${cloudXAd.revenue} from ${cloudXAd.networkName}")
}
override fun onDestroy() {
super.onDestroy()
mrecAd.destroy()
}
}
```
```java Java
public class MainActivity extends AppCompatActivity implements CloudXAdViewListener, CloudXAdRevenueListener {
private CloudXAdView mrecAd;
private void createMrecAd() {
mrecAd = CloudX.createMREC(this, "your-mrec-ad-unit-id");
mrecAd.setListener(this);
mrecAd.setRevenueListener(this);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
layoutParams.gravity = Gravity.CENTER_HORIZONTAL;
((LinearLayout) findViewById(R.id.mrec_container)).addView(mrecAd, layoutParams);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mrecAd != null) {
mrecAd.destroy();
}
}
// CloudXAdViewListener callbacks (same as Banner)
@Override
public void onAdLoaded(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "MREC ad loaded from " + cloudXAd.getNetworkName());
}
@Override
public void onAdLoadFailed(@NonNull String adUnitId, @NonNull CloudXError cloudXError) {
Log.e("CloudX", "MREC ad failed to load: " + cloudXError.getMessage());
}
@Override
public void onAdClicked(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "MREC ad clicked");
}
@Override
public void onAdExpanded(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "MREC ad expanded");
}
@Override
public void onAdCollapsed(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "MREC ad collapsed");
}
// CloudXAdRevenueListener callback
@Override
public void onAdRevenuePaid(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "MREC bid-time revenue: " + cloudXAd.getRevenue() + " from " + cloudXAd.getNetworkName());
}
}
```
MREC ads also auto-refresh by default. Use the same refresh control methods as Banner ads.
---
# Interstitial Ads
> Integrate CloudX interstitial ads in Android apps
#### CloudX-rendered creatives
CloudX-served creatives are rendered by the built-in renderer, which ships inside the core SDK — no separate renderer adapter module is required. The creative type is selected automatically from the bid response, so no publisher integration is needed to render either format. The renderer supports MRAID 3.0 (HTML) and VAST creatives across the ad formats:
| Ad format | HTML (MRAID 3.0) | VAST |
| ------------ | ---------------- | ---- |
| Banner | ✓ | — |
| MREC | ✓ | ✓ |
| Interstitial | ✓ | ✓ |
| Rewarded | ✓ | ✓ |
```kotlin Kotlin
class MainActivity : AppCompatActivity(), CloudXInterstitialListener, CloudXAdRevenueListener {
private lateinit var interstitialAd: CloudXInterstitialAd
private var retryAttempt = 0
private fun createInterstitialAd() {
interstitialAd = CloudX.createInterstitial(this, "your-interstitial-ad-unit-id")
interstitialAd.listener = this
interstitialAd.revenueListener = this
interstitialAd.load()
}
private fun showInterstitialAd() {
if (interstitialAd.isAdReady) {
// Basic show
interstitialAd.show(this)
// Or with optional placement and custom data for tracking
// interstitialAd.show(this, "level_complete", "level:5,score:1000")
} else {
Log.w("CloudX", "Interstitial ad not ready yet")
}
}
// CloudXInterstitialListener callbacks
override fun onAdLoaded(cloudXAd: CloudXAd) {
Log.d("CloudX", "Interstitial ad loaded from ${cloudXAd.networkName}")
retryAttempt = 0
}
/**
* Retries loading with exponential backoff: delay doubles with each attempt
* (2s → 4s → 8s … 32s max) to avoid hammering the server during transient
* failures. [retryAttempt] resets to 0 in [onAdLoaded].
*/
override fun onAdLoadFailed(adUnitId: String, cloudXError: CloudXError) {
Log.e("CloudX", "Interstitial ad failed to load: ${cloudXError.message}")
retryAttempt++
val delayMillis = TimeUnit.SECONDS.toMillis(2.0.pow(retryAttempt.coerceAtMost(5)).toLong())
Handler(Looper.getMainLooper()).postDelayed({ interstitialAd.load() }, delayMillis)
}
override fun onAdDisplayed(cloudXAd: CloudXAd) {
Log.d("CloudX", "Interstitial ad displayed")
}
override fun onAdDisplayFailed(cloudXAd: CloudXAd, cloudXError: CloudXError) {
Log.e("CloudX", "Interstitial ad failed to display: ${cloudXError.message}")
interstitialAd.load()
}
override fun onAdHidden(cloudXAd: CloudXAd) {
Log.d("CloudX", "Interstitial ad hidden")
// Reload for next use
interstitialAd.load()
}
override fun onAdClicked(cloudXAd: CloudXAd) {
Log.d("CloudX", "Interstitial ad clicked")
}
// CloudXAdRevenueListener callback
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
Log.d("CloudX", "Interstitial bid-time revenue: ${cloudXAd.revenue} from ${cloudXAd.networkName}")
}
override fun onDestroy() {
super.onDestroy()
interstitialAd.destroy()
}
}
```
```java Java
public class MainActivity extends AppCompatActivity implements CloudXInterstitialListener, CloudXAdRevenueListener {
private CloudXInterstitialAd interstitialAd;
private int retryAttempt;
private void createInterstitialAd() {
interstitialAd = CloudX.createInterstitial(this, "your-interstitial-ad-unit-id");
interstitialAd.setListener(this);
interstitialAd.setRevenueListener(this);
interstitialAd.load();
}
private void showInterstitialAd() {
if (interstitialAd.isAdReady()) {
// Basic show
interstitialAd.show(this);
// Or with optional placement and custom data for tracking
// interstitialAd.show(this, "level_complete", "level:5,score:1000");
} else {
Log.w("CloudX", "Interstitial ad not ready yet");
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (interstitialAd != null) {
interstitialAd.destroy();
}
}
// CloudXInterstitialListener callbacks
@Override
public void onAdLoaded(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Interstitial ad loaded from " + cloudXAd.getNetworkName());
retryAttempt = 0;
}
@Override
public void onAdLoadFailed(@NonNull String adUnitId, @NonNull CloudXError cloudXError) {
Log.e("CloudX", "Interstitial ad failed to load: " + cloudXError.getMessage());
retryAttempt++;
long delayMillis = TimeUnit.SECONDS.toMillis((long) Math.pow(2, Math.min(5, retryAttempt)));
new Handler(Looper.getMainLooper()).postDelayed(() -> interstitialAd.load(), delayMillis);
}
@Override
public void onAdDisplayed(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Interstitial ad displayed");
}
@Override
public void onAdDisplayFailed(@NonNull CloudXAd cloudXAd, @NonNull CloudXError cloudXError) {
Log.e("CloudX", "Interstitial ad failed to display: " + cloudXError.getMessage());
interstitialAd.load();
}
@Override
public void onAdHidden(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Interstitial ad hidden");
// Reload for next use
interstitialAd.load();
}
@Override
public void onAdClicked(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Interstitial ad clicked");
}
// CloudXAdRevenueListener callback
@Override
public void onAdRevenuePaid(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Interstitial bid-time revenue: " + cloudXAd.getRevenue() + " from " + cloudXAd.getNetworkName());
}
}
```
#### Expired ads
A loaded ad that is not shown before it expires is reloaded automatically on the same ad object. The current ad stays ready and can still be shown until the replacement is ready.
* To be told when a replacement is ready, set an `expirationListener`. A replacement that loads on its own does not call `onAdLoaded` again.
* If you show the ad while the replacement loads, the replacement is kept. It becomes ready after the ad closes, and the `expirationListener` is called then.
* If you call `load()` while the replacement is loading, including a `load()` from `onAdHidden`, no second request starts. You receive `onAdLoaded` with the new ad, followed by `onExpiredAdReloaded`.
* If the replacement fails to load while the expired ad is still loaded, `onAdLoadFailed` fires and the ad is no longer ready. Call `load()` again, after a delay rather than in a tight loop.
```kotlin Kotlin
interstitialAd.expirationListener = CloudXAdExpirationListener { expiredAd, newAd ->
Log.d("CloudX", "Expired interstitial ad replaced by an ad from ${newAd.networkName}")
}
```
```java Java
interstitialAd.setExpirationListener((expiredAd, newAd) ->
Log.d("CloudX", "Expired interstitial ad replaced by an ad from " + newAd.getNetworkName()));
```
---
# Native Ads
> Load and render CloudX native ads in custom Android layouts
Native ads expose individual creative assets—such as the title, body, icon, media, and call to action—so you can render them in a layout that matches your app. CloudX populates the bound views and delegates impression and click tracking to the winning network adapter.
The integration has three steps:
1. Bind your layout's UI components.
2. Create a loader and load or render the native ad.
3. Destroy the ad and loader when they are no longer needed.
> **Note:** This page covers standalone Native ad units loaded with `CloudXNativeAdLoader`. Native Banner and Native MREC creatives served in Banner or MREC slots use the regular [`CloudXAdView` integration](/en/android/ad-formats/banner-mrec); they do not use a publisher-provided native layout or binder.
## Supported networks
Per-network Native support — including the Native Banner and Native MREC variants — is maintained in the [Native ad format matrix](/en/ad-formats/native). Adapter-specific dependencies and setup notes are documented on each adapter's overview page.
## 1. Bind UI components
Create a layout containing the native assets you want to display, then map each view ID with `CloudXNativeAdViewBinder`. A network may omit optional assets, so make sure your layout can collapse or hide empty views.
> **Warning:** Networks require their key assets — most importantly the media container bound with `setMediaContentViewGroupId(...)` — to be rendered and visible on screen, or they do not count the impression. The ad still displays, so the revenue loss is silent. Follow each network's asset requirements on its adapter overview page, linked from the [Native ad format matrix](/en/ad-formats/native).
The options container is required: CloudX uses it to display the network-provided AdChoices or privacy control.
```xml res/layout/native_ad_layout.xml
```
```kotlin Kotlin
val binder = CloudXNativeAdViewBinder.Builder(R.layout.native_ad_layout)
.setTitleTextViewId(R.id.native_ad_title)
.setBodyTextViewId(R.id.native_ad_body)
.setIconImageViewId(R.id.native_ad_icon)
.setMediaContentViewGroupId(R.id.native_ad_media_container)
.setCallToActionButtonId(R.id.native_ad_cta)
.setOptionsContentViewGroupId(R.id.native_ad_options)
.setAdvertiserTextViewId(R.id.native_ad_advertiser)
.setStarRatingContentViewGroupId(R.id.native_ad_star_rating)
.build()
```
```java Java
CloudXNativeAdViewBinder binder = new CloudXNativeAdViewBinder.Builder(R.layout.native_ad_layout)
.setTitleTextViewId(R.id.native_ad_title)
.setBodyTextViewId(R.id.native_ad_body)
.setIconImageViewId(R.id.native_ad_icon)
.setMediaContentViewGroupId(R.id.native_ad_media_container)
.setCallToActionButtonId(R.id.native_ad_cta)
.setOptionsContentViewGroupId(R.id.native_ad_options)
.setAdvertiserTextViewId(R.id.native_ad_advertiser)
.setStarRatingContentViewGroupId(R.id.native_ad_star_rating)
.build();
```
### Star rating
CloudX fills the star rating container only when the network provides a rating of 3.0 or higher; otherwise the container is left empty. Design your layout so an empty star rating container collapses or is hidden.
## 2. Create a loader
```kotlin Kotlin
class YourActivity : AppCompatActivity(), CloudXNativeAdListener, CloudXAdRevenueListener {
private lateinit var nativeAdLoader: CloudXNativeAdLoader
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
nativeAdLoader = CloudX.createNativeAdLoader(this, "your-native-ad-unit-id")
nativeAdLoader.nativeAdListener = this
nativeAdLoader.revenueListener = this
}
override fun onDestroy() {
super.onDestroy()
nativeAdLoader.destroy()
}
}
```
```java Java
public class YourActivity extends AppCompatActivity implements CloudXNativeAdListener, CloudXAdRevenueListener {
private CloudXNativeAdLoader nativeAdLoader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
nativeAdLoader = CloudX.createNativeAdLoader(this, "your-native-ad-unit-id");
nativeAdLoader.setNativeAdListener(this);
nativeAdLoader.setRevenueListener(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (nativeAdLoader != null) {
nativeAdLoader.destroy();
}
}
}
```
### Load the ad
Choose one loading flow.
#### Load into a pre-built view
Use this flow when the destination layout is already available. CloudX populates and registers the view before returning it through `onNativeAdLoaded`.
```kotlin Kotlin
val adView = CloudXNativeAdView(this, binder)
nativeAdLoader.loadAd(adView)
```
```java Java
CloudXNativeAdView adView = new CloudXNativeAdView(this, binder);
nativeAdLoader.loadAd(adView);
```
#### Load and render separately
Use deferred rendering when you want to load the ad before its destination view exists. Call `render(...)` before adding the view to your hierarchy.
```kotlin Kotlin
nativeAdLoader.loadAd()
override fun onNativeAdLoaded(adView: CloudXNativeAdView?, ad: CloudXAd) {
val newAdView = CloudXNativeAdView(this@YourActivity, binder)
nativeAdLoader.render(newAdView, ad)
container.addView(newAdView)
}
```
```java Java
nativeAdLoader.loadAd();
@Override
public void onNativeAdLoaded(CloudXNativeAdView adView, CloudXAd ad) {
CloudXNativeAdView newAdView = new CloudXNativeAdView(YourActivity.this, binder);
nativeAdLoader.render(newAdView, ad);
container.addView(newAdView);
}
```
### Handle callbacks
Loaded native ads expire one hour after load; `onNativeAdExpired` fires so you can destroy the expired ad and load a fresh one. In `onNativeAdLoadFailed`, avoid immediately retrying in a tight loop — retry after a delay or at the next natural display opportunity.
```kotlin Kotlin
// Required callbacks
override fun onNativeAdLoaded(adView: CloudXNativeAdView?, ad: CloudXAd) {
Log.d("CloudX", "Native ad loaded from ${ad.networkName}")
ad.nativeAd?.let { nativeAd ->
if (nativeAd.isVideoContent) {
Log.d("CloudX", "Video duration: ${nativeAd.videoDuration}s")
}
}
adView?.let { container.addView(it) }
}
override fun onNativeAdLoadFailed(adUnitId: String, error: CloudXError) {
Log.e("CloudX", "Native ad failed to load: ${error.message}")
}
override fun onNativeAdClicked(ad: CloudXAd) {
Log.d("CloudX", "Native ad clicked")
}
// Optional callbacks (default no-op)
override fun onNativeAdExpired(ad: CloudXAd) {
Log.d("CloudX", "Native ad expired — destroy and reload")
nativeAdLoader.destroy(ad)
nativeAdLoader.loadAd()
}
override fun onNativeAdClosed(ad: CloudXAd) {
Log.d("CloudX", "User dismissed the ad via AdChoices")
nativeAdLoader.destroy(ad)
}
// Revenue callback
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
Log.d("CloudX", "Native ad revenue: ${cloudXAd.revenue} from ${cloudXAd.networkName}")
}
```
```java Java
// Required callbacks
@Override
public void onNativeAdLoaded(CloudXNativeAdView adView, CloudXAd ad) {
Log.d("CloudX", "Native ad loaded from " + ad.getNetworkName());
CloudXNativeAd nativeAd = ad.getNativeAd();
if (nativeAd != null && nativeAd.isVideoContent()) {
Log.d("CloudX", "Video duration: " + nativeAd.getVideoDuration() + "s");
}
if (adView != null) {
container.addView(adView);
}
}
@Override
public void onNativeAdLoadFailed(@NonNull String adUnitId, @NonNull CloudXError error) {
Log.e("CloudX", "Native ad failed to load: " + error.getMessage());
}
@Override
public void onNativeAdClicked(@NonNull CloudXAd ad) {
Log.d("CloudX", "Native ad clicked");
}
// Optional callbacks (default no-op)
@Override
public void onNativeAdExpired(@NonNull CloudXAd ad) {
Log.d("CloudX", "Native ad expired — destroy and reload");
nativeAdLoader.destroy(ad);
nativeAdLoader.loadAd();
}
@Override
public void onNativeAdClosed(@NonNull CloudXAd ad) {
Log.d("CloudX", "User dismissed the ad via AdChoices");
nativeAdLoader.destroy(ad);
}
// Revenue callback
@Override
public void onAdRevenuePaid(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Native ad revenue: " + cloudXAd.getRevenue() + " from " + cloudXAd.getNetworkName());
}
```
## 3. Destroy native ads
Destroy an individual ad when it is replaced or expires. Destroy the loader when its owning screen or component is finished. This releases network media views and prevents resources from accumulating over time.
```kotlin Kotlin
// Destroy a specific loaded ad
nativeAdLoader.destroy(ad)
// Destroy the loader and all associated resources
nativeAdLoader.destroy()
```
```java Java
// Destroy a specific loaded ad
nativeAdLoader.destroy(ad);
// Destroy the loader and all associated resources
nativeAdLoader.destroy();
```
## Native ad assets
The `CloudXNativeAd` interface is available via `ad.nativeAd` in listener callbacks:
| Property | Type | Description |
| ------------------------- | ---------------------- | -------------------------------------------- |
| `title` | `String?` | Headline text |
| `body` | `String?` | Body / description text |
| `callToAction` | `String?` | CTA button text (e.g., "Install Now") |
| `advertiser` | `String?` | Advertiser name |
| `icon` | `CloudXNativeAdImage?` | App icon (as `Drawable` or `Uri`) |
| `mainImage` | `CloudXNativeAdImage?` | Main image (static creatives) |
| `mediaView` | `View?` | Video/media player view (adapter-provided) |
| `optionsView` | `View?` | AdChoices or options view (adapter-provided) |
| `mediaContentAspectRatio` | `Float` | Aspect ratio of the media content |
| `starRating` | `Double?` | App store rating (0–5) |
| `isVideoContent` | `Boolean` | Whether the creative is a video |
| `videoDuration` | `Double` | Video length in seconds (0.0 if unknown) |
| `isExpired` | `Boolean` | Whether the ad has expired |
Assets vary by network and creative. Treat nullable values as optional and hide the corresponding UI when an asset is unavailable. A `mediaContentAspectRatio` or `videoDuration` value of `0` means the network did not provide a usable value yet.
### Native video metadata
Use `isVideoContent` to distinguish video from static native creatives. When it is `true`, `videoDuration` contains the duration in seconds if the network has made it available. Query duration after rendering for the most accurate value.
---
# Rewarded Ads
> Integrate CloudX rewarded ads in Android apps
Rewarded ads give users a reward — typically in-app currency or bonus content — in exchange for watching a fullscreen ad.
#### CloudX-rendered creatives
CloudX-served creatives are rendered by the built-in renderer, which ships inside the core SDK — no separate renderer adapter module is required. The creative type is selected automatically from the bid response, so no publisher integration is needed to render either format. The renderer supports MRAID 3.0 (HTML) and VAST creatives across the ad formats:
| Ad format | HTML (MRAID 3.0) | VAST |
| ------------ | ---------------- | ---- |
| Banner | ✓ | — |
| MREC | ✓ | ✓ |
| Interstitial | ✓ | ✓ |
| Rewarded | ✓ | ✓ |
```kotlin Kotlin
class MainActivity : AppCompatActivity(), CloudXRewardedListener, CloudXAdRevenueListener {
private lateinit var rewardedAd: CloudXRewardedAd
private var retryAttempt = 0
private fun createRewardedAd() {
rewardedAd = CloudX.createRewarded(this, "your-rewarded-ad-unit-id")
rewardedAd.listener = this
rewardedAd.revenueListener = this
rewardedAd.load()
}
private fun showRewardedAd() {
if (rewardedAd.isAdReady) {
// Basic show
rewardedAd.show(this)
// Or with optional placement and custom data for tracking
// rewardedAd.show(this, "bonus_coins", "level:5,coins:100")
} else {
Log.w("CloudX", "Rewarded ad not ready yet")
}
}
// CloudXRewardedListener callbacks
override fun onAdLoaded(cloudXAd: CloudXAd) {
Log.d("CloudX", "Rewarded ad loaded from ${cloudXAd.networkName}")
retryAttempt = 0
}
/**
* Retries loading with exponential backoff: delay doubles with each attempt
* (2s → 4s → 8s … 32s max) to avoid hammering the server during transient
* failures. [retryAttempt] resets to 0 in [onAdLoaded].
*/
override fun onAdLoadFailed(adUnitId: String, cloudXError: CloudXError) {
Log.e("CloudX", "Rewarded ad failed to load: ${cloudXError.message}")
retryAttempt++
val delayMillis = TimeUnit.SECONDS.toMillis(2.0.pow(retryAttempt.coerceAtMost(5)).toLong())
Handler(Looper.getMainLooper()).postDelayed({ rewardedAd.load() }, delayMillis)
}
override fun onAdDisplayed(cloudXAd: CloudXAd) {
Log.d("CloudX", "Rewarded ad displayed")
}
override fun onAdDisplayFailed(cloudXAd: CloudXAd, cloudXError: CloudXError) {
Log.e("CloudX", "Rewarded ad failed to display: ${cloudXError.message}")
rewardedAd.load()
}
override fun onAdHidden(cloudXAd: CloudXAd) {
Log.d("CloudX", "Rewarded ad hidden")
// Reload for next use
rewardedAd.load()
}
override fun onAdClicked(cloudXAd: CloudXAd) {
Log.d("CloudX", "Rewarded ad clicked")
}
override fun onUserRewarded(cloudXAd: CloudXAd, reward: CloudXReward) {
Log.d("CloudX", "User rewarded: ${reward.amount} ${reward.label}")
// Grant the reward to the user
}
// CloudXAdRevenueListener callback
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
Log.d("CloudX", "Rewarded bid-time revenue: ${cloudXAd.revenue} from ${cloudXAd.networkName}")
}
override fun onDestroy() {
super.onDestroy()
rewardedAd.destroy()
}
}
```
```java Java
public class MainActivity extends AppCompatActivity implements CloudXRewardedListener, CloudXAdRevenueListener {
private CloudXRewardedAd rewardedAd;
private int retryAttempt;
private void createRewardedAd() {
rewardedAd = CloudX.createRewarded(this, "your-rewarded-ad-unit-id");
rewardedAd.setListener(this);
rewardedAd.setRevenueListener(this);
rewardedAd.load();
}
private void showRewardedAd() {
if (rewardedAd.isAdReady()) {
// Basic show
rewardedAd.show(this);
// Or with optional placement and custom data for tracking
// rewardedAd.show(this, "bonus_coins", "level:5,coins:100");
} else {
Log.w("CloudX", "Rewarded ad not ready yet");
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (rewardedAd != null) {
rewardedAd.destroy();
}
}
// CloudXRewardedListener callbacks
@Override
public void onAdLoaded(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Rewarded ad loaded from " + cloudXAd.getNetworkName());
retryAttempt = 0;
}
@Override
public void onAdLoadFailed(@NonNull String adUnitId, @NonNull CloudXError cloudXError) {
Log.e("CloudX", "Rewarded ad failed to load: " + cloudXError.getMessage());
retryAttempt++;
long delayMillis = TimeUnit.SECONDS.toMillis((long) Math.pow(2, Math.min(5, retryAttempt)));
new Handler(Looper.getMainLooper()).postDelayed(() -> rewardedAd.load(), delayMillis);
}
@Override
public void onAdDisplayed(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Rewarded ad displayed");
}
@Override
public void onAdDisplayFailed(@NonNull CloudXAd cloudXAd, @NonNull CloudXError cloudXError) {
Log.e("CloudX", "Rewarded ad failed to display: " + cloudXError.getMessage());
rewardedAd.load();
}
@Override
public void onAdHidden(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Rewarded ad hidden");
// Reload for next use
rewardedAd.load();
}
@Override
public void onAdClicked(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Rewarded ad clicked");
}
@Override
public void onUserRewarded(@NonNull CloudXAd cloudXAd, @NonNull CloudXReward reward) {
Log.d("CloudX", "User rewarded: " + reward.getAmount() + " " + reward.getLabel());
// Grant the reward to the user
}
// CloudXAdRevenueListener callback
@Override
public void onAdRevenuePaid(@NonNull CloudXAd cloudXAd) {
Log.d("CloudX", "Rewarded bid-time revenue: " + cloudXAd.getRevenue() + " from " + cloudXAd.getNetworkName());
}
}
```
#### Expired ads
A loaded ad that is not shown before it expires is reloaded automatically on the same ad object. The current ad stays ready and can still be shown until the replacement is ready.
* To be told when a replacement is ready, set an `expirationListener`. A replacement that loads on its own does not call `onAdLoaded` again.
* If you show the ad while the replacement loads, the replacement is kept. It becomes ready after the ad closes, and the `expirationListener` is called then.
* If you call `load()` while the replacement is loading, including a `load()` from `onAdHidden`, no second request starts. You receive `onAdLoaded` with the new ad, followed by `onExpiredAdReloaded`.
* If the replacement fails to load while the expired ad is still loaded, `onAdLoadFailed` fires and the ad is no longer ready. Call `load()` again, after a delay rather than in a tight loop.
```kotlin Kotlin
rewardedAd.expirationListener = CloudXAdExpirationListener { expiredAd, newAd ->
Log.d("CloudX", "Expired rewarded ad replaced by an ad from ${newAd.networkName}")
}
```
```java Java
rewardedAd.setExpirationListener((expiredAd, newAd) ->
Log.d("CloudX", "Expired rewarded ad replaced by an ad from " + newAd.getNetworkName()));
```
---
# App Open Ads
> Integrate CloudX App Open ads in Android apps
App Open ads are fullscreen placements intended for app foreground moments, such as when a user returns to your app after it has been backgrounded.
#### Supported Networks
App Open demand is served by the following bidding networks:
| Network | Adapter |
| --------- | ---------------------------------------------------- |
| Mintegral | [Mintegral](/en/android/adapters/mintegral/overview) |
| Pangle | [Pangle](/en/android/adapters/pangle/overview) |
| Vungle | [Vungle](/en/android/adapters/vungle/overview) |
The [Google Mobile Ads waterfall adapter](/en/android/adapters/google-estimated/overview) also supports App Open placements for AdMob waterfall demand.
```kotlin Kotlin
class MainActivity : AppCompatActivity(), CloudXAppOpenListener, CloudXAdRevenueListener {
private lateinit var appOpenAd: CloudXAppOpenAd
private fun createAppOpenAd() {
appOpenAd = CloudX.createAppOpen(this, "your-app-open-ad-unit-id")
appOpenAd.listener = this
appOpenAd.revenueListener = this
appOpenAd.load()
}
private fun showAppOpenAd() {
if (appOpenAd.isAdReady) {
appOpenAd.show(this, "app_foreground")
}
}
// CloudXAppOpenListener callbacks
override fun onAdLoaded(cloudXAd: CloudXAd) {
Log.d("CloudX", "App Open ad loaded from ${cloudXAd.networkName}")
}
override fun onAdLoadFailed(adUnitId: String, cloudXError: CloudXError) {
Log.e("CloudX", "App Open ad failed to load: ${cloudXError.message}")
}
override fun onAdDisplayed(cloudXAd: CloudXAd) {
Log.d("CloudX", "App Open ad displayed")
}
override fun onAdDisplayFailed(cloudXAd: CloudXAd, cloudXError: CloudXError) {
Log.e("CloudX", "App Open ad failed to display: ${cloudXError.message}")
appOpenAd.load()
}
override fun onAdHidden(cloudXAd: CloudXAd) {
appOpenAd.load()
}
override fun onAdClicked(cloudXAd: CloudXAd) {}
// CloudXAdRevenueListener callback
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
Log.d("CloudX", "App Open bid-time revenue: ${cloudXAd.revenue}")
}
override fun onDestroy() {
super.onDestroy()
appOpenAd.destroy()
}
}
```
Use App Open ads sparingly and only at natural app-open or app-resume transitions. Avoid showing one while the user is already interacting with app content.
#### Expired ads
A loaded ad that is not shown before it expires is reloaded automatically on the same ad object. The current ad stays ready and can still be shown until the replacement is ready.
* To be told when a replacement is ready, set an `expirationListener`. A replacement that loads on its own does not call `onAdLoaded` again.
* If you show the ad while the replacement loads, the replacement is kept. It becomes ready after the ad closes, and the `expirationListener` is called then.
* If you call `load()` while the replacement is loading, including a `load()` from `onAdHidden`, no second request starts. You receive `onAdLoaded` with the new ad, followed by `onExpiredAdReloaded`.
* If the replacement fails to load while the expired ad is still loaded, `onAdLoadFailed` fires and the ad is no longer ready. Call `load()` again, after a delay rather than in a tight loop.
```kotlin Kotlin
appOpenAd.expirationListener = CloudXAdExpirationListener { expiredAd, newAd ->
Log.d("CloudX", "Expired App Open ad replaced by an ad from ${newAd.networkName}")
}
```
```java Java
appOpenAd.setExpirationListener((expiredAd, newAd) ->
Log.d("CloudX", "Expired App Open ad replaced by an ad from " + newAd.getNetworkName()));
```
---
# Changelog
> Android SDK release history
## \[4.10.0] - 2026-10-13
### Added
* **Reward at Grant** - `onUserRewarded` can now fire as soon as the ad network grants the reward, instead of when the ad closes. This is a server-configured setting and works with rewarded adapter releases that support it.
### Fixed
* **Native Ad Renderer Crash** - CloudX-rendered native ads no longer crash your app when Android's WebView renderer process stops. The ad hides its main image and keeps its text, clicks and impression tracking.
* **Network Lookup Crash** - The SDK no longer crashes your app on devices where reading the active network fails inside Android itself. It now treats that failure as a normal network error and continues.
## \[4.9.0] - 2026-10-07
### Added
* **Ad Quality Gate** - Register a `CloudXAdQualityGate` with `CloudX.setAdQualityGate()` to screen each loaded ad before `onAdLoaded`. A blocked ad is discarded and the SDK tries the next bid. A gate that times out or throws lets the ad through.
* **Ad Size** - `CloudXAd.adSize` reports the size of a loaded ad in dp, such as 320x50 for banners and 300x250 for MRECs. Custom implementations of `CloudXAd` must be rebuilt, and Java implementations must also add `getAdSize()`.
* **Multiple End Cards** - CloudX-rendered fullscreen video ads can now show more than two end cards when the creative provides them.
### Changed
* **Mediation Debugger** - `CloudX.showMediationDebugger(context)` now opens only after SDK initialization completes and returns `false` before that. Call it after initialization, and recompile code built against 4.8.x.
* **Ad Expiry** - Fullscreen and native ads now expire when their bid expires, or 50 minutes after the bid when it sets no expiry. An expired fullscreen ad reports `isAdReady()` as false and fails `show()` with `CloudXErrorCode.ADAPTER_AD_EXPIRED`; load a new ad.
* **Test-Mode Revenue** - Test-mode sessions now deliver revenue callbacks again, including to global listeners and MMP connectors, with `revenue` set to 0. Use them to check your revenue wiring on test devices.
* **Banner Failures After Load** - A banner or MREC that fails after it loaded now reports `onAdLoadFailed`. While auto-refresh is stopped, the SDK no longer reloads on its own; call `load()` to request a new ad.
* **Startup Performance** - At process start the SDK now only schedules its warm-up, and runs WebView and network warm-up after the app first comes to the foreground. Background processes such as FCM and WorkManager skip it, which reduces ANR risk.
### Fixed
* **Native Ads on Dark Themes** - CloudX native ads in banner and MREC slots now take their text colors from your Activity theme, so titles and body text stay readable on dark themes.
* **HTML Ad Layout** - CloudX-rendered HTML ads with absolutely positioned content no longer appear shifted or clipped. Creatives smaller than their slot now align to the top-left corner instead of the center.
* **Mediation Debugger Consent Detection** - The Mediation Debugger now detects CMPs that store consent only in GPP and lists the frameworks it found instead of reporting no CMP.
## \[4.8.2] - 2026-09-29
### Fixed
* **Reload After Fullscreen Close** - A `load()` call made while an interstitial or rewarded ad is closing now completes with a load callback. Apps that reload from `onAdHidden` no longer stop loading that ad unit after a click-out and return.
## \[4.8.1] - 2026-09-22
### Fixed
* **Initialization on Filtered Networks** - SDK initialization now recovers when a DNS filter blocks the CloudX initialization host.
## \[4.8.0] - 2026-09-21
### Added
* **Ad Unit Configuration After Initialization** - `CloudXSdkConfiguration.adUnitConfigurations` maps each ad unit ID to its `isOrchestratorEnabled` state once initialization completes. Custom implementations of `CloudXSdkConfiguration` must implement this new member.
* **Mediation Debugger** - Open an in-app integration report with `CloudX.showMediationDebugger(context)`. It shows adapter and ad unit status, privacy state, and test-ad load and show controls, and can share a plain-text report with CloudX support.
* **Creative Identifier** - `CloudXAd.creativeId` exposes the creative id reported by the demand source, so you can identify and block problem creatives. Null when the source does not send one, including Meta, Digital Turbine, and Unity Ads today.
* **Mediator Identity** - `CloudXAd.mediatorName` and `mediatorAdUnitId` report which mediator and mediator ad unit served an ad. CloudX-won ads report `cloudx` and their CloudX ad unit id.
* **Arbiter Bid Metadata** - `CloudXArbiterBid.cloudX(ad, extras)` accepts optional metadata when creating a CloudX bid candidate for third-party arbitration.
### Changed
* **Realized Revenue** - When an ad source reports impression-level revenue, such as the AdMob and GAM estimated bidders and AppLovin MAX, `CloudXAd.revenue` in `onAdRevenuePaid` now carries the realized amount and `revenuePrecision` its precision. CloudX bids still report the bid-time estimate.
* **Connector Revenue Forwarding** - MMP connectors and global ad revenue listeners no longer receive CloudX wins from bidders that already report impression revenue to your MMP, such as AppLovin MAX and the AdMob and GAM estimated bidders. Per-ad revenue callbacks are unchanged.
* **Vungle Error Reporting** - Creative-download failures now report `CloudXErrorCode.ADAPTER_INTERNAL_ERROR` with storage/network guidance instead of `ADAPTER_INVALID_LOAD_STATE`, and HTTP transport failures now report `ADAPTER_SERVER_ERROR`. Update any branching on error code 602.
* **Kotlin Requirement** - The SDK is now compiled with Kotlin 2.2. Apps must build with Kotlin 2.1 or newer.
### Fixed
* **Fullscreen Ads in the App Task** - CloudX-rendered interstitial and rewarded ads now open in your app's own task. Apps whose launcher Activity sets `android:taskAffinity=""`, the Flutter template default, no longer end up with a stuck ad and blocked later loads after returning from Home.
* **AppLovin Initialization Recovery** - CloudX now completes initialization when the app starts AppLovin MAX first, instead of staying pending after MAX becomes ready.
## \[4.7.0] - 2026-09-09
### Added
* **MREC Video** - CloudX-rendered MREC ads now support inline VAST video. Playback starts muted and pauses when the ad moves off-screen.
* **Fullscreen Preloading** - When enabled in CloudX configuration, showing a fullscreen ad can preload the next ad for the same ad unit.
* **BIGO Ads Adapter Availability** - The BIGO Ads Android adapter is now available as `io.cloudx:adapter-bigo:6.0.1.0`, supporting Banner, MREC, Interstitial, and Rewarded ad formats on CloudX Android SDK 4.7.0 or newer.
### Changed
* **Inline Video Audio** - HTML/MRAID video in CloudX-rendered banner and MREC ads now starts muted. Users can unmute through the video controls.
* **Concurrent Loads** - Each ad unit allows two simultaneous publisher loads by default, with a configurable limit. Excess loads fail with `CloudXErrorCode.LOAD_REJECTED_CONCURRENCY`; wait for a pending load to finish before retrying.
* **Load During Display** - Calling `load()` while a fullscreen ad is showing queues one request until dismissal. Repeated calls do not add more queued requests.
* **Test-Mode Revenue** - Test sessions no longer deliver per-ad or global revenue callbacks, including connector notifications. Impression and reward events remain available.
* **Initialization Performance** - Reduced main-thread work during SDK initialization and improved preparation for the first ad request.
### Fixed
* **First-Load Reliability** - Improved ad loading while networks initialize and reduced repeated preparation work before ad requests.
* **Native Ad Rendering** - Native loads now fail when click and impression registration cannot complete, instead of returning an unusable ad.
* **Banner Refresh Control** - Disabling auto-refresh in CloudX configuration now remains effective after an app calls `startAutoRefresh()`.
* **Device Detection** - Fixed physical Android devices being incorrectly identified as emulators.
## \[4.6.0] - 2026-08-23
### Added
* **Google Ad Manager Prebid Integration** - Now available as `io.cloudx:integration-gam-prebid:1.0.0`, letting CloudX demand compete as prebid line items in your Google Ad Manager auction for banner, MREC, interstitial, rewarded, and native. Requires CloudX Android SDK 4.6.0.
### Changed
* **First-Auction Performance** - Reduced network setup before the first auction by starting the CloudX connection during SDK initialization.
## \[4.5.2] - 2026-08-18
### Changed
* **Play SDK Console Verification** - The published Android SDK artifact now includes the ownership metadata required for verification in Google Play SDK Console.
## \[4.5.1] - 2026-08-18
### Fixed
* **Vertical Banner Reliability** - Rotated banners now report correct viewability exposure and MRAID positions, enabling reliable left- and right-side vertical banner placements in wrapper integrations.
* **VAST Impression Tracking** - VAST tracker URLs up to 10 KB are now delivered, preventing valid impression beacons with long query parameters from being dropped.
* **Native Video in Banner Slots** - Video native ads that cannot render in 320x50 slots are now rejected instead of producing invisible impressions, allowing fallback demand to continue.
## \[4.5.0] - 2026-08-10
### Added
* **AdMob and Google Ad Manager Demand via TPA** - Adds AdMob and Google Ad Manager demand to third-party arbitration with `CloudXArbiterBid.adMob(adUnitId)` and `CloudXArbiterBid.gam(adUnitId)`. Bids are priced automatically from realized revenue, or manually via `manualRevenuePerImpressionUSD`.
* **Google Ad Manager in Google Adapters** - Existing Google adapters can now serve Google Ad Manager demand alongside AdMob, selected per placement through CloudX configuration with no code changes.
* **Native Test Ads in Banner and MREC** - CloudX test ads can now render native creatives in banner (320x50) and MREC (300x250) placements.
### Fixed
* **Fullscreen Callback Reliability** - Fixed a race where showing an interstitial or rewarded ad could replay a stale error, and ad network callbacks arriving after an ad is destroyed are now handled safely.
* **Digital Turbine Native Impressions** - Digital Turbine native ads now fire impressions and revenue callbacks. Requires the updated Digital Turbine adapter.
## \[4.4.0] - 2026-07-28
### Added
* **Publisher Revenue Platforms** - Publisher-reported revenue can now use `CloudXRevenuePlatform.INMOBI`, `CloudXRevenuePlatform.TOPON`, or `CloudXRevenuePlatform.custom(name)` in addition to AdMob, so apps can forward impression-level revenue from more mediation and reporting platforms.
### Changed
* **Revenue Amount Units** - `CloudXRevenueData.builder(platform, revenue, adFormat)` now takes `revenue` as a `Double` in currency units, and `CloudXRevenueData.revenue` is now a `Double`. For AdMob `AdValue.valueMicros`, divide by `1_000_000.0` before creating `CloudXRevenueData`.
### Fixed
* **Telephony Permission Guard** - The SDK now checks phone-state permission before reading the telephony network type.
* **VAST Tracker Compatibility** - Fixed the VAST event tracker to avoid an API 24-only collection call when running on the SDK's minimum supported Android version.
## \[4.3.0] - 2026-07-21
### Added
* **AppsFlyer Ad Revenue Connector** - Added a managed AppsFlyer connector and a global `CloudX.addAdRevenueListener()` API to forward CloudX ad revenue to AppsFlyer or any MMP with one dependency and no glue code. The listener fires once per CloudX-won impression across all ad formats.
### Changed
* **Google Waterfall Prefetch Expiry** - Prefetched Google Waterfall fills now expire on per-format time limits (4 hours for App Open, 1 hour for other formats) and refill proactively, so stale cached fills no longer render blank or fail at show.
* **Duplicate load() Handling** - Repeated `load()` calls while a load is in flight are now handled safely and consistently across all ad formats. Previously, rapid duplicate banner and MREC loads could cancel the in-flight auction and starve the app of fills.
* **Creative Reporting** - Creative identifiers are now included in impression and revenue telemetry across native and Mintegral-backed placements for more accurate creative-level reporting.
### Fixed
* **Pre-Init Load Callbacks** - Banner and MREC `load()` calls made before initialization completes are now queued and always produce a callback. Previously such calls could be dropped silently, and a failed initialization is now reported as a load failure instead of hanging with no callback.
* **Native Impression Revenue** - Fixed a timing gap where a native impression fired before the SDK's observer subscribed could be lost, so the publisher's revenue callback did not fire. Affected synchronous-impression native paths such as Moloco standalone native.
* **Optimistic Banner Leak** - Fixed an adapter view leak when an optimistically loaded banner is destroyed before its background creative load dispatches.
## \[4.2.0] - 2026-07-07
### Added
* **App Open Ads** - Added App Open ad support through `CloudX.createAppOpen()` and `CloudXAdFormat.APP_OPEN` for app foreground placements.
* **Publisher Revenue Reporting** - Added `CloudX.reportRevenueData()` and `CloudXRevenueData` so publishers can forward AdMob impression-level revenue events into CloudX reporting.
* **Trusted Arbiter Custom Bids** - Added mediator-agnostic custom bid inputs with `CloudXArbiterBid.custom()`, `CloudXArbiterPlatform.CUSTOM`, and `CloudXArbiterPrecision`.
* **Expanded Google Waterfall Support** - Added Google Waterfall Interstitial, Rewarded, Native, and App Open support.
* **Banner Auto-Refresh Configuration** - Added banner and MREC support for server-disabled auto-refresh settings.
* **TaurusX Adapter Availability** - The TaurusX Android adapter is now available as `io.cloudx:adapter-taurusx:1.18.3.0`, supporting Banner, MREC, Interstitial, Rewarded, and Native ad formats on CloudX Android SDK 4.2.0 or newer.
### Changed
* **Adapter Format Metadata** - Adapters now report supported ad formats to CloudX, helping server-provisioned configuration match demand to compatible adapters.
* **Google Waterfall Reporting** - Improved Google Waterfall response identifiers and realized-price handling for more accurate waterfall diagnostics and reporting.
* **Renderer Reliability** - Fullscreen CloudX renderer behavior now better respects device auto-rotate settings and rewarded close/reward eligibility.
* **Fullscreen Network Names** - Fullscreen ad callbacks now report network names consistently with the raw adapter code used by other ad formats.
* **Initialization Resilience** - Improved SDK recovery from stale or poisoned OS DNS resolver state during initialization and network calls.
### Fixed
* **Moloco Native Banner Impressions** - Fixed Moloco Native Banner and Native MREC impression timing so impressions are handled after the ad view is attached to the CloudX ad view container.
* **Banner Render Timing** - Improved banner and MREC load/render timing so adapter render latency is tracked separately from the initial load signal.
* **Google Waterfall Prefetch Reliability** - Improved Google Waterfall prefetch callback handling and impression reporting so stale loader callbacks cannot affect current fills.
* **Trusted Arbiter PubMatic Comparison** - Fixed fallback arbitration so PubMatic OpenWrap CPM prices are normalized before comparison with per-impression revenue bids.
## \[4.1.7] - 2026-06-25
### Changed
* **Faster SDK Initialization** - Reduced initialization latency by applying privacy consent to ad network adapters in parallel and moving non-critical startup work off the initialization path.
## \[4.1.6] - 2026-06-25
### Changed
* **Quicker Initialization** - The SDK can reuse cached configuration on warmed sessions, reducing initialization latency on subsequent launches.
* **Trusted Arbiter Revenue Accuracy** - Normalized fallback revenue units used in arbitration so CloudX bid comparisons and revenue reporting are more accurate.
## \[4.1.5] - 2026-06-19
### Added
* **MobileFuse Adapter Availability** - The MobileFuse Android adapter is now available as `io.cloudx:adapter-mobilefuse:1.11.0.0`, supporting Banner, MREC, Interstitial, Rewarded, and Native ad formats.
### Changed
* **Meta Native-in-Banner Routing** - Meta native banner demand now uses Meta's native-banner rendering path only for banner-sized placements; native MREC placements use the regular native ad path.
* **Vungle Native Ad Support** - Added support for Vungle native, native banner, and native MREC ad formats.
## \[4.1.4] - 2026-06-18
### Added
* **Pangle Adapter Readiness** - Added the SDK-side metadata needed for the independently released Pangle Android adapter. Publishers can integrate Pangle demand with `io.cloudx:adapter-pangle:8.1.0.3.0` on CloudX Android SDK 4.1.4 or newer.
* **CloudX Fullscreen HTML Rendering** - Added SDK support for CloudX-served MRAID fullscreen creatives in interstitial and rewarded placements, including OMID viewability measurement for CloudX HTML ads.
### Changed
* **SDK Startup Performance** - Reduced SDK initialization and ad-request stalls caused by device signal collection.
* **MRAID Fullscreen Reliability** - Improved fullscreen CloudX HTML ads with load watchdogs, a loading indicator, larger close hit targets, `mraid.unload()` support, more reliable hide callbacks, and safer reward handling when rewarded ads are closed early.
* **Native Ad Rendering** - Improved native image handling for local file-backed assets and prevented stale async image loads from overwriting reused native ad views.
* **Telemetry Timing Accuracy** - SDK telemetry durations now use monotonic app-uptime timing for more reliable diagnostics.
## \[4.1.3] - 2026-05-31
### Changed
* **Resilient Adapter Initialization** - A slow ad network adapter no longer blocks SDK initialization. Each adapter gets a soft init timeout; the SDK finishes initializing while it continues starting up in the background. Not-ready adapters are held out of auctions until they finish.
## \[4.1.2] - 2026-05-28
### Added
* **Native-in-Banner Rendering** - Native ad demand from supported adapters, including Meta, Moloco, and Vungle, can now serve into standard banner and MREC slots through the existing banner public API.
### Changed
* **Server-Driven Always-Reward Mode** - Rewarded ad adapters now honor a `shouldAlwaysRewardUser` flag configured per-line-item in the dashboard. The SDK fires the reward callback even for networks that don't always deliver a reward signal. No integration change required.
## \[4.1.1] - 2026-05-26
### Added
* **Digital Turbine Adapter Support** - Added SDK support for the Digital Turbine Android adapter. Adapter artifacts continue to be released independently.
* **Expanded Network Mappings** - Added SDK network mappings for BidMachine, Chartboost, Digital Turbine, LoopMe, MobileFuse, Pangle, PubMatic, and Yandex so compatible adapters can be released independently as they become available.
### Changed
* **Digital Turbine Adapter Metadata** - Updated Digital Turbine adapter metadata and error reporting compatibility for CloudX SDK 4.1.1.
* **Google Waterfall Adapter** - Updated Google Waterfall support to Google Mobile Ads SDK 25.1.0 and added fallback banner/MREC impression reporting when AdMob impression-level revenue callbacks are unavailable.
## \[4.1.0] - 2026-05-22
### Added
* **Trusted Arbiter** - Added support for comparing CloudX bids with supported third-party bids from Unity LevelPlay and PubMatic, and selecting the winning platform through CloudX arbitration.
* **LevelPlay Impression-Level Revenue Data** - Added impression-level revenue tracking via Unity LevelPlay integration, alongside the existing AppLovin ILRD support.
* **Ad Value Accessors** - Added `CloudXAd.adValues` for SDK-provided ad metadata.
* **Expanded Network Support** - Added SDK support for Google Waterfall, Moloco, and Verve adapters. Adapter artifacts continue to be released independently.
* **MRAID 3.0 Ad View Rendering** - Added CloudX renderer support for MRAID 3.0 banner and MREC creatives.
## \[4.0.0] - 2026-04-27
### Breaking Changes
* **Adapter Version Compatibility** - Adapters now declare their minimum CloudX SDK version. The SDK skips incompatible adapters during initialization.
### Added
* **Independent Adapter Versions** - CloudX adapters are now released independently from the core SDK, allowing compatible adapter updates to ship on their own release schedule.
### Changed
* **Publisher Release Builds** - Improved release-build compatibility by preserving the CloudX runtime classes required for SDK initialization and adapter loading.
### Fixed
* **Release Build Initialization** - Fixed an issue that could prevent SDK initialization in minified publisher builds.
## \[3.2.0] - 2026-04-24
### Added
* **Magnite Adapter** - Added Magnite adapter support for banner, interstitial, and rewarded ads.
* **Per-Request Bid Floor Overrides** - Added APIs for per-request bid floor overrides so publishers can send request-scoped floor controls through the SDK bid pipeline.
### Changed
* Improved banner and MREC lifecycle handling across adapters for more consistent rendering, refresh, and cleanup behavior.
### Fixed
* Improved SDK resilience when optional adapter or network SDK dependencies are missing at runtime.
* Fixed Vungle banner load errors so invalid banner load states are surfaced through SDK error callbacks.
## \[3.1.0] - 2026-04-17
### Added
* **Native Ads** - Added native ad support with `CloudX.createNativeAdLoader()`, `CloudXNativeAdLoader`, `CloudXNativeAd`, `CloudXNativeAdListener`, and `CloudXNativeLoaderConfiguration`, backed by the Meta adapter.
### Changed
* Improved adapter initialization, privacy forwarding, and ad load/show reliability across supported networks.
* Reduced startup and initialization work on the main thread to improve app launch stability and lower ANR risk.
* **Unity 2022 Meta Compatibility** - `adapter-meta` now brings in Meta Audience Network `6.20.0` transitively by default for Unity 2022 compatibility. If your integration uses native ads, explicitly add Meta Audience Network `6.21.0` or newer in your app:
```kotlin
dependencies {
implementation("io.cloudx:adapter-meta:3.1.0")
implementation("com.facebook.android:audience-network-sdk:6.21.0")
}
```
### Fixed
* Improved SDK resilience for missing network SDK dependencies and adapter load failures.
## \[3.0.0] - 2026-04-06
### Breaking Changes
* `CloudX.initialize()` now requires an Android `Context`, and the public ad creation APIs now require `Context` as well: `createBanner()`, `createMREC()`, `createInterstitial()`, and `createRewarded()`.
### Changed
* Removed automatic process-start initialization; apps now provide `Context` explicitly through SDK initialization and ad creation APIs.
* Reduced SDK work before `CloudX.initialize()` is called, lowering main-thread startup overhead and ANR risk.
## \[2.2.7] - 2026-04-02
### Fixed
* Reduced startup ANR risk for apps that include the SDK before calling `CloudX.initialize()`.
## \[2.2.6] - 2026-04-01
### Changed
* Improved ad lifecycle, revenue, and crash reporting reliability across SDK and network-adapter events.
* Improved banner, interstitial, and rewarded event attribution so placement and custom data are tracked consistently.
### Fixed
* Improved error reporting for bid response parsing, adapter initialization, and ad load failure paths.
## \[2.2.5] - 2026-03-30
### Added
* Added `CloudXErrorCode.LOAD_NOT_ALLOWED_WHILE_SHOWING` for fullscreen `load()` calls made while the same ad instance is actively showing.
* Added fullscreen adapter readiness checks across interstitial and rewarded adapters.
### Changed
* Improved fullscreen ad readiness handling so expired or invalidated network ads are no longer reported as ready.
* Improved fullscreen ad reuse behavior to avoid serving stale cached ads.
### Fixed
* Fixed fullscreen `load()` being accepted during an active show; it now fails immediately with `onAdLoadFailed(..., LOAD_NOT_ALLOWED_WHILE_SHOWING)`.
* Fixed stale fullscreen ads being reported as ready after the network SDK had already invalidated or expired the underlying ad object.
* Fixed Mintegral GDPR consent propagation for CMP-managed integrations.
## \[2.2.4] - 2026-03-26
### Added
* **Server-Driven Location Sharing** - Added server-controlled location sharing with publisher opt-out support and reduced coordinate precision.
* Added server-controlled remote log collection to help diagnose publisher integration issues.
* Added content-language targeting support in bid requests.
* Added more granular no-fill reasons in SDK error messages.
### Changed
* Improved SDK diagnostic and crash reporting reliability for ad lifecycle events.
### Fixed
* Fixed release-build rules so CloudX adapters are preserved correctly in minified apps.
* Fixed Mintegral adapter crash on Android API 23.
## \[2.2.3] - 2026-03-20
### Added
* Added server-controlled diagnostic log collection for integration support.
### Changed
* Renamed the Unity adapter artifact to `adapter-unityads` and aligned Unity Ads naming across SDK setup and documentation.
### Fixed
* Fixed Unity Ads network name casing to match server bidder configuration.
## \[2.2.2] - 2026-03-17
### Added
* Improved SDK initialization diagnostics by reporting adapter metadata.
### Fixed
* Fixed InMobi bid token retrieval by ensuring token collection runs on the main thread.
## \[2.2.1] - 2026-03-11
### Changed
* Hardened the Mintegral adapter against null values and ad load exceptions across supported ad formats.
* Bumped Mintegral SDK from `17.0.61` to `17.0.91`.
### Fixed
* Fixed adapter bid extras collection so an exception from one adapter no longer prevents extras from subsequent adapters being included.
## \[2.2.0] - 2026-03-10
### Added
* **Impression-Level Revenue Data** - Added impression-level revenue tracking with CloudX auction correlation via AppLovin integration.
* **Ad Click Tracking** - Added click notification support when users tap ads.
* **Manual Privacy API** - Added `CloudX.setHasUserConsent()` and `CloudX.setDoNotSell()` with adapter privacy forwarding.
* **Kotlin Suspend Initialization** - Added a suspend `CloudX.initialize(context, config)` overload for Kotlin callers.
* **Device-Level Reporting** - Added Android ID based device-level reporting for DAU/MAU analytics when available.
* **Adapter Error Diagnostics** - Added adapter error reporting across banner, interstitial, and rewarded ads.
* **Network SDK Crash Reporting** - Added crash reporting for crashes originating from supported ad network SDKs.
* **Creative Type Support** - Added creative type support in the CloudX renderer.
### Changed
* Moved the CloudX renderer into the core SDK, so publishers no longer need a separate CloudX renderer adapter module.
* Removed `appcompat` and `webkit` transitive dependencies from the SDK to reduce dependency surface area and APK size.
* Improved reporting delivery reliability by retaining events for retry on transient send failures.
* Bumped Vungle SDK from `7.6.1` to `7.7.1`.
* Bumped InMobi SDK from `11.1.0` to `11.1.1`, including an upstream `BroadcastReceiver` leak fix.
### Fixed
* Fixed location data handling for geo-based targeting and reporting.
* Fixed incorrect `INVALID_APP_KEY` errors for non-config HTTP responses.
* Improved retry classification for transient network and server errors.
## \[2.0.1] - 2026-03-04
### Changed
* Improved Unity 2022.3 compatibility by removing database runtime dependencies from SDK event persistence.
* Downgraded Meta Audience Network SDK from `6.21.0` to `6.20.0` to avoid Unity 2022.3 build incompatibilities.
### Fixed
* Fixed a concurrency issue that could occur during impression tracking.
* Fixed Meta rewarded ad handling for apps using `singleTask` launch mode.
## \[2.0.0] - 2026-02-04
This release replaces placement names with **Ad Unit IDs** from the CloudX dashboard. Update your `createBanner()`, `createMREC()`, `createInterstitial()`, and `createRewarded()` calls to use the ad unit ID instead of a placement name.
### Added
* **Rewarded Ads** - Added rewarded ads with `CloudX.createRewarded()`, `CloudXRewardedAd`, `CloudXRewardedListener`, and `CloudXReward`.
* **InMobi Adapter** - Added InMobi adapter support for banner, MREC, interstitial, and rewarded ads.
* **Vungle Rewarded Ads** - Added rewarded ad support to the Vungle adapter.
* Added `CloudX.isInitialized()` to check SDK initialization state.
* Added `CloudX.getVersion()` to get the SDK version string.
* Added `setPlacement()` and `setCustomData()` on `CloudXAdView` for tracking.
* Added `show(activity, placement, customData)` overloads on fullscreen ads for tracking.
* Added `CloudXSdkConfiguration` in the `onInitialized()` callback.
* Added `CloudXAd.networkPlacement` for network-specific placement IDs.
* Added new error codes: `NETWORK_NO_CONNECTION`, `AD_ALREADY_SHOWING`, `DONT_KEEP_ACTIVITIES_ENABLED`, `AD_NOT_READY`, and `INVALID_APP_KEY`.
### Breaking Changes
* Bumped `minSdk` from 21 to 23.
* Replaced `CloudXInitializationParams` with `CloudXInitializationConfiguration`.
* Renamed `placementName` parameters to `adUnitId` in `createBanner()`, `createMREC()`, `createInterstitial()`, and `createRewarded()`.
* Renamed `CloudXAd.placementId` to `adUnitId`.
* Removed `CloudXAd.placementName`.
* Renamed `CloudXAd.bidderName` to `networkName`.
* Renamed `CloudXErrorCode.INVALID_PLACEMENT` to `INVALID_AD_UNIT`.
* Changed `onAdLoadFailed(placementName, cloudXError)` to `onAdLoadFailed(adUnitId, cloudXError)`.
* Changed `onAdDisplayFailed(cloudXError)` to `onAdDisplayFailed(cloudXAd, cloudXError)`.
* Changed `onInitialized()` to `onInitialized(configuration: CloudXSdkConfiguration)`.
* Removed the `testMode` initialization parameter; test mode is now server-controlled.
* Changed `show()` to `show(activity)` for interstitial and rewarded ads.
### Changed
* Updated Meta Audience Network SDK from `6.17.0` to `6.21.0`.
### Fixed
* Fixed ad reload behavior in `onAdHidden` and `onAdDisplayFailed` callbacks.
* Ensured all `load()` and `show()` callbacks are delivered on the UI thread.
## \[0.12.1] - 2025-12-23
### Fixed
* Fixed an ANR risk in the Meta adapter caused by bid token collection on the main thread.
## \[0.12.0] - 2025-12-14
### Breaking Changes
* Removed `CloudXError.effectiveMessage`; use `message` directly instead.
* Removed `setLoggingEnabled()`; use `setMinLogLevel(CloudXLogLevel.NONE)` to disable logging.
* Removed `CloudXPrivacy`; privacy is now handled automatically through standard privacy signals.
### Changed
* Enhanced GDPR support with GPP Section 2 parsing and vendor consent checking.
* Simplified TCF purpose checks to only require purposes 1 and 2.
* Added CCPA support via GPP with legacy US Privacy fallback.
* Removed COPPA handling.
* Added `CloudXLogLevel.NONE` for disabling logs.
### Fixed
* Fixed geo API failure handling during initialization.
## \[0.11.1] - 2025-12-23
### Fixed
* Fixed an ANR risk in the Meta adapter caused by bid token collection on the main thread.
## \[0.11.0] - 2025-12-09
### Changed
* Migrated the SDK HTTP client from Ktor `3.0.3` to OkHttp `4.12.0`.
* Added destroyed-state guards across ad formats.
## \[0.10.0] - 2025-12-03
### Added
* **Vungle Adapter** - Added Vungle adapter support for banner and interstitial ads.
### Changed
* Simplified the log tag to `CloudX` for easier logcat filtering.
* Added public API entry logging for easier integration debugging.
* SDK initialization now fails early with a clear error when no adapters are found.
### Fixed
* Fixed banner refresh stopping after load failure.
## \[0.9.0] - 2025-11-26
### Added
* Added ad revenue tracking via `CloudXAdRevenueListener.onAdRevenuePaid()`.
### Changed
* Upgraded Ktor from `2.3.8` to `3.0.3` for improved compatibility with apps using Ktor 3.x.
### Fixed
* Fixed memory leaks in ad lifecycle management.
* Improved SDK stability.
## \[0.6.0] - 2025-11-03
### Added
* Added `testMode` support to `CloudXInitializationParams` for requesting test ads during development.
## \[0.4.0] - 2025-10-27
### Added
* Initial alpha release of CloudX Android SDK.
* Added banner and MREC ad support.
* Added interstitial ad support.
* Added real-time bidding with CloudX mediation.
* Added CloudX and Meta Audience Network adapter support.
* Added privacy compliance support for GDPR, CCPA, US Privacy, TCF, and GPP signals.
* Added session depth tracking for bid optimization.
* Added ad lifecycle callbacks and error handling.
* Added debug logging with configurable log levels.
---
# Third-Party Notices
> Third-party software used in the CloudX Android SDK and Android adapter integrations
This page applies to CloudX Android SDK version 4.4.0.
The CloudX Android SDK uses the following third-party software. The primary published dependencies of the core `io.cloudx:sdk` AAR use permissive licenses such as Apache 2.0 or the Android SDK License. As noted below, OkHttp also includes Public Suffix List data with its own notice.
## Direct Dependencies
| Maven Coordinate | Version | License |
| --------------------------------------------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------- |
| [`com.squareup.okhttp3:okhttp`](https://github.com/square/okhttp) | 4.12.0 | Apache 2.0 |
| [`com.squareup.okhttp3:logging-interceptor`](https://github.com/square/okhttp) | 4.12.0 | Apache 2.0 |
| [`org.jetbrains.kotlin:kotlin-stdlib`](https://github.com/JetBrains/kotlin) | 2.1.21 | Apache 2.0 |
| [`org.jetbrains.kotlinx:kotlinx-coroutines-android`](https://github.com/Kotlin/kotlinx.coroutines) | 1.10.2 | Apache 2.0 |
| [`com.google.android.gms:play-services-ads-identifier`](https://developers.google.com/android/guides/overview) | 18.0.1 | [Android SDK License](https://developer.android.com/studio/terms.html) |
| [`com.google.android.gms:play-services-appset`](https://developers.google.com/android/guides/overview) | 16.1.0 | [Android SDK License](https://developer.android.com/studio/terms.html) |
| [`com.google.android.gms:play-services-auth-blockstore`](https://developers.google.com/android/guides/overview) | 16.4.0 | [Android SDK License](https://developer.android.com/studio/terms.html) |
| [`androidx.core:core-ktx`](https://developer.android.com/jetpack/androidx) | 1.12.0 | Apache 2.0 |
| [`androidx.activity:activity-ktx`](https://developer.android.com/jetpack/androidx) | 1.9.3 | Apache 2.0 |
| [`androidx.browser:browser`](https://developer.android.com/jetpack/androidx) | 1.7.0 | Apache 2.0 |
| [`androidx.webkit:webkit`](https://developer.android.com/jetpack/androidx) | 1.10.0 | Apache 2.0 |
| [`androidx.lifecycle:lifecycle-process`](https://developer.android.com/jetpack/androidx) | 2.7.0 | Apache 2.0 |
| [`io.cloudx:omsdk`](https://central.sonatype.com/artifact/io.cloudx/omsdk) | 1.6.3 | MIT License |
## Embedded Third-Party Notice
### OkHttp — Public Suffix List
OkHttp includes the following embedded notice for the Public Suffix List data bundled in `publicsuffixes.gz`:
> Note that publicsuffixes.gz is compiled from The Public Suffix List:
> [https://publicsuffix.org/list/public\_suffix\_list.dat](https://publicsuffix.org/list/public_suffix_list.dat)
>
> It is subject to the terms of the Mozilla Public License, v. 2.0:
> [https://mozilla.org/MPL/2.0/](https://mozilla.org/MPL/2.0/)
OkHttp itself is licensed under Apache 2.0. The Public Suffix List data is used as-is and has not been modified.
## Ad Network SDKs
The CloudX adapter modules depend on proprietary ad network SDKs. These SDKs are separate dependencies of the corresponding adapter modules and are not bundled inside the core `io.cloudx:sdk` AAR. They are distributed under each vendor's proprietary commercial license and are not open-source dependencies of the core `io.cloudx:sdk` artifact:
* BIGO Ads
* Digital Turbine
* Google Mobile Ads
* InMobi
* Magnite
* Meta Audience Network
* Mintegral
* MobileFuse
* Moloco
* Unity Ads
* Verve HyBid
* Vungle (Liftoff)
Publishers integrate these SDKs under their own agreements with the respective vendors.
## Adapter Integrations
If a publisher integrates one or more CloudX adapter artifacts, the effective third-party software set expands beyond the core `io.cloudx:sdk` AAR. The current Android adapter modules map to the following proprietary network SDK dependencies:
| CloudX Adapter Module | Proprietary Network SDK Dependency | License Type |
| -------------------------- | ---------------------------------------------- | ------------------------------ |
| `:adapter-bigo` | `com.bigossp:bigo-ads` | Proprietary commercial license |
| `:adapter-digitalturbine` | `com.fyber:marketplace-sdk` | Proprietary commercial license |
| `:adapter-googlewaterfall` | `com.google.android.gms:play-services-ads` | Android SDK License |
| `:adapter-inmobi` | `com.inmobi.monetization:inmobi-ads-kotlin` | Proprietary commercial license |
| `:adapter-magnite` | `com.magnite.sdk:ads-sdk` | Proprietary commercial license |
| `:adapter-meta` | `com.facebook.android:audience-network-sdk` | Proprietary commercial license |
| `:adapter-mintegral` | `com.mbridge.msdk.oversea:mbridge_android_sdk` | Proprietary commercial license |
| `:adapter-mobilefuse` | `com.mobilefuse.sdk:mobilefuse-sdk-core` | Proprietary commercial license |
| `:adapter-moloco` | `com.moloco.sdk:moloco-sdk` | Proprietary commercial license |
| `:adapter-pangle` | `com.pangle.global:pag-sdk` | Proprietary commercial license |
| `:adapter-taurusx` | `com.taurusx.tax:ads` | Proprietary commercial license |
| `:adapter-unityads` | `com.unity3d.ads:unity-ads` | Proprietary commercial license |
| `:adapter-verve` | `net.pubnative:hybid.sdk` | Proprietary commercial license |
| `:adapter-vungle` | `com.vungle:vungle-ads` | Proprietary commercial license |
These adapter integrations are separate from the direct dependency list above and may apply depending on which CloudX adapter artifacts a publisher chooses to include.
## Apache License 2.0
```
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by the Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding any notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
```
---
# Integration
> AdMob/GAM estimated adapter for CloudX Android SDK. Serves AdMob waterfall demand on either the Google Mobile Ads SDK or the GMA Next-Gen SDK, and Google Ad Manager demand on the Google Mobile Ads SDK, for Banner, MREC, Interstitial, Rewarded, Native, and App Open placements.
> **Warning:** AdMob/GAM estimated is in controlled beta, and Google has not reviewed or endorsed it. See the [publisher setup guide](/en/networks/google-estimated) for details and access.
For dashboard configuration, see the [AdMob/GAM estimated publisher setup guide](/en/networks/google-estimated).
> **Note:** AdMob estimated was previously called Google Waterfall. You will still see `googleWaterfall` in adapter artifact names.
Pricing and the Google-side eCPM floor are dashboard and console decisions, not app-side ones. See the "Pricing: automatic or manual" and "Set an eCPM floor: Google Optimized or Manual" sections of the [AdMob/GAM estimated publisher setup guide](/en/networks/google-estimated).
The Google adapter serves both **AdMob estimated** and **Google Ad Manager estimated** demand — AdMob waterfall and Google Ad Manager, respectively. On Android there are two adapter artifacts, one for each generation of Google's ad SDK. Pick the one that matches the Google SDK your app integrates.
## Which Google demand you get
The adapter serves two independent Google demand sources:
* **AdMob estimated** — AdMob waterfall demand.
* **Google Ad Manager estimated** — Google Ad Manager demand.
Which one runs is a dashboard decision, not a code one. You enable the ad source per placement in the CloudX dashboard; there is no extra dependency and no app-side change. Both sources may run in the same app, each with its own mapping and CPM target, and both may bid in the same auction. Which artifact you integrate does constrain Ad Manager demand — see below.
Google Ad Manager demand is supported from `adapter-googlewaterfall` 25.2.0.2. Ad Manager placements are served through the same Google Mobile Ads SDK you already integrate, so no extra dependency is required. CloudX selects them per placement from your server-side configuration — there is nothing to enable in your app. `adapter-admob` does not serve Ad Manager demand and skips those placements.
## Which SDK are you on?
You must integrate **exactly one** of the two artifacts below. The two Google SDK generations ship colliding classes and cannot coexist in a single app.
* **Google Mobile Ads SDK** (`play-services-ads` 25.x) — the standard AdMob SDK. Use `io.cloudx:adapter-googlewaterfall`.
* **GMA Next-Gen SDK** (`com.google.android.libraries.ads.mobile.sdk:ads-mobile-sdk` 1.5.0) — Google's Kotlin-first replacement. Use `io.cloudx:adapter-admob`.
Both artifacts have full format parity: Banner, MREC, Interstitial, Rewarded, App Open, and Native. Dashboard mapping, placements provisioning, and revenue semantics are identical.
Ad Manager ad unit ids take the form `/NNNNNNN/placement/name`, unlike AdMob's plain ad unit id.
## Install
### Google Mobile Ads SDK
Use this track if your app integrates the Google Mobile Ads SDK (`play-services-ads` 25.x).
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:adapter-googlewaterfall:25.5.0.0")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:adapter-googlewaterfall:25.5.0.0'
}
```
Adapter 25.5.0.0 requires **Android 7.0 (API 24) or later**. Set your app’s `minSdk` to at least 24. Apps supporting API 23 must stay on adapter 25.4.0.0.
> **Info:** Google will sunset the Google Mobile Ads SDK in **June 2028**. The GMA Next-Gen SDK track is available when you are ready to move.
### Next-Gen SDK
Use this track if your app integrates the GMA Next-Gen SDK (`ads-mobile-sdk` 1.5.0).
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:adapter-admob:1.5.0.0")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:adapter-admob:1.5.0.0'
}
```
> **Info:** The GMA Next-Gen SDK requires **minSdk 24**. Raise your app's `minSdkVersion` to 24 or higher before adopting this track.
## Android Manifest
Publisher setup is the same for both tracks. The Google SDK requires an AdMob app ID in your application's manifest:
```xml
```
If this metadata is missing, the adapter fails initialization before Google SDK init runs. Apps that only use Google Ad Manager may declare the boolean `com.google.android.gms.ads.AD_MANAGER_APP` meta-data instead of an app ID.
## Impression-Level Revenue Data
Impression-Level Ad Revenue must be on for the AdMob account, or CloudX records no revenue for Google impressions and automatic pricing never learns a realized price. See the "Turn on Impression-Level Ad Revenue" section of the [AdMob/GAM estimated publisher setup guide](/en/networks/google-estimated). Google Ad Manager estimated demand reports impression-level revenue through your Ad Manager setup instead.
## Compatibility
### Google Mobile Ads SDK — `adapter-googlewaterfall`
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | ------------------------------ | ------------------ |
| `25.5.0.0` | Google Mobile Ads SDK `25.5.0` | `4.5.0` |
| `25.4.0.1` | Google Mobile Ads SDK `25.4.0` | `4.5.0` |
| `25.4.0.0` | Google Mobile Ads SDK `25.4.0` | `4.5.0` |
| `25.2.0.6` | Google Mobile Ads SDK `25.2.0` | `4.5.0` |
| `25.2.0.5` | Google Mobile Ads SDK `25.2.0` | `4.5.0` |
| `25.2.0.4` | Google Mobile Ads SDK `25.2.0` | `4.5.0` |
| `25.2.0.3` | Google Mobile Ads SDK `25.2.0` | `4.5.0` |
| `25.2.0.2` | Google Mobile Ads SDK `25.2.0` | `4.5.0` |
| `25.2.0.1` | Google Mobile Ads SDK `25.2.0` | `4.2.0` |
| `25.2.0.0` | Google Mobile Ads SDK `25.2.0` | `4.2.0` |
| `25.1.0.6` | Google Mobile Ads SDK `25.1.0` | `4.5.0` |
| `25.1.0.5` | Google Mobile Ads SDK `25.1.0` | `4.5.0` |
| `25.1.0.4` | Google Mobile Ads SDK `25.1.0` | `4.2.0` |
| `25.1.0.3` | Google Mobile Ads SDK `25.1.0` | `4.2.0` |
| `25.1.0.2` | Google Mobile Ads SDK `25.1.0` | `4.2.0` |
| `25.1.0.1` | Google Mobile Ads SDK `25.1.0` | `4.1.0` |
| `25.1.0.0` | Google Mobile Ads SDK `25.1.0` | `4.1.0` |
| `24.9.0.1` | Google Mobile Ads SDK `24.9.0` | `4.5.0` |
| `24.9.0.0` | Google Mobile Ads SDK `24.9.0` | `4.2.0` |
| `23.6.0.0` | Google Mobile Ads SDK `23.6.0` | `4.1.0` |
### Next-Gen SDK — `adapter-admob`
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | ------------------------ | ------------------ |
| `1.5.0.0` | GMA Next-Gen SDK `1.5.0` | `4.5.0` |
| `1.4.0.1` | GMA Next-Gen SDK `1.4.0` | `4.5.0` |
| `1.4.0.0` | GMA Next-Gen SDK `1.4.0` | `4.5.0` |
| `1.3.1.0` | GMA Next-Gen SDK `1.3.1` | `4.5.0` |
| `1.3.0.4` | GMA Next-Gen SDK `1.3.0` | `4.5.0` |
| `1.3.0.3` | GMA Next-Gen SDK `1.3.0` | `4.5.0` |
| `1.3.0.2` | GMA Next-Gen SDK `1.3.0` | `4.5.0` |
| `1.3.0.1` | GMA Next-Gen SDK `1.3.0` | `4.5.0` |
| `1.3.0.0` | GMA Next-Gen SDK `1.3.0` | `4.2.0` |
Requires `minSdk 24`.
The adapter is discovered automatically when your server-provisioned CloudX configuration includes AdMob estimated or Google Ad Manager estimated demand. No manual adapter registration is required.
---
# Changelog
> AdMob estimated adapter release history for CloudX Android SDK
This page covers both AdMob estimated adapter artifacts. Each entry is labeled with the artifact it applies to: `adapter-admob` (GMA Next-Gen SDK) or `adapter-googlewaterfall` (legacy Google Mobile Ads SDK).
## `adapter-admob` \[1.5.0.0] - 2026-09-29
* Certified with Google Mobile Ads Next-Gen SDK 1.5.0.
* Requires minimum CloudX SDK version be 4.5.0.
## `adapter-googlewaterfall` \[25.5.0.0] - 2026-09-23
* Certified with Google Mobile Ads SDK 25.5.0.
* Requires minimum CloudX SDK version be 4.5.0.
* Requires Android 7.0 (API 24) or later. Apps supporting Android 6.0 (API 23) must stay on adapter 25.4.0.0.
## `adapter-admob` \[1.4.0.1] - 2026-10-01
* Ad prefetch no longer runs while the app is in the background. Fullscreen preloading and banner, MREC, and native prefetch start on the first foreground, so a process woken in the background, for example by a push, sends no ad requests.
* Backgrounding pauses banner, MREC, and native prefetch. Cached ads are kept, fullscreen preload buffers survive, and prefetch resumes when the app returns to the foreground.
* Release on the Google Mobile Ads Next-Gen SDK 1.4.0 line. No integration changes are required.
* Requires minimum CloudX SDK version be 4.5.0.
## `adapter-admob` \[1.4.0.0] - 2026-09-23
* Certified with Google Mobile Ads Next-Gen SDK 1.4.0.
* Requires minimum CloudX SDK version be 4.5.0.
## `adapter-admob` \[1.3.1.0] - 2026-09-23
* Certified with Google Mobile Ads Next-Gen SDK 1.3.1.
* Requires minimum CloudX SDK version be 4.5.0.
## `adapter-googlewaterfall` \[25.4.0.1] - 2026-10-01
* Ad prefetch no longer runs while the app is in the background. Fullscreen preloading and banner, MREC, and native prefetch start on the first foreground, so a process woken in the background, for example by a push, sends no ad requests.
* Backgrounding pauses banner, MREC, and native prefetch. Cached ads are kept, fullscreen preload buffers survive, and prefetch resumes when the app returns to the foreground.
* Release on the Google Mobile Ads SDK 25.4.0 line, which supports Android 6.0 (API 23). No integration changes are required.
* Requires minimum CloudX SDK version be 4.5.0.
## `adapter-googlewaterfall` \[25.4.0.0] - 2026-09-11
* Certified with Google Mobile Ads SDK 25.4.0, moving the adapter off 25.2.0. Google Mobile Ads SDK 25.3.0 and newer require Kotlin 2.2 or later in your app.
* No adapter changes. No integration changes are required.
* Requires minimum CloudX SDK version be 4.5.0.
## `adapter-googlewaterfall` \[25.2.0.6] - 2026-09-29
* Ad prefetch no longer runs while the app is in the background. Fullscreen preloading and banner, MREC, and native prefetch start on the first foreground, so a process woken in the background, for example by a push, sends no ad requests.
* Backgrounding pauses banner, MREC, and native prefetch. Cached ads are kept, fullscreen preload buffers survive, and prefetch resumes when the app returns to the foreground.
* Release on the Google Mobile Ads SDK 25.2.0 line. No integration changes are required.
* Requires minimum CloudX SDK version be 4.5.0.
## `adapter-googlewaterfall` \[25.2.0.5] - 2026-08-26
* Fixes native ad clicks when the ad is rendered in a container the CloudX SDK did not build, such as a host mediation layer's own native view. The clickable views you supply are now registered with Google, so taps register. Video media stays unregistered on that path; images are unaffected.
* Adapter initialization no longer holds up CloudX SDK initialization. Only the manifest application-ID check gates the callback, and the Google Mobile Ads SDK starts in the background. A bid that arrives before Google finishes initializing fails that single auction.
* No integration changes are required.
* Requires minimum CloudX SDK version be 4.5.0.
## `adapter-admob` \[1.3.0.4] - 2026-08-26
* Adapter initialization no longer holds up CloudX SDK initialization. Only the manifest application-ID check gates the callback, and the GMA Next-Gen SDK starts in the background. A bid that arrives before Google finishes initializing fails that single auction.
* No integration changes are required.
* Requires minimum CloudX SDK version be 4.5.0.
## `adapter-googlewaterfall` \[25.1.0.6] - 2026-08-26
* Fixes native ad clicks when the ad is rendered in a container the CloudX SDK did not build, such as a host mediation layer's own native view. Backport of the 25.2.0.5 fix to the Google Mobile Ads SDK 25.1.0 line.
* Adapter initialization no longer holds up CloudX SDK initialization; the Google Mobile Ads SDK starts in the background. A bid that arrives before Google finishes initializing fails that single auction.
* No integration changes are required.
* Requires minimum CloudX SDK version be 4.5.0.
## `adapter-googlewaterfall` \[25.2.0.4] - 2026-08-21
* Fullscreen ad prefetch now uses Google's ad-preloading API, so the Google Mobile Ads SDK manages loading, expiry, and refill of interstitial, rewarded, and app-open ads. This removes the adapter's own retry and refresh timers.
* No integration changes are required.
* Requires Google Mobile Ads SDK 24.4.0 or newer, and minimum CloudX SDK version be 4.5.0.
## `adapter-admob` \[1.3.0.3] - 2026-08-21
* Fullscreen ad prefetch now uses Google's ad-preloading API, so the GMA Next-Gen SDK manages loading, expiry, and refill of interstitial, rewarded, and app-open ads. This removes the adapter's own retry and refresh timers.
* No integration changes are required.
* Requires minimum CloudX SDK version be 4.5.0.
## `adapter-googlewaterfall` \[25.1.0.5] - 2026-08-21
* Fullscreen ad prefetch now uses Google's ad-preloading API, so Google manages loading, retry, expiry, and refill. For integrations pinned to the Google Mobile Ads SDK 25.1.0 line.
* This release also adds Google Ad Manager demand and realized-price reporting previously only on the 25.2.0 line.
* Requires minimum CloudX SDK version be 4.5.0 (up from 4.2.0).
## `adapter-admob` \[1.3.0.2] - 2026-08-14
* Fixes native ads that could render with an invisible media view when the ad's media container was returned hidden.
* No integration changes are required.
* Requires minimum CloudX SDK version be 4.5.0.
## `adapter-googlewaterfall` \[25.2.0.3] - 2026-08-14
* Fixes native ads that could render with an invisible media view when the ad's media container was returned hidden.
* No integration changes are required.
* Requires minimum CloudX SDK version be 4.5.0.
## `adapter-googlewaterfall` \[25.1.0.4] - 2026-08-14
* Fixes native ads that could render with an invisible media view when the ad's media container was returned hidden. Backport of the 25.2.0.3 fix to the Google Mobile Ads SDK 25.1.0 line.
* No integration changes are required.
* Requires minimum CloudX SDK version be 4.2.0.
## `adapter-admob` \[1.3.0.1] - 2026-08-10
* Moves realized-price capture into the CloudX SDK. The adapter no longer keeps its own realized-price store; the SDK now records the price from the adapter's impression-level revenue data.
* No integration changes are required.
* Requires minimum CloudX SDK version be 4.5.0.
## `adapter-googlewaterfall` \[25.2.0.2] - 2026-08-10
* Adds Google Ad Manager demand. This is the first adapter version to serve Ad Manager placements, which run on the Google Mobile Ads SDK you already integrate and are selected per placement from your CloudX configuration. No app-side changes are required.
* Moves realized-price capture into the CloudX SDK. The adapter no longer keeps its own realized-price store; the SDK now records the price from the adapter's impression-level revenue data.
* No integration changes are required.
* Requires minimum CloudX SDK version be 4.5.0.
## `adapter-admob` \[1.3.0.0] - 2026-07-29
* Initial release of the AdMob estimated adapter on the GMA Next-Gen SDK 1.3.0.
* Full ad-format parity with the legacy `adapter-googlewaterfall` artifact: Banner, MREC, Interstitial, Rewarded, Native, and App Open.
* Requires `minSdk 24` (a GMA Next-Gen SDK requirement).
* Requires minimum CloudX SDK version be 4.2.0.
## `adapter-googlewaterfall` \[25.2.0.1] - 2026-07-21
* Expires prefetched Google Mobile Ads fills after their per-format freshness window and proactively refills, so a stale cached fill is no longer served for an impression.
* Requires minimum CloudX SDK version be 4.2.0.
## `adapter-googlewaterfall` \[25.2.0.0] - 2026-07-08
* Certified with Google Mobile Ads SDK 25.2.0.
* Requires minimum CloudX SDK version be 4.2.0.
## `adapter-googlewaterfall` \[25.1.0.3] - 2026-07-21
* Expires prefetched Google Mobile Ads fills after their per-format freshness window and proactively refills, so a stale cached fill is no longer served for an impression. For integrations pinned to the Google Mobile Ads SDK 25.1.0 line.
* Requires minimum CloudX SDK version be 4.2.0.
## `adapter-googlewaterfall` \[25.1.0.2] - 2026-07-07
* Adds Google Waterfall Interstitial, Rewarded, Native, and App Open support.
* Improves Google response ID reporting and uses Google paid-event revenue to update realized prices for future Google Waterfall bids.
* Improves prefetch reliability by ignoring stale loader callbacks and using single-shot impression handling across Google Waterfall formats.
* Reports supported ad formats to CloudX so server-provisioned configuration can match demand only to compatible placements.
* Requires minimum CloudX SDK version be 4.2.0.
## `adapter-googlewaterfall` \[25.1.0.1] - 2026-06-02
* Raises the prefetch load timeout above the Google Mobile Ads SDK's own load timeout, so a slow but valid mediation cascade is no longer discarded as a stuck load.
* Clarifies the no-probe-labels log to list the likely causes — probe misconfiguration, the bid clearing above the highest configured probe, or a test ad — so a probe ceiling set too low is distinguishable from broken mediation.
## `adapter-googlewaterfall` \[25.1.0.0] - 2026-05-25
* Initial release.
* Certified with Google Mobile Ads SDK 25.1.0.
* Supports AdMob waterfall Banner and MREC ad formats.
* Tags outgoing AdRequests with `setRequestAgent("cloudx")` so Google attributes prefetch traffic to CloudX.
* Falls back to `AdListener.onAdImpression` when ILRD `OnPaidEventListener` does not fire within 250 ms, so impressions on AdMob accounts without Impression-Level Revenue Data still reach CloudX accounting. The ILRD path emits the revenue bundle; the fallback emits an empty bundle.
* Auto-refresh impressions on the same `AdView` are intentionally dropped — GMA exposes no SDK-side refresh kill switch, so v1 counts one CloudX impression per served creative.
* Requires minimum CloudX SDK version be 4.1.0.
## `adapter-googlewaterfall` \[24.9.0.1] - 2026-09-01
* Brings the Google Mobile Ads SDK 24.9.0 line up to date with the 25.2.0 line, for integrations pinned to Google Mobile Ads SDK 24.9.0.
* Fixes native ads that could render with an invisible media view when the ad's media container was returned hidden.
* Fixes native ad clicks when the ad is rendered in a container the CloudX SDK did not build, such as a host mediation layer's own native view.
* Fullscreen ad prefetch now uses Google's ad-preloading API, so Google manages loading, retry, expiry, and refill.
* Adapter initialization no longer holds up CloudX SDK initialization; the Google Mobile Ads SDK starts in the background. A bid that arrives before Google finishes initializing fails that single auction.
* This release also adds Google Ad Manager demand and realized-price reporting previously only on the 25.x lines.
* No integration changes are required.
* Requires minimum CloudX SDK version be 4.5.0 (up from 4.2.0).
## `adapter-googlewaterfall` \[24.9.0.0] - 2026-07-30
* Certified with Google Mobile Ads SDK 24.9.0, for integrations pinned to the 24.9.0 network SDK line ahead of the GMA 25 upgrade.
* Feature parity with the 25.2.0 line.
* Requires minimum CloudX SDK version be 4.2.0.
## `adapter-googlewaterfall` \[23.6.0.0] - 2026-06-02
* Certified with Google Mobile Ads SDK 23.6.0, for integrations pinned to the 23.6.0 network SDK line.
* Feature parity with 25.1.0.1 (the load-timeout and no-probe-labels logging improvements are included).
* Requires minimum CloudX SDK version be 4.1.0.
---
# Integration
> BIGO Ads adapter for CloudX Android SDK. Supports Banner, MREC, Interstitial, and Rewarded.
For CloudX dashboard mapping and BIGO Ads-side ID setup, see the [BIGO Ads bidder guide](/en/networks/bigo-ads). This page covers Android-specific integration details only.
## Install
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:adapter-bigo:6.1.0.0")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:adapter-bigo:6.1.0.0'
}
```
The BIGO Ads SDK dependency, `com.bigossp:bigo-ads`, is published on Maven Central. No additional Maven repository is required.
## Network Security Configuration
The BIGO Ads SDK serves some creative assets from a local loopback server on the device to reduce ad latency. Allow cleartext traffic to `127.0.0.1` in your app's network security configuration and reference that file from the `` element of `AndroidManifest.xml`:
```xml res/xml/network_security_config.xml
127.0.0.1
```
```xml AndroidManifest.xml
```
If your app already has a network security configuration, add the `127.0.0.1` domain to it instead of replacing the file. The adapter does not change your app's network security configuration itself.
## Compatibility
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | -------------------- | ------------------ |
| `6.1.0.0` | BIGO Ads SDK `6.1.0` | `4.7.0` |
| `6.0.1.0` | BIGO Ads SDK `6.0.1` | `4.7.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes BIGO Ads demand. No manual adapter registration is required.
---
# Changelog
> BIGO Ads adapter release history for CloudX Android SDK
## \[6.1.0.0] - 2026-09-21
* Certified with BIGO Ads SDK 6.1.0.
* Requires minimum CloudX SDK version be 4.7.0.
## \[6.0.1.0] - 2026-09-09
* Initial release.
* Certified with BIGO Ads SDK 6.0.1.
* Supports Banner, MREC, Interstitial, and Rewarded ad formats.
* Reports supported ad formats to CloudX so server-provisioned configuration can match demand only to compatible placements.
* Requires minimum CloudX SDK version be 4.7.0.
---
# Integration
> Digital Turbine adapter for CloudX Android SDK. Supports Banner, MREC, Interstitial, Rewarded, Native, Native Banner, and Native MREC.
For CloudX dashboard mapping and Digital Turbine-side ID setup, see the [Digital Turbine bidder guide](/en/networks/digitalturbine). This page covers Android-specific integration details only.
## Install
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:adapter-digitalturbine:8.4.7.2")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:adapter-digitalturbine:8.4.7.2'
}
```
## Native Ads
The adapter does not expose the main image asset directly: `ad.nativeAd.mainImage` is not populated. Media content — image or video — renders through the adapter-provided media view, which CloudX attaches to the container bound with `setMediaContentViewGroupId(...)`. Bind a visible media container as described in [Native ads](/en/android/ad-formats/native).
## Compatibility
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | --------------------------------------- | ------------------ |
| `8.4.7.2` | Digital Turbine Marketplace SDK `8.4.7` | `4.2.0` |
| `8.4.7.1` | Digital Turbine Marketplace SDK `8.4.7` | `4.2.0` |
| `8.4.7.0` | Digital Turbine Marketplace SDK `8.4.7` | `4.2.0` |
| `8.4.6.0` | Digital Turbine Marketplace SDK `8.4.6` | `4.2.0` |
| `8.4.5.1` | Digital Turbine Marketplace SDK `8.4.5` | `4.2.0` |
| `8.4.5.0` | Digital Turbine Marketplace SDK `8.4.5` | `4.1.1` |
| `8.4.3.0` | Digital Turbine Marketplace SDK `8.4.3` | `4.2.0` |
| `8.4.2.0` | Digital Turbine Marketplace SDK `8.4.2` | `4.1.1` |
| `8.4.0.0` | Digital Turbine Marketplace SDK `8.4.0` | `4.1.1` |
| `8.3.7.0` | Digital Turbine Marketplace SDK `8.3.7` | `4.2.0` |
| `8.3.3.0` | Digital Turbine Marketplace SDK `8.3.3` | `4.1.1` |
Adapter `8.3.7.0` supports Banner, MREC, Interstitial, and Rewarded. Native, Native Banner, and Native MREC require adapter `8.4.5.0` or later.
The adapter is discovered automatically when your server-provisioned CloudX configuration includes Digital Turbine demand. No manual adapter registration is required.
---
# Changelog
> Digital Turbine adapter release history for CloudX Android SDK
## \[8.4.7.2] - 2026-10-13
* Adds support for a server-configured setting that fires `onUserRewarded` as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudX SDK 4.10.0 or later; with older CloudX SDK versions the callback still fires at close.
* Requires minimum CloudX SDK version be 4.2.0.
## \[8.4.7.1] - 2026-08-13
* Fixes native ads never firing an impression: the adapter never constructed or bound a `MediaView`, so viewability could never be satisfied.
* No Digital Turbine Marketplace SDK change (stays 8.4.7) and no minimum CloudX SDK change (stays 4.2.0).
## \[8.4.7.0] - 2026-07-28
* Certified with Digital Turbine Marketplace SDK 8.4.7.
* Requires minimum CloudX SDK version be 4.2.0.
## \[8.4.6.0] - 2026-07-08
* Certified with Digital Turbine Marketplace SDK 8.4.6.
* Requires minimum CloudX SDK version be 4.2.0.
## \[8.4.5.1] - 2026-07-07
* Reports supported ad formats to CloudX so server-provisioned configuration can match demand only to compatible placements.
* Adds support for server-configured rewarded callbacks when a rewarded placement should always grant the reward after close.
* Requires minimum CloudX SDK version be 4.2.0.
## \[8.4.5.0] - 2026-05-26
* Certified with Digital Turbine Marketplace SDK 8.4.5.
* Supports Banner, MREC, Interstitial, Rewarded, and Native ads.
* Requires minimum CloudX SDK version be 4.1.1.
## \[8.4.3.0] - 2026-08-29
* Release for the Digital Turbine Marketplace SDK 8.4.x line, for apps pinned to that network SDK below 8.4.7.
* Certified with Digital Turbine Marketplace SDK 8.4.3.
* Supports Banner, MREC, Interstitial, Rewarded, and Native ads.
* Digital Turbine reads IAB GPP consent automatically only from Marketplace SDK 8.4.4 onward, so GPP is not applied on this line. TCF and US Privacy are unaffected.
* Requires minimum CloudX SDK version be 4.2.0.
## \[8.4.2.0] - 2026-05-31
* Certified with Digital Turbine Marketplace SDK 8.4.2.
* Supports Banner, MREC, Interstitial, and Rewarded ads.
* Requires minimum CloudX SDK version be 4.1.1.
## \[8.4.0.0] - 2026-05-31
* Certified with Digital Turbine Marketplace SDK 8.4.0.
* Supports Banner, MREC, Interstitial, and Rewarded ads.
* Requires minimum CloudX SDK version be 4.1.1.
## \[8.3.7.0] - 2026-08-17
* Adds a compatibility adapter for publishers that must remain on Digital Turbine Marketplace SDK 8.3.7. Supports Banner, MREC, Interstitial, and Rewarded.
* Requires CloudX SDK 4.2.0 or later.
## \[8.3.3.0] - 2026-07-15
* Certified with Digital Turbine Marketplace SDK 8.3.3.
* Supports Banner, MREC, Interstitial, and Rewarded ads.
* Requires minimum CloudX SDK version be 4.1.1.
---
# Integration
> InMobi adapter for CloudX Android SDK. Supports Banner, MREC, Interstitial, Rewarded, Native, Native Banner, and Native MREC.
For CloudX dashboard mapping and InMobi-side ID setup, see the [InMobi bidder guide](/en/networks/inmobi). This page covers Android-specific integration details only.
## Install
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:adapter-inmobi:11.5.0.1")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:adapter-inmobi:11.5.0.1'
}
```
## Native Ads
The adapter does not expose the main image asset directly: `ad.nativeAd.mainImage` is not populated. Media content — image or video — renders through the adapter-provided media view, which CloudX attaches to the container bound with `setMediaContentViewGroupId(...)`. Bind a visible media container as described in [Native ads](/en/android/ad-formats/native).
## Compatibility
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | ------------------- | ------------------ |
| `11.5.0.1` | InMobi SDK `11.5.0` | `4.2.0` |
| `11.5.0.0` | InMobi SDK `11.5.0` | `4.2.0` |
| `11.4.1.0` | InMobi SDK `11.4.1` | `4.2.0` |
| `11.4.0.1` | InMobi SDK `11.4.0` | `4.2.0` |
| `11.4.0.0` | InMobi SDK `11.4.0` | `4.2.0` |
| `11.3.0.1` | InMobi SDK `11.3.0` | `4.2.0` |
| `11.3.0.0` | InMobi SDK `11.3.0` | `4.0.0` |
| `11.2.0.1` | InMobi SDK `11.2.0` | `4.0.0` |
| `11.2.0.0` | InMobi SDK `11.2.0` | `4.0.0` |
| `11.1.1.0` | InMobi SDK `11.1.1` | `4.0.0` |
| `10.8.3.0` | InMobi SDK `10.8.3` | `4.2.0` |
| `10.8.2.0` | InMobi SDK `10.8.2` | `4.2.0` |
| `10.8.0.0` | InMobi SDK `10.8.0` | `4.0.0` |
| `10.7.4.0` | InMobi SDK `10.7.4` | `4.0.0` |
Adapters `10.8.2.0` and `10.8.3.0` support Banner, MREC, Interstitial, and Rewarded. Native, Native Banner, and Native MREC require adapter `11.3.0.0` or later.
## Known Issues
When CloudX runs alongside another mediation SDK that also initializes InMobi in the same app process, InMobi SDK versions earlier than `11.4.0` can leave one initializer without a completion callback when two callers invoke `InMobiSdk.init()` before SDK initialization completes. For multi-mediator integrations, use CloudX InMobi adapter `11.4.0.0` or later, or configure the app so only one mediation stack initializes InMobi.
The adapter is discovered automatically when your server-provisioned CloudX configuration includes InMobi demand. No manual adapter registration is required.
---
# Changelog
> InMobi adapter release history for CloudX Android SDK
## \[11.5.0.1] - 2026-10-13
* Adds support for a server-configured setting that fires `onUserRewarded` as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudX SDK 4.10.0 or later; with older CloudX SDK versions the callback still fires at close.
* Reduces memory use for native ad icons: icons are decoded at no more than 512 px on the longest edge, and icons larger than 5 MiB are skipped while the ad still loads.
* Requires minimum CloudX SDK version be 4.2.0.
## \[11.5.0.0] - 2026-10-06
* Certified with InMobi SDK 11.5.0.
* Applies the server-configured mute setting to InMobi Interstitial and Rewarded ads.
* Requires minimum CloudX SDK version be 4.2.0.
## \[11.4.1.0] - 2026-09-22
* Certified with InMobi SDK 11.4.1.
* Requires minimum CloudX SDK version be 4.2.0.
## \[11.4.0.1] - 2026-07-10
* Sends both InMobi mediation version keys so SDK state and bid token requests include the CloudX SDK version.
* Requires minimum CloudX SDK version be 4.2.0.
## \[11.4.0.0] - 2026-07-09
* Certified with InMobi SDK 11.4.0.
* Requires minimum CloudX SDK version be 4.2.0.
## \[11.3.0.1] - 2026-07-07
* Reports supported ad formats to CloudX so server-provisioned configuration can match demand only to compatible placements.
* Requires minimum CloudX SDK version be 4.2.0.
## \[11.3.0.0] - 2026-06-18
* Certified with InMobi SDK 11.3.0.
* Adds support for InMobi Native, Native Banner, and Native MREC ads.
* Encodes InMobi bid payloads as UTF-8 before loading Banner, Interstitial, Rewarded, and Native ads.
* Requires minimum CloudX SDK version be 4.0.0.
## \[11.2.0.1] - 2026-06-04
* Deduplicate InMobi SDK initialization when CloudX runs alongside another mediator, preventing redundant init and signal-collection failures.
* Requires minimum CloudX SDK version be 4.0.0.
## \[11.2.0.0] - 2026-04-28
* Certified with InMobi SDK 11.2.0.
* Requires minimum CloudX SDK version be 4.0.0.
## \[11.1.1.0] - 2026-04-27
* Certified with InMobi SDK 11.1.1.
* Requires minimum CloudX SDK version be 4.0.0.
## \[10.8.3.0] - 2026-08-17
* Adds a compatibility adapter for publishers that must remain on InMobi SDK 10.8.3. Supports Banner, MREC, Interstitial, and Rewarded.
* Requires CloudX SDK 4.2.0 or later.
* InMobi SDK 10.8.3 cannot be enabled in CloudX and another mediation stack simultaneously within the same app. Configure each app to run InMobi through only one mediator.
## \[10.8.2.0] - 2026-09-08
* Certified with InMobi SDK 10.8.2.
* Requires minimum CloudX SDK version be 4.2.0.
## \[10.8.0.0] - 2026-04-27
* Certified with InMobi SDK 10.8.0.
* Requires minimum CloudX SDK version be 4.0.0.
## \[10.7.4.0] - 2026-07-15
* Certified with InMobi SDK 10.7.4.
* Adds support for server-configured rewarded callbacks when a rewarded placement should always grant the reward after close.
* Requires minimum CloudX SDK version be 4.0.0.
---
# Integration
> Magnite adapter for CloudX Android SDK. Supports Banner, MREC, Interstitial, Rewarded, and Native.
For CloudX dashboard mapping and Magnite-side ID setup, see the [Magnite bidder guide](/en/networks/magnite). This page covers Android-specific integration details only.
## Install
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:adapter-magnite:1.0.2.1")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:adapter-magnite:1.0.2.1'
}
```
> **Note:** Magnite SDK 1.0.1 and later depend on kotlin-stdlib 2.4.0. Apps that include this adapter must compile with Kotlin 2.3 or later.
## Compatibility
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | -------------------- | ------------------ |
| `1.0.2.1` | Magnite SDK `1.0.2` | `4.2.0` |
| `1.0.2.0` | Magnite SDK `1.0.2` | `4.2.0` |
| `1.0.0.2` | Magnite SDK `1.0.0` | `4.2.0` |
| `1.0.0.1` | Magnite SDK `1.0.0` | `4.2.0` |
| `1.0.0.0` | Magnite SDK `1.0.0` | `4.0.0` |
| `0.0.14.0` | Magnite SDK `0.0.14` | `4.0.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes Magnite demand. No manual adapter registration is required.
---
# Changelog
> Magnite adapter release history for CloudX Android SDK
## \[1.0.2.1] - 2026-10-13
* Adds support for a server-configured setting that fires `onUserRewarded` as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudX SDK 4.10.0 or later; with older CloudX SDK versions the callback still fires at close.
* Requires minimum CloudX SDK version be 4.2.0.
## \[1.0.2.0] - 2026-10-12
* Certified with Magnite SDK 1.0.2.
* Requires Kotlin 2.3 or later to compile your app, because Magnite SDK 1.0.1 and later depend on kotlin-stdlib 2.4.0.
## \[1.0.0.2] - 2026-09-16
* Adds Native auction and waterfall support.
* Stops Interstitial and Rewarded callbacks after the ad is destroyed.
* Moves bid-token collection off the caller thread.
## \[1.0.0.1] - 2026-07-07
* Reports supported ad formats to CloudX so server-provisioned configuration can match demand only to compatible placements.
* Adds support for server-configured rewarded callbacks when a rewarded placement should always grant the reward after close.
* Requires minimum CloudX SDK version be 4.2.0.
## \[1.0.0.0] - 2026-05-26
* Certified with Magnite SDK 1.0.0.
* Network SDK API surface preserved across this major bump; adapter source unchanged.
* Requires minimum CloudX SDK version be 4.0.0.
## \[0.0.14.0] - 2026-04-27
* Certified with Magnite SDK 0.0.14.
* Requires minimum CloudX SDK version be 4.0.0.
---
# Integration
> Meta Audience Network adapter for CloudX Android SDK. Supports Banner, MREC, Interstitial, Rewarded, Native, Native Banner, and Native MREC.
For CloudX dashboard mapping and Meta-side ID setup, see the [Meta Audience Network bidder guide](/en/networks/meta-audience-network). This page covers Android-specific integration details only.
## Install
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:adapter-meta:6.22.0.1")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:adapter-meta:6.22.0.1'
}
```
> **Warning:** Adapter `6.22.0.1` brings in Meta Audience Network `6.22.0`, which depends on `androidx.browser:1.9.0` and requires your app to compile against Android API 36. Set `compileSdk = 36` in your module's build file; `targetSdk` does not need to change.
## Native Ads
The Meta adapter supports CloudX native ads, including native video creatives. The adapter brings in Meta Audience Network transitively; you do not need to declare `com.facebook.android:audience-network-sdk` separately unless your app uses Meta APIs directly.
### Impression tracking requirements
Meta requires a specific asset to be rendered and visible on screen before it counts an impression, depending on the Meta placement format:
| Meta placement format | Required asset | Required view |
| --------------------- | -------------- | ------------- |
| Native | Media view | `MediaView` |
| Native Banner | App icon | `ImageView` |
> **Warning:** Meta does not record impressions if these rendering requirements are not met. The ad still renders, so the failure is silent: you serve the creative but earn no Meta revenue.
See Meta's [Native Ads](https://developers.facebook.com/docs/audience-network/setting-up/ad-setup/android/native) and [Native Banner Ads](https://developers.facebook.com/docs/audience-network/setting-up/ad-setup/android/native-banner) guides for the full asset rendering requirements.
### Mapping Meta placements to CloudX ad units
| Meta placement format | CloudX ad unit | CloudX API | Who renders the required asset |
| --------------------- | --------------- | -------------------------- | --------------------------------------------- |
| Native | Native | `CloudXNativeAdLoader` | Your layout, via the binder's media container |
| Native | MREC (300×250) | `CloudX.createMREC(...)` | CloudX-provided layout |
| Native Banner | Banner (320×50) | `CloudX.createBanner(...)` | CloudX-provided layout |
For standalone Meta Native ads, bind a visible media container with `CloudXNativeAdViewBinder.setMediaContentViewGroupId(...)`. CloudX attaches Meta's `MediaView` to that container. See [Native ads](/en/android/ad-formats/native) for the complete layout and loading flow.
If your layout cannot host Meta's `MediaView`, use a Meta Native Banner placement mapped to a CloudX Banner ad unit instead. Meta Native Banner placements are not supported in CloudX Native ad units: there is no icon-only binder path, so impressions will not track.
Native Banner and MREC creatives served through `CloudXAdView` use CloudX-provided layouts, so no native binder is required in publisher code.
## Compatibility
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | ------------------------------ | ------------------ |
| `6.22.0.1` | Meta Audience Network `6.22.0` | `4.2.0` |
| `6.22.0.0` | Meta Audience Network `6.22.0` | `4.2.0` |
| `6.21.0.3` | Meta Audience Network `6.21.0` | `4.2.0` |
| `6.21.0.2` | Meta Audience Network `6.21.0` | `4.0.0` |
| `6.21.0.1` | Meta Audience Network `6.21.0` | `4.0.0` |
| `6.21.0.0` | Meta Audience Network `6.21.0` | `4.0.0` |
| `6.20.0.0` | Meta Audience Network `6.20.0` | `4.0.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes Meta demand. No manual adapter registration is required.
---
# Changelog
> Meta Audience Network adapter release history for CloudX Android SDK
## \[6.22.0.1] - 2026-10-13
* Adds support for a server-configured setting that fires `onUserRewarded` as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudX SDK 4.10.0 or later; with older CloudX SDK versions the callback still fires at close.
* Requires minimum CloudX SDK version be 4.2.0.
## \[6.22.0.0] - 2026-08-10
* Certified with Meta Audience Network SDK 6.22.0.
* Requires apps to compile against Android API 36 (compileSdk 36), matching Meta Audience Network 6.22.0's requirements.
* Requires minimum CloudX SDK version be 4.2.0.
## \[6.21.0.3] - 2026-07-07
* Reports supported ad formats to CloudX so server-provisioned configuration can match demand only to compatible placements.
* Requires minimum CloudX SDK version be 4.2.0.
## \[6.21.0.2] - 2026-06-19
* Routes Meta native banner demand through Meta's native-banner path only for banner-sized placements; native MREC placements use the regular native ad path.
* Requires minimum CloudX SDK version be 4.0.0.
## \[6.21.0.1] - 2026-06-12
* Publish Meta Audience Network SDK 6.21.0 directly without the legacy 6.20.0 dependency metadata workaround.
* Add native-in-banner rendering for Meta: `meta_native` bids now render in standard banner and MREC ad slots. Native creatives load from the bid payload so Meta selects the correct creative subtype (`NativeAd` or `NativeBannerAd`).
* Requires minimum CloudX SDK version be 4.0.0.
## \[6.21.0.0] - 2026-04-27
* Certified with Meta Audience Network SDK 6.21.0.
* Requires minimum CloudX SDK version be 4.0.0.
## \[6.20.0.0] - 2026-04-27
* Certified with Meta Audience Network SDK 6.20.0.
* Requires minimum CloudX SDK version be 4.0.0.
---
# Integration
> Mintegral adapter for CloudX Android SDK. Supports Banner, MREC, Interstitial, Rewarded, App Open, Native, Native Banner, and Native MREC.
For CloudX dashboard mapping and Mintegral-side ID setup, see the [Mintegral bidder guide](/en/networks/mintegral). This page covers Android-specific integration details only.
## Install
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:adapter-mintegral:17.1.81.1")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:adapter-mintegral:17.1.81.1'
}
```
## Maven Repository
Gradle must resolve Mintegral's mediation SDK artifacts, such as `com.mbridge.msdk.oversea:mbridge_android_sdk`, when you add `io.cloudx:adapter-mintegral`.
Those artifacts are hosted on Mintegral's Maven repository, not Maven Central. `google()` and `mavenCentral()` alone are not enough; builds fail with an error such as `Could not find com.mbridge.msdk.oversea:mbridge_android_sdk` until this repository is declared.
Add the repository next to your other Maven repositories. Common locations are the root `build.gradle` / `build.gradle.kts` inside `allprojects { repositories { ... } }`, or `dependencyResolutionManagement.repositories` in `settings.gradle`, depending on your Gradle template:
```kotlin Kotlin DSL
maven {
url = uri("https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea")
}
```
```groovy Groovy
maven { url 'https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea' }
```
Use the published adapter version for your selected Mintegral adapter line, for example `implementation("io.cloudx:adapter-mintegral:17.1.81.1")`.
## Compatibility
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | ------------------------ | ------------------ |
| `17.1.81.1` | Mintegral SDK `17.1.81` | `4.2.0` |
| `17.1.81.0` | Mintegral SDK `17.1.81` | `4.2.0` |
| `17.1.71.1` | Mintegral SDK `17.1.71` | `4.2.0` |
| `17.1.71.0` | Mintegral SDK `17.1.71` | `4.2.0` |
| `17.1.61.3` | Mintegral SDK `17.1.61` | `4.2.0` |
| `17.1.61.2` | Mintegral SDK `17.1.61` | `4.2.0` |
| `17.1.61.1` | Mintegral SDK `17.1.61` | `4.0.0` |
| `17.1.61.0` | Mintegral SDK `17.1.61` | `4.0.0` |
| `17.1.51.0` | Mintegral SDK `17.1.51` | `4.0.0` |
| `17.0.91.0` | Mintegral SDK `17.0.91` | `4.0.0` |
| `17.0.61.1` | Mintegral SDK `17.0.61` | `4.2.0` |
| `17.0.61.0` | Mintegral SDK `17.0.61` | `4.2.0` |
| `17.0.51.0` | Mintegral SDK `17.0.51` | `4.0.0` |
| `16.10.11.0` | Mintegral SDK `16.10.11` | `4.2.0` |
| `16.9.71.0` | Mintegral SDK `16.9.71` | `4.0.0` |
| `16.9.61.0` | Mintegral SDK `16.9.61` | `4.2.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes Mintegral demand. No manual adapter registration is required.
---
# Changelog
> Mintegral adapter release history for CloudX Android SDK
## \[17.1.81.1] - 2026-10-14
* Adds support for Mintegral Native, Native Banner, and Native MREC ads.
* Requires minimum CloudX SDK version be 4.2.0.
## \[17.1.81.0] - 2026-09-21
* Certified with Mintegral SDK 17.1.81.
* Requires minimum CloudX SDK version be 4.2.0.
## \[17.1.71.1] - 2026-08-11
* Initializes the Mintegral SDK only when it is genuinely uninitialized; if Mintegral is already initialized, the adapter reuses that state instead of calling init again.
* Waits for an in-flight Mintegral initialization started by another mediation platform to settle instead of re-initializing, preventing a race that could drop the other platform's initialization callback.
* Requires minimum CloudX SDK version be 4.2.0.
## \[17.1.71.0] - 2026-07-28
* Certified with Mintegral SDK 17.1.71.
* Requires minimum CloudX SDK version be 4.2.0.
## \[17.1.61.3] - 2026-08-31
* Fixes lost interstitial and rewarded callbacks when two ads use the same Mintegral ad unit: each ad now gets its own Mintegral handler, so a newly loading ad can no longer take over the callbacks of an ad that is still showing.
* Requires minimum CloudX SDK version be 4.2.0.
## \[17.1.61.2] - 2026-07-07
* Adds App Open support.
* Requests Mintegral bid tokens with a format-specific ad-type filter so App Open, fullscreen, banner, MREC, and native requests use the correct Mintegral demand type.
* Reports supported ad formats to CloudX so server-provisioned configuration can match demand only to compatible placements.
* Requires minimum CloudX SDK version be 4.2.0.
## \[17.1.61.1] - 2026-07-04
* Moves Mintegral bid-token collection off the app main thread to avoid blocking UI work during synchronous Mintegral buyer ID lookup.
* Requires minimum CloudX SDK version be 4.0.0.
## \[17.1.61.0] - 2026-06-29
* Certified with Mintegral SDK 17.1.61.
* Requires minimum CloudX SDK version be 4.0.0.
## \[17.1.51.0] - 2026-04-28
* Certified with Mintegral SDK 17.1.51.
* Requires minimum CloudX SDK version be 4.0.0.
## \[17.0.91.0] - 2026-04-27
* Certified with Mintegral SDK 17.0.91.
* Requires minimum CloudX SDK version be 4.0.0.
## \[17.0.61.1] - 2026-08-31
* Fixes lost interstitial and rewarded callbacks when two ads use the same Mintegral ad unit: each ad now gets its own Mintegral handler, so a newly loading ad can no longer take over the callbacks of an ad that is still showing.
* Requires minimum CloudX SDK version be 4.2.0.
## \[17.0.61.0] - 2026-08-12
* Release for the Mintegral SDK 17.0.61 line, for apps pinned to that network SDK.
* Certified with Mintegral SDK 17.0.61.
* Requires minimum CloudX SDK version be 4.2.0.
## \[17.0.51.0] - 2026-04-28
* Certified with Mintegral SDK 17.0.51.
* Requires minimum CloudX SDK version be 4.0.0.
## \[16.10.11.0] - 2026-09-05
* Release for the Mintegral SDK 16.10.11 line, for apps pinned to that network SDK.
* Certified with Mintegral SDK 16.10.11.
* Carries the current adapter's fixes: per-ad Mintegral handlers that prevent lost interstitial and rewarded callbacks, off-main-thread bid-token collection, and initialization that coexists with another mediation platform's Mintegral init.
* Requires minimum CloudX SDK version be 4.2.0.
## \[16.9.71.0] - 2026-04-27
* Certified with Mintegral SDK 16.9.71.
* Requires minimum CloudX SDK version be 4.0.0.
## \[16.9.61.0] - 2026-09-08
* Certified with Mintegral SDK 16.9.61.
* Requires minimum CloudX SDK version be 4.2.0.
---
# Integration
> MobileFuse adapter for CloudX Android SDK. Supports Banner, MREC, Interstitial, Rewarded, Native, Native Banner, and Native MREC.
For publisher onboarding and CloudX dashboard setup, see the [MobileFuse bidder guide](/en/networks/mobilefuse).
## Install
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:adapter-mobilefuse:1.12.0.1")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:adapter-mobilefuse:1.12.0.1'
}
```
## Native Ads
The adapter does not expose the main image asset directly: `ad.nativeAd.mainImage` is not populated. Media content — image or video — renders through the adapter-provided media view, which CloudX attaches to the container bound with `setMediaContentViewGroupId(...)`. Bind a visible media container as described in [Native ads](/en/android/ad-formats/native).
## Compatibility
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | ----------------------- | ------------------ |
| `1.12.0.1` | MobileFuse SDK `1.12.0` | `4.2.0` |
| `1.12.0.0` | MobileFuse SDK `1.12.0` | `4.2.0` |
| `1.11.0.1` | MobileFuse SDK `1.11.0` | `4.2.0` |
| `1.11.0.0` | MobileFuse SDK `1.11.0` | `4.1.1` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes MobileFuse demand. No manual adapter registration is required.
---
# Changelog
> MobileFuse adapter release history for CloudX Android SDK
## \[1.12.0.1] - 2026-10-13
* Adds support for a server-configured setting that fires `onUserRewarded` as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudX SDK 4.10.0 or later; with older CloudX SDK versions the callback still fires at close.
* Requires minimum CloudX SDK version be 4.2.0.
## \[1.12.0.0] - 2026-09-08
* Certified with MobileFuse SDK 1.12.0.
* Requires minimum CloudX SDK version be 4.2.0.
## \[1.11.0.1] - 2026-07-07
* Reports supported ad formats to CloudX so server-provisioned configuration can match demand only to compatible placements.
* Requires minimum CloudX SDK version be 4.2.0.
## \[1.11.0.0] - 2026-06-19
* Initial release.
* Certified with MobileFuse SDK 1.11.0.
* Supports Banner, MREC, Interstitial, Rewarded, and Native ad formats.
* Requires minimum CloudX SDK version be 4.1.1.
---
# Integration
> Moloco adapter for CloudX Android SDK. Supports Banner, MREC, leader/tablet Banner, Interstitial, Rewarded, Native, Native Banner, and Native MREC.
Contact your CloudX account manager to confirm availability and complete publisher onboarding.
## Install
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:adapter-moloco:4.13.0.1")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:adapter-moloco:4.13.0.1'
}
```
## Compatibility
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | ------------------- | ------------------ |
| `4.13.0.1` | Moloco SDK `4.13.0` | `4.2.0` |
| `4.13.0.0` | Moloco SDK `4.13.0` | `4.2.0` |
| `4.12.0.0` | Moloco SDK `4.12.0` | `4.2.0` |
| `4.11.1.0` | Moloco SDK `4.11.1` | `4.2.0` |
| `4.11.0.0` | Moloco SDK `4.11.0` | `4.2.0` |
| `4.10.1.0` | Moloco SDK `4.10.1` | `4.2.0` |
| `4.9.0.1` | Moloco SDK `4.9.0` | `4.2.0` |
| `4.9.0.0` | Moloco SDK `4.9.0` | `4.1.0` |
| `4.8.0.0` | Moloco SDK `4.8.0` | `4.1.0` |
| `4.5.0.0` | Moloco SDK `4.5.0` | `4.2.0` |
| `4.4.0.0` | Moloco SDK `4.4.0` | `4.1.0` |
| `4.3.1.0` | Moloco SDK `4.3.1` | `4.1.0` |
| `4.0.0.0` | Moloco SDK `4.0.0` | `4.1.0` |
| `3.12.1.0` | Moloco SDK `3.12.1` | `4.2.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes Moloco demand. No manual adapter registration is required.
---
# Changelog
> Moloco adapter release history for CloudX Android SDK
## \[4.13.0.1] - 2026-10-13
* Adds support for a server-configured setting that fires `onUserRewarded` as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudX SDK 4.10.0 or later; with older CloudX SDK versions the callback still fires at close.
* Requires minimum CloudX SDK version be 4.2.0.
## \[4.13.0.0] - 2026-10-06
* Certified with Moloco SDK 4.13.0.
* Requires minimum CloudX SDK version be 4.2.0.
## \[4.12.0.0] - 2026-09-14
* Certified with Moloco SDK 4.12.0.
* Requires minimum CloudX SDK version be 4.2.0.
## \[4.11.1.0] - 2026-09-14
* Certified with Moloco SDK 4.11.1.
* Requires minimum CloudX SDK version be 4.2.0.
## \[4.11.0.0] - 2026-07-28
* Certified with Moloco SDK 4.11.0.
* Requires minimum CloudX SDK version be 4.2.0.
## \[4.10.1.0] - 2026-07-09
* Certified with Moloco SDK 4.10.1.
* Requires minimum CloudX SDK version be 4.2.0.
## \[4.9.0.1] - 2026-07-07
* Fixes Moloco Native Banner and Native MREC impression timing by reporting impressions only after the ad view is attached to the CloudX ad view container.
* Reports supported ad formats to CloudX so server-provisioned configuration can match demand only to compatible placements.
* Requires minimum CloudX SDK version be 4.2.0.
## \[4.9.0.0] - 2026-06-18
* Certified with Moloco SDK 4.9.0.
* Adds full support for Moloco Native, Native Banner, and Native MREC ads.
* Delegates initialization-state checks to the Moloco SDK, improving behavior when Moloco is initialized by another mediator in the same app process.
* Improves Native ad interaction handling by rejecting stale Native ad registration after the ad is destroyed or replaced.
* Adds support for server-configured rewarded callbacks when a rewarded placement should always grant the reward after close.
* Requires minimum CloudX SDK version be 4.1.0.
## \[4.8.0.0] - 2026-05-26
* Initial release.
* Certified with Moloco SDK 4.8.0.
* Supports Banner, MREC, Leader (Tablet Banner), Interstitial, Rewarded, and Native ad formats.
* Requires minimum CloudX SDK version be 4.1.0.
## \[4.5.0.0] - 2026-08-12
* Release for the Moloco SDK 4.5.x line, for apps pinned to that network SDK.
* Certified with Moloco SDK 4.5.0.
* Supports Banner, MREC, Leader (Tablet Banner), Interstitial, Rewarded, and Native ad formats.
* Requires minimum CloudX SDK version be 4.2.0.
## \[4.4.0.0] - 2026-05-31
* Certified with Moloco SDK 4.4.0.
* Requires minimum CloudX SDK version be 4.1.0.
## \[4.3.1.0] - 2026-07-15
* Certified with Moloco SDK 4.3.1.
* Requires minimum CloudX SDK version be 4.1.0.
## \[4.0.0.0] - 2026-05-31
* Certified with Moloco SDK 4.0.0.
* Requires minimum CloudX SDK version be 4.1.0.
## \[3.12.1.0] - 2026-09-05
* Release for the Moloco SDK 3.12.1 line, for apps pinned to that network SDK.
* Certified with Moloco SDK 3.12.1.
* Supports Banner, MREC, Interstitial, Rewarded, Native, Native Banner, and Native MREC ad formats with the current adapter's behavior.
* Requires minimum CloudX SDK version be 4.2.0.
---
# Integration
> Pangle adapter for CloudX Android SDK. Supports Banner, MREC, Interstitial, Rewarded, App Open, Native, Native Banner, and Native MREC.
Contact your CloudX account manager to confirm availability and complete publisher onboarding.
## Install
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:adapter-pangle:8.3.0.4.2")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:adapter-pangle:8.3.0.4.2'
}
```
## Maven Repository
Gradle must resolve Pangle SDK artifacts when you add `io.cloudx:adapter-pangle`.
Those artifacts are hosted on ByteDance's Maven repository. Add it alongside `google()` and `mavenCentral()`:
```kotlin Kotlin DSL
maven {
url = uri("https://artifact.bytedance.com/repository/pangle")
}
```
```groovy Groovy
maven { url 'https://artifact.bytedance.com/repository/pangle' }
```
## Compatibility
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | -------------------- | ------------------ |
| `8.3.0.4.2` | Pangle SDK `8.3.0.4` | `4.3.0` |
| `8.3.0.4.1` | Pangle SDK `8.3.0.4` | `4.3.0` |
| `8.3.0.4.0` | Pangle SDK `8.3.0.4` | `4.3.0` |
| `8.3.0.3.0` | Pangle SDK `8.3.0.3` | `4.3.0` |
| `8.2.0.4.0` | Pangle SDK `8.2.0.4` | `4.3.0` |
| `8.1.0.7.0` | Pangle SDK `8.1.0.7` | `4.3.0` |
| `8.1.0.5.0` | Pangle SDK `8.1.0.5` | `4.2.0` |
| `8.1.0.4.1` | Pangle SDK `8.1.0.4` | `4.2.0` |
| `8.1.0.4.0` | Pangle SDK `8.1.0.4` | `4.1.4` |
| `8.1.0.3.0` | Pangle SDK `8.1.0.3` | `4.1.4` |
| `7.8.6.0.0` | Pangle SDK `7.8.6.0` | `4.3.0` |
| `7.8.5.9.0` | Pangle SDK `7.8.5.9` | `4.3.0` |
| `7.8.5.2.0` | Pangle SDK `7.8.5.2` | `4.1.4` |
| `7.7.0.2.0` | Pangle SDK `7.7.0.2` | `4.3.0` |
| `7.6.0.3.0` | Pangle SDK `7.6.0.3` | `4.3.0` |
| `7.2.0.3.0` | Pangle SDK `7.2.0.3` | `4.3.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes Pangle demand. No manual adapter registration is required.
---
# Changelog
> Pangle adapter release history for CloudX Android SDK
## \[8.3.0.4.2] - 2026-10-14
* Adds support for Pangle Native, Native Banner, and Native MREC ads.
* Requires minimum CloudX SDK version be 4.3.0.
## \[8.3.0.4.1] - 2026-10-13
* Adds support for a server-configured setting that fires `onUserRewarded` as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudX SDK 4.10.0 or later; with older CloudX SDK versions the callback still fires at close.
* Requires minimum CloudX SDK version be 4.3.0.
## \[8.3.0.4.0] - 2026-09-21
* Certified with Pangle SDK 8.3.0.4.
* Requires minimum CloudX SDK version be 4.3.0.
## \[8.3.0.3.0] - 2026-09-14
* Certified with Pangle SDK 8.3.0.3.
* Requires minimum CloudX SDK version be 4.3.0.
## \[8.2.0.4.0] - 2026-07-28
* Certified with Pangle SDK 8.2.0.4.
* Requires minimum CloudX SDK version be 4.3.0.
## \[8.1.0.7.0] - 2026-09-07
* Release for the Pangle SDK 8.1.x line, for apps pinned to that network SDK below the 8.2 upgrade.
* Certified with Pangle SDK 8.1.0.7.
* Supports Banner, MREC, Interstitial, Rewarded, and App Open ad formats.
* Carries the current adapter code: Pangle SDK ProGuard keep rules now ship as consumer rules, so minified apps no longer need to add them; loads run off the main thread; CloudX mediation attribution is re-applied on every load.
* Requires minimum CloudX SDK version be 4.3.0.
## \[8.1.0.5.0] - 2026-07-13
* Certified with Pangle SDK 8.1.0.5.
* Requires minimum CloudX SDK version be 4.2.0.
## \[8.1.0.4.1] - 2026-07-07
* Adds App Open support.
* Reports supported ad formats to CloudX so server-provisioned configuration can match demand only to compatible placements.
* Requires minimum CloudX SDK version be 4.2.0.
## \[8.1.0.4.0] - 2026-06-29
* Certified with Pangle SDK 8.1.0.4.
* Supports Banner, MREC, Interstitial, and Rewarded ad formats.
* Requires minimum CloudX SDK version be 4.1.4.
## \[8.1.0.3.0] - 2026-06-18
* Initial release.
* Certified with Pangle SDK 8.1.0.3.
* Supports Banner, MREC, Interstitial, and Rewarded ad formats.
* Requires minimum CloudX SDK version be 4.1.4.
## \[7.8.6.0.0] - 2026-08-29
* Release for the Pangle SDK 7.8.x line, for apps pinned to that network SDK below the 8.x upgrade.
* Certified with Pangle SDK 7.8.6.0.
* Supports Banner, MREC, Interstitial, Rewarded, and App Open ad formats.
* Pangle reports no failure reason for bid-token collection on this SDK line, so a failed collection is logged without detail. Bidding is unaffected.
* Requires minimum CloudX SDK version be 4.3.0.
## \[7.8.5.9.0] - 2026-07-30
* Release for the Pangle SDK 7.8.x line, for apps pinned to that network SDK below the 8.x upgrade.
* Certified with Pangle SDK 7.8.5.9.
* Supports Banner, MREC, Interstitial, Rewarded, and App Open ad formats.
* Requires minimum CloudX SDK version be 4.3.0.
## \[7.8.5.2.0] - 2026-06-22
* Release for the Pangle SDK 7.8.x line, for apps pinned to that network SDK.
* Certified with Pangle SDK 7.8.5.2.
* Supports Banner, MREC, Interstitial, and Rewarded ad formats.
* Requires minimum CloudX SDK version be 4.1.4.
## \[7.7.0.2.0] - 2026-09-05
* Release for the Pangle SDK 7.7.x line, for apps pinned to that network SDK.
* Certified with Pangle SDK 7.7.0.2.
* Supports Banner, MREC, Interstitial, Rewarded, and App Open ad formats.
* Sets GDPR consent on the Pangle SDK explicitly; Pangle SDK versions before 7.9.1 do not read the IAB TCF string automatically.
* Pangle reports no failure reason for bid-token collection on this SDK line, so a failed collection is logged without detail. Bidding is unaffected.
* Requires minimum CloudX SDK version be 4.3.0.
## \[7.6.0.3.0] - 2026-07-28
* Release for the Pangle SDK 7.6.x line, for apps pinned to that network SDK.
* Certified with Pangle SDK 7.6.0.3.
* Supports Banner, MREC, Interstitial, Rewarded, and App Open ad formats.
* Sets GDPR consent on the Pangle SDK explicitly; Pangle SDK versions before 7.9.1 do not read the IAB TCF string automatically.
* Requires minimum CloudX SDK version be 4.3.0.
## \[7.2.0.3.0] - 2026-08-17
* Release for the Pangle SDK 7.2.x line, for AppCentral apps pinned to that network SDK.
* Certified with Pangle SDK 7.2.0.3.
* Supports Banner, MREC, Interstitial, Rewarded, and App Open ad formats.
* Sets GDPR consent on the Pangle SDK explicitly; Pangle SDK versions before 7.9.1 do not read the IAB TCF string automatically.
* Requires minimum CloudX SDK version be 4.3.0.
---
# Integration
> TaurusX adapter for CloudX Android SDK. Supports Banner, MREC, Interstitial, Rewarded, and Native.
Contact your CloudX account manager to confirm availability and complete publisher onboarding.
## Install
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:adapter-taurusx:1.19.1.1")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:adapter-taurusx:1.19.1.1'
}
```
## Maven Repository
TaurusX artifacts are hosted on TaurusX's Maven repository. Add it alongside `google()` and `mavenCentral()`:
```kotlin Kotlin DSL
maven {
url = uri("https://artifact.taurusx.com/artifactory/taurusx-sdk/")
}
```
```groovy Groovy
maven { url 'https://artifact.taurusx.com/artifactory/taurusx-sdk/' }
```
## Native Ads
The adapter does not expose the main image asset directly: `ad.nativeAd.mainImage` is not populated. Media content — image or video — renders through the adapter-provided media view, which CloudX attaches to the container bound with `setMediaContentViewGroupId(...)`. Bind a visible media container as described in [Native ads](/en/android/ad-formats/native).
## Compatibility
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | -------------------- | ------------------ |
| `1.19.1.1` | TaurusX SDK `1.19.1` | `4.2.0` |
| `1.19.1.0` | TaurusX SDK `1.19.1` | `4.2.0` |
| `1.18.7.0` | TaurusX SDK `1.18.7` | `4.2.0` |
| `1.18.6.0` | TaurusX SDK `1.18.6` | `4.2.0` |
| `1.18.4.0` | TaurusX SDK `1.18.4` | `4.2.0` |
| `1.18.3.0` | TaurusX SDK `1.18.3` | `4.2.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes TaurusX demand. No manual adapter registration is required.
---
# Changelog
> TaurusX adapter release history for CloudX Android SDK
## \[1.19.1.1] - 2026-10-13
* Adds support for a server-configured setting that fires `onUserRewarded` as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudX SDK 4.10.0 or later; with older CloudX SDK versions the callback still fires at close.
* Reduces memory use for native ad icons: icons are decoded at no more than 512 px on the longest edge, and icons larger than 5 MiB are skipped while the ad still loads.
* Requires minimum CloudX SDK version be 4.2.0.
## \[1.19.1.0] - 2026-09-29
* Certified with TaurusX SDK 1.19.1.
* Requires minimum CloudX SDK version be 4.2.0.
## \[1.18.7.0] - 2026-09-28
* Certified with TaurusX SDK 1.18.7.
* Requires minimum CloudX SDK version be 4.2.0.
## \[1.18.6.0] - 2026-09-28
* Certified with TaurusX SDK 1.18.6.
* Requires minimum CloudX SDK version be 4.2.0.
## \[1.18.4.0] - 2026-09-28
* Certified with TaurusX SDK 1.18.4.
* Requires minimum CloudX SDK version be 4.2.0.
## \[1.18.3.0] - 2026-07-07
* Initial release.
* Certified with TaurusX SDK 1.18.3.
* Supports Banner, MREC, Interstitial, Rewarded, and Native ad formats.
* Reports supported ad formats to CloudX so server-provisioned configuration can match demand only to compatible placements.
* Requires minimum CloudX SDK version be 4.2.0.
---
# Integration
> Unity Ads adapter for CloudX Android SDK. Supports Banner, MREC, Interstitial, and Rewarded.
For CloudX dashboard mapping and Unity Ads-side ID setup, see the [Unity Ads bidder guide](/en/networks/unity-ads). This page covers Android-specific integration details only.
## Install
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:adapter-unityads:4.21.0.2")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:adapter-unityads:4.21.0.2'
}
```
## Compatibility
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | ---------------------- | ------------------ |
| `4.21.0.2` | Unity Ads SDK `4.21.0` | `4.2.0` |
| `4.21.0.1` | Unity Ads SDK `4.21.0` | `4.2.0` |
| `4.21.0.0` | Unity Ads SDK `4.21.0` | `4.2.0` |
| `4.20.1.0` | Unity Ads SDK `4.20.1` | `4.2.0` |
| `4.20.0.0` | Unity Ads SDK `4.20.0` | `4.2.0` |
| `4.19.0.2` | Unity Ads SDK `4.19.0` | `4.2.0` |
| `4.19.0.1` | Unity Ads SDK `4.19.0` | `4.2.0` |
| `4.19.0.0` | Unity Ads SDK `4.19.0` | `4.0.0` |
| `4.17.0.0` | Unity Ads SDK `4.17.0` | `4.0.0` |
| `4.16.6.0` | Unity Ads SDK `4.16.6` | `4.0.0` |
| `4.16.5.1` | Unity Ads SDK `4.16.5` | `4.0.0` |
| `4.16.5.0` | Unity Ads SDK `4.16.5` | `4.0.0` |
| `4.16.1.0` | Unity Ads SDK `4.16.1` | `4.0.0` |
| `4.15.0.0` | Unity Ads SDK `4.15.0` | `4.2.0` |
| `4.12.5.1` | Unity Ads SDK `4.12.5` | `4.0.0` |
| `4.12.5.0` | Unity Ads SDK `4.12.5` | `4.0.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes Unity Ads demand. No manual adapter registration is required.
## Known issues
### Unity Ads 4.15 Banner and MREC context requirement
**Affected:** Adapter `4.15.0.0` Banner and MREC integrations that use an application context, including the React Native imperative Banner and MREC APIs.
**Symptom:** The ad fails to load when an Android Activity cannot be resolved.
**Workaround:** Native Android apps must create Banner and MREC ads from an Activity-backed UI context. Use adapter `4.19.0.2` when Unity Ads 4.19 compatibility is acceptable. Interstitial and Rewarded are unaffected.
---
# Changelog
> Unity Ads adapter release history for CloudX Android SDK
## \[4.21.0.2] - 2026-10-15
* Fixed Unity Ads initialization never completing (or failing) in apps where Unity's androidx.startup initializer is removed from the manifest.
* Requires minimum CloudX SDK version be 4.2.0.
## \[4.21.0.1] - 2026-10-13
* Adds support for a server-configured setting that fires `onUserRewarded` as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudX SDK 4.10.0 or later; with older CloudX SDK versions the callback still fires at close.
* Requires minimum CloudX SDK version be 4.2.0.
## \[4.21.0.0] - 2026-10-06
* Certified with Unity Ads SDK 4.21.0.
* Requires minimum CloudX SDK version be 4.2.0.
## \[4.20.1.0] - 2026-09-15
* Certified with Unity Ads SDK 4.20.1.
* Requires minimum CloudX SDK version be 4.2.0.
## \[4.20.0.0] - 2026-09-15
* Certified with Unity Ads SDK 4.20.0.
* Requires minimum CloudX SDK version be 4.2.0.
## \[4.19.0.2] - 2026-10-15
* Fixed Unity Ads initialization never completing (or failing) in apps where Unity's androidx.startup initializer is removed from the manifest.
* Requires minimum CloudX SDK version be 4.2.0.
## \[4.19.0.1] - 2026-07-07
* Reports supported ad formats to CloudX so server-provisioned configuration can match demand only to compatible placements.
* Requires minimum CloudX SDK version be 4.2.0.
## \[4.19.0.0] - 2026-06-29
* Certified with Unity Ads SDK 4.19.0.
* Requires minimum CloudX SDK version be 4.0.0.
## \[4.17.0.0] - 2026-04-27
* Certified with Unity Ads SDK 4.17.0.
* Requires minimum CloudX SDK version be 4.0.0.
## \[4.16.6.0] - 2026-07-22
* Certified with Unity Ads SDK 4.16.6.
* Keeps bid token collection scoped to Banner, MREC, Interstitial, and Rewarded formats supported by Unity Ads SDK 4.16.6.
* Requires minimum CloudX SDK version be 4.0.0.
## \[4.16.5.1] - 2026-04-30
* Requires minimum CloudX SDK version be 4.0.0.
* Fixes bidding load options for Unity Ads SDK 4.16.5 banner, MREC, interstitial, and rewarded ads.
## \[4.16.5.0] - 2026-04-30
* Certified with Unity Ads SDK 4.16.5.
* Requires minimum CloudX SDK version be 4.0.0.
## \[4.16.1.0] - 2026-07-15
* Certified with Unity Ads SDK 4.16.1.
* Keeps bid token collection scoped to Banner, MREC, Interstitial, and Rewarded formats supported by Unity Ads SDK 4.16.1.
* Requires minimum CloudX SDK version be 4.0.0.
## \[4.15.0.0] - 2026-08-17
* Adds a compatibility adapter for publishers that must remain on Unity Ads SDK 4.15.0.
* Requires CloudX SDK 4.2.0 or later.
* Banner and MREC require an Activity-backed Android context. React Native imperative Banner and MREC APIs are unsupported; Interstitial and Rewarded are unaffected.
## \[4.12.5.1] - 2026-04-30
* Requires minimum CloudX SDK version be 4.0.0.
* Fixes bidding load options for Unity Ads SDK 4.12.5 banner, MREC, interstitial, and rewarded ads.
## \[4.12.5.0] - 2026-04-30
* Certified with Unity Ads SDK 4.12.5.
* Requires minimum CloudX SDK version be 4.0.0.
* Bid token requested without per-format hint (Unity Ads 4.12.5 has no `TokenConfiguration` API).
* Banner adapter resolves the foreground Activity via `ActivityLifecycleCallbacks`; banner load fails if no Activity is in foreground when load is invoked.
---
# Integration
> Verve adapter for CloudX Android SDK. Supports Banner, MREC, Interstitial, Rewarded, Native, Native Banner, and Native MREC.
Contact your CloudX account manager to confirm availability and complete publisher onboarding.
## Install
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:adapter-verve:3.9.3.0")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:adapter-verve:3.9.3.0'
}
```
## Maven Repository
Verve HyBid artifacts are hosted on Verve's Maven repository. Add it alongside `google()` and `mavenCentral()`:
```kotlin Kotlin DSL
maven {
url = uri("https://verve.jfrog.io/artifactory/verve-gradle-release")
}
```
```groovy Groovy
maven { url 'https://verve.jfrog.io/artifactory/verve-gradle-release' }
```
## Compatibility
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | ----------------------- | ------------------ |
| `3.9.3.0` | Verve HyBid SDK `3.9.3` | `4.2.0` |
| `3.9.2.1` | Verve HyBid SDK `3.9.2` | `4.2.0` |
| `3.9.2.0` | Verve HyBid SDK `3.9.2` | `4.2.0` |
| `3.9.1.0` | Verve HyBid SDK `3.9.1` | `4.2.0` |
| `3.9.0.1` | Verve HyBid SDK `3.9.0` | `4.2.0` |
| `3.9.0.0` | Verve HyBid SDK `3.9.0` | `4.2.0` |
| `3.8.2.0` | Verve HyBid SDK `3.8.2` | `4.2.0` |
| `3.8.1.1` | Verve HyBid SDK `3.8.1` | `4.2.0` |
| `3.8.1.0` | Verve HyBid SDK `3.8.1` | `4.1.0` |
| `3.7.1.0` | Verve HyBid SDK `3.7.1` | `4.2.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes Verve demand. No manual adapter registration is required.
---
# Changelog
> Verve adapter release history for CloudX Android SDK
## \[3.9.3.0] - 2026-10-15
* Updated to HyBid SDK 3.9.3, which no longer declares the `ACCESS_ADSERVICES_TOPICS` permission.
* Requires minimum CloudX SDK version be 4.2.0.
## \[3.9.2.1] - 2026-10-13
* Adds support for a server-configured setting that fires `onUserRewarded` as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudX SDK 4.10.0 or later; with older CloudX SDK versions the callback still fires at close.
* Requires minimum CloudX SDK version be 4.2.0.
## \[3.9.2.0] - 2026-09-21
* Certified with Verve HyBid SDK 3.9.2.
* Requires minimum CloudX SDK version be 4.2.0.
## \[3.9.1.0] - 2026-09-21
* Certified with Verve HyBid SDK 3.9.1.
* Requires minimum CloudX SDK version be 4.2.0.
## \[3.9.0.1] - 2026-07-10
* Reports Verve HyBid interstitial and rewarded show/render failures as display failures so CloudX fullscreen listeners receive a terminal display-failed callback.
* Requires minimum CloudX SDK version be 4.2.0.
## \[3.9.0.0] - 2026-07-08
* Certified with Verve HyBid SDK 3.9.0.
* Requires minimum CloudX SDK version be 4.2.0.
## \[3.8.2.0] - 2026-07-08
* Certified with Verve HyBid SDK 3.8.2.
* Requires minimum CloudX SDK version be 4.2.0.
## \[3.8.1.1] - 2026-07-07
* Reports supported ad formats to CloudX so server-provisioned configuration can match demand only to compatible placements.
* Adds support for server-configured rewarded callbacks when a rewarded placement should always grant the reward after close.
* Requires minimum CloudX SDK version be 4.2.0.
## \[3.8.1.0] - 2026-05-26
* Initial release.
* Certified with Verve HyBid SDK 3.8.1.
* Supports Banner, MREC, Interstitial, Rewarded, and Native ad formats.
* Requires minimum CloudX SDK version be 4.1.0.
## \[3.7.1.0] - 2026-08-29
* Release for the Verve HyBid SDK 3.7.x line, for apps pinned to that network SDK below 3.8.1.
* Certified with Verve HyBid SDK 3.7.1.
* Supports Banner, MREC, Interstitial, Rewarded, and Native ad formats.
* Interstitial and rewarded display failures are reported when the app resolves Verve HyBid SDK 3.9.0 or newer, which the network only began reporting in that version.
* Requires minimum CloudX SDK version be 4.2.0.
---
# Integration
> Vungle adapter for CloudX Android SDK. Supports Banner, MREC, Interstitial, Rewarded, App Open, Native, Native Banner, and Native MREC.
For CloudX dashboard mapping and Vungle-side ID setup, see the [Vungle bidder guide](/en/networks/vungle). This page covers Android-specific integration details only.
## Install
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:adapter-vungle:7.7.9.1")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:adapter-vungle:7.7.9.1'
}
```
## Native Ads
The adapter does not expose the main image asset directly: `ad.nativeAd.mainImage` is not populated. Media content — image or video — renders through the adapter-provided media view, which CloudX attaches to the container bound with `setMediaContentViewGroupId(...)`. Bind a visible media container as described in [Native ads](/en/android/ad-formats/native).
## Compatibility
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | ------------------ | ------------------ |
| `7.7.9.1` | Vungle SDK `7.7.9` | `4.2.0` |
| `7.7.9.0` | Vungle SDK `7.7.9` | `4.2.0` |
| `7.7.8.0` | Vungle SDK `7.7.8` | `4.2.0` |
| `7.7.7.0` | Vungle SDK `7.7.7` | `4.2.0` |
| `7.7.6.0` | Vungle SDK `7.7.6` | `4.2.0` |
| `7.7.5.0` | Vungle SDK `7.7.5` | `4.2.0` |
| `7.7.4.1` | Vungle SDK `7.7.4` | `4.2.0` |
| `7.7.4.0` | Vungle SDK `7.7.4` | `4.0.0` |
| `7.7.3.0` | Vungle SDK `7.7.3` | `4.0.0` |
| `7.7.1.0` | Vungle SDK `7.7.1` | `4.0.0` |
| `7.7.0.0` | Vungle SDK `7.7.0` | `4.2.0` |
| `7.6.3.0` | Vungle SDK `7.6.3` | `4.0.0` |
| `7.6.1.0` | Vungle SDK `7.6.1` | `4.2.0` |
| `7.6.0.0` | Vungle SDK `7.6.0` | `4.0.0` |
| `7.5.0.0` | Vungle SDK `7.5.0` | `4.2.0` |
| `7.4.2.0` | Vungle SDK `7.4.2` | `4.0.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes Vungle demand. No manual adapter registration is required.
## Known issues
Live issues only. Resolved entries move to the adapter changelog.
### Native Banner and Native MREC are temporarily disabled
**Affected:** Vungle Native Banner and Native MREC creatives served in Banner and MREC slots on Android. Standalone Native is unaffected.
**Symptom:** Vungle does not serve Native Banner or Native MREC demand.
**Status:** temporarily disabled on the CloudX side; no publisher action required. The [Native ad format matrix](/en/ad-formats/native) reflects current availability.
---
# Changelog
> Vungle adapter release history for CloudX Android SDK
## \[7.7.9.1] - 2026-10-13
* Adds support for a server-configured setting that fires `onUserRewarded` as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudX SDK 4.10.0 or later; with older CloudX SDK versions the callback still fires at close.
* Requires minimum CloudX SDK version be 4.2.0.
## \[7.7.9.0] - 2026-10-06
* Certified with Vungle SDK 7.7.9.
* Requires minimum CloudX SDK version be 4.2.0.
## \[7.7.8.0] - 2026-09-21
* Certified with Vungle SDK 7.7.8.
* Requires minimum CloudX SDK version be 4.2.0.
## \[7.7.7.0] - 2026-07-24
* Certified with Vungle SDK 7.7.7.
* Requires minimum CloudX SDK version be 4.2.0.
## \[7.7.6.0] - 2026-07-21
* Certified with Vungle SDK 7.7.6.
* Requires minimum CloudX SDK version be 4.2.0.
## \[7.7.5.0] - 2026-07-08
* Certified with Vungle SDK 7.7.5.
* Requires minimum CloudX SDK version be 4.2.0.
## \[7.7.4.1] - 2026-07-07
* Adds App Open support.
* Improves fullscreen show compatibility by passing Vungle the application context when showing interstitial-backed placements.
* Reports supported ad formats to CloudX so server-provisioned configuration can match demand only to compatible placements.
* Requires minimum CloudX SDK version be 4.2.0.
## \[7.7.4.0] - 2026-06-19
* Certified with Vungle SDK 7.7.4.
* Adds full support for Vungle Native, Native Banner, and Native MREC ads.
* Requires minimum CloudX SDK version be 4.0.0.
## \[7.7.3.0] - 2026-04-28
* Certified with Vungle SDK 7.7.3.
* Registers CloudX adapter integration metadata with the Vungle SDK.
* Requires minimum CloudX SDK version be 4.0.0.
## \[7.7.1.0] - 2026-04-27
* Certified with Vungle SDK 7.7.1.
* Requires minimum CloudX SDK version be 4.0.0.
## \[7.7.0.0] - 2026-08-12
* Release for the Vungle SDK 7.7.0 line, for apps pinned to that network SDK.
* Certified with Vungle SDK 7.7.0.
* Vungle SDK 7.7.0 does not support mediation integration identification, so Vungle-side attribution of CloudX traffic is unavailable. Use independent ad units, or prefer a newer adapter version.
* Requires minimum CloudX SDK version be 4.2.0.
## \[7.6.3.0] - 2026-04-28
* Certified with Vungle SDK 7.6.3.
* Requires minimum CloudX SDK version be 4.0.0.
## \[7.6.1.0] - 2026-09-11
* Release for the Vungle SDK 7.6.1 line, for apps pinned to that network SDK.
* Certified with Vungle SDK 7.6.1.
* Vungle SDK 7.6.1 does not support mediation integration identification, so Vungle-side attribution of CloudX traffic is unavailable. Use independent ad units, or prefer a newer adapter version.
* Requires minimum CloudX SDK version be 4.2.0.
## \[7.6.0.0] - 2026-07-15
* Certified with Vungle SDK 7.6.0.
* Adds support for server-configured rewarded callbacks when a rewarded placement should always grant the reward after close.
* Requires minimum CloudX SDK version be 4.0.0.
## \[7.5.0.0] - 2026-08-17
* Adds a compatibility adapter for publishers that must remain on Vungle SDK 7.5.0. Supports Banner, MREC, Interstitial, Rewarded, App Open, Native, Native Banner, and Native MREC.
* Requires CloudX SDK 4.2.0 or later.
## \[7.4.2.0] - 2026-04-27
* Certified with Vungle SDK 7.4.2.
* Requires minimum CloudX SDK version be 4.0.0.
---
# Integration
> Adjust ad-revenue connector for CloudX Android SDK. Forwards CloudX ad revenue to Adjust with no publisher glue code.
Connectors pass data between the CloudX SDK and another product. The Adjust connector forwards CloudX ad revenue to your mobile measurement partner (MMP) so ad revenue appears alongside your other Adjust data and feeds ROAS measurement. Add the dependency and revenue reporting is automatic — the CloudX SDK discovers the connector at initialization and attaches it to the same global ad-revenue listener list that `CloudX.addAdRevenueListener()` uses.
## Install
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:connector-adjust:5.0.0.0")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:connector-adjust:5.0.0.0'
}
```
No code changes are required. The connector never initializes Adjust; it uses your existing Adjust setup and app token.
## Requirements
* CloudX Android SDK `4.3.0` or newer.
* Your app already integrates and initializes the Adjust SDK with your own app token. The connector depends on the Adjust SDK at compile time only, so your app must declare `com.adjust.sdk:adjust-android` itself and your runtime version is the one used.
* A supported Adjust SDK version: `5.0.0` or newer (the v5 line).
## What gets reported
The connector calls Adjust's `trackAdRevenue` once per CloudX-won impression, across all CloudX ad formats — banner, MREC, interstitial, rewarded, native, and app open. Each call carries the winning ad network, the CloudX revenue estimate for the impression in USD, and callback parameters with the CloudX ad unit, format (`ad_type`), and placement.
> **Note:** Revenue is reported on Adjust's generic `publisher_sdk` source — Adjust has confirmed this is the supported path until a dedicated CloudX source ships in a future Adjust release. Revenue lands in Adjust and feeds ROAS identically; it just appears under the generic publisher source rather than a named CloudX one. When the named source ships, historical revenue stays under `publisher_sdk`, so a report spanning the cutover must union both sources.
> **Warning:** If your app already calls `Adjust.trackAdRevenue` from a CloudX ad-revenue callback, remove that code when you add this connector. Otherwise every impression is reported to Adjust twice.
## Finding CloudX revenue in the Adjust dashboard
Adjust doesn't have a dedicated CloudX ad revenue source yet, so there's nothing CloudX-specific to configure in the Adjust dashboard. Once the connector is in your build, CloudX ad revenue arrives on its own.
To see it, open Adjust's ad revenue metrics with the Ad Revenue Sources filter left empty, which shows all sources. For now, CloudX revenue is included in those totals under a generic source rather than its own CloudX label.
Impressions won in the CloudX auction by AppLovin MAX or the Google waterfall aren't forwarded, because those SDKs already report that revenue to Adjust.
> **Warning:** Adjust's partner page for CloudX (**Partners → CloudX**) is the demand side: it is for attributing installs from user-acquisition campaigns you buy on CloudX. The tracking link there, and the related "Dynamic ad network data access" setting in your app's Data management tab, have no effect on ad revenue reporting. Setting them up will not make CloudX ad revenue appear, and you do not need either unless you run UA on CloudX.
If Adjust shows no ad revenue at all, from CloudX or any other source, check these before investigating the connector:
* The connector dependency is in the build you are measuring, not only in your local project.
* Ad revenue reporting is enabled on your Adjust account. Your Adjust account manager can confirm this.
* If the build you're testing initializes Adjust in sandbox mode, switch Datascape to sandbox data to see it.
## Compatibility
| Connector version | Adjust SDK | Minimum CloudX SDK |
| ----------------- | ----------------------------- | ------------------ |
| `5.0.0.0` | Adjust Android SDK `>= 5.0.0` | `4.3.0` |
## Reporting revenue yourself instead
If you would rather control the reporting call, skip the connector and subscribe to CloudX ad revenue directly:
```kotlin
CloudX.addAdRevenueListener(object : CloudXAdRevenueListener {
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
// Forward to Adjust, or any other destination, using your own mapping.
}
})
```
`CloudX.addAdRevenueListener()` is the stable public surface. Use either the connector or your own listener — not both.
---
# Changelog
> Adjust connector release history for CloudX Android SDK
## \[5.0.0.0] - 2026-08-04
* Initial release of the CloudX Adjust ad-revenue connector. Forwards CloudX ad revenue to Adjust via `trackAdRevenue` with no publisher glue code — add the dependency and revenue reporting is automatic, firing once per CloudX-won impression across all ad formats.
* Reports on Adjust's generic `publisher_sdk` source until a dedicated CloudX source ships in a future Adjust release; revenue feeds ROAS identically either way.
* Compiles against Adjust SDK 5.0.0; supports Adjust SDK 5.0.0 or newer.
* Requires minimum CloudX SDK version be 4.3.0.
---
# Integration
> AppsFlyer ad-revenue connector for CloudX Android SDK. Forwards CloudX ad revenue to AppsFlyer with no publisher glue code.
Connectors pass data between the CloudX SDK and another product. The AppsFlyer connector forwards CloudX ad revenue to your mobile measurement partner (MMP) so ad revenue appears alongside your other AppsFlyer data. Add the dependency and revenue reporting is automatic — the CloudX SDK discovers the connector at initialization and attaches it to the same global ad-revenue listener list that `CloudX.addAdRevenueListener()` uses.
## Install
```kotlin Kotlin DSL
dependencies {
implementation("io.cloudx:connector-appsflyer:6.15.0.0")
}
```
```groovy Groovy
dependencies {
implementation 'io.cloudx:connector-appsflyer:6.15.0.0'
}
```
No code changes are required. The connector never initializes AppsFlyer; it uses your existing AppsFlyer setup and dev key.
## Requirements
* CloudX Android SDK `4.3.0` or newer.
* Your app already integrates and initializes the AppsFlyer SDK with your own dev key. The connector depends on the AppsFlyer SDK at compile time only, so your app must declare `com.appsflyer:af-android-sdk` itself and your runtime version is the one used.
* A supported AppsFlyer SDK version: `6.15.0` up to (excluding) `7.0.0`.
## What gets reported
The connector calls AppsFlyer's `logAdRevenue` once per CloudX-won impression, across all CloudX ad formats — banner, MREC, interstitial, rewarded, native, and app open. Each call carries the winning ad network, the CloudX revenue estimate for the impression in USD, and the CloudX ad unit, format, and placement.
> **Warning:** If your app already calls `AppsFlyerLib.logAdRevenue` from a CloudX ad-revenue callback, remove that code when you add this connector. Otherwise every impression is reported to AppsFlyer twice.
## Compatibility
| Connector version | AppsFlyer SDK | Minimum CloudX SDK |
| ----------------- | --------------------------------------- | ------------------ |
| `6.15.0.0` | AppsFlyer Android SDK `[6.15.0, 7.0.0)` | `4.3.0` |
## Reporting revenue yourself instead
If you would rather control the reporting call, skip the connector and subscribe to CloudX ad revenue directly:
```kotlin
CloudX.addAdRevenueListener(object : CloudXAdRevenueListener {
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
// Forward to AppsFlyer, or any other destination, using your own mapping.
}
})
```
`CloudX.addAdRevenueListener()` is the stable public surface. Use either the connector or your own listener — not both.
---
# Changelog
> AppsFlyer connector release history for CloudX Android SDK
## \[6.15.0.0] - 2026-07-29
* Initial release of the CloudX AppsFlyer ad-revenue connector. Forwards CloudX ad revenue to AppsFlyer via `logAdRevenue` with no publisher glue code — add the dependency and revenue reporting is automatic, firing once per CloudX-won impression across all ad formats.
* Compiles against AppsFlyer SDK 6.15.0; supports AppsFlyer SDK versions 6.15.0 up to (excluding) 7.0.0.
* Requires minimum CloudX SDK version be 4.3.0.
---
# Integration
> Singular ad-revenue connector for CloudX Android SDK. Forwards CloudX ad revenue to Singular with no publisher glue code.
Connectors pass data between the CloudX SDK and another product. The Singular connector forwards CloudX ad revenue to your mobile measurement partner (MMP) so ad revenue appears alongside your other Singular data and feeds ROAS measurement. Add the dependency and revenue reporting is automatic — the connector registers itself at process start, joining the same global ad-revenue listener list that `CloudX.addAdRevenueListener()` uses. Its manifest is merged into your app's, so there is nothing to call and no initialization order to get right.
## Install
```kotlin Kotlin DSL
repositories {
maven(url = uri("https://maven.singular.net")) // you already have this
}
dependencies {
implementation("io.cloudx:connector-singular:12.0.0.0")
}
```
```groovy Groovy
repositories {
maven { url 'https://maven.singular.net' } // you already have this
}
dependencies {
implementation 'io.cloudx:connector-singular:12.0.0.0'
}
```
Singular publishes its Android SDK to its own Maven repository rather than Maven Central, which is why the repository line is part of the snippet. If your app already integrates Singular you will have it; declare it once, wherever your build resolves repositories.
No code changes are required. The connector never initializes Singular; it uses your existing Singular setup and SDK key.
## Requirements
* CloudX Android SDK `4.3.0` or newer.
* Your app already integrates and initializes the Singular SDK with your own SDK key and secret. The connector depends on the Singular SDK at compile time only, so your app must declare both `com.singular.sdk:singular_sdk` and the Singular Maven repository (`https://maven.singular.net`) itself, and your runtime version is the one used.
* A supported Singular SDK version: `12.0.0` or newer. The connector compiles against `12.0.0`, and the ad-revenue API it calls is verified unchanged through `12.16.0`.
## What gets reported
The connector calls Singular's `Singular.adRevenue` once per CloudX-won impression, across all CloudX ad formats — banner, MREC, interstitial, rewarded, native, and app open. Each call carries:
| Singular field | Value |
| ----------------- | --------------------------------------------------------------------- |
| Ad platform | `CloudX` |
| Currency | `USD` |
| Revenue | The CloudX revenue estimate for the impression |
| Revenue precision | `estimated` |
| Network name | The winning ad network, or `CloudX` when the network is not reported |
| Ad unit id | The CloudX ad unit id |
| Ad type | `BANNER`, `MREC`, `INTERSTITIAL`, `REWARDED`, `NATIVE`, or `APP_OPEN` |
| Ad placement name | The CloudX placement |
| Ad unit name | The network placement |
> **Note:** Revenue appears in Singular under the ad platform `CloudX`. No Singular-side configuration is needed beyond having ad-revenue attribution enabled on your Singular account.
> **Warning:** If your app already calls `Singular.adRevenue` from a CloudX ad-revenue callback, remove that code when you add this connector. Otherwise every impression is reported to Singular twice.
## Compatibility
| Connector version | Singular SDK | Minimum CloudX SDK |
| ----------------- | -------------------------------- | ------------------ |
| `12.0.0.0` | Singular Android SDK `>= 12.0.0` | `4.3.0` |
## Reporting revenue yourself instead
If you would rather control the reporting call, skip the connector and subscribe to CloudX ad revenue directly:
```kotlin
CloudX.addAdRevenueListener(object : CloudXAdRevenueListener {
override fun onAdRevenuePaid(cloudXAd: CloudXAd) {
// Forward to Singular, or any other destination, using your own mapping.
}
})
```
`CloudX.addAdRevenueListener()` is the stable public surface. Use either the connector or your own listener — not both.
---
# Changelog
> Singular connector release history for CloudX Android SDK
## \[12.0.0.0] - 2026-08-27
* Initial release of the CloudX Singular ad-revenue connector. Forwards CloudX ad revenue to Singular via `Singular.adRevenue` with no publisher glue code — add the dependency and revenue reporting is automatic, firing once per CloudX-won impression across all ad formats.
* Revenue is reported under the ad platform `CloudX` in USD, with the winning network, ad unit id, ad type, placement name, and network placement attached.
* Compiles against Singular SDK 12.0.0; supports Singular SDK 12.0.0 or newer.
* Requires minimum CloudX SDK version be 4.3.0.
---
# Overview
> Overview of CloudX iOS SDK setup and core features
Requires iOS 13.0+, Xcode 16.0+, and Swift 6.0+.
> **Info:** Prefer AI-assisted setup? Point your coding agent at the CloudX agent package — see [AI-assisted integration](/en/ai-integration).
## Installation
### Swift Package Manager
See [Swift Package Manager installation](/en/ios/swift-package-manager) for supported packages, exact adapter versions, and project requirements.
### CocoaPods
> **Warning:** Including Digital Turbine adapter 8.4.10.1 requires an app deployment target of iOS 15.0+ and Xcode 26.1+. These higher requirements apply only when you include this adapter.
```ruby Podfile
platform :ios, '15.0'
target 'YourApp' do
use_frameworks!
# Core SDK
pod 'CloudXCore', '~> 3.11.0'
# Adapters for ad networks (add as needed).
# Each adapter versions independently as .
# and is built against that exact network SDK version.
pod 'CloudXBigoAdapter', '~> 6.1.0.0' # BigoADS 6.1.0
pod 'CloudXMetaAdapter', '~> 6.22.0.1' # FBAudienceNetwork 6.22.0
pod 'CloudXVungleAdapter', '~> 7.7.6.1' # VungleAds 7.7.6
pod 'CloudXInMobiAdapter', '~> 11.5.0.0' # InMobiSDK 11.5.0
pod 'CloudXMintegralAdapter', '~> 8.1.7.0' # MintegralAdSDK 8.1.7
pod 'CloudXUnityAdsAdapter', '~> 4.20.1.1' # UnityAds 4.20.1
pod 'CloudXMagniteAdapterV2', '~> 1.0.4.2' # MagniteSDK 1.0.4
pod 'CloudXMobileFuseAdapter', '~> 1.12.0.0' # MobileFuseSDK 1.12.0
pod 'CloudXMolocoAdapter', '~> 4.11.1.1' # MolocoSDKiOS 4.11.1
pod 'CloudXVerveAdapter', '~> 3.9.0.1' # HyBid 3.9.0
pod 'CloudXDigitalTurbineAdapter', '~> 8.4.10.1' # Fyber Marketplace SDK 8.4.10
pod 'CloudXGoogleWaterfallAdapter', '~> 13.9.0.0' # Google Mobile Ads SDK 13.9.0
pod 'CloudXPangleAdapter', '~> 8.3.0.6.1' # Ads-Global (Pangle / ByteDance) 8.3.0.6
pod 'CloudXTaurusXAdapter', '~> 1.18.2.1' # TaurusxAdsSDK 1.18.2
end
```
```bash
pod install --repo-update
```
## Initialization
```objc Objective-C
#import
CLXInitializationConfiguration *config =
[CLXInitializationConfiguration configurationWithAppKey:@"your-app-key-here"];
[[CloudXCore shared] initializeWithConfiguration:config completion:^(CLXSdkConfiguration *sdkConfig, CLXError * _Nullable error) {
if (sdkConfig) {
NSLog(@"CloudX SDK initialized successfully");
} else {
NSLog(@"Failed to initialize CloudX SDK: %@", error.localizedDescription);
}
}];
```
```swift Swift
import CloudXCore
let config = CLXInitializationConfiguration.configuration(appKey: "your-app-key-here", builderBlock: nil)
CloudXCore.shared.initialize(with: config) { sdkConfig, error in
if sdkConfig != nil {
print("CloudX SDK initialized successfully")
} else {
print("Failed to initialize CloudX SDK: \(error?.localizedDescription ?? "Unknown error")")
}
}
```
### App Startup Warm-Up
When CloudXCore is linked, the SDK prepares some of its work at app launch, before you call `initialize`. This is a performance optimization: it lets the SDK initialize and load its first ad faster, without slowing your app's launch.
The warm-up keeps work off your app's launch path and waits until your app is active before doing anything more than lightweight background setup. Launches that never become active, such as background fetches or silent pushes, do only that lightweight setup. App extensions skip the warm-up entirely.
This does not initialize the SDK. You still call `initialize`, and nothing about when or how you call it changes. The warm-up is best-effort: if it has not finished, the SDK does the work when it needs it, and it cannot cause your app's launch to fail.
To turn the warm-up off, add this key to your app's Info.plist:
```xml Info.plist
CloudXStartupWarmDisabled
```
Behavior is the same either way. With the key set, the SDK does this preparation when you call `initialize`, so your first initialization and first ad load are noticeably slower.
## Ad Formats
CloudX supports banner, MREC, interstitial, rewarded, native, and app open ad integrations. Use the format-specific guides for implementation details:
* [Banner & MREC ads](/en/ios/ad-formats/banner-mrec)
Create fixed-size display placements with optional refresh control.
* [Interstitial ads](/en/ios/ad-formats/interstitial)
Load and show full-screen interstitial placements.
* [Native ads](/en/ios/ad-formats/native)
Render native creatives in custom app layouts.
* [Rewarded ads](/en/ios/ad-formats/rewarded)
Reward users after completed rewarded ad views.
* [App Open ads](/en/ios/ad-formats/app-open)
Full-screen placements for app launch and foreground moments.
### Ad Information (CLXAd)
The `CLXAd` object is passed to delegate callbacks and contains information about the loaded/displayed ad:
| Property | Type | Description |
| -------------------- | -------------------------------------- | --------------------------------------------------------------------------------- |
| `adFormat` | `CLXAdFormat` | Ad format (banner, MREC, interstitial, rewarded, native) |
| `adUnitId` | `NSString?` | The ad unit ID |
| `adUnitName` | `NSString?` | The ad unit name |
| `networkName` | `NSString?` | Name of the winning ad network |
| `networkPlacement` | `NSString?` | Network-specific placement ID |
| `placement` | `NSString?` | Custom placement set via `placement` property |
| `revenue` | `NSNumber?` | Impression-level revenue in USD |
| `revenuePrecision` | `NSString?` | Revenue precision, when provided by the winning network |
| `creativeIdentifier` | `NSString?` | Creative identifier for creative-level issue reporting |
| `requestLatency` | `NSTimeInterval` | Time in seconds from ad request to ad response |
| `nativeAd` | `CLXNativeAd?` | Native ad asset container for native ads; `nil` for non-native formats |
| `adValues` | `NSDictionary` | SDK-defined metadata for the loaded ad; values may be absent by format or network |
`creativeIdentifier` is `nil` when the demand source does not report a creative id. It is distinct from the `creativeId` you pass in [`CLXRevenueData`](#publisher-reported-revenue-data), which describes an impression won outside the CloudX ad flow.
```objc Objective-C
- (void)didLoadAd:(CLXAd *)ad {
NSLog(@"Ad format: %ld", (long)ad.adFormat);
NSLog(@"Network: %@", ad.networkName);
NSLog(@"Revenue: %@", ad.revenue);
}
```
```swift Swift
func didLoad(_ ad: CLXAd) {
print("Ad format: \(ad.adFormat)")
print("Network: \(ad.networkName ?? "unknown")")
print("Revenue: \(ad.revenue ?? 0)")
}
```
### Error Handling
All SDK errors are returned as `CLXError` objects in delegate callbacks:
| Property | Type | Description |
| ---------------------- | -------------- | -------------------------- |
| `code` | `CLXErrorCode` | Error category |
| `localizedDescription` | `NSString` | Human-readable description |
| `underlyingError` | `NSError?` | Optional underlying error |
#### Error Code Categories
| Range | Category | Common Codes |
| ------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| 0 | General | `CLXErrorCodeInternalError` |
| 100-199 | Network | `CLXErrorCodeNetworkError`, `CLXErrorCodeNetworkTimeout`, `CLXErrorCodeServerError`, `CLXErrorCodeNoConnection` |
| 200-299 | Initialization | `CLXErrorCodeNotInitialized`, `CLXErrorCodeSDKDisabled`, `CLXErrorCodeNoAdaptersFound`, `CLXErrorCodeInvalidAppKey` |
| 300-399 | Ad Loading | `CLXErrorCodeNoFill`, `CLXErrorCodeInvalidAdUnit`, `CLXErrorCodeAdsDisabled` |
| 400-499 | Display | `CLXErrorCodeAdNotReady`, `CLXErrorCodeAdAlreadyShowing` |
| 600-699 | Adapter | `CLXErrorCodeAdapterNoFill`, `CLXErrorCodeAdapterTimeout`, `CLXErrorCodeAdapterLoadTimeout`, `CLXErrorCodeAdapterInitializationError` |
## Advanced Features
### Debug Logging
```objc Objective-C
[CloudXCore setMinLogLevel:CLXLogLevelDebug]; // Enable debug logging
[CloudXCore setMinLogLevel:CLXLogLevelNone]; // Disable all logging
```
```swift Swift
CloudXCore.setMinLogLevel(.debug) // Enable debug logging
CloudXCore.setMinLogLevel(.none) // Disable all logging
```
**Log Levels:** `verbose` \< `debug` \< `info` \< `warn` \< `error` \< `none`
### Impression-Level Revenue Tracking
Set a `revenueDelegate` on any ad format to receive impression-level revenue (ILR) callbacks. The `CLXAd` object contains the revenue value in USD and the winning network name.
```objc Objective-C
self.bannerAd.revenueDelegate = self;
- (void)didPayRevenueForAd:(CLXAd *)ad {
NSLog(@"Revenue: %@ from %@", ad.revenue, ad.networkName);
}
```
```swift Swift
bannerAd?.revenueDelegate = self
func didPayRevenue(for ad: CLXAd) {
print("Revenue: \(ad.revenue ?? 0) from \(ad.networkName ?? "unknown")")
}
```
Works with all ad formats (banner, MREC, interstitial, rewarded, native).
### Sending Ad Revenue to an MMP
To forward CloudX ad revenue to a mobile measurement partner, add the matching connector instead of wiring a revenue delegate yourself: [Adjust](/en/ios/connectors/adjust/overview), [AppsFlyer](/en/ios/connectors/appsflyer/overview), or [Singular](/en/ios/connectors/singular/overview). Each one reports once per CloudX-won impression with no publisher glue code. Impressions won in the CloudX auction by AppLovin MAX or the Google waterfall aren't forwarded, because those SDKs already report that revenue to your MMP.
No CloudX-specific setup is needed in the MMP's dashboard, though ad revenue reporting must be enabled on your MMP account. For Adjust, revenue appears under a generic source rather than a CloudX label for now. See [Finding CloudX revenue in the Adjust dashboard](/en/ios/connectors/adjust/overview#finding-cloudx-revenue-in-the-adjust-dashboard).
### Publisher-Reported Revenue Data
If your app receives impression-level revenue callbacks or bid metadata from AdMob, InMobi, TopOn, or another mediation platform outside the CloudX ad flow, forward those events to CloudX after initialization:
| Field | Required | Description |
| ------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `platform` | Yes | `CLXRevenuePlatformAdMob`, `CLXRevenuePlatformInMobi`, `CLXRevenuePlatformTopOn`, or `CLXRevenuePlatformCustom(@"MyProvider")` in Objective-C; `.adMob`, `.inMobi`, `.topOn`, or `.custom("MyProvider")` in Swift |
| `revenue` | Yes | Revenue for one impression in the supplied currency, not CPM/eCPM |
| `adFormat` | Yes | Ad format string such as `banner`, `mrec`, `interstitial`, `rewarded`, `native`, or `app_open` |
| `currencyCode` | No | ISO 4217 currency code, if known |
| `precision` | No | `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 |
`reportRevenueData(_:)` returns `true` when the event is accepted into the CloudX revenue pipeline. It returns `false` if the SDK is not initialized, server-side revenue tracking is disabled, or the platform name is blank. Acceptance is not a delivery guarantee.
#### AdMob paid events
AdMob iOS `AdValue.value` is already in the supplied currency units, so pass it directly to CloudX. Do not divide it by `1_000_000.0`.
```objc Objective-C
static CLXRevenuePrecision *CLXRevenuePrecisionFromGAD(GADAdValuePrecision precision) {
switch (precision) {
case GADAdValuePrecisionPrecise: return CLXRevenuePrecision.exact;
case GADAdValuePrecisionEstimated: return CLXRevenuePrecision.estimated;
case GADAdValuePrecisionPublisherProvided: return CLXRevenuePrecision.publisherDefined;
case GADAdValuePrecisionUnknown: return CLXRevenuePrecision.undefined;
}
return CLXRevenuePrecision.undefined;
}
- (BOOL)reportAdMobPaidEventWithAdValue:(GADAdValue *)adValue
adFormat:(NSString *)adFormat
adUnitId:(NSString *)adUnitId
responseInfo:(GADResponseInfo *)responseInfo {
GADAdNetworkResponseInfo *servedBy = responseInfo.loadedAdNetworkResponseInfo;
CLXRevenueData *data =
[CLXRevenueData revenueDataWithPlatform:CLXRevenuePlatformAdMob
revenue:adValue.value.doubleValue
adFormat:adFormat
builderBlock:^(CLXRevenueDataBuilder *builder) {
builder.currencyCode = adValue.currencyCode;
builder.precision = CLXRevenuePrecisionFromGAD(adValue.precision);
builder.networkName = servedBy.adSourceName;
builder.adUnitId = adUnitId;
builder.thirdPartyAdPlacementId = servedBy.adSourceInstanceName;
}];
return [[CloudXCore shared] reportRevenueData:data];
}
__weak GADBannerView *weakBannerView = bannerView;
bannerView.paidEventHandler = ^(GADAdValue *adValue) {
[self reportAdMobPaidEventWithAdValue:adValue
adFormat:@"banner"
adUnitId:adUnitId
responseInfo:weakBannerView.responseInfo];
};
```
```swift Swift
private func toCloudXRevenuePrecision(_ precision: AdValuePrecision) -> CLXRevenuePrecision {
switch precision {
case .precise:
return .exact
case .estimated:
return .estimated
case .publisherProvided:
return .publisherDefined
case .unknown:
return .undefined
@unknown default:
return .undefined
}
}
private func reportAdMobPaidEvent(
_ adValue: AdValue,
adFormat: String,
adUnitId: String,
responseInfo: ResponseInfo?
) -> Bool {
let servedBy = responseInfo?.loadedAdNetworkResponseInfo
let data = CLXRevenueData.revenueData(
platform: .adMob,
revenue: adValue.value.doubleValue,
adFormat: adFormat
) { builder in
builder.currencyCode = adValue.currencyCode
builder.precision = toCloudXRevenuePrecision(adValue.precision)
builder.networkName = servedBy?.adSourceName
builder.adUnitId = adUnitId
builder.thirdPartyAdPlacementId = servedBy?.adSourceInstanceName
}
return CloudXCore.shared.reportRevenueData(data)
}
bannerView.paidEventHandler = { [weak bannerView] adValue in
_ = reportAdMobPaidEvent(
adValue,
adFormat: "banner",
adUnitId: adUnitId,
responseInfo: bannerView?.responseInfo
)
}
```
#### InMobi impression events
For InMobi, save the `IMAdMetaInfo` object from `banner(_:didReceiveWithMetaInfo:)`. When `bannerAdImpressed(_:)` fires, send the saved `metaInfo.getBid()` to CloudX, then clear the saved value.
```objc Objective-C
@property (nonatomic, strong, nullable) IMAdMetaInfo *latestInMobiMetaInfo;
- (void)banner:(IMBanner *)banner didReceiveWithMetaInfo:(IMAdMetaInfo *)info {
self.latestInMobiMetaInfo = info;
}
- (void)bannerAdImpressed:(IMBanner *)banner {
if (!self.latestInMobiMetaInfo) {
return;
}
[self reportInMobiImpressionWithMetaInfo:self.latestInMobiMetaInfo
adFormat:@"banner"
placementId:inMobiPlacementId];
self.latestInMobiMetaInfo = nil;
}
- (BOOL)reportInMobiImpressionWithMetaInfo:(IMAdMetaInfo *)metaInfo
adFormat:(NSString *)adFormat
placementId:(NSString *)placementId {
CLXRevenueData *data =
[CLXRevenueData revenueDataWithPlatform:CLXRevenuePlatformInMobi
revenue:[metaInfo getBid]
adFormat:adFormat
builderBlock:^(CLXRevenueDataBuilder *builder) {
builder.precision = CLXRevenuePrecision.estimated;
builder.thirdPartyAdPlacementId = placementId;
builder.creativeId = metaInfo.creativeID;
}];
return [[CloudXCore shared] reportRevenueData:data];
}
```
```swift Swift
private var latestInMobiMetaInfo: IMAdMetaInfo?
func banner(_ banner: IMBanner, didReceiveWithMetaInfo info: IMAdMetaInfo) {
latestInMobiMetaInfo = info
}
func bannerAdImpressed(_ banner: IMBanner) {
guard let metaInfo = latestInMobiMetaInfo else {
return
}
_ = reportInMobiImpression(
metaInfo,
adFormat: "banner",
placementId: inMobiPlacementId
)
latestInMobiMetaInfo = nil
}
private func reportInMobiImpression(
_ metaInfo: IMAdMetaInfo,
adFormat: String,
placementId: String
) -> Bool {
let data = CLXRevenueData.revenueData(
platform: .inMobi,
revenue: metaInfo.getBid(),
adFormat: adFormat
) { builder in
builder.precision = .estimated
builder.thirdPartyAdPlacementId = placementId
builder.creativeId = metaInfo.creativeID
}
return CloudXCore.shared.reportRevenueData(data)
}
```
For interstitial and rewarded InMobi ads, use the same pattern with `IMInterstitialDelegate`: save `IMAdMetaInfo` in `interstitial(_:didReceiveWithMetaInfo:)`, then report it in `interstitialAdImpressed(_:)`.
#### TopOn revenue events
TopOn iOS reports revenue in `didRevenueForPlacementID:extra:`. Use `publisher_revenue` from `extra` as one-impression revenue and `currency` as the currency code.
```objc Objective-C
static NSString *CLXTopOnStringValue(NSDictionary *extra, NSString *key) {
id value = extra[key];
return [value isKindOfClass:NSString.class] ? value : nil;
}
static CLXRevenuePrecision *CLXRevenuePrecisionFromTopOn(NSString *precision) {
if ([precision isEqualToString:@"exact"]) return CLXRevenuePrecision.exact;
if ([precision isEqualToString:@"estimated"]) return CLXRevenuePrecision.estimated;
if ([precision isEqualToString:@"publisher_defined"]) return CLXRevenuePrecision.publisherDefined;
return CLXRevenuePrecision.undefined;
}
- (BOOL)reportTopOnRevenueForPlacementID:(NSString *)placementID
extra:(NSDictionary *)extra
adFormat:(NSString *)adFormat {
NSNumber *revenue = extra[@"publisher_revenue"];
if (![revenue isKindOfClass:NSNumber.class]) {
return NO;
}
CLXRevenueData *data =
[CLXRevenueData revenueDataWithPlatform:CLXRevenuePlatformTopOn
revenue:revenue.doubleValue
adFormat:adFormat
builderBlock:^(CLXRevenueDataBuilder *builder) {
builder.currencyCode = CLXTopOnStringValue(extra, @"currency");
builder.precision = CLXRevenuePrecisionFromTopOn(CLXTopOnStringValue(extra, @"precision"));
builder.networkName = CLXTopOnStringValue(extra, @"network_name");
builder.adUnitId = placementID;
builder.thirdPartyAdPlacementId = CLXTopOnStringValue(extra, @"network_placement_id");
builder.networkPlacement = CLXTopOnStringValue(extra, @"adsource_id");
builder.countryCode = CLXTopOnStringValue(extra, @"country");
}];
return [[CloudXCore shared] reportRevenueData:data];
}
- (void)didRevenueForPlacementID:(NSString *)placementID extra:(NSDictionary *)extra {
[self reportTopOnRevenueForPlacementID:placementID
extra:extra
adFormat:@"banner"];
}
```
```swift Swift
private func topOnRevenuePrecision(_ precision: String?) -> CLXRevenuePrecision {
switch precision {
case "exact":
return .exact
case "estimated":
return .estimated
case "publisher_defined":
return .publisherDefined
default:
return .undefined
}
}
private func reportTopOnRevenue(
placementID: String,
extra: [AnyHashable: Any],
adFormat: String
) -> Bool {
guard let revenue = (extra["publisher_revenue"] as? NSNumber)?.doubleValue else {
return false
}
let data = CLXRevenueData.revenueData(
platform: .topOn,
revenue: revenue,
adFormat: adFormat
) { builder in
builder.currencyCode = extra["currency"] as? String
builder.precision = topOnRevenuePrecision(extra["precision"] as? String)
builder.networkName = extra["network_name"] as? String
builder.adUnitId = placementID
builder.thirdPartyAdPlacementId = extra["network_placement_id"] as? String
builder.networkPlacement = extra["adsource_id"] as? String
builder.countryCode = extra["country"] as? String
}
return CloudXCore.shared.reportRevenueData(data)
}
func didRevenue(forPlacementID placementID: String, extra: [AnyHashable: Any]) {
_ = reportTopOnRevenue(
placementID: placementID,
extra: extra,
adFormat: "banner"
)
}
```
#### Custom platform events
Use `Custom` for providers that do not have a CloudX SDK constant like AdMob, InMobi, and TopOn do. The value is only the provider name, such as `CLXRevenuePlatformCustom(@"TradPlus")` in Objective-C or `CLXRevenuePlatform.custom("TradPlus")` in Swift. 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 `TradPlus`, revenue `0.01`, and currency code `USD`. If your source reports CPM/eCPM, divide by `1_000.0` first.
```objc Objective-C
- (BOOL)reportCustomRevenueEventWithProviderName:(NSString *)providerName
revenue:(double)revenue
adFormat:(NSString *)adFormat
currencyCode:(NSString *)currencyCode
adUnitId:(NSString *)adUnitId
placementId:(NSString *)placementId {
CLXRevenueData *data =
[CLXRevenueData revenueDataWithPlatform:CLXRevenuePlatformCustom(providerName)
revenue:revenue
adFormat:adFormat
builderBlock:^(CLXRevenueDataBuilder *builder) {
builder.currencyCode = currencyCode;
builder.precision = CLXRevenuePrecision.publisherDefined;
builder.adUnitId = adUnitId;
builder.thirdPartyAdPlacementId = placementId;
}];
return [[CloudXCore shared] reportRevenueData:data];
}
```
```swift Swift
private func reportCustomRevenueEvent(
providerName: String,
revenue: Double,
adFormat: String,
currencyCode: String,
adUnitId: String,
placementId: String
) -> Bool {
let data = CLXRevenueData.revenueData(
platform: .custom(providerName),
revenue: revenue,
adFormat: adFormat
) { builder in
builder.currencyCode = currencyCode
builder.precision = .publisherDefined
builder.adUnitId = adUnitId
builder.thirdPartyAdPlacementId = placementId
}
return CloudXCore.shared.reportRevenueData(data)
}
```
### Delegate Threading
Publisher delegate callbacks are delivered on the main queue and may fire inline relative to the SDK call that triggered them. Keep delegate handlers re-entrant-safe if they call back into the SDK.
### Test Mode
Test mode is **server-controlled** via device whitelisting. This provides better security and control over which devices receive test ads.
**To enable test mode:**
1. Initialize the SDK and check the logs for your device IFA:
```
[CloudX][INFO] Device IFA for test whitelisting: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
```
2. Copy the IFA and add it to your device whitelist on the CloudX server dashboard
3. The SDK will automatically configure adapters for test mode and include the test flag in bid requests
> **Note:** Test mode is determined by the server, so you don't need to change any code between development and production builds.
### Privacy Compliance
The CloudX SDK supports GDPR and CCPA privacy compliance by reading standard IAB privacy strings from `NSUserDefaults`. These values are typically set automatically by your Consent Management Platform (CMP) such as Google UMP, OneTrust, or Sourcepoint.
#### How It Works
The SDK automatically detects user location and reads consent signals:
1. **EU Users (GDPR)**: Checks TCF v2 consent for purposes 1 and 2 per the [IAB Global Vendor List](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework) and vendor consent ([CloudX Vendor ID: 1510](https://vendor-list.consensu.org/v3/vendor-list.json))
2. **US Users (CCPA)**: Checks for sale/sharing opt-out signals
3. **Other Regions**: No restrictions applied
When consent is denied or user opts out, the SDK removes PII from ad requests:
* Advertising ID (IDFA) is cleared
* Geo coordinates (lat/lon) are removed
* User key-values are not sent
* Hashed user ID is removed from bidder-bound requests and suppressed from impression reporting
#### Supported Privacy Keys
| Key | Standard | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `IABGPP_HDR_GppString` | [GPP](https://github.com/InteractiveAdvertisingBureau/Global-Privacy-Platform) | Global Privacy Platform string (modern) |
| `IABGPP_GppSID` | GPP | Section IDs (e.g., "2" for EU, "7" for US-National, "8" for US-CA) |
| `IABTCF_TCString` | [TCF v2](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework) | GDPR consent string (legacy) |
| `IABTCF_gdprApplies` | TCF v2 | Whether GDPR applies (1 = yes, 0 = no) |
| `IABUSPrivacy_String` | [US Privacy](https://github.com/InteractiveAdvertisingBureau/USPrivacy) | CCPA privacy string (legacy, e.g., "1YNN") |
> **Note:** The SDK prioritizes GPP (modern standard) over legacy TCF/US Privacy strings when both are available.
#### App Tracking Transparency (ATT)
On iOS 14.5+, you must request App Tracking Transparency authorization before the SDK can access the IDFA. Request ATT permission before initializing the CloudX SDK:
```objc Objective-C
#import
if (@available(iOS 14.5, *)) {
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
// Initialize CloudX SDK after ATT response
[self initializeCloudX];
}];
} else {
[self initializeCloudX];
}
```
```swift Swift
import AppTrackingTransparency
if #available(iOS 14.5, *) {
ATTrackingManager.requestTrackingAuthorization { status in
// Initialize CloudX SDK after ATT response
self.initializeCloudX()
}
} else {
initializeCloudX()
}
```
Add the `NSUserTrackingUsageDescription` key to your Info.plist with a description of why you need tracking permission.
#### Manual Privacy API
If you manage user consent yourself (without a CMP), you can set GDPR and CCPA privacy status directly. **Call these before initializing the SDK** — some ad network SDKs require privacy settings at initialization time and will not apply values set after init.
```objc Objective-C
// Set privacy BEFORE initializing the SDK
[CloudXCore setHasUserConsent:@YES];
[CloudXCore setDoNotSell:@NO];
[[CloudXCore shared] initializeWithConfiguration:config completion:completion];
```
```swift Swift
// Set privacy BEFORE initializing the SDK
CloudXCore.setHasUserConsent(true)
CloudXCore.setDoNotSell(false)
CloudXCore.shared.initialize(with: config) { sdkConfig, error in
// ...
}
```
> **Note:** When both manual values and CMP signals are present, CMP signals (GPP/TCF/US Privacy) take priority. Manual values act as a fallback when no CMP is integrated. Pass `nil` to clear manual values and defer entirely to your CMP.
### User Targeting
```objc Objective-C
// Set hashed user ID for targeting
[[CloudXCore shared] setHashedUserID:@"hashed-user-id"];
// Set custom user key-value pairs (cleared by privacy regulations)
[[CloudXCore shared] setUserKeyValue:@"age" value:@"25"];
[[CloudXCore shared] setUserKeyValue:@"gender" value:@"male"];
[[CloudXCore shared] setUserKeyValue:@"location" value:@"US"];
// Set custom app key-value pairs for request targeting
[[CloudXCore shared] setAppKeyValue:@"app_version" value:@"1.0.0"];
[[CloudXCore shared] setAppKeyValue:@"user_level" value:@"premium"];
// Clear all custom key-values
[[CloudXCore shared] clearAllKeyValues];
```
```swift Swift
// Set hashed user ID for targeting
CloudXCore.shared.setHashedUserID("hashed-user-id")
// Set custom user key-value pairs (cleared by privacy regulations)
CloudXCore.shared.setUserKeyValue("age", value: "25")
CloudXCore.shared.setUserKeyValue("gender", value: "male")
CloudXCore.shared.setUserKeyValue("location", value: "US")
// Set custom app key-value pairs for request targeting
CloudXCore.shared.setAppKeyValue("app_version", value: "1.0.0")
CloudXCore.shared.setAppKeyValue("user_level", value: "premium")
// Clear all custom key-values
CloudXCore.shared.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](/en/cli/exports#cloudx-export-requests) and [impression](/en/cli/exports#cloudx-export-impressions) 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.
User and app key-values are also available in the [request activity export](/en/cli/exports#cloudx-export-requests). Use `setUserKeyValue` for session-wide user attributes and `setAppKeyValue` for session-wide app attributes. The SDK sends current values with each later auction request.
#### 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](https://unifiedid.com/docs/ref-info/ref-tokens) |
| `euid.eu` | EUID advertising token, not the refresh token. Do not decrypt it. [EUID docs](https://euid.eu/docs/ref-info/ref-tokens) |
| `liveramp.com` | LiveRamp ATS envelope, not a RampID. [LiveRamp docs](https://developers.liveramp.com/authenticatedtraffic-api/docs/pass-mobile-envelopes-to-the-bidstream) |
| `id5-sync.com` | ID5 universal UID. Do not pass `0`. [ID5 docs](https://wiki.id5.io/docs/mobile-in-app-integration) |
| `intentiq.com` | Intent IQ ID (IIQ ID) from your Intent IQ integration. [Intent IQ docs](https://docs.prebid.org/dev-docs/modules/userid-submodules/intentiq.html) |
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.
```swift Swift
CloudXCore.shared.setUserKeyValue("uidapi.com", value: uid2Token)
CloudXCore.shared.setUserKeyValue("euid.eu", value: euidToken)
CloudXCore.shared.setUserKeyValue("liveramp.com", value: liveRampEnvelope)
CloudXCore.shared.setUserKeyValue("id5-sync.com", value: id5Id)
CloudXCore.shared.setUserKeyValue("intentiq.com", value: intentIQId)
```
```objc Objective-C
[[CloudXCore shared] setUserKeyValue:@"uidapi.com" value:uid2Token];
[[CloudXCore shared] setUserKeyValue:@"euid.eu" value:euidToken];
[[CloudXCore shared] setUserKeyValue:@"liveramp.com" value:liveRampEnvelope];
[[CloudXCore shared] setUserKeyValue:@"id5-sync.com" value:id5Id];
[[CloudXCore shared] setUserKeyValue:@"intentiq.com" value:intentIQId];
```
> **Warning:** Use `setUserKeyValue` only. Do not put these tokens in `setAppKeyValue` or `setExtraParameter`. Extra parameters apply to one ad load. Key names must match exactly.
> **Note:** Do not set a key until you have a value. Setting it again replaces the previous value. If a value expires without a replacement, set that key to an empty string to stop sending its EID. `clearAllKeyValues()` clears all user and app key-values and the hashed user ID.
For metadata that belongs to one ad object or load, call `setExtraParameter` before `load`:
```swift Swift
banner?.setExtraParameter("requestId", value: "request-456")
banner?.setExtraParameter("impressionKey", value: "impression-789")
banner?.load()
```
```objc Objective-C
[self.banner setExtraParameter:@"requestId" value:@"request-456"];
[self.banner setExtraParameter:@"impressionKey" value:@"impression-789"];
[self.banner load];
```
Extra parameters remain on that ad object until changed or cleared. The SDK snapshots them for each load. CloudX returns the complete bag in the `extra_parameters` request-export column. The reserved `tags` key also controls [tag-based routing](/en/dashboard/tags); use another key for correlation metadata.
CloudX stores each of the three compact JSON bags only when the entire bag is at most 256 UTF-8 bytes. The limit is independent for each export column and includes all combined keys, values, quotes, separators, and braces—not 256 bytes per K/V pair. When privacy rules prohibit publisher-data persistence, or a bag is malformed or too large, its export cell is empty. Do not include raw personal data, secrets, or consent strings.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Sample App
> Run the public CloudX iOS demo app and point it at your own app
The [cloudx-io/cloudx-ios](https://github.com/cloudx-io/cloudx-ios) repository holds a runnable demo app, in Swift (`demo-app-swift/`) and in Objective-C (`demo-app-objc/`). It opens on a screen that picks one of three flows. General shows banner, MREC, interstitial, rewarded, app open and native ads. First Look loads CloudX first and falls back to AdMob, as described in [First Look](/en/ios/integrations/first-look). Arbiter/TPA loads CloudX and AdMob in parallel and lets [Trusted Arbiter](/en/ios/trusted-arbiter) pick the one to show. It ships with CloudX demo dashboard IDs and Google's AdMob test IDs, so it runs without an account: run `pod install` in either folder and open the workspace.
## Use your own CloudX app
To point it at your own CloudX app, replace the app key and ad unit IDs in `Configuration/CLXDemoConfigManager`, then set the bundle identifier registered for that app. Bid requests are authorized per app key and bundle identifier, so both must match your dashboard app or the SDK gets no fill.
---
# Swift Package Manager
> Install CloudX Core and supported iOS adapters with Swift Package Manager
CloudX Core and the adapters on this page are available as Swift packages.
> **Warning:** Only the adapters listed on this page support Swift Package Manager. If your app needs another CloudX adapter, use CocoaPods for all CloudX dependencies in that app target.
## Add a package in Xcode
1. **Open the package dependency dialog.** In Xcode, select **File > Add Package Dependencies**.
2. **Enter the package URL.** Copy a package URL from the packages below. Add each required adapter package separately.
3. **Select a version.** For Core, select **Up to Next Major Version** starting at `3.11.0`. For each adapter, select the exact version listed below.
4. **Add the product.** Add the product shown below to your app target. An adapter package also installs CloudX Core and its vendor SDK.
5. **Add the linker flag.** Add `$(inherited) -ObjC` to **Other Linker Flags** for every app target that uses CloudX.
## Packages
### Core
```text
https://github.com/cloudx-io/cloudx-ios-swift-package-core.git
```
* Product: `CloudXCore`
* Latest package version: [`3.11.0`](https://github.com/cloudx-io/cloudx-ios-swift-package-core/releases/tag/3.11.0)
### Google Waterfall
```text
https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-googlewaterfall.git
```
* Product: `CloudXGoogleWaterfallAdapter`
* Latest package version: [`13090000.0.0`](https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-googlewaterfall/releases/tag/13090000.0.0)
* [Integration instructions](/en/ios/adapters/google-estimated/overview)
### Vungle
```text
https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-vungle.git
```
* Product: `CloudXVungleAdapter`
* Latest package version: [`7070601.0.0`](https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-vungle/releases/tag/7070601.0.0)
* [Integration instructions](/en/ios/adapters/vungle/overview)
### Meta
```text
https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-meta.git
```
* Product: `CloudXMetaAdapter`
* Latest package version: [`6220001.0.0`](https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-meta/releases/tag/6220001.0.0)
* [Integration instructions](/en/ios/adapters/meta/overview)
### Mintegral
```text
https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-mintegral.git
```
* Product: `CloudXMintegralAdapter`
* Latest package version: [`8010700.0.0`](https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-mintegral/releases/tag/8010700.0.0)
* [Integration instructions](/en/ios/adapters/mintegral/overview)
### Digital Turbine
```text
https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-digitalturbine.git
```
* Product: `CloudXDigitalTurbineAdapter`
* Latest package version: [`8041001.0.0`](https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-digitalturbine/releases/tag/8041001.0.0)
* [Integration instructions](/en/ios/adapters/digitalturbine/overview)
### Unity Ads
```text
https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-unityads.git
```
* Product: `CloudXUnityAdsAdapter`
* Latest package version: [`4200101.0.0`](https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-unityads/releases/tag/4200101.0.0)
* [Integration instructions](/en/ios/adapters/unityads/overview)
Import `CloudXCore` in your application. The adapter packages register themselves when the application loads.
This page shows the latest package version for each adapter. Open the adapter integration page for its complete setup instructions.
> **Warning:** Do not install the same CloudX component or vendor SDK through both CocoaPods and Swift Package Manager. Duplicate frameworks can cause build or runtime failures.
All adapter packages require CloudX Core `3.9.1` or a later compatible `3.x` version.
## Platform requirements
The CloudX iOS SDK requires iOS 13 or later, Xcode 16 or later, and Swift 6 or later. These packages have additional requirements:
* Meta `6220001.0.0` requires iOS 15 or later.
* Digital Turbine `8041001.0.0` requires iOS 15 or later and Xcode 26.1 or later.
* Unity Ads requires Xcode 26.1 or later.
After installation, continue with [CloudX initialization](/en/ios/integration#initialization).
---
# First Look
> Give CloudX the first chance to fill an ad placement, then fall back to your existing mediation setup
First Look gives CloudX the first chance to fill a placement while preserving your existing mediation setup as the fallback path. Start with one placement, verify load and show behavior, then expand to more placements.
This example uses an AdMob interstitial fallback. The same pattern applies to other fallback mediators: load CloudX first, load the fallback only if CloudX does not load, and keep the app flow moving when neither source is ready.
> **Info:** Initialize CloudX and Google Mobile Ads before creating the controller. Keep your existing pacing, retry, and placement timing rules around this controller.
```swift FirstLookInterstitialController.swift
import CloudXCore
import GoogleMobileAds
import UIKit
@MainActor
final class FirstLookInterstitialController: NSObject {
enum Source {
case cloudX
case adMob
}
protocol Listener: AnyObject {
func firstLookInterstitialController(_ controller: FirstLookInterstitialController, didLoad source: Source)
func firstLookInterstitialController(_ controller: FirstLookInterstitialController, didFailToLoad source: Source, message: String?)
func firstLookInterstitialController(_ controller: FirstLookInterstitialController, didShow source: Source)
func firstLookInterstitialController(_ controller: FirstLookInterstitialController, didFailToShow source: Source, message: String?)
func firstLookInterstitialController(_ controller: FirstLookInterstitialController, didClose source: Source)
func firstLookInterstitialController(_ controller: FirstLookInterstitialController, didClick source: Source)
}
weak var listener: Listener?
private weak var viewController: UIViewController?
private let cloudXInterstitial: CLXInterstitial
private let adMobAdUnitId: String
private var adMobInterstitial: InterstitialAd?
private var isLoadingCloudX = false
private var isLoadingAdMob = false
init?(
viewController: UIViewController,
cloudXAdUnitId: String,
adMobAdUnitId: String
) {
guard let cloudXInterstitial = CloudXCore.shared.createInterstitial(adUnitId: cloudXAdUnitId) else {
return nil
}
self.viewController = viewController
self.cloudXInterstitial = cloudXInterstitial
self.adMobAdUnitId = adMobAdUnitId
super.init()
self.cloudXInterstitial.delegate = self
}
func load() {
guard !isLoadingCloudX,
!isLoadingAdMob,
!cloudXInterstitial.isReady,
adMobInterstitial == nil else {
return
}
isLoadingCloudX = true
cloudXInterstitial.load()
}
func show() -> Bool {
guard let viewController else {
return false
}
if cloudXInterstitial.isReady {
cloudXInterstitial.show(from: viewController)
return true
}
return showAdMobFallback(from: viewController)
}
func destroy() {
cloudXInterstitial.destroy()
adMobInterstitial = nil
listener = nil
}
private func loadAdMobFallback() {
guard !isLoadingAdMob, adMobInterstitial == nil else {
return
}
isLoadingAdMob = true
InterstitialAd.load(with: adMobAdUnitId, request: Request()) { [weak self] ad, error in
guard let self else { return }
self.isLoadingAdMob = false
if let error {
self.adMobInterstitial = nil
self.listener?.firstLookInterstitialController(self, didFailToLoad: .adMob, message: error.localizedDescription)
return
}
guard let ad else {
self.adMobInterstitial = nil
self.listener?.firstLookInterstitialController(self, didFailToLoad: .adMob, message: nil)
return
}
ad.fullScreenContentDelegate = self
self.adMobInterstitial = ad
self.listener?.firstLookInterstitialController(self, didLoad: .adMob)
}
}
private func showAdMobFallback(from viewController: UIViewController) -> Bool {
guard let adMobInterstitial else {
return false
}
adMobInterstitial.present(from: viewController)
return true
}
}
extension FirstLookInterstitialController: CLXInterstitialDelegate {
func didLoad(_ ad: CLXAd) {
isLoadingCloudX = false
listener?.firstLookInterstitialController(self, didLoad: .cloudX)
}
func didFailToLoadAd(_ adUnitId: String, error: CLXError) {
isLoadingCloudX = false
loadAdMobFallback()
}
func didDisplay(_ ad: CLXAd) {
listener?.firstLookInterstitialController(self, didShow: .cloudX)
}
func didFailToDisplay(_ ad: CLXAd, error: CLXError) {
if let viewController, showAdMobFallback(from: viewController) {
return
}
listener?.firstLookInterstitialController(self, didFailToShow: .cloudX, message: error.localizedDescription)
}
func didHide(_ ad: CLXAd) {
listener?.firstLookInterstitialController(self, didClose: .cloudX)
}
func didClick(_ ad: CLXAd) {
listener?.firstLookInterstitialController(self, didClick: .cloudX)
}
}
extension FirstLookInterstitialController: FullScreenContentDelegate {
func adWillPresentFullScreenContent(_ ad: FullScreenPresentingAd) {
adMobInterstitial = nil
listener?.firstLookInterstitialController(self, didShow: .adMob)
}
func adDidDismissFullScreenContent(_ ad: FullScreenPresentingAd) {
adMobInterstitial = nil
listener?.firstLookInterstitialController(self, didClose: .adMob)
}
func ad(_ ad: FullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
adMobInterstitial = nil
listener?.firstLookInterstitialController(self, didFailToShow: .adMob, message: error.localizedDescription)
}
func adDidRecordClick(_ ad: FullScreenPresentingAd) {
listener?.firstLookInterstitialController(self, didClick: .adMob)
}
}
```
Use `show()` at the placement moment. If it returns `false`, neither CloudX nor the fallback had a ready ad, so continue the app flow without showing an ad.
```swift
if firstLookInterstitial?.show() != true {
continueToNextScreen()
}
```
After a show, close, or terminal failure, call `load()` again when your app is ready to prepare the next placement opportunity.
> **Tip:** This sample keeps the fallback lazy: AdMob loads only after CloudX fails to load. Do not parallel-load both sources unless CloudX recommends a different rollout strategy for your app.
## Demo App
The [CloudX iOS demo app](https://github.com/cloudx-io/cloudx-ios) runs this flow on device, in Swift and Objective-C. It structures the flow as three files, the controller and one source per SDK, and its host screen retries a failed load or show with a 2 to 60 second backoff.
* [FirstLookInterstitialController.swift](https://github.com/cloudx-io/cloudx-ios/blob/main/demo-app-swift/CloudXSwiftRemotePods/Ads/FirstLook/FirstLookInterstitialController.swift)
Gives CloudX the first load attempt. AdMob loads only after CloudX fails or is unavailable.
* [CloudXFirstLookSource.swift](https://github.com/cloudx-io/cloudx-ios/blob/main/demo-app-swift/CloudXSwiftRemotePods/Ads/FirstLook/CloudXFirstLookSource.swift)
CloudX's leg of the flow.
* [AdMobFirstLookSource.swift](https://github.com/cloudx-io/cloudx-ios/blob/main/demo-app-swift/CloudXSwiftRemotePods/Ads/FirstLook/AdMobFirstLookSource.swift)
The lazy AdMob fallback. A loaded ad shows once and expires after one hour.
* [FirstLookViewController.swift](https://github.com/cloudx-io/cloudx-ios/blob/main/demo-app-swift/CloudXSwiftRemotePods/Views/FirstLook/FirstLookViewController.swift)
Brings up both SDKs, retries with backoff and wires the Show button. This is demo-only layout; take the rules, not the file.
Copy the first three together. The two sources log through the demo's `DemoAppLogger`; swap in your own logging when you copy them. The Objective-C versions are in [`demo-app-objc/CloudXObjCRemotePods/Ads/FirstLook/`](https://github.com/cloudx-io/cloudx-ios/tree/main/demo-app-objc/CloudXObjCRemotePods/Ads/FirstLook).
---
# Trusted Arbiter
> Compare CloudX bids with supported third-party bids in iOS apps
Trusted Arbiter compares a loaded CloudX bid with supported third-party bids and returns the selected platform. CloudX iOS SDK 3.4.0 supports CloudX, Unity LevelPlay, and PubMatic bid inputs. CloudX iOS SDK 3.5.0 and later also supports custom publisher-supplied bid inputs.
## Why use Trusted Arbiter?
When your app loads ads from several platforms, it needs a way to choose which ad to show. Trusted Arbiter provides a shared comparison API for supported bids.
### Less comparison logic to maintain
Submit the supported bids to Trusted Arbiter and use the returned platform to select the ad. Your app continues to manage partner SDK integration, ad loading, and display.
### Use available bid values
Trusted Arbiter can compare actual bid values where available. Some demand uses estimated prices. See the pricing guidance below for each supported input.
> **Info:** Call `CloudXCore.shared.arbiter(with:completion:)` after CloudX initialization has completed, and when the candidate ads finish loading — not on the show path. See [When to run the arbiter](#when-to-run-the-arbiter). Until the arbiter service is available, the SDK falls back to selecting the highest comparable USD bid from the supplied inputs.
## Demo App
The [CloudX iOS demo app](https://github.com/cloudx-io/cloudx-ios) runs this page's cycle for interstitials, in Swift and Objective-C. CloudX and AdMob load in parallel, the loaded ads become bids, `CloudXCore.shared.arbiter(with:completion:)` picks the winner, and the winner is shown from a stored result so the show path makes no network call.
* [ArbiterInterstitialController.swift](https://github.com/cloudx-io/cloudx-ios/blob/main/demo-app-swift/CloudXSwiftRemotePods/Ads/Arbiter/ArbiterInterstitialController.swift)
The whole load, arbitrate and show cycle, with both SDKs' calls and the AdMob revenue report in one file. Rewarded follows this file with the rewarded calls substituted.
* [ArbiterViewController.swift](https://github.com/cloudx-io/cloudx-ios/blob/main/demo-app-swift/CloudXSwiftRemotePods/Views/Arbiter/ArbiterViewController.swift)
Brings up both SDKs, retries with a 2 to 60 second backoff and wires the Show button. This is demo-only layout; take the rules, not the file.
* [CLXDemoConfigManager.swift](https://github.com/cloudx-io/cloudx-ios/blob/main/demo-app-swift/CloudXSwiftRemotePods/Configuration/CLXDemoConfigManager.swift)
App key and ad unit IDs. This is the first file to edit when you run the demo yourself.
* [ArbiterInterstitialController.m](https://github.com/cloudx-io/cloudx-ios/blob/main/demo-app-objc/CloudXObjCRemotePods/Ads/Arbiter/ArbiterInterstitialController.m)
The Objective-C version of the controller.
The controller logs through the demo's `DemoAppLogger`; swap in your own logging when you copy it. The demo ships Google's AdMob test units, which report revenue of 0. CloudX does not keep a price of 0, so the AdMob bid has no comparable price while you test. Point the demo at a paying unit to watch prices compete, or give the AdMob bid a manual price for one launch as the demo's README shows.
## When to run the arbiter
Run the arbiter when the candidate ads finish loading — never on the show path. The arbiter call is a network round trip, so a user who taps a button that shows an ad must never be left waiting on it. Arbitrate ahead of the placement, and the show itself becomes an immediate, local decision.
### Fullscreen formats (interstitial, rewarded, app open): prepare ahead
Load every candidate in parallel. When they have settled — loaded or failed — submit the loaded candidates to the arbiter and **store** the result. At the placement, show the stored winner immediately; no arbiter call happens there. Start the cycle again after the ad is shown or hidden, after it fails to display, or when a candidate expires.
If the placement arrives before a winner has been stored, either continue without an ad or show the single candidate that did load. That is an acceptable degraded path, never the primary one.
```swift Swift
// Runs as soon as the candidates have loaded, ahead of the placement.
func prepareWinner(configuration: CLXArbiterConfiguration) {
CloudXCore.shared.arbiter(with: configuration) { [weak self] result in
self?.nextWinner = result
}
}
// Runs at the placement. No network call here.
func showInterstitial(from viewController: UIViewController) {
switch nextWinner?.platform.name {
case CLXArbiterPlatform.cloudX.name:
cloudXInterstitial?.show(from: viewController)
case CLXArbiterPlatform.adMob.name:
adMobInterstitial?.present(fromRootViewController: viewController)
default:
break // no winner prepared; continue without an ad
}
nextWinner = nil
}
```
```objc Objective-C
// Runs as soon as the candidates have loaded, ahead of the placement.
- (void)prepareWinnerWithConfiguration:(CLXArbiterConfiguration *)configuration {
__weak typeof(self) weakSelf = self;
[[CloudXCore shared] arbiterWithConfiguration:configuration completion:^(CLXArbiterResult *result) {
weakSelf.nextWinner = result;
}];
}
// Runs at the placement. No network call here.
- (void)showInterstitialFromViewController:(UIViewController *)viewController {
NSString *platform = self.nextWinner.platform.name;
if ([platform isEqualToString:CLXArbiterPlatform.cloudX.name]) {
[self.cloudXInterstitial showFromViewController:viewController];
} else if ([platform isEqualToString:CLXArbiterPlatform.adMob.name]) {
[self.adMobInterstitial presentFromRootViewController:viewController];
}
// no winner prepared; continue without an ad
self.nextWinner = nil;
}
```
### View formats (banner, MREC, native): arbitrate, then render
Nothing user-initiated is waiting on a view format, so here it is correct to attach or render the winner directly inside the completion handler — the arbiter completing *is* the trigger to show. Only the winner's view or assets may ever be attached; see [Banner and MREC arbitration](#banner-and-mrec-arbitration) for the attachment and refresh rules.
> **Note:** The SDK owns the arbiter's timeout and fallback, so do not wrap the call in a timer of your own. The per-call timeout comes from your server-side init configuration, and the completion handler always runs: on timeout, error, or an unavailable arbiter service, the SDK falls back locally to the highest comparable USD bid among the inputs you supplied. A single supplied bid wins immediately without any service call. `CLXArbiterPlatform.none` means no winner could be selected — either no bids were supplied, or, with more than one bid, none of them carried a locally comparable price during fallback (for example, Google bids without `manualRevenuePerImpressionUSD`).
## Supported ad formats
Trusted Arbiter is format-agnostic: it takes any loaded CloudX ad and compares it against the supplied third-party bids, regardless of format. Arbiter payloads are attached to CloudX ads of every format — banner, MREC, native, interstitial, rewarded, and app open.
* **Interstitial, rewarded, and app open** (fullscreen formats) prepare a winner ahead of the placement and show the stored result there, as described in [When to run the arbiter](#when-to-run-the-arbiter). The step-by-step and controller patterns later on this page follow that rule. Worked examples: [Interstitial example](#interstitial-example) and [App Open example](#app-open-example). Rewarded follows the interstitial pattern unchanged — only the ad type differs.
* **Banner, MREC, and native** (view formats) arbitrate and then render, and require the additional handling described in [Banner and MREC arbitration](#banner-and-mrec-arbitration) below, because the losing bid's view must never be attached to the view hierarchy and the refresh cycle has to be coordinated manually. Worked examples: [Banner example](#banner-example) and [Native example](#native-example).
## Basic API
Create bid candidates from loaded ads, then pass them to the arbiter.
```objc Objective-C
// cloudXAd is the CLXAd object from a CloudX load callback.
// levelPlayAdInfo is the Unity LevelPlay ad info object.
// pubMaticPrice and pubMaticPartnerName come from the PubMatic/OpenWrap bid object.
CLXArbiterBid *cloudXBid = [CLXArbiterBid cloudXBidWithAd:cloudXAd];
CLXArbiterBid *levelPlayBid =
[CLXArbiterBid levelPlayBidWithNetworkName:levelPlayAdInfo.adNetwork
revenue:levelPlayAdInfo.revenue.doubleValue
precision:levelPlayAdInfo.precision];
CLXArbiterBid *pubMaticBid =
[CLXArbiterBid pubMaticBidWithPrice:pubMaticPrice
partnerName:pubMaticPartnerName
extras:nil];
CLXArbiterConfiguration *configuration =
[CLXArbiterConfiguration configurationWithBids:@[cloudXBid, levelPlayBid, pubMaticBid]];
[[CloudXCore shared] arbiterWithConfiguration:configuration completion:^(CLXArbiterResult *result) {
NSLog(@"Selected platform: %@", result.platform.name);
}];
```
```swift Swift
// cloudXAd is the CLXAd object from a CloudX load callback.
// levelPlayAdInfo is the Unity LevelPlay ad info object.
// pubMaticPrice and pubMaticPartnerName come from the PubMatic/OpenWrap bid object.
let cloudXBid = CLXArbiterBid.cloudX(ad: cloudXAd)
let levelPlayBid = CLXArbiterBid.levelPlay(
networkName: levelPlayAdInfo.adNetwork,
revenue: levelPlayAdInfo.revenue?.doubleValue ?? 0,
precision: levelPlayAdInfo.precision
)
let pubMaticBid = CLXArbiterBid.pubMatic(
price: pubMaticPrice,
partnerName: pubMaticPartnerName,
extras: nil
)
let configuration = CLXArbiterConfiguration.configuration(
bids: [cloudXBid, levelPlayBid, pubMaticBid],
builderBlock: nil
)
CloudXCore.shared.arbiter(with: configuration) { result in
print("Selected platform: \(result.platform.name)")
}
```
`CLXArbiterBid.cloudX` accepts the `CLXAd` object from a CloudX load callback. `CLXArbiterBid.levelPlay` accepts Unity LevelPlay ad info values. `CLXArbiterBid.pubMatic` accepts a PubMatic OpenWrap bid price and optional partner name. The `extras` map is optional on both the LevelPlay and PubMatic bids, and `partnerName` is optional on PubMatic. The completion callback runs on the main thread, so you can show an ad or update UI directly from it.
`result.platform` is `CLXArbiterPlatform.cloudX`, `levelPlay`, or `pubMatic` for the selected platform, or `CLXArbiterPlatform.none` when no winner could be selected — either no bids were supplied, or, with more than one bid, none of them carried a locally comparable price during fallback.
The completion handler is where you store the result, not where you show a fullscreen ad. See [When to run the arbiter](#when-to-run-the-arbiter).
## AdMob and Google Ad Manager
CloudX compares a loaded CloudX ad with a loaded AdMob or Google Ad Manager ad. AdMob and Ad Manager are separate demand sources, so both may bid in the same arbitration. This applies to publisher-managed mediation setups, including accounts described commercially as AdMob Pro; there is no separate `AdMob Pro` SDK API.
Google demand does not normally reveal the price of a loaded ad before it is shown, so there is no pre-bid price for the arbiter to compare against CloudX's bid. CloudX prices the bid automatically from your historical Google performance, so you do not supply a price and no pre-bid pricing API is required. See [how AdMob/GAM estimated pricing works](/en/networks/google-estimated).
If your AdMob account exposes impression-level revenue data pre-bid, you can supply that exact price yourself instead of using the estimate — see [Manual input with pre-bid ILRD](#manual-input-with-pre-bid-ilrd) below.
### Report Google paid events back to CloudX (required)
Forwarding Google's paid events is a required part of the Trusted Arbiter AdMob and Ad Manager integration, not an optional analytics extra. The revenue you report back feeds the historical Google performance CloudX prices your bids from.
After you show an AdMob or Ad Manager ad that won an arbitration, forward Google's impression-level revenue — the `GADAdValue` delivered to the ad's `paidEventHandler` — into the CloudX SDK with `reportRevenueData(_:)`. Use `CLXRevenuePlatformAdMob` for AdMob ads and `CLXRevenuePlatformGAM` for Ad Manager ads. Without this feedback CloudX never learns the realized prices for your ad units, and future arbiter estimates degrade.
`GADAdValue.value` is an `NSDecimalNumber` already expressed in currency units on iOS, so pass it straight through. Do not divide by 1,000,000 — only the Android and Unity Google Mobile Ads SDKs report ad values in micros.
```swift Swift
func revenuePrecision(from precision: GADAdValuePrecision) -> CLXRevenuePrecision {
switch precision {
case .precise: return .exact
case .estimated: return .estimated
case .publisherProvided: return .publisherDefined
default: return .undefined
}
}
// Attach once to each Google ad you loaded as an arbiter candidate.
adMobInterstitial.paidEventHandler = { [weak adMobInterstitial] adValue in
let servedBy = adMobInterstitial?.responseInfo.loadedAdNetworkResponseInfo
let data = CLXRevenueData.revenueData(
platform: .adMob, // .gam for an Ad Manager ad
revenue: adValue.value.doubleValue,
adFormat: "interstitial"
) { builder in
builder.currencyCode = adValue.currencyCode
builder.precision = revenuePrecision(from: adValue.precision)
builder.networkName = servedBy?.adSourceName
builder.adUnitId = adMobAdUnitId
builder.thirdPartyAdPlacementId = servedBy?.adSourceInstanceName
}
CloudXCore.shared.reportRevenueData(data)
}
```
```objc Objective-C
static CLXRevenuePrecision *CLXRevenuePrecisionFromGAD(GADAdValuePrecision precision) {
switch (precision) {
case GADAdValuePrecisionPrecise: return CLXRevenuePrecision.exact;
case GADAdValuePrecisionEstimated: return CLXRevenuePrecision.estimated;
case GADAdValuePrecisionPublisherProvided: return CLXRevenuePrecision.publisherDefined;
case GADAdValuePrecisionUnknown: return CLXRevenuePrecision.undefined;
}
return CLXRevenuePrecision.undefined;
}
// Attach once to each Google ad you loaded as an arbiter candidate.
__weak GADInterstitialAd *weakAd = adMobInterstitial;
adMobInterstitial.paidEventHandler = ^(GADAdValue *adValue) {
GADAdNetworkResponseInfo *servedBy = weakAd.responseInfo.loadedAdNetworkResponseInfo;
CLXRevenueData *data =
[CLXRevenueData revenueDataWithPlatform:CLXRevenuePlatformAdMob // CLXRevenuePlatformGAM for Ad Manager
revenue:adValue.value.doubleValue
adFormat:@"interstitial"
builderBlock:^(CLXRevenueDataBuilder *builder) {
builder.currencyCode = adValue.currencyCode;
builder.precision = CLXRevenuePrecisionFromGAD(adValue.precision);
builder.networkName = servedBy.adSourceName;
builder.adUnitId = adMobAdUnitId;
builder.thirdPartyAdPlacementId = servedBy.adSourceInstanceName;
}];
[[CloudXCore shared] reportRevenueData:data];
};
```
Attach the handler once per ad object. AdMob re-fires paid events on banner and MREC auto-refresh against the same view, so a handler installed once keeps reporting every refreshed impression. See [Publisher-Reported Revenue Data](/en/ios/integration#publisher-reported-revenue-data) for the full reporting setup and the complete field list.
Create the bid from the ad unit id of the ad you loaded, run the arbiter once the candidates have loaded, and store the winner for the placement:
```swift Swift
// An AdMob ad unit. networkName is optional; pass the winning ad source when you know it,
// e.g. responseInfo.loadedAdapterResponseInfo?.adSourceName.
let adMobBid = CLXArbiterBid.adMob(
adUnitId: adMobAdUnitId,
networkName: adMobNetworkName ?? "admob",
manualRevenuePerImpressionUSD: nil,
extras: [:]
)
// An Ad Manager ad unit id takes the form /NNNNNNN/placement/name.
let adManagerBid = CLXArbiterBid.gam(adUnitId: "/21775744923/example/interstitial")
let configuration = CLXArbiterConfiguration.configuration(
bids: [CLXArbiterBid.cloudX(ad: cloudXAd), adMobBid, adManagerBid],
builderBlock: nil
)
// Runs once the candidates have loaded, ahead of the placement.
CloudXCore.shared.arbiter(with: configuration) { [weak self] result in
self?.nextWinner = result
}
```
```objc Objective-C
CLXArbiterBid *adMobBid =
[CLXArbiterBid adMobBidWithAdUnitId:adMobAdUnitId
networkName:adMobNetworkName ?: @"admob"
manualRevenuePerImpressionUSD:nil
extras:@{}];
CLXArbiterBid *adManagerBid =
[CLXArbiterBid gamBidWithAdUnitId:@"/21775744923/example/interstitial"];
CLXArbiterConfiguration *configuration =
[CLXArbiterConfiguration configurationWithBids:@[
[CLXArbiterBid cloudXBidWithAd:cloudXAd],
adMobBid,
adManagerBid
]];
// Runs once the candidates have loaded, ahead of the placement.
__weak typeof(self) weakSelf = self;
[[CloudXCore shared] arbiterWithConfiguration:configuration completion:^(CLXArbiterResult *result) {
weakSelf.nextWinner = result;
}];
```
At the placement, show the stored winner. No network call happens here.
```swift Swift
func showInterstitial(from viewController: UIViewController) {
switch nextWinner?.platform.name {
case CLXArbiterPlatform.cloudX.name:
cloudXInterstitial.show(from: viewController)
case CLXArbiterPlatform.adMob.name:
adMobInterstitial.present(fromRootViewController: viewController)
case CLXArbiterPlatform.gam.name:
adManagerInterstitial.present(fromRootViewController: viewController)
default:
break // no winner prepared; continue without an ad
}
nextWinner = nil
}
```
```objc Objective-C
- (void)showInterstitialFromViewController:(UIViewController *)viewController {
NSString *platform = self.nextWinner.platform.name;
if ([platform isEqualToString:CLXArbiterPlatform.cloudX.name]) {
[self.cloudXInterstitial showFromViewController:viewController];
} else if ([platform isEqualToString:CLXArbiterPlatform.adMob.name]) {
[self.adMobInterstitial presentFromRootViewController:viewController];
} else if ([platform isEqualToString:CLXArbiterPlatform.gam.name]) {
[self.adManagerInterstitial presentFromRootViewController:viewController];
}
// no winner prepared; continue without an ad
self.nextWinner = nil;
}
```
A winning Google bid reports its own platform — `CLXArbiterPlatform.adMob` or `CLXArbiterPlatform.gam`. You no longer inspect `result.platformName` to tell these sources apart, as you would for a [custom bid](#custom-bid-inputs).
> **Note:** Google's paid-event callback fires after an impression, so it cannot price the same loaded ad before the arbiter runs. Post-impression revenue is exactly what you forward through `reportRevenueData(_:)`.
Pass a blank ad unit id and the bid still builds rather than crashing your app, but it carries no usable identity: it is never priced and the server rejects it.
## Manual input with pre-bid ILRD
Some AdMob accounts expose impression-level revenue data pre-bid: the `GADAdValue` for the loaded ad is available at load time, before the ad is shown. This is a legacy, account-gated capability, so check with your Google account team whether it is enabled for your account. An exact per-impression price that you know before show is the one case where supplying your own price beats CloudX's estimate.
> **Warning:** We recommend manually assigned prices only when they come from pre-bid ILRD. Prices from weaker sources — average eCPMs, guesses, or post-impression paid events — degrade arbitration rather than improving it. Leave `manualRevenuePerImpressionUSD` nil in those cases and let CloudX's estimate price the bid instead.
Pass the pre-bid ad value as `manualRevenuePerImpressionUSD` and it overrides the estimate:
```swift Swift
// GADAdValue.value is an NSDecimalNumber already expressed in currency units,
// so pass it through as-is. Do not divide it by anything.
let adMobBid = CLXArbiterBid.adMob(
adUnitId: adMobAdUnitId,
networkName: adMobNetworkName ?? "admob",
manualRevenuePerImpressionUSD: preBidAdValue.value,
extras: [:]
)
```
```objc Objective-C
// GADAdValue.value is an NSDecimalNumber already expressed in currency units,
// so pass it through as-is. Do not divide it by anything.
CLXArbiterBid *adMobBid =
[CLXArbiterBid adMobBidWithAdUnitId:adMobAdUnitId
networkName:adMobNetworkName ?: @"admob"
manualRevenuePerImpressionUSD:preBidAdValue.value
extras:@{}];
```
How the value is treated:
* `0.0` is a real price. It means this bid is worth nothing — not that the price is missing.
* Negative and non-finite values are not prices, so they are treated as absent and logged.
* A blank ad unit id drops the manual price entirely, because a bid with no identity cannot be validated.
`manualRevenuePerImpressionUSD` is revenue for a single impression, in USD — not CPM. Convert whatever your source reports:
* **An AdMob ad value** needs no scaling on iOS. `GADAdValue.value` is an `NSDecimalNumber` already expressed in currency units, so a value of `0.005` is `0.005` per impression. Pass it through as-is — do not divide by 1,000, and do not divide by 1,000,000 either. Only the Android and Unity Google Mobile Ads SDKs report ad values in micros.
* **A non-USD amount** must be converted to USD first.
## Step-by-step: arbitrate CloudX and LevelPlay
This walkthrough shows exactly which Unity LevelPlay callback to read and which values to pass into the arbiter. It uses an interstitial, but the same field mapping applies to any format — see [Supported ad formats](#supported-ad-formats) for what changes with Banner and MREC.
1. **Load both candidates.** Create the CloudX and LevelPlay interstitials, set their delegates, and start a load on each platform.
```objc Objective-C
self.cloudXInterstitial = [[CloudXCore shared] createInterstitialWithAdUnitId:@"YOUR_CLOUDX_AD_UNIT_ID"];
self.cloudXInterstitial.delegate = self;
[self.cloudXInterstitial load];
self.levelPlayInterstitial = [[LPMInterstitialAd alloc] initWithAdUnitId:@"YOUR_LEVELPLAY_AD_UNIT_ID"];
self.levelPlayInterstitial.delegate = self;
[self.levelPlayInterstitial loadAd];
```
```swift Swift
cloudXInterstitial = CloudXCore.shared.createInterstitial(adUnitId: "YOUR_CLOUDX_AD_UNIT_ID")
cloudXInterstitial?.delegate = self
cloudXInterstitial?.load()
levelPlayInterstitial = LPMInterstitialAd(adUnitId: "YOUR_LEVELPLAY_AD_UNIT_ID")
levelPlayInterstitial.setDelegate(self)
levelPlayInterstitial.loadAd()
```
2. **Capture each platform's loaded ad.** LevelPlay delivers an `LPMAdInfo` in its load callback; CloudX delivers a `CLXAd`. Hold onto both because you read the arbiter inputs from them in the next step.
```objc Objective-C
// Properties: @property (nonatomic, strong) CLXAd *cloudXAd;
// @property (nonatomic, strong) LPMAdInfo *levelPlayInfo;
// CLXInterstitialDelegate
- (void)didLoadAd:(CLXAd *)ad {
self.cloudXAd = ad;
}
// LPMInterstitialAdDelegate
- (void)didLoadAdWithAdInfo:(LPMAdInfo *)adInfo {
self.levelPlayInfo = adInfo;
}
```
```swift Swift
var cloudXAd: CLXAd?
var levelPlayInfo: LPMAdInfo?
// CLXInterstitialDelegate
func didLoad(_ ad: CLXAd) {
cloudXAd = ad
}
// LPMInterstitialAdDelegate
func didLoadAd(with adInfo: LPMAdInfo) {
levelPlayInfo = adInfo
}
```
3. **Map the values into bids.** Read the LevelPlay fields off `LPMAdInfo` and pass them to `CLXArbiterBid.levelPlay`. The CloudX bid takes the `CLXAd` directly. Submit only the platforms that actually loaded.
| `LPMAdInfo` field | Type | `CLXArbiterBid.levelPlay` parameter |
| ----------------- | ------------ | -------------------------------------- |
| `adNetwork` | `NSString *` | `networkName` |
| `revenue` | `NSNumber *` | `revenue` (unwrap with `.doubleValue`) |
| `precision` | `NSString *` | `precision` |
```objc Objective-C
NSMutableArray *bids = [NSMutableArray array];
if (self.cloudXAd) {
[bids addObject:[CLXArbiterBid cloudXBidWithAd:self.cloudXAd]];
}
if (self.levelPlayInfo) {
CLXArbiterBid *levelPlayBid =
[CLXArbiterBid levelPlayBidWithNetworkName:self.levelPlayInfo.adNetwork
revenue:self.levelPlayInfo.revenue.doubleValue
precision:self.levelPlayInfo.precision];
[bids addObject:levelPlayBid];
}
CLXArbiterConfiguration *configuration =
[CLXArbiterConfiguration configurationWithBids:bids];
```
```swift Swift
var bids: [CLXArbiterBid] = []
if let cloudXAd {
bids.append(CLXArbiterBid.cloudX(ad: cloudXAd))
}
if let levelPlayInfo {
bids.append(CLXArbiterBid.levelPlay(
networkName: levelPlayInfo.adNetwork,
revenue: levelPlayInfo.revenue?.doubleValue ?? 0,
precision: levelPlayInfo.precision
))
}
let configuration = CLXArbiterConfiguration.configuration(
bids: bids,
builderBlock: nil
)
```
4. **Run the arbiter and store the winner.** Pass the configuration to the arbiter with a completion handler. Run it only after both interstitials have settled: track each load callback and load failure, then submit only the candidates that loaded. This happens ahead of the placement, so the completion handler stores the result rather than showing an ad. The completion runs on the main thread.
```objc Objective-C
__weak typeof(self) weakSelf = self;
[[CloudXCore shared] arbiterWithConfiguration:configuration completion:^(CLXArbiterResult *result) {
weakSelf.nextWinner = result;
}];
```
```swift Swift
CloudXCore.shared.arbiter(with: configuration) { [weak self] result in
self?.nextWinner = result
}
```
5. **Show the stored winner at the placement.** At the placement, compare the stored result's `platform.name` against the platform constants and show that platform's ad. No arbiter call happens here. `CLXArbiterPlatform.none`, or no stored winner at all, means there is nothing to show, so continue without an ad. Clear the stored winner afterwards and start the next load cycle.
```objc Objective-C
- (void)showStoredWinner {
NSString *platform = self.nextWinner.platform.name;
if ([platform isEqualToString:CLXArbiterPlatform.cloudX.name]) {
[self.cloudXInterstitial showFromViewController:self];
} else if ([platform isEqualToString:CLXArbiterPlatform.levelPlay.name]) {
[self.levelPlayInterstitial showAdWithViewController:self placementName:nil];
}
// no winner stored, or CLXArbiterPlatform.none: continue without an ad
self.nextWinner = nil;
}
```
```swift Swift
func showStoredWinner() {
switch nextWinner?.platform.name {
case CLXArbiterPlatform.cloudX.name:
cloudXInterstitial?.show(from: self)
case CLXArbiterPlatform.levelPlay.name:
levelPlayInterstitial.showAd(viewController: self, placementName: nil)
default:
break // no winner stored, or CLXArbiterPlatform.none: continue without an ad
}
nextWinner = nil
}
```
> **Note:** `LPMAdInfo.revenue` is an `NSNumber` and may be absent, so unwrap it to a `double` before passing it to the non-null `revenue:` parameter: `revenue.doubleValue` in Objective-C, `revenue?.doubleValue ?? 0` in Swift.
The `ArbiterInterstitialController` below is the reference implementation of the prepare-ahead rule: it packages these same steps into a reusable component.
## Interstitial example
This interstitial example is the reference implementation of the prepare-ahead rule, arbitrating between two platforms: CloudX and Unity LevelPlay. A winner is prepared before the placement is reached:
1. Load CloudX and LevelPlay in parallel.
2. Wait until both platforms have loaded or failed.
3. Submit only loaded candidates to Trusted Arbiter.
4. Cache the selected platform.
5. At the placement, show the cached winner immediately.
If both platforms fail, start another load cycle. If the placement is reached before a winner is prepared, continue the app flow without showing an ad.
```swift ArbiterInterstitialController.swift
/// Prepares a Trusted Arbiter winner ahead of time so an interstitial can be shown
/// instantly when a placement is reached.
///
/// Loads the CloudX and LevelPlay interstitials in parallel, waits until both have
/// finished loading or failing, submits the loaded candidates to CloudXCore.shared.arbiter,
/// and caches the selected CLXArbiterPlatform in nextWinner.
final class ArbiterInterstitialController: NSObject {
protocol Listener: AnyObject {
/// Called when the arbiter has selected a platform for the next show.
func arbiterInterstitialController(
_ controller: ArbiterInterstitialController,
didPrepareWinner platform: CLXArbiterPlatform
)
}
weak var listener: Listener?
private let cloudXInterstitial: CLXInterstitial
private let levelPlayInterstitial: LPMInterstitialAd
private var cloudXAd: CLXAd?
private var cloudXLoadDone = false
private var levelPlayAdInfo: LPMAdInfo?
private var levelPlayLoadDone = false
private var nextWinner: CLXArbiterPlatform?
init(cloudXInterstitial: CLXInterstitial, levelPlayInterstitial: LPMInterstitialAd) {
self.cloudXInterstitial = cloudXInterstitial
self.levelPlayInterstitial = levelPlayInterstitial
super.init()
self.cloudXInterstitial.delegate = self
self.levelPlayInterstitial.setDelegate(self)
}
/// Starts a load for each platform that does not currently hold a cached ad.
func loadMissingAds() {
if cloudXAd == nil { cloudXInterstitial.load() }
if levelPlayAdInfo == nil { levelPlayInterstitial.loadAd() }
}
/// Shows the prepared winner, returning true only when a show call was made.
///
/// Returns false when no winner is ready or the cached ad is no longer available, in which
/// case a fresh load cycle is started.
func showAtPlacement(from viewController: UIViewController, placementName: String? = nil) -> Bool {
guard let platformName = nextWinner?.name else { return false }
if platformName == CLXArbiterPlatform.cloudX.name {
return showCloudX(from: viewController, placementName: placementName)
}
if platformName == CLXArbiterPlatform.levelPlay.name {
return showLevelPlay(from: viewController, placementName: placementName)
}
return false
}
/// Runs the arbiter once both platforms have settled, then caches the winning platform.
///
/// Returns early until both loads complete. If neither platform loaded, it restarts the
/// load cycle; otherwise it submits the loaded candidates to CloudXCore.shared.arbiter.
private func maybePrepareWinner() {
guard cloudXLoadDone, levelPlayLoadDone else { return }
if cloudXAd == nil && levelPlayAdInfo == nil {
cloudXLoadDone = false
levelPlayLoadDone = false
loadMissingAds()
return
}
var bids: [CLXArbiterBid] = []
if let cloudXAd {
bids.append(CLXArbiterBid.cloudX(ad: cloudXAd))
}
if let levelPlayAdInfo {
bids.append(CLXArbiterBid.levelPlay(
networkName: levelPlayAdInfo.adNetwork,
revenue: levelPlayAdInfo.revenue?.doubleValue ?? 0,
precision: levelPlayAdInfo.precision
))
}
let configuration = CLXArbiterConfiguration.configuration(bids: bids, builderBlock: nil)
CloudXCore.shared.arbiter(with: configuration) { [weak self] result in
guard let self else { return }
nextWinner = result.platform
listener?.arbiterInterstitialController(self, didPrepareWinner: result.platform)
}
}
private func showCloudX(from viewController: UIViewController, placementName: String?) -> Bool {
if cloudXInterstitial.isReady {
if let placementName {
cloudXInterstitial.show(from: viewController, placement: placementName, customData: nil)
} else {
cloudXInterstitial.show(from: viewController)
}
return true
}
clearCloudXAndLoadMissingAds()
return false
}
private func showLevelPlay(from viewController: UIViewController, placementName: String?) -> Bool {
if levelPlayInterstitial.isAdReady() {
levelPlayInterstitial.showAd(viewController: viewController, placementName: placementName)
return true
}
clearLevelPlayAndLoadMissingAds()
return false
}
private func clearCloudXAndLoadMissingAds() {
cloudXAd = nil
cloudXLoadDone = false
nextWinner = nil
loadMissingAds()
}
private func clearLevelPlayAndLoadMissingAds() {
levelPlayAdInfo = nil
levelPlayLoadDone = false
nextWinner = nil
loadMissingAds()
}
}
extension ArbiterInterstitialController: CLXInterstitialDelegate {
func didLoad(_ ad: CLXAd) {
cloudXAd = ad
cloudXLoadDone = true
maybePrepareWinner()
}
func didFailToLoadAd(_ adUnitId: String, error: CLXError) {
cloudXAd = nil
cloudXLoadDone = true
maybePrepareWinner()
}
func didDisplay(_ ad: CLXAd) {}
func didFailToDisplay(_ ad: CLXAd, error: CLXError) {
clearCloudXAndLoadMissingAds()
}
func didHide(_ ad: CLXAd) {
clearCloudXAndLoadMissingAds()
}
func didClick(_ ad: CLXAd) {}
}
extension ArbiterInterstitialController: LPMInterstitialAdDelegate {
func didLoadAd(with adInfo: LPMAdInfo) {
levelPlayAdInfo = adInfo
levelPlayLoadDone = true
maybePrepareWinner()
}
func didFailToLoadAd(withAdUnitId adUnitId: String, error: Error) {
levelPlayAdInfo = nil
levelPlayLoadDone = true
maybePrepareWinner()
}
func didChangeAdInfo(_ adInfo: LPMAdInfo) {
levelPlayAdInfo = adInfo
}
func didDisplayAd(with adInfo: LPMAdInfo) {}
func didFailToDisplayAd(with adInfo: LPMAdInfo, error: Error) {
clearLevelPlayAndLoadMissingAds()
}
func didCloseAd(with adInfo: LPMAdInfo) {
clearLevelPlayAndLoadMissingAds()
}
func didClickAd(with adInfo: LPMAdInfo) {}
}
```
`showAtPlacement(from:placementName:)` returns `true` only when an ad show call was made. `didChangeAdInfo(_:)` keeps the cached LevelPlay candidate up to date while it remains loaded.
For PubMatic OpenWrap, create a third-party bid with `CLXArbiterBid.pubMatic(price:partnerName:extras:)`. If the arbiter service is unavailable, times out, or fails, the SDK falls back to the highest comparable USD bid among the supplied supported bid inputs.
## App Open example
App Open is a fullscreen format, so it prepares a winner ahead of the placement exactly like interstitial and rewarded. What differs is the trigger. Nothing in your UI asks for an App Open ad, so the decision has to be made while the app is in the background — a user returning to the app must never wait on a load or an arbiter round trip.
`ArbiterAppOpenController` arbitrates a CloudX App Open ad against an AdMob App Open ad:
1. Load both platforms at launch, and again after every show.
2. Run the arbiter once both have settled, and store the winner.
3. On the next foreground transition, show the stored winner and start the next cycle.
4. If no winner is stored, return the user to the app without an ad and start a fresh load.
```swift ArbiterAppOpenController.swift
/// Arbitrates a CloudX App Open ad against an AdMob App Open ad and shows the winner on the
/// next foreground transition.
///
/// App Open has no user-initiated show, so the winner must already be chosen by the time the
/// user comes back. loadMissingAds() starts both loads, the arbiter runs as soon as both
/// platforms settle, and showOnForeground(from:) only shows a result that was stored earlier.
final class ArbiterAppOpenController: NSObject {
private let cloudXAppOpen: CLXAppOpen?
private let adMobAdUnitId: String
private var cloudXAd: CLXAd?
private var cloudXLoadDone = false
private var adMobAppOpen: GADAppOpenAd?
/// The ad that is presenting, or presented last. Held so a late paid event still has an ad.
private var presentedAdMobAd: GADAppOpenAd?
private var adMobLoadDone = false
private var cloudXLoadInFlight = false
private var adMobLoadInFlight = false
private var nextWinner: CLXArbiterPlatform?
private var isShowingAd = false
init(cloudXAdUnitId: String, adMobAdUnitId: String) {
cloudXAppOpen = CloudXCore.shared.createAppOpen(adUnitId: cloudXAdUnitId)
self.adMobAdUnitId = adMobAdUnitId
super.init()
cloudXAppOpen?.delegate = self
}
/// Starts a load for each platform that holds neither a cached ad nor a request in flight.
///
/// Each load it starts clears that platform's `LoadDone` flag, so a round only arbitrates
/// once every request it started has come back. Without that reset, a retry for the platform
/// that failed last round lands against a flag still set from the previous round, and the
/// arbiter runs on a one-candidate snapshot.
func loadMissingAds() {
if cloudXAd == nil, !cloudXLoadInFlight {
cloudXLoadInFlight = true
cloudXLoadDone = false
cloudXAppOpen?.load()
}
if adMobAppOpen == nil, !adMobLoadInFlight {
adMobLoadInFlight = true
adMobLoadDone = false
loadAdMob()
}
}
/// Shows the prepared winner, returning true only when a show call was made.
///
/// Call this from a foreground observer. It never holds up the return to the app: with an ad
/// already on screen, or no winner prepared, it returns false and starts the next load cycle.
func showOnForeground(from viewController: UIViewController) -> Bool {
guard !isShowingAd else { return false }
switch nextWinner?.name {
case CLXArbiterPlatform.cloudX.name:
return showCloudX(from: viewController)
case CLXArbiterPlatform.adMob.name:
return showAdMob(from: viewController)
case CLXArbiterPlatform.none.name:
// The arbiter settled on no winner, so both cached ads are unusable. Drop them:
// loadMissingAds() only requests a platform whose slot is empty, so without this
// the same two candidates would sit there and no later round could ever run.
discardCandidatesAndReload()
return false
default:
// nextWinner is nil — a cycle is still in flight. Leave it alone.
return false
}
}
/// Releases both platforms' ads. Call this from the owning object's teardown.
func destroy() {
cloudXAppOpen?.destroy()
adMobAppOpen = nil
presentedAdMobAd = nil
}
/// Runs the arbiter once both platforms have settled, then caches the winning platform.
///
/// Returns early until both loads complete. If neither platform loaded, it restarts the
/// load cycle; otherwise it submits the loaded candidates to CloudXCore.shared.arbiter.
private func maybePrepareWinner() {
guard cloudXLoadDone, adMobLoadDone else { return }
if cloudXAd == nil && adMobAppOpen == nil {
loadMissingAds()
return
}
var bids: [CLXArbiterBid] = []
if let cloudXAd {
bids.append(CLXArbiterBid.cloudX(ad: cloudXAd))
}
if adMobAppOpen != nil {
bids.append(CLXArbiterBid.adMob(adUnitId: adMobAdUnitId))
}
let configuration = CLXArbiterConfiguration.configuration(bids: bids, builderBlock: nil)
CloudXCore.shared.arbiter(with: configuration) { [weak self] result in
self?.nextWinner = result.platform
}
}
private func showCloudX(from viewController: UIViewController) -> Bool {
guard let cloudXAppOpen, cloudXAppOpen.isReady else {
clearCloudXAndLoadMissingAds()
return false
}
isShowingAd = true
cloudXAppOpen.show(from: viewController, placement: "app_foreground", customData: nil)
return true
}
private func showAdMob(from viewController: UIViewController) -> Bool {
guard let ad = adMobAppOpen else {
clearAdMobAndLoadMissingAds()
return false
}
ad.fullScreenContentDelegate = self
// A GADAppOpenAd is single-use: present consumes it. Move it aside now, so a load that
// lands mid-show cannot arbitrate a spent ad. It stays alive in presentedAdMobAd
// because AdMob can deliver the paid event after the ad has already been dismissed,
// and that revenue is what prices the next AdMob bid.
presentedAdMobAd = ad
adMobAppOpen = nil
isShowingAd = true
ad.present(fromRootViewController: viewController)
return true
}
private func loadAdMob() {
GADAppOpenAd.load(withAdUnitID: adMobAdUnitId, request: GADRequest()) { [weak self] ad, error in
guard let self else { return }
if let error {
print("AdMob App Open failed to load: \(error.localizedDescription)")
adMobAppOpen = nil
} else {
ad?.paidEventHandler = { [weak self, weak ad] adValue in
self?.reportAdMobPaidEvent(ad, adValue: adValue)
}
adMobAppOpen = ad
}
adMobLoadInFlight = false
adMobLoadDone = true
maybePrepareWinner()
}
}
/// Forwards AdMob's realized revenue to CloudX. This is a required part of the integration,
/// not optional analytics: CloudX prices future AdMob bids from what you report back.
///
/// revenuePrecision(from:) is the mapping shown in
/// "Report Google paid events back to CloudX" above.
private func reportAdMobPaidEvent(_ ad: GADAppOpenAd?, adValue: GADAdValue) {
let servedBy = ad?.responseInfo.loadedAdNetworkResponseInfo
let data = CLXRevenueData.revenueData(
platform: .adMob,
revenue: adValue.value.doubleValue,
adFormat: "app_open"
) { builder in
builder.currencyCode = adValue.currencyCode
builder.precision = revenuePrecision(from: adValue.precision)
builder.networkName = servedBy?.adSourceName
builder.adUnitId = self.adMobAdUnitId
builder.thirdPartyAdPlacementId = servedBy?.adSourceInstanceName
}
// reportRevenueData returns false when nothing consumed the event. CloudX prices future
// AdMob bids from what you report here, so a silent false is worth surfacing.
if !CloudXCore.shared.reportRevenueData(data) {
print("AdMob App Open paid event was not accepted by the CloudX SDK")
}
}
/// Drops both candidates and starts a new cycle. Used when the arbiter reports no winner.
private func discardCandidatesAndReload() {
cloudXAd = nil
adMobAppOpen = nil
cloudXLoadDone = false
adMobLoadDone = false
nextWinner = nil
loadMissingAds()
}
private func clearCloudXAndLoadMissingAds() {
cloudXAd = nil
cloudXLoadDone = false
nextWinner = nil
isShowingAd = false
loadMissingAds()
}
private func clearAdMobAndLoadMissingAds() {
adMobAppOpen = nil
adMobLoadDone = false
nextWinner = nil
isShowingAd = false
loadMissingAds()
}
}
extension ArbiterAppOpenController: CLXAppOpenDelegate {
func didLoad(_ ad: CLXAd) {
cloudXAd = ad
cloudXLoadInFlight = false
cloudXLoadDone = true
maybePrepareWinner()
}
func didFailToLoadAd(_ adUnitId: String, error: CLXError) {
print("CloudX App Open failed to load: \(error.localizedDescription)")
cloudXAd = nil
cloudXLoadInFlight = false
cloudXLoadDone = true
maybePrepareWinner()
}
func didDisplay(_ ad: CLXAd) {}
func didFailToDisplay(_ ad: CLXAd, error: CLXError) {
print("CloudX App Open failed to display: \(error.localizedDescription)")
clearCloudXAndLoadMissingAds()
}
func didHide(_ ad: CLXAd) {
clearCloudXAndLoadMissingAds()
}
func didClick(_ ad: CLXAd) {}
}
extension ArbiterAppOpenController: GADFullScreenContentDelegate {
func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
clearAdMobAndLoadMissingAds()
}
func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
print("AdMob App Open failed to show: \(error.localizedDescription)")
clearAdMobAndLoadMissingAds()
}
}
```
`showOnForeground(from:)` returns `true` only when a show call was made. `isShowingAd` keeps the ad's own fullscreen presentation from being read as a second foreground transition — without it, a scene or app lifecycle observer can re-enter `showOnForeground(from:)` while an ad is already on screen.
Drive it from `willEnterForegroundNotification`, which fires only when the app comes back from the background. Do not use `didBecomeActiveNotification`: that also fires every time the app merely resumes activity, so an App Open ad would appear after the App Tracking Transparency prompt, Control Center, or an incoming call.
```swift
// In the type that owns the controller, for example your scene delegate:
let controller = ArbiterAppOpenController(cloudXAdUnitId: "…", adMobAdUnitId: "…")
NotificationCenter.default.addObserver(
forName: UIApplication.willEnterForegroundNotification,
object: nil,
queue: .main
) { [weak self] _ in
guard let self, let viewController = self.topViewController() else { return }
_ = self.controller.showOnForeground(from: viewController)
}
```
A winning App Open bid reports its platform the same way as any other format: `CLXArbiterPlatform.cloudX` or `CLXArbiterPlatform.adMob`. Keep the App Open placement rules from [App Open Ads](/en/ios/ad-formats/app-open) — arbitration changes which network fills the placement, not when it is appropriate to show one.
## Banner and MREC arbitration
Banner and MREC are view-based formats: every candidate network renders its ad into a view as soon as it loads, whether or not that view ends up on screen. Trusted Arbiter still selects the winner the same way, but you take on two responsibilities that fullscreen formats don't have.
### Disable auto-refresh
Trusted Arbiter needs full control over when a new fill is requested and when the displayed ad changes, so each network's own refresh timer must be off:
* Disable auto-refresh for the ad unit in the CloudX dashboard.
* Call `stopAutoRefresh` on the `CLXBannerAdView` immediately after creating it (see [Banner Ads (320x50)](/en/ios/ad-formats/banner-mrec#banner-ads-320x50)).
* Disable auto-refresh on the equivalent API for every other network you arbitrate against.
### View attachment
Only the winning bid's view may be added to the view hierarchy. A losing network's banner view still renders and fires its own impression the moment it is attached to a superview, so hold every non-winning view off-screen (do not call `addSubview:`/`addSubview(_:)` on it) until, or unless, it wins a later round. This differs from the [standard banner integration](/en/ios/ad-formats/banner-mrec), which adds the view with `addSubview` at creation time — with Trusted Arbiter, the view must not be attached at creation, only after arbitration selects it as the winner.
### Refresh cycle
With auto-refresh off, drive the cycle yourself:
1. Run the parallel loads, submit the loaded candidates to the arbiter, and attach the winner's view.
2. As soon as the winner's impression fires, immediately start loading a new fill from the winning network.
3. Retain the non-winning networks' already-filled ads for the next round. Only re-request a load from a network that did not fill in the previous round.
4. Once the outstanding load responses come back, run the arbiter again over the current set of loaded candidates.
5. Refresh the displayed ad on a 20-30 second interval, swapping in the new winner's view each time. Refreshing faster than 20 seconds decreases CPM performance.
### Banner example
`ArbiterBannerController` below is the reference implementation of the arbitrate-then-render rule for view formats: it arbitrates a CloudX and LevelPlay banner, keeps exactly one view attached at a time, and drives the refresh cycle described above.
```objc Objective-C ArbiterBannerController.m
/// Arbitrates a CloudX and LevelPlay banner on a 20-30 second refresh cycle.
///
/// Attaches only the winning bid's view. Non-winning views are kept loaded but
/// detached so they never render or fire an impression. After the displayed
/// winner's impression fires, starts a new load from that network and keeps
/// the other network's already-filled ad for the next arbitration round.
@interface ArbiterBannerController ()
@property (nonatomic, weak) UIView *containerView;
@property (nonatomic, weak) UIViewController *presentingViewController;
@property (nonatomic, strong) CLXBannerAdView *cloudXBanner;
@property (nonatomic, strong) LPMBannerAdView *levelPlayBanner;
@property (nonatomic, strong) CLXAd *cloudXAd;
@property (nonatomic, assign) BOOL cloudXLoadDone;
@property (nonatomic, strong) LPMAdInfo *levelPlayAdInfo;
@property (nonatomic, assign) BOOL levelPlayLoadDone;
@property (nonatomic, copy) NSString *attachedPlatformName;
@property (nonatomic, strong) NSTimer *refreshTimer;
@end
@implementation ArbiterBannerController
- (instancetype)initWithContainerView:(UIView *)containerView
presentingViewController:(UIViewController *)presentingViewController
cloudXAdUnitId:(NSString *)cloudXAdUnitId
levelPlayAdUnitId:(NSString *)levelPlayAdUnitId {
self = [super init];
if (self) {
_containerView = containerView;
_presentingViewController = presentingViewController;
_cloudXBanner = [[CloudXCore shared] createBannerWithAdUnitId:cloudXAdUnitId];
_cloudXBanner.delegate = self;
_cloudXBanner.revenueDelegate = self;
[_cloudXBanner stopAutoRefresh];
LPMBannerAdViewConfigBuilder *levelPlayConfigBuilder = [[LPMBannerAdViewConfigBuilder alloc] init];
LPMBannerAdViewConfig *levelPlayConfig = [levelPlayConfigBuilder build];
_levelPlayBanner = [[LPMBannerAdView alloc] initWithAdUnitId:levelPlayAdUnitId
config:levelPlayConfig];
_levelPlayBanner.delegate = self;
// LevelPlay auto-refresh is disabled via LevelPlay's own dashboard/API configuration.
}
return self;
}
/// Starts a load for each network that does not currently hold a filled ad.
- (void)loadMissingAds {
if (!self.cloudXAd) { [self.cloudXBanner load]; }
if (!self.levelPlayAdInfo) {
[self.levelPlayBanner loadAdWithViewController:self.presentingViewController];
}
}
/// Starts the recurring 20-30 second refresh timer. Call once, after the first load cycle begins.
- (void)startRefreshTimer {
[self.refreshTimer invalidate];
self.refreshTimer = [NSTimer scheduledTimerWithTimeInterval:25.0
target:self
selector:@selector(runArbiterIfReady)
userInfo:nil
repeats:YES];
}
- (void)runArbiterIfReady {
if (!self.cloudXLoadDone || !self.levelPlayLoadDone) { return; }
NSMutableArray *bids = [NSMutableArray array];
if (self.cloudXAd) {
[bids addObject:[CLXArbiterBid cloudXBidWithAd:self.cloudXAd]];
}
if (self.levelPlayAdInfo) {
[bids addObject:[CLXArbiterBid levelPlayBidWithNetworkName:self.levelPlayAdInfo.adNetwork
revenue:self.levelPlayAdInfo.revenue.doubleValue
precision:self.levelPlayAdInfo.precision]];
}
if (bids.count == 0) { return; }
CLXArbiterConfiguration *configuration = [CLXArbiterConfiguration configurationWithBids:bids];
[[CloudXCore shared] arbiterWithConfiguration:configuration completion:^(CLXArbiterResult *result) {
[self attachWinner:result.platform.name];
}];
}
/// Detaches the previous winner's view, attaches the new winner's view, and starts a
/// fresh load from the winning network once its impression fires.
- (void)attachWinner:(NSString *)platformName {
[self.cloudXBanner removeFromSuperview];
[self.levelPlayBanner removeFromSuperview];
if ([platformName isEqualToString:CLXArbiterPlatform.cloudX.name]) {
[self.containerView addSubview:self.cloudXBanner];
self.attachedPlatformName = platformName;
} else if ([platformName isEqualToString:CLXArbiterPlatform.levelPlay.name]) {
[self.containerView addSubview:self.levelPlayBanner];
self.attachedPlatformName = platformName;
} else {
self.attachedPlatformName = nil;
}
}
#pragma mark - CLXBannerDelegate
- (void)didLoadAd:(CLXAd *)ad {
self.cloudXAd = ad;
self.cloudXLoadDone = YES;
[self runArbiterIfReady];
}
- (void)didFailToLoadAd:(NSString *)adUnitId error:(CLXError *)error {
self.cloudXAd = nil;
self.cloudXLoadDone = YES;
[self runArbiterIfReady];
}
#pragma mark - CLXAdRevenueDelegate
- (void)didPayRevenueForAd:(CLXAd *)ad {
if ([self.attachedPlatformName isEqualToString:CLXArbiterPlatform.cloudX.name]) {
self.cloudXAd = nil;
self.cloudXLoadDone = NO;
[self.cloudXBanner load];
}
}
#pragma mark - LPMBannerAdViewDelegate
- (void)didLoadAdWithAdInfo:(LPMAdInfo *)adInfo {
self.levelPlayAdInfo = adInfo;
self.levelPlayLoadDone = YES;
[self runArbiterIfReady];
}
- (void)didFailToLoadAdWithAdUnitId:(NSString *)adUnitId error:(NSError *)error {
self.levelPlayAdInfo = nil;
self.levelPlayLoadDone = YES;
[self runArbiterIfReady];
}
- (void)didDisplayAdWithAdInfo:(LPMAdInfo *)adInfo {
if ([self.attachedPlatformName isEqualToString:CLXArbiterPlatform.levelPlay.name]) {
self.levelPlayAdInfo = nil;
self.levelPlayLoadDone = NO;
[self.levelPlayBanner loadAdWithViewController:self.presentingViewController];
}
}
@end
```
```swift Swift ArbiterBannerController.swift
/// Arbitrates a CloudX and LevelPlay banner on a 20-30 second refresh cycle.
///
/// Attaches only the winning bid's view. Non-winning views are kept loaded but
/// detached so they never render or fire an impression. After the displayed
/// winner's impression fires, starts a new load from that network and keeps
/// the other network's already-filled ad for the next arbitration round.
final class ArbiterBannerController: NSObject {
private weak var containerView: UIView?
private weak var presentingViewController: UIViewController?
private let cloudXBanner: CLXBannerAdView
private let levelPlayBanner: LPMBannerAdView
private var cloudXAd: CLXAd?
private var cloudXLoadDone = false
private var levelPlayAdInfo: LPMAdInfo?
private var levelPlayLoadDone = false
private var attachedPlatformName: String?
private var refreshTimer: Timer?
init(containerView: UIView, presentingViewController: UIViewController, cloudXAdUnitId: String, levelPlayAdUnitId: String) {
self.containerView = containerView
self.presentingViewController = presentingViewController
cloudXBanner = CloudXCore.shared.createBanner(adUnitId: cloudXAdUnitId)
let levelPlayConfig = LPMBannerAdViewConfigBuilder().build()
levelPlayBanner = LPMBannerAdView(adUnitId: levelPlayAdUnitId, config: levelPlayConfig)
super.init()
cloudXBanner.delegate = self
cloudXBanner.revenueDelegate = self
cloudXBanner.stopAutoRefresh()
levelPlayBanner.setDelegate(self)
// LevelPlay auto-refresh is disabled via LevelPlay's own dashboard/API configuration.
}
/// Starts a load for each network that does not currently hold a filled ad.
func loadMissingAds() {
if cloudXAd == nil { cloudXBanner.load() }
if levelPlayAdInfo == nil, let presentingViewController {
levelPlayBanner.loadAd(with: presentingViewController)
}
}
/// Starts the recurring 20-30 second refresh timer. Call once, after the first load cycle begins.
func startRefreshTimer() {
refreshTimer?.invalidate()
refreshTimer = Timer.scheduledTimer(withTimeInterval: 25.0, repeats: true) { [weak self] _ in
self?.runArbiterIfReady()
}
}
private func runArbiterIfReady() {
guard cloudXLoadDone, levelPlayLoadDone else { return }
var bids: [CLXArbiterBid] = []
if let cloudXAd {
bids.append(CLXArbiterBid.cloudX(ad: cloudXAd))
}
if let levelPlayAdInfo {
bids.append(CLXArbiterBid.levelPlay(
networkName: levelPlayAdInfo.adNetwork,
revenue: levelPlayAdInfo.revenue?.doubleValue ?? 0,
precision: levelPlayAdInfo.precision
))
}
guard !bids.isEmpty else { return }
let configuration = CLXArbiterConfiguration.configuration(bids: bids, builderBlock: nil)
CloudXCore.shared.arbiter(with: configuration) { [weak self] result in
self?.attachWinner(result.platform.name)
}
}
/// Detaches the previous winner's view, attaches the new winner's view, and starts a
/// fresh load from the winning network once its impression fires.
private func attachWinner(_ platformName: String) {
cloudXBanner.removeFromSuperview()
levelPlayBanner.removeFromSuperview()
switch platformName {
case CLXArbiterPlatform.cloudX.name:
containerView?.addSubview(cloudXBanner)
attachedPlatformName = platformName
case CLXArbiterPlatform.levelPlay.name:
containerView?.addSubview(levelPlayBanner)
attachedPlatformName = platformName
default:
attachedPlatformName = nil
}
}
}
extension ArbiterBannerController: CLXBannerDelegate {
func didLoad(_ ad: CLXAd) {
cloudXAd = ad
cloudXLoadDone = true
runArbiterIfReady()
}
func didFailToLoadAd(_ adUnitId: String, error: CLXError) {
cloudXAd = nil
cloudXLoadDone = true
runArbiterIfReady()
}
}
extension ArbiterBannerController: CLXAdRevenueDelegate {
func didPayRevenue(for ad: CLXAd) {
guard attachedPlatformName == CLXArbiterPlatform.cloudX.name else { return }
cloudXAd = nil
cloudXLoadDone = false
cloudXBanner.load()
}
}
extension ArbiterBannerController: LPMBannerAdViewDelegate {
func didLoadAd(with adInfo: LPMAdInfo) {
levelPlayAdInfo = adInfo
levelPlayLoadDone = true
runArbiterIfReady()
}
func didFailToLoadAd(withAdUnitId adUnitId: String, error: Error) {
levelPlayAdInfo = nil
levelPlayLoadDone = true
runArbiterIfReady()
}
func didDisplayAd(with adInfo: LPMAdInfo) {
guard attachedPlatformName == CLXArbiterPlatform.levelPlay.name,
let presentingViewController else { return }
levelPlayAdInfo = nil
levelPlayLoadDone = false
levelPlayBanner.loadAd(with: presentingViewController)
}
}
```
`didPayRevenue(for:)` (CloudX's impression signal) and `didDisplayAd(with:)` (LevelPlay's impression signal) are what trigger the next load for whichever network is currently attached; the losing network's already-filled ad is left untouched until it either wins a round or is consumed. `runArbiterIfReady()` is invoked both by load callbacks and by the refresh timer, so a round only actually swaps the attached view when both networks have settled.
## Native example
Native is a view format, so it arbitrates and then renders — but unlike banner and MREC, a native ad has no view until you make one. That is what makes native the easiest format to get wrong: `loadAd(into:)` renders the ad at load time, before the arbiter has chosen anything.
> **Warning:** Load native candidates with `loadAd()` and no ad view. Rendering is what fires a native impression, so rendering a bid the arbiter has not selected bills an impression against a loser. Call `renderNativeAdView(_:with:)` only for the winner.
`ArbiterNativeController` arbitrates a CloudX native ad against an AdMob native ad and renders exactly one of them into the container view:
1. Load both platforms with no view attached.
2. Run the arbiter once both have settled.
3. Build a view for the winner only, render into it, and attach it.
4. Keep the losing platform's ad loaded and unrendered for the next round.
```swift ArbiterNativeController.swift
/// Arbitrates a CloudX native ad against an AdMob native ad and renders only the winner into
/// the container view.
///
/// Both platforms load without a view: CLXNativeAdLoader.loadAd() is called with no ad view so
/// nothing is rendered at load time, and the AdMob ad is held unrendered. The losing bid's
/// assets are never rendered or attached, so they never fire an impression.
final class ArbiterNativeController: NSObject {
private weak var containerView: UIView?
private let cloudXLoader: CLXNativeAdLoader
private let adMobAdUnitId: String
private let makeAdMobAdView: (GADNativeAd) -> GADNativeAdView
private var cloudXAd: CLXAd?
private var cloudXLoadDone = false
private var adMobNativeAd: GADNativeAd?
private var adMobLoadDone = false
private var adMobAdLoader: GADAdLoader?
private var cloudXLoadInFlight = false
private var adMobLoadInFlight = false
private var renderedPlatformName: String?
private var renderedAdView: UIView?
/// The ads behind the attached view. They outlive their own candidate state: a rendered ad
/// stays on screen after its impression, until its replacement is attached.
private var renderedCloudXAd: CLXAd?
private var renderedAdMobAd: GADNativeAd?
/// - Parameter makeAdMobAdView: builds and populates your own GADNativeAdView for a loaded
/// AdMob ad, exactly as in a non-arbitrated AdMob native integration. It is called only
/// for a winning AdMob bid.
init(
containerView: UIView,
cloudXAdUnitId: String,
adMobAdUnitId: String,
makeAdMobAdView: @escaping (GADNativeAd) -> GADNativeAdView
) {
self.containerView = containerView
cloudXLoader = CloudXCore.shared.createNativeAdLoader(adUnitIdentifier: cloudXAdUnitId)
self.adMobAdUnitId = adMobAdUnitId
self.makeAdMobAdView = makeAdMobAdView
super.init()
cloudXLoader.nativeAdDelegate = self
cloudXLoader.revenueDelegate = self
}
/// Starts the first round: loads every platform that is not already holding a fill.
func start() {
loadMissingAds()
}
/// Releases both platforms' ads and clears the container.
func destroy() {
detachRenderedAd()
cloudXLoader.destroy()
adMobNativeAd = nil
adMobAdLoader = nil
}
/// Starts a load for each platform that holds neither a filled ad nor a request in flight.
///
/// Each load it starts clears that platform's `LoadDone` flag, so a round only arbitrates
/// once every request it started has come back.
private func loadMissingAds() {
// loadAd() with no ad view defers rendering. Never use loadAd(into:) here: that renders
// the CloudX ad at load time, before the arbiter has chosen a winner.
if cloudXAd == nil, !cloudXLoadInFlight {
cloudXLoadInFlight = true
cloudXLoadDone = false
cloudXLoader.loadAd()
}
if adMobNativeAd == nil, !adMobLoadInFlight {
adMobLoadInFlight = true
adMobLoadDone = false
loadAdMob()
}
}
/// Runs once both platforms have settled (filled or failed), then renders the winner.
private func maybeArbitrate() {
guard cloudXLoadDone, adMobLoadDone else { return }
var bids: [CLXArbiterBid] = []
if let cloudXAd {
bids.append(CLXArbiterBid.cloudX(ad: cloudXAd))
}
if adMobNativeAd != nil {
bids.append(CLXArbiterBid.adMob(adUnitId: adMobAdUnitId))
}
guard !bids.isEmpty else {
loadMissingAds()
return
}
let configuration = CLXArbiterConfiguration.configuration(bids: bids, builderBlock: nil)
CloudXCore.shared.arbiter(with: configuration) { [weak self] result in
self?.renderWinner(result.platform)
}
}
/// Renders only the winning platform's assets; the loser stays unrendered and unattached.
private func renderWinner(_ platform: CLXArbiterPlatform) {
// Rendering is what fires the impression, so never render the same ad twice.
// maybeArbitrate() runs again whenever the losing candidate reloads, and the same
// platform winning that round is a normal outcome — but by then the served ad has been
// dropped as a candidate, so a repeat win is always a new ad and does render.
guard !isAlreadyRendered(platform) else { return }
guard platform.name != CLXArbiterPlatform.none.name else {
// No winner. Keep whatever is already on screen — detaching it here would drop a
// perfectly good ad — and drop only the candidates that lost, so the next round
// has something new to compare.
discardUnrenderedCandidatesAndReload()
return
}
detachRenderedAd()
switch platform.name {
case CLXArbiterPlatform.cloudX.name:
renderCloudX()
case CLXArbiterPlatform.adMob.name:
renderAdMob()
default:
break
}
}
/// True when this platform's current candidate is the ad already attached to the container.
private func isAlreadyRendered(_ platform: CLXArbiterPlatform) -> Bool {
switch platform.name {
case CLXArbiterPlatform.cloudX.name:
return cloudXAd != nil && cloudXAd === renderedCloudXAd
case CLXArbiterPlatform.adMob.name:
return adMobNativeAd != nil && adMobNativeAd === renderedAdMobAd
default:
return false
}
}
/// Drops every candidate that is not currently rendered, then starts a new cycle.
private func discardUnrenderedCandidatesAndReload() {
if renderedPlatformName != CLXArbiterPlatform.cloudX.name {
cloudXAd = nil
cloudXLoadDone = false
}
if renderedPlatformName != CLXArbiterPlatform.adMob.name {
adMobNativeAd = nil
adMobLoadDone = false
}
loadMissingAds()
}
private func renderCloudX() {
guard let cloudXAd, let containerView else { return }
let adView = CLXNativeAdView()
adView.bindViews(with: nativeAdViewBinder())
guard cloudXLoader.renderNativeAdView(adView, with: cloudXAd) else {
print("CloudX native ad could not be rendered; skipping this round")
clearCloudXAndReload()
return
}
adView.frame = containerView.bounds
adView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
containerView.addSubview(adView)
renderedAdView = adView
renderedPlatformName = CLXArbiterPlatform.cloudX.name
renderedCloudXAd = cloudXAd
}
private func renderAdMob() {
guard let adMobNativeAd, let containerView else { return }
let adView = makeAdMobAdView(adMobNativeAd)
adView.frame = containerView.bounds
adView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
containerView.addSubview(adView)
renderedAdView = adView
renderedPlatformName = CLXArbiterPlatform.adMob.name
renderedAdMobAd = adMobNativeAd
}
private func loadAdMob() {
let loader = GADAdLoader(
adUnitID: adMobAdUnitId,
rootViewController: nil,
adTypes: [.native],
options: nil
)
loader.delegate = self
adMobAdLoader = loader
loader.load(GADRequest())
}
/// Forwards AdMob's realized revenue to CloudX. This is a required part of the integration,
/// not optional analytics: CloudX prices future AdMob bids from what you report back.
///
/// revenuePrecision(from:) is the mapping shown in
/// "Report Google paid events back to CloudX" above.
private func reportAdMobPaidEvent(_ ad: GADNativeAd?, adValue: GADAdValue) {
let servedBy = ad?.responseInfo.loadedAdNetworkResponseInfo
let data = CLXRevenueData.revenueData(
platform: .adMob,
revenue: adValue.value.doubleValue,
adFormat: "native"
) { builder in
builder.currencyCode = adValue.currencyCode
builder.precision = revenuePrecision(from: adValue.precision)
builder.networkName = servedBy?.adSourceName
builder.adUnitId = self.adMobAdUnitId
builder.thirdPartyAdPlacementId = servedBy?.adSourceInstanceName
}
// reportRevenueData returns false when nothing consumed the event. CloudX prices future
// AdMob bids from what you report here, so a silent false is worth surfacing.
if !CloudXCore.shared.reportRevenueData(data) {
print("AdMob native paid event was not accepted by the CloudX SDK")
}
}
/// Removes only the ad view this controller attached, so anything else the publisher keeps
/// in the container — a placeholder, a spinner, a label — survives the round.
///
/// This is also the only place a rendered ad is released. It stays alive for as long as it
/// is on screen, and goes away when its replacement is attached or the controller is
/// destroyed.
private func detachRenderedAd() {
renderedAdView?.removeFromSuperview()
renderedAdView = nil
renderedPlatformName = nil
if let renderedCloudXAd { cloudXLoader.destroyAd(renderedCloudXAd) }
renderedCloudXAd = nil
renderedAdMobAd = nil
}
/// Drops the CloudX candidate, detaching it first if it is the one on screen.
private func clearCloudXAndReload() {
if renderedPlatformName == CLXArbiterPlatform.cloudX.name {
detachRenderedAd()
}
cloudXAd = nil
cloudXLoadDone = false
cloudXLoadInFlight = false
loadMissingAds()
}
/// Starts the next cycle once the winner's impression has fired.
///
/// The rendered view stays on screen and the ad behind it stays alive. A native slot has
/// nothing to fall back to, so detaching the ad the moment it is billed would leave the slot
/// blank for as long as the next round takes. Only the candidate state is dropped, so the
/// spent ad is never bid or rendered again; the ad itself is released in detachRenderedAd(),
/// when its replacement goes up. This is the same rule the banner controller follows.
private func onWinnerImpression(_ platformName: String) {
switch platformName {
case CLXArbiterPlatform.cloudX.name:
cloudXAd = nil
cloudXLoadDone = false
cloudXLoadInFlight = false
case CLXArbiterPlatform.adMob.name:
adMobNativeAd = nil
adMobLoadDone = false
adMobLoadInFlight = false
default:
return
}
loadMissingAds()
}
private func nativeAdViewBinder() -> CLXNativeAdViewBinder {
CLXNativeAdViewBinder { builder in
builder.titleLabelTag = CLXNativeAdViewTagTitleLabel
builder.bodyLabelTag = CLXNativeAdViewTagBodyLabel
builder.iconImageViewTag = CLXNativeAdViewTagIconImageView
builder.callToActionButtonTag = CLXNativeAdViewTagCallToActionButton
builder.mediaContentViewTag = CLXNativeAdViewTagMediaViewContainer
builder.optionsContentViewTag = CLXNativeAdViewTagOptionsContentView
builder.advertiserLabelTag = CLXNativeAdViewTagAdvertiserLabel
}
}
}
extension ArbiterNativeController: CLXNativeAdDelegate {
// nativeAdView is nil here because loadAd() was called without one. The ad is rendered
// later, and only if it wins.
func didLoadNativeAd(_ nativeAdView: CLXNativeAdView?, for ad: CLXAd) {
cloudXAd = ad
cloudXLoadInFlight = false
cloudXLoadDone = true
maybeArbitrate()
}
func didFailToLoadNativeAd(forAdUnitIdentifier adUnitId: String, error: CLXError) {
print("CloudX native ad failed to load: \(error.localizedDescription)")
cloudXAd = nil
cloudXLoadInFlight = false
cloudXLoadDone = true
maybeArbitrate()
}
func didClickNativeAd(_ ad: CLXAd) {}
func didExpireNativeAd(_ ad: CLXAd) {
// detachRenderedAd() releases the rendered ad, so hand it this one instead of
// destroying it twice.
if ad !== renderedCloudXAd { cloudXLoader.destroyAd(ad) }
clearCloudXAndReload()
}
func didCloseNativeAd(_ ad: CLXAd) {
if ad !== renderedCloudXAd { cloudXLoader.destroyAd(ad) }
clearCloudXAndReload()
}
}
extension ArbiterNativeController: GADNativeAdLoaderDelegate {
func adLoader(_ adLoader: GADAdLoader, didReceive nativeAd: GADNativeAd) {
nativeAd.paidEventHandler = { [weak self, weak nativeAd] adValue in
self?.reportAdMobPaidEvent(nativeAd, adValue: adValue)
}
nativeAd.delegate = self
adMobNativeAd = nativeAd
adMobLoadInFlight = false
adMobLoadDone = true
maybeArbitrate()
}
func adLoader(_ adLoader: GADAdLoader, didFailToReceiveAdWithError error: Error) {
print("AdMob native ad failed to load: \(error.localizedDescription)")
adMobNativeAd = nil
adMobLoadInFlight = false
adMobLoadDone = true
maybeArbitrate()
}
}
extension ArbiterNativeController: CLXAdRevenueDelegate {
/// CloudX's impression signal. The rendered ad is spent as a candidate, so request the next
/// one — the same rule the banner controller follows.
func didPayRevenue(for ad: CLXAd) {
guard renderedPlatformName == CLXArbiterPlatform.cloudX.name else { return }
onWinnerImpression(CLXArbiterPlatform.cloudX.name)
}
}
extension ArbiterNativeController: GADNativeAdDelegate {
/// The winning AdMob ad is consumed the moment its impression fires, so request the next one
/// here. Without this the controller keeps bidding a spent ad for the rest of the session.
func nativeAdDidRecordImpression(_ nativeAd: GADNativeAd) {
guard renderedPlatformName == CLXArbiterPlatform.adMob.name else { return }
onWinnerImpression(CLXArbiterPlatform.adMob.name)
}
}
```
`didLoadNativeAd(_:for:)` delivers a nil `nativeAdView` because the load was started without one — that nil is the signal that rendering is still yours to trigger. Build the `CLXNativeAdView` in `renderCloudX()` and pass it to `renderNativeAdView(_:with:)` only after the arbiter has named CloudX the winner. That call returns `false` when the ad can no longer be rendered, which is treated here as a lost round rather than ignored.
The losing platform keeps its filled ad for the next round, so only the platform that was consumed or expired is re-requested. A rendered ad is spent, so each platform drops it as a candidate as soon as the impression fires — `didPayRevenue(for:)` for CloudX, `nativeAdDidRecordImpression(_:)` for AdMob — and requests a new one.
Neither handler detaches the view. A native slot has nothing to fall back to, so removing the served ad at its impression would leave the slot blank for as long as the next round takes. The rendered ad stays up and is released in `detachRenderedAd()`, at the moment its replacement is attached or the controller is destroyed — the same rule the banner controller follows. An ad that expires or is closed while it is on screen is the one case that does detach early, which is why `renderedPlatformName` is tracked.
`renderWinner(_:)` returns early when the arbiter picks the platform already on screen, because rendering is what fires the impression and that round did not change the winner.
To refresh a native slot on a timer, use the same cycle as [Banner and MREC arbitration](#banner-and-mrec-arbitration): re-arbitrate on a 20-30 second interval and swap the rendered view for the new winner's. For a Reels-style feed, create one controller per slot — see [Native Ads](/en/ios/ad-formats/native).
## Custom Bid Inputs
Use `CLXArbiterBid.custom(...)` when you want Trusted Arbiter to compare CloudX with a third-party platform that does not have a dedicated bid helper.
```objc Objective-C
CLXArbiterBid *customBid =
[CLXArbiterBid customBidWithPlatformName:@"my_mediation_platform"
networkName:@"winning_demand_source"
revenuePerImpressionUSD:0.00125
precision:CLXArbiterPrecision.exact
extras:@{@"ad_unit": @"third-party-ad-unit-id"}];
CLXArbiterConfiguration *configuration =
[CLXArbiterConfiguration configurationWithBids:@[
[CLXArbiterBid cloudXBidWithAd:cloudXAd],
customBid
]];
```
```swift Swift
let customBid = CLXArbiterBid.custom(
platformName: "my_mediation_platform",
networkName: "winning_demand_source",
revenuePerImpressionUSD: 0.00125,
precision: .exact,
extras: ["ad_unit": "third-party-ad-unit-id"]
)
let configuration = CLXArbiterConfiguration.configuration(
bids: [CLXArbiterBid.cloudX(ad: cloudXAd), customBid],
builderBlock: nil
)
```
When a custom bid wins, `result.platform` is `CLXArbiterPlatform.custom` and `result.platformName` contains the `platformName` supplied on the bid. Pass `revenuePerImpressionUSD` as revenue for one impression in USD, not CPM. Use `CLXArbiterPrecision.exact`, `estimated`, `publisherDefined`, or `undefined` to describe that revenue value.
---
# Banner & MREC Ads
> Integrate CloudX banner and MREC ads in iOS apps
Banner and MREC placements support standard network display ads, CloudX-rendered HTML creatives with MRAID 3.0 support, and native creatives served into banner/MREC slots when configured server-side. Native-in-banner and native-in-MREC demand is supported for Meta, Vungle, InMobi, Moloco, Digital Turbine, Mintegral, Pangle, and Verve without app-side API changes.
## Banner Ads (320x50)
```objc Objective-C
@interface YourViewController ()
@property (nonatomic, strong) CLXBannerAdView *bannerAd;
@end
@implementation YourViewController
- (void)createBannerAd {
self.bannerAd = [[CloudXCore shared] createBannerWithAdUnitId:@"your-banner-ad-unit-id"];
self.bannerAd.delegate = self;
self.bannerAd.revenueDelegate = self;
if (self.bannerAd) {
self.bannerAd.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.bannerAd];
[NSLayoutConstraint activateConstraints:@[
[self.bannerAd.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor],
[self.bannerAd.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[self.bannerAd.widthAnchor constraintEqualToConstant:320],
[self.bannerAd.heightAnchor constraintEqualToConstant:50]
]];
[self.bannerAd load];
}
}
- (void)dealloc {
[self.bannerAd destroy];
}
#pragma mark - CLXBannerDelegate
- (void)didLoadAd:(CLXAd *)ad {
NSLog(@"Banner ad loaded from %@", ad.networkName);
}
- (void)didFailToLoadAd:(NSString *)adUnitId error:(CLXError *)error {
NSLog(@"Banner ad failed to load: %@", error.localizedDescription);
}
- (void)didClickAd:(CLXAd *)ad {
NSLog(@"Banner ad clicked");
}
// Optional: Called when banner expands (e.g., MRAID)
- (void)didExpandAd:(CLXAd *)ad {
NSLog(@"Banner ad expanded");
}
// Optional: Called when banner collapses
- (void)didCollapseAd:(CLXAd *)ad {
NSLog(@"Banner ad collapsed");
}
#pragma mark - CLXAdRevenueDelegate
- (void)didPayRevenueForAd:(CLXAd *)ad {
NSLog(@"Banner revenue: %@ from %@", ad.revenue, ad.networkName);
}
@end
```
```swift Swift
class YourViewController: UIViewController, CLXBannerDelegate, CLXAdRevenueDelegate {
private var bannerAd: CLXBannerAdView?
func createBannerAd() {
bannerAd = CloudXCore.shared.createBanner(adUnitId: "your-banner-ad-unit-id")
bannerAd?.delegate = self
bannerAd?.revenueDelegate = self
if let bannerAd = bannerAd {
bannerAd.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(bannerAd)
NSLayoutConstraint.activate([
bannerAd.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
bannerAd.centerXAnchor.constraint(equalTo: view.centerXAnchor),
bannerAd.widthAnchor.constraint(equalToConstant: 320),
bannerAd.heightAnchor.constraint(equalToConstant: 50)
])
bannerAd.load()
}
}
deinit {
bannerAd?.destroy()
}
// MARK: - CLXBannerDelegate
func didLoad(_ ad: CLXAd) {
print("Banner ad loaded from \(ad.networkName ?? "unknown")")
}
func didFailToLoadAd(_ adUnitId: String, error: CLXError) {
print("Banner ad failed to load: \(error.localizedDescription)")
}
func didClick(_ ad: CLXAd) {
print("Banner ad clicked")
}
// Optional: Called when banner expands (e.g., MRAID)
func didExpand(_ ad: CLXAd) {
print("Banner ad expanded")
}
// Optional: Called when banner collapses
func didCollapse(_ ad: CLXAd) {
print("Banner ad collapsed")
}
// MARK: - CLXAdRevenueDelegate
func didPayRevenue(for ad: CLXAd) {
print("Banner revenue: \(ad.revenue ?? 0) from \(ad.networkName ?? "unknown")")
}
}
```
Banner ads auto-refresh by default. To control refresh manually:
```objc Objective-C
[self.bannerAd stopAutoRefresh]; // Stop auto-refresh
[self.bannerAd load]; // Manually load a new ad
[self.bannerAd startAutoRefresh]; // Re-enable auto-refresh
```
```swift Swift
bannerAd?.stopAutoRefresh() // Stop auto-refresh
bannerAd?.load() // Manually load a new ad
bannerAd?.startAutoRefresh() // Re-enable auto-refresh
```
Optional placement and custom data for tracking:
```objc Objective-C
self.bannerAd.placement = @"home_screen";
self.bannerAd.customData = @"level:5,coins:100";
```
```swift Swift
bannerAd?.placement = "home_screen"
bannerAd?.customData = "level:5,coins:100"
```
## MREC Ads (300x250)
```objc Objective-C
@interface YourViewController ()
@property (nonatomic, strong) CLXBannerAdView *mrecAd;
@end
@implementation YourViewController
- (void)createMRECAd {
self.mrecAd = [[CloudXCore shared] createMRECWithAdUnitId:@"your-mrec-ad-unit-id"];
self.mrecAd.delegate = self;
self.mrecAd.revenueDelegate = self;
if (self.mrecAd) {
self.mrecAd.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.mrecAd];
[NSLayoutConstraint activateConstraints:@[
[self.mrecAd.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
[self.mrecAd.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.bottomAnchor constant:-20],
[self.mrecAd.widthAnchor constraintEqualToConstant:300],
[self.mrecAd.heightAnchor constraintEqualToConstant:250]
]];
[self.mrecAd load];
}
}
- (void)dealloc {
[self.mrecAd destroy];
}
#pragma mark - CLXBannerDelegate
- (void)didLoadAd:(CLXAd *)ad {
NSLog(@"MREC ad loaded from %@", ad.networkName);
}
- (void)didFailToLoadAd:(NSString *)adUnitId error:(CLXError *)error {
NSLog(@"MREC ad failed to load: %@", error.localizedDescription);
}
- (void)didClickAd:(CLXAd *)ad {
NSLog(@"MREC ad clicked");
}
// Optional: Called when MREC expands (e.g., MRAID)
- (void)didExpandAd:(CLXAd *)ad {
NSLog(@"MREC ad expanded");
}
// Optional: Called when MREC collapses
- (void)didCollapseAd:(CLXAd *)ad {
NSLog(@"MREC ad collapsed");
}
#pragma mark - CLXAdRevenueDelegate
- (void)didPayRevenueForAd:(CLXAd *)ad {
NSLog(@"MREC revenue: %@ from %@", ad.revenue, ad.networkName);
}
@end
```
```swift Swift
class YourViewController: UIViewController, CLXBannerDelegate, CLXAdRevenueDelegate {
private var mrecAd: CLXBannerAdView?
func createMRECAd() {
mrecAd = CloudXCore.shared.createMREC(adUnitId: "your-mrec-ad-unit-id")
mrecAd?.delegate = self
mrecAd?.revenueDelegate = self
if let mrecAd = mrecAd {
mrecAd.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(mrecAd)
NSLayoutConstraint.activate([
mrecAd.centerXAnchor.constraint(equalTo: view.centerXAnchor),
mrecAd.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
mrecAd.widthAnchor.constraint(equalToConstant: 300),
mrecAd.heightAnchor.constraint(equalToConstant: 250)
])
mrecAd.load()
}
}
deinit {
mrecAd?.destroy()
}
// MARK: - CLXBannerDelegate
func didLoad(_ ad: CLXAd) {
print("MREC ad loaded from \(ad.networkName ?? "unknown")")
}
func didFailToLoadAd(_ adUnitId: String, error: CLXError) {
print("MREC ad failed to load: \(error.localizedDescription)")
}
func didClick(_ ad: CLXAd) {
print("MREC ad clicked")
}
// Optional: Called when MREC expands (e.g., MRAID)
func didExpand(_ ad: CLXAd) {
print("MREC ad expanded")
}
// Optional: Called when MREC collapses
func didCollapse(_ ad: CLXAd) {
print("MREC ad collapsed")
}
// MARK: - CLXAdRevenueDelegate
func didPayRevenue(for ad: CLXAd) {
print("MREC revenue: \(ad.revenue ?? 0) from \(ad.networkName ?? "unknown")")
}
}
```
MREC ads also auto-refresh by default. Use the same refresh control methods as Banner ads.
---
# Interstitial Ads
> Integrate CloudX interstitial ads in iOS apps
```objc Objective-C
@interface YourViewController ()
@property (nonatomic, strong) CLXInterstitial *interstitialAd;
@property (nonatomic, assign) NSInteger retryAttempt;
@end
@implementation YourViewController
- (void)createInterstitialAd {
self.interstitialAd = [[CloudXCore shared] createInterstitialWithAdUnitId:@"your-interstitial-ad-unit-id"];
self.interstitialAd.delegate = self;
self.interstitialAd.revenueDelegate = self;
[self.interstitialAd load];
}
- (void)showInterstitialAd {
if (self.interstitialAd.isReady) {
// Basic show
[self.interstitialAd showFromViewController:self];
// Or with optional placement and custom data for tracking
// [self.interstitialAd showFromViewController:self placement:@"level_complete" customData:@"level:5,score:1000"];
} else {
NSLog(@"Interstitial ad not ready");
}
}
- (void)dealloc {
[self.interstitialAd destroy];
}
#pragma mark - CLXInterstitialDelegate
- (void)didLoadAd:(CLXAd *)ad {
NSLog(@"Interstitial ad loaded from %@", ad.networkName);
self.retryAttempt = 0;
}
- (void)didFailToLoadAd:(NSString *)adUnitId error:(CLXError *)error {
NSLog(@"Interstitial ad failed to load: %@", error.localizedDescription);
self.retryAttempt++;
NSTimeInterval delay = pow(2.0, MIN(5, self.retryAttempt));
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.interstitialAd load];
});
}
- (void)didDisplayAd:(CLXAd *)ad {
NSLog(@"Interstitial ad displayed");
}
- (void)didFailToDisplayAd:(CLXAd *)ad error:(CLXError *)error {
NSLog(@"Interstitial ad failed to display: %@", error.localizedDescription);
[self.interstitialAd load];
}
- (void)didHideAd:(CLXAd *)ad {
NSLog(@"Interstitial ad hidden");
[self.interstitialAd load]; // Reload for next use
}
- (void)didClickAd:(CLXAd *)ad {
NSLog(@"Interstitial ad clicked");
}
#pragma mark - CLXAdRevenueDelegate
- (void)didPayRevenueForAd:(CLXAd *)ad {
NSLog(@"Interstitial revenue: %@ from %@", ad.revenue, ad.networkName);
}
@end
```
```swift Swift
class YourViewController: UIViewController, CLXInterstitialDelegate, CLXAdRevenueDelegate {
private var interstitialAd: CLXInterstitial?
private var retryAttempt = 0
func createInterstitialAd() {
interstitialAd = CloudXCore.shared.createInterstitial(adUnitId: "your-interstitial-ad-unit-id")
interstitialAd?.delegate = self
interstitialAd?.revenueDelegate = self
interstitialAd?.load()
}
func showInterstitialAd() {
if interstitialAd?.isReady == true {
// Basic show
interstitialAd?.show(from: self)
// Or with optional placement and custom data for tracking
// interstitialAd?.show(from: self, placement: "level_complete", customData: "level:5,score:1000")
} else {
print("Interstitial ad not ready")
}
}
deinit {
interstitialAd?.destroy()
}
// MARK: - CLXInterstitialDelegate
func didLoad(_ ad: CLXAd) {
print("Interstitial ad loaded from \(ad.networkName ?? "unknown")")
retryAttempt = 0
}
func didFailToLoadAd(_ adUnitId: String, error: CLXError) {
print("Interstitial ad failed to load: \(error.localizedDescription)")
retryAttempt += 1
let delay = pow(2.0, Double(min(5, retryAttempt)))
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
self?.interstitialAd?.load()
}
}
func didDisplay(_ ad: CLXAd) {
print("Interstitial ad displayed")
}
func didFailToDisplay(_ ad: CLXAd, error: CLXError) {
print("Interstitial ad failed to display: \(error.localizedDescription)")
interstitialAd?.load()
}
func didHide(_ ad: CLXAd) {
print("Interstitial ad hidden")
interstitialAd?.load() // Reload for next use
}
func didClick(_ ad: CLXAd) {
print("Interstitial ad clicked")
}
// MARK: - CLXAdRevenueDelegate
func didPayRevenue(for ad: CLXAd) {
print("Interstitial revenue: \(ad.revenue ?? 0) from \(ad.networkName ?? "unknown")")
}
}
```
#### Expired ads
A loaded ad that is not shown before it expires is reloaded automatically on the same ad object. The current ad stays ready and can still be shown until the replacement is ready.
* To be told when a replacement is ready, set an `expirationDelegate`. A replacement that loads on its own does not call `didLoadAd:` (Swift `didLoad(_:)`) again.
* If you show the ad while the replacement loads, the replacement is kept. It becomes ready after the ad closes, and the `expirationDelegate` is called then.
* If you call `load` while the replacement is loading, including a `load` from `didHideAd:` (Swift `didHide(_:)`), no second request starts. You receive `didLoadAd:` with the new ad, followed by `didReloadExpiredAd:withNewAd:` (Swift `didReloadExpiredAd(_:with:)`).
* If the replacement fails to load while the expired ad is still loaded, `didFailToLoadAd:error:` (Swift `didFailToLoadAd(_:error:)`) fires and the ad is no longer ready. Call `load` again, after a delay rather than in a tight loop.
```objc Objective-C
// Add CLXAdExpirationDelegate to the protocols YourViewController adopts
self.interstitialAd.expirationDelegate = self;
- (void)didReloadExpiredAd:(CLXAd *)expiredAd withNewAd:(CLXAd *)newAd {
NSLog(@"Expired interstitial ad replaced by an ad from %@", newAd.networkName);
}
```
```swift Swift
// Add CLXAdExpirationDelegate to the protocols YourViewController adopts
interstitialAd?.expirationDelegate = self
func didReloadExpiredAd(_ expiredAd: CLXAd, with newAd: CLXAd) {
print("Expired interstitial ad replaced by an ad from \(newAd.networkName ?? "unknown")")
}
```
---
# Native Ads
> Load and render CloudX native ads in custom iOS layouts
Native ads expose individual creative assets—such as the title, body, icon, media, and call to action—so you can render them in a layout that matches your app. CloudX populates the bound views and delegates impression and click tracking to the winning network adapter.
The integration has three steps:
1. Bind your layout's UI components.
2. Create a loader and load or render the native ad.
3. Destroy the ad and loader when they are no longer needed.
> **Note:** This page covers standalone Native ad units loaded with `CLXNativeAdLoader`. Native Banner and Native MREC creatives served in Banner or MREC slots use the regular [`CLXBannerAdView` integration](/en/ios/ad-formats/banner-mrec); they do not use a publisher-provided native layout or binder.
## Supported networks
Per-network Native support — including the Native Banner and Native MREC variants — is maintained in the [Native ad format matrix](/en/ad-formats/native). Adapter-specific dependencies and setup notes are documented on each adapter's overview page.
## 1. Bind UI components
Create a `CLXNativeAdView` using a view binder that maps your custom subviews to asset roles. A network may omit optional assets, so make sure your layout can collapse or hide empty views.
> **Warning:** Networks require their key assets — most importantly the media container bound with `mediaContentViewTag` — to be rendered and visible on screen, or they do not count the impression. The ad still displays, so the revenue loss is silent. Follow each network's asset requirements on its adapter overview page, linked from the [Native ad format matrix](/en/ad-formats/native).
The options container is required: CloudX uses it to display the network-provided AdChoices or privacy control.
```objc Objective-C
- (CLXNativeAdView *)createNativeAdView {
CLXNativeAdViewBinder *binder = [[CLXNativeAdViewBinder alloc] initWithBuilderBlock:^(CLXNativeAdViewBinderBuilder *builder) {
builder.titleLabelTag = CLXNativeAdViewTagTitleLabel;
builder.bodyLabelTag = CLXNativeAdViewTagBodyLabel;
builder.iconImageViewTag = CLXNativeAdViewTagIconImageView;
builder.callToActionButtonTag = CLXNativeAdViewTagCallToActionButton;
builder.mediaContentViewTag = CLXNativeAdViewTagMediaViewContainer;
builder.optionsContentViewTag = CLXNativeAdViewTagOptionsContentView;
builder.advertiserLabelTag = CLXNativeAdViewTagAdvertiserLabel;
builder.starRatingContentViewTag = CLXNativeAdViewTagStarRatingContentView;
}];
CLXNativeAdView *adView = [[CLXNativeAdView alloc] init];
[adView bindViewsWithViewBinder:binder];
return adView;
}
```
```swift Swift
func createNativeAdView() -> CLXNativeAdView {
let binder = CLXNativeAdViewBinder { builder in
builder.titleLabelTag = CLXNativeAdViewTagTitleLabel
builder.bodyLabelTag = CLXNativeAdViewTagBodyLabel
builder.iconImageViewTag = CLXNativeAdViewTagIconImageView
builder.callToActionButtonTag = CLXNativeAdViewTagCallToActionButton
builder.mediaContentViewTag = CLXNativeAdViewTagMediaViewContainer
builder.optionsContentViewTag = CLXNativeAdViewTagOptionsContentView
builder.advertiserLabelTag = CLXNativeAdViewTagAdvertiserLabel
builder.starRatingContentViewTag = CLXNativeAdViewTagStarRatingContentView
}
let adView = CLXNativeAdView()
adView.bindViews(with: binder)
return adView
}
```
Alternatively, set outlets directly on the `CLXNativeAdView`:
```objc Objective-C
CLXNativeAdView *adView = [[CLXNativeAdView alloc] init];
adView.titleLabel = myTitleLabel;
adView.bodyLabel = myBodyLabel;
adView.iconImageView = myIconImageView;
adView.callToActionButton = myCTAButton;
adView.mediaContentView = myMediaContainer;
adView.optionsContentView = myOptionsContainer;
adView.advertiserLabel = myAdvertiserLabel;
```
```swift Swift
let adView = CLXNativeAdView()
adView.titleLabel = myTitleLabel
adView.bodyLabel = myBodyLabel
adView.iconImageView = myIconImageView
adView.callToActionButton = myCTAButton
adView.mediaContentView = myMediaContainer
adView.optionsContentView = myOptionsContainer
adView.advertiserLabel = myAdvertiserLabel
```
### Star rating
CloudX fills the star rating container only when the network provides a rating of 3.0 or higher; otherwise the container is left empty. Design your layout so an empty star rating container collapses or is hidden.
## 2. Create a loader
```objc Objective-C
@interface YourViewController ()
@property (nonatomic, strong) CLXNativeAdLoader *nativeAdLoader;
@end
@implementation YourViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.nativeAdLoader = [[CloudXCore shared] createNativeAdLoaderWithAdUnitIdentifier:@"your-native-ad-unit-id"];
self.nativeAdLoader.nativeAdDelegate = self;
self.nativeAdLoader.revenueDelegate = self;
}
- (void)dealloc {
[self.nativeAdLoader destroy];
}
@end
```
```swift Swift
class YourViewController: UIViewController, CLXNativeAdDelegate, CLXAdRevenueDelegate {
private var nativeAdLoader: CLXNativeAdLoader?
override func viewDidLoad() {
super.viewDidLoad()
nativeAdLoader = CloudXCore.shared.createNativeAdLoader(adUnitIdentifier: "your-native-ad-unit-id")
nativeAdLoader?.nativeAdDelegate = self
nativeAdLoader?.revenueDelegate = self
}
deinit {
nativeAdLoader?.destroy()
}
}
```
### Load the ad
Choose one loading flow.
#### Load into a pre-built view
Use this flow when the destination view is already available. CloudX populates and registers the view before returning it through `didLoadNativeAd`.
```objc Objective-C
CLXNativeAdView *adView = [self createNativeAdView];
[self.nativeAdLoader loadAdIntoAdView:adView];
```
```swift Swift
let adView = createNativeAdView()
nativeAdLoader?.loadAd(into: adView)
```
#### Load and render separately
Use deferred rendering when you want to load the ad before its destination view exists. Call `renderNativeAdView(...)` before adding the view to your hierarchy.
```objc Objective-C
[self.nativeAdLoader loadAd];
- (void)didLoadNativeAd:(nullable CLXNativeAdView *)nativeAdView forAd:(CLXAd *)ad {
CLXNativeAdView *adView = /* create your ad view */;
[self.nativeAdLoader renderNativeAdView:adView withAd:ad];
[self.view addSubview:adView];
}
```
```swift Swift
nativeAdLoader?.loadAd()
func didLoadNativeAd(_ nativeAdView: CLXNativeAdView?, for ad: CLXAd) {
let adView = /* create your ad view */
nativeAdLoader?.renderNativeAdView(adView, with: ad)
view.addSubview(adView)
}
```
### Handle callbacks
Loaded native ads expire one hour after load; `didExpireNativeAd` fires so you can destroy the expired ad and load a fresh one. In `didFailToLoadNativeAd`, avoid immediately retrying in a tight loop — retry after a delay or at the next natural display opportunity.
```objc Objective-C
#pragma mark - CLXNativeAdDelegate (Required)
- (void)didLoadNativeAd:(nullable CLXNativeAdView *)nativeAdView forAd:(CLXAd *)ad {
NSLog(@"Native ad loaded from %@", ad.networkName);
if (ad.nativeAd.isVideoContent) {
NSLog(@"Video duration: %.1fs", ad.nativeAd.videoDuration);
}
if (nativeAdView) {
[self.view addSubview:nativeAdView];
}
}
- (void)didFailToLoadNativeAdForAdUnitIdentifier:(NSString *)adUnitId error:(CLXError *)error {
NSLog(@"Native ad failed to load: %@", error.localizedDescription);
}
- (void)didClickNativeAd:(CLXAd *)ad {
NSLog(@"Native ad clicked");
}
#pragma mark - CLXNativeAdDelegate (Optional)
- (void)didExpireNativeAd:(CLXAd *)ad {
NSLog(@"Native ad expired — destroy and reload");
[self.nativeAdLoader destroyAd:ad];
[self.nativeAdLoader loadAd];
}
- (void)didCloseNativeAd:(CLXAd *)ad {
NSLog(@"User dismissed the ad via AdChoices");
[self.nativeAdLoader destroyAd:ad];
}
#pragma mark - CLXAdRevenueDelegate
- (void)didPayRevenueForAd:(CLXAd *)ad {
NSLog(@"Native ad revenue: %@ from %@", ad.revenue, ad.networkName);
}
```
```swift Swift
// MARK: - CLXNativeAdDelegate (Required)
func didLoadNativeAd(_ nativeAdView: CLXNativeAdView?, for ad: CLXAd) {
print("Native ad loaded from \(ad.networkName ?? "unknown")")
if let nativeAd = ad.nativeAd, nativeAd.isVideoContent {
print("Video duration: \(nativeAd.videoDuration)s")
}
if let nativeAdView = nativeAdView {
view.addSubview(nativeAdView)
}
}
func didFailToLoadNativeAd(forAdUnitIdentifier adUnitId: String, error: CLXError) {
print("Native ad failed to load: \(error.localizedDescription)")
}
func didClickNativeAd(_ ad: CLXAd) {
print("Native ad clicked")
}
// MARK: - CLXNativeAdDelegate (Optional)
func didExpireNativeAd(_ ad: CLXAd) {
print("Native ad expired — destroy and reload")
nativeAdLoader?.destroyAd(ad)
nativeAdLoader?.loadAd()
}
func didCloseNativeAd(_ ad: CLXAd) {
print("User dismissed the ad via AdChoices")
nativeAdLoader?.destroyAd(ad)
}
// MARK: - CLXAdRevenueDelegate
func didPayRevenue(for ad: CLXAd) {
print("Native ad revenue: \(ad.revenue ?? 0) from \(ad.networkName ?? "unknown")")
}
```
## 3. Destroy native ads
Destroy an individual ad when it is replaced or expires. Destroy the loader when its owning screen or component is finished. This releases network media views and prevents resources from accumulating over time.
```objc Objective-C
// Destroy a specific loaded ad
[self.nativeAdLoader destroyAd:ad];
// Destroy the loader and all associated resources
[self.nativeAdLoader destroy];
```
```swift Swift
// Destroy a specific loaded ad
nativeAdLoader?.destroyAd(ad)
// Destroy the loader and all associated resources
nativeAdLoader?.destroy()
```
## Native ad assets
The `CLXNativeAd` object is available via `ad.nativeAd` in delegate callbacks:
| Property | Type | Description |
| ------------------------- | ------------------- | -------------------------------------------- |
| `title` | `NSString?` | Headline text |
| `body` | `NSString?` | Body / description text |
| `callToAction` | `NSString?` | CTA button text (e.g., "Install Now") |
| `advertiser` | `NSString?` | Advertiser name |
| `icon` | `CLXNativeAdImage?` | App icon image |
| `mainImage` | `CLXNativeAdImage?` | Main image (static creatives) |
| `mediaView` | `UIView?` | Video/media player view (adapter-provided) |
| `optionsView` | `UIView?` | AdChoices or options view (adapter-provided) |
| `mediaContentAspectRatio` | `CGFloat` | Aspect ratio of the media content |
| `starRating` | `NSNumber?` | App store rating (0–5) |
| `isVideoContent` | `BOOL` | Whether the creative is a video |
| `videoDuration` | `NSTimeInterval` | Video length in seconds (0 if unknown) |
| `expired` | `BOOL` | Whether the ad has expired |
Assets vary by network and creative. Treat nullable values as optional and hide the corresponding UI when an asset is unavailable. A `mediaContentAspectRatio` or `videoDuration` value of `0` means the network did not provide a usable value yet.
### Native video metadata
Use `isVideoContent` to distinguish video from static native creatives. When it is `true`, `videoDuration` contains the duration in seconds if the network has made it available. Query duration after rendering for the most accurate value.
---
# Rewarded Ads
> Integrate CloudX rewarded ads in iOS apps
```objc Objective-C
@interface YourViewController ()
@property (nonatomic, strong) CLXRewarded *rewardedAd;
@property (nonatomic, assign) NSInteger retryAttempt;
@end
@implementation YourViewController
- (void)createRewardedAd {
self.rewardedAd = [[CloudXCore shared] createRewardedWithAdUnitId:@"your-rewarded-ad-unit-id"];
self.rewardedAd.delegate = self;
self.rewardedAd.revenueDelegate = self;
[self.rewardedAd load];
}
- (void)showRewardedAd {
if (self.rewardedAd.isReady) {
// Basic show
[self.rewardedAd showFromViewController:self];
// Or with optional placement and custom data for tracking
// [self.rewardedAd showFromViewController:self placement:@"bonus_coins" customData:@"level:5,coins:100"];
} else {
NSLog(@"Rewarded ad not ready");
}
}
- (void)dealloc {
[self.rewardedAd destroy];
}
#pragma mark - CLXRewardedDelegate
- (void)didLoadAd:(CLXAd *)ad {
NSLog(@"Rewarded ad loaded from %@", ad.networkName);
self.retryAttempt = 0;
}
- (void)didFailToLoadAd:(NSString *)adUnitId error:(CLXError *)error {
NSLog(@"Rewarded ad failed to load: %@", error.localizedDescription);
self.retryAttempt++;
NSTimeInterval delay = pow(2.0, MIN(5, self.retryAttempt));
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.rewardedAd load];
});
}
- (void)didDisplayAd:(CLXAd *)ad {
NSLog(@"Rewarded ad displayed");
}
- (void)didFailToDisplayAd:(CLXAd *)ad error:(CLXError *)error {
NSLog(@"Rewarded ad failed to display: %@", error.localizedDescription);
[self.rewardedAd load];
}
- (void)didHideAd:(CLXAd *)ad {
NSLog(@"Rewarded ad hidden");
[self.rewardedAd load]; // Reload for next use
}
- (void)didClickAd:(CLXAd *)ad {
NSLog(@"Rewarded ad clicked");
}
- (void)didRewardUserForAd:(CLXAd *)ad withReward:(CLXReward *)reward {
NSLog(@"User rewarded: %ld %@", (long)reward.amount, reward.label);
// Grant the reward to the user
}
#pragma mark - CLXAdRevenueDelegate
- (void)didPayRevenueForAd:(CLXAd *)ad {
NSLog(@"Rewarded revenue: %@ from %@", ad.revenue, ad.networkName);
}
@end
```
```swift Swift
class YourViewController: UIViewController, CLXRewardedDelegate, CLXAdRevenueDelegate {
private var rewardedAd: CLXRewarded?
private var retryAttempt = 0
func createRewardedAd() {
rewardedAd = CloudXCore.shared.createRewarded(adUnitId: "your-rewarded-ad-unit-id")
rewardedAd?.delegate = self
rewardedAd?.revenueDelegate = self
rewardedAd?.load()
}
func showRewardedAd() {
if rewardedAd?.isReady == true {
// Basic show
rewardedAd?.show(from: self)
// Or with optional placement and custom data for tracking
// rewardedAd?.show(from: self, placement: "bonus_coins", customData: "level:5,coins:100")
} else {
print("Rewarded ad not ready")
}
}
deinit {
rewardedAd?.destroy()
}
// MARK: - CLXRewardedDelegate
func didLoad(_ ad: CLXAd) {
print("Rewarded ad loaded from \(ad.networkName ?? "unknown")")
retryAttempt = 0
}
func didFailToLoadAd(_ adUnitId: String, error: CLXError) {
print("Rewarded ad failed to load: \(error.localizedDescription)")
retryAttempt += 1
let delay = pow(2.0, Double(min(5, retryAttempt)))
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
self?.rewardedAd?.load()
}
}
func didDisplay(_ ad: CLXAd) {
print("Rewarded ad displayed")
}
func didFailToDisplay(_ ad: CLXAd, error: CLXError) {
print("Rewarded ad failed to display: \(error.localizedDescription)")
rewardedAd?.load()
}
func didHide(_ ad: CLXAd) {
print("Rewarded ad hidden")
rewardedAd?.load() // Reload for next use
}
func didClick(_ ad: CLXAd) {
print("Rewarded ad clicked")
}
func didRewardUser(for ad: CLXAd, with reward: CLXReward) {
print("User rewarded: \(reward.amount) \(reward.label)")
// Grant the reward to the user
}
// MARK: - CLXAdRevenueDelegate
func didPayRevenue(for ad: CLXAd) {
print("Rewarded revenue: \(ad.revenue ?? 0) from \(ad.networkName ?? "unknown")")
}
}
```
#### Expired ads
A loaded ad that is not shown before it expires is reloaded automatically on the same ad object. The current ad stays ready and can still be shown until the replacement is ready.
* To be told when a replacement is ready, set an `expirationDelegate`. A replacement that loads on its own does not call `didLoadAd:` (Swift `didLoad(_:)`) again.
* If you show the ad while the replacement loads, the replacement is kept. It becomes ready after the ad closes, and the `expirationDelegate` is called then.
* If you call `load` while the replacement is loading, including a `load` from `didHideAd:` (Swift `didHide(_:)`), no second request starts. You receive `didLoadAd:` with the new ad, followed by `didReloadExpiredAd:withNewAd:` (Swift `didReloadExpiredAd(_:with:)`).
* If the replacement fails to load while the expired ad is still loaded, `didFailToLoadAd:error:` (Swift `didFailToLoadAd(_:error:)`) fires and the ad is no longer ready. Call `load` again, after a delay rather than in a tight loop.
```objc Objective-C
// Add CLXAdExpirationDelegate to the protocols YourViewController adopts
self.rewardedAd.expirationDelegate = self;
- (void)didReloadExpiredAd:(CLXAd *)expiredAd withNewAd:(CLXAd *)newAd {
NSLog(@"Expired rewarded ad replaced by an ad from %@", newAd.networkName);
}
```
```swift Swift
// Add CLXAdExpirationDelegate to the protocols YourViewController adopts
rewardedAd?.expirationDelegate = self
func didReloadExpiredAd(_ expiredAd: CLXAd, with newAd: CLXAd) {
print("Expired rewarded ad replaced by an ad from \(newAd.networkName ?? "unknown")")
}
```
---
# App Open Ads
> Integrate CloudX App Open ads in iOS apps
App Open ads are fullscreen placements intended for app foreground moments, such as when a user returns to your app after it has been backgrounded.
#### Supported Networks
App Open demand is served by the following bidding networks:
| Network | Adapter |
| --------- | ------------------------------------------------ |
| Mintegral | [Mintegral](/en/ios/adapters/mintegral/overview) |
| Pangle | [Pangle](/en/ios/adapters/pangle/overview) |
| Vungle | [Vungle](/en/ios/adapters/vungle/overview) |
The [Google Mobile Ads waterfall adapter](/en/ios/adapters/google-estimated/overview) also supports App Open placements for AdMob waterfall demand.
```objc Objective-C
@interface YourViewController ()
@property (nonatomic, strong) CLXAppOpen *appOpenAd;
@end
@implementation YourViewController
- (void)createAppOpenAd {
self.appOpenAd = [[CloudXCore shared] createAppOpenWithAdUnitId:@"your-app-open-ad-unit-id"];
self.appOpenAd.delegate = self;
self.appOpenAd.revenueDelegate = self;
[self.appOpenAd load];
}
- (void)showAppOpenAd {
if (self.appOpenAd.isReady) {
[self.appOpenAd showFromViewController:self];
} else {
NSLog(@"App Open ad not ready");
}
}
- (void)dealloc {
[self.appOpenAd destroy];
}
#pragma mark - CLXAppOpenDelegate
- (void)didLoadAd:(CLXAd *)ad {
NSLog(@"App Open ad loaded from %@", ad.networkName);
}
- (void)didFailToLoadAd:(NSString *)adUnitId error:(CLXError *)error {
NSLog(@"App Open ad failed to load: %@", error.localizedDescription);
}
- (void)didDisplayAd:(CLXAd *)ad {
NSLog(@"App Open ad displayed");
}
- (void)didFailToDisplayAd:(CLXAd *)ad error:(CLXError *)error {
NSLog(@"App Open ad failed to display: %@", error.localizedDescription);
[self.appOpenAd load];
}
- (void)didHideAd:(CLXAd *)ad {
NSLog(@"App Open ad hidden");
[self.appOpenAd load]; // Reload for next use
}
- (void)didClickAd:(CLXAd *)ad {
NSLog(@"App Open ad clicked");
}
#pragma mark - CLXAdRevenueDelegate
- (void)didPayRevenueForAd:(CLXAd *)ad {
NSLog(@"App Open revenue: %@ from %@", ad.revenue, ad.networkName);
}
@end
```
```swift Swift
class YourViewController: UIViewController, CLXAppOpenDelegate, CLXAdRevenueDelegate {
private var appOpenAd: CLXAppOpen?
func createAppOpenAd() {
appOpenAd = CloudXCore.shared.createAppOpen(adUnitId: "your-app-open-ad-unit-id")
appOpenAd?.delegate = self
appOpenAd?.revenueDelegate = self
appOpenAd?.load()
}
func showAppOpenAd() {
if appOpenAd?.isReady == true {
appOpenAd?.show(from: self)
} else {
print("App Open ad not ready")
}
}
deinit {
appOpenAd?.destroy()
}
// MARK: - CLXAppOpenDelegate
func didLoad(_ ad: CLXAd) {
print("App Open ad loaded from \(ad.networkName ?? "unknown")")
}
func didFailToLoadAd(_ adUnitId: String, error: CLXError) {
print("App Open ad failed to load: \(error.localizedDescription)")
}
func didDisplay(_ ad: CLXAd) {
print("App Open ad displayed")
}
func didFailToDisplay(_ ad: CLXAd, error: CLXError) {
print("App Open ad failed to display: \(error.localizedDescription)")
appOpenAd?.load()
}
func didHide(_ ad: CLXAd) {
print("App Open ad hidden")
appOpenAd?.load() // Reload for next use
}
func didClick(_ ad: CLXAd) {
print("App Open ad clicked")
}
// MARK: - CLXAdRevenueDelegate
func didPayRevenue(for ad: CLXAd) {
print("App Open revenue: \(ad.revenue ?? 0) from \(ad.networkName ?? "unknown")")
}
}
```
Use App Open ads sparingly and only at natural app-open or app-resume transitions. Avoid showing one while the user is already interacting with app content.
#### Expired ads
A loaded ad that is not shown before it expires is reloaded automatically on the same ad object. The current ad stays ready and can still be shown until the replacement is ready.
* To be told when a replacement is ready, set an `expirationDelegate`. A replacement that loads on its own does not call `didLoadAd:` (Swift `didLoad(_:)`) again.
* If you show the ad while the replacement loads, the replacement is kept. It becomes ready after the ad closes, and the `expirationDelegate` is called then.
* If you call `load` while the replacement is loading, including a `load` from `didHideAd:` (Swift `didHide(_:)`), no second request starts. You receive `didLoadAd:` with the new ad, followed by `didReloadExpiredAd:withNewAd:` (Swift `didReloadExpiredAd(_:with:)`).
* If the replacement fails to load while the expired ad is still loaded, `didFailToLoadAd:error:` (Swift `didFailToLoadAd(_:error:)`) fires and the ad is no longer ready. Call `load` again, after a delay rather than in a tight loop.
```objc Objective-C
// Add CLXAdExpirationDelegate to the protocols YourViewController adopts
self.appOpenAd.expirationDelegate = self;
- (void)didReloadExpiredAd:(CLXAd *)expiredAd withNewAd:(CLXAd *)newAd {
NSLog(@"Expired App Open ad replaced by an ad from %@", newAd.networkName);
}
```
```swift Swift
// Add CLXAdExpirationDelegate to the protocols YourViewController adopts
appOpenAd?.expirationDelegate = self
func didReloadExpiredAd(_ expiredAd: CLXAd, with newAd: CLXAd) {
print("Expired App Open ad replaced by an ad from \(newAd.networkName ?? "unknown")")
}
```
---
# Changelog
> iOS SDK release history
***
## Magnite adapter 1.0.4.2 - 2026-10-15
### Fixed
* **Launch crash when consent is set early** — Apps that set user consent or do-not-sell before CloudX finished initializing could crash on devices with 1.0.4.0 and 1.0.4.1. The adapter now holds those values and applies them once Magnite initializes.
* **No integration change required** — 1.0.4.0 and 1.0.4.1 are withdrawn; update to 1.0.4.2. MagniteSDK 1.0.4 and `CloudXCore >= 3.9.0` are unchanged. Install: `pod 'CloudXMagniteAdapterV2', '~> 1.0.4.2'`.
***
## InMobi adapter 11.5.0.0 - 2026-10-15
### Changed
* **Updated to InMobi SDK 11.5.0** — The SDK pin is exact, so an app that pins InMobi SDK 11.4.1 through another integration cannot install this version alongside it. Install: `pod 'CloudXInMobiAdapter', '~> 11.5.0.0'`.
***
## Mintegral adapter 8.1.7.0 - 2026-10-15
### Changed
* **Certified with Mintegral SDK 8.1.7** — Updates to Mintegral SDK 8.1.7. Install: `pod 'CloudXMintegralAdapter', '~> 8.1.7.0'`.
***
## MobileFuse adapter 1.12.0.0 - 2026-10-15
### Changed
* **Updated to MobileFuse SDK 1.12.0** — Fixes a crash risk during bidding-token generation. Install: `pod 'CloudXMobileFuseAdapter', '~> 1.12.0.0'`.
***
## Digital Turbine adapter 8.4.10.1 - 2026-10-14
### Changed
* **Rewarded callback timing** — `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) can now fire as soon as the ad network grants the reward, instead of when the ad closes, when CloudX enables it. Takes effect with CloudXCore 3.11.0 or later.
* **No integration change required** — Fyber Marketplace SDK 8.4.10 and `CloudXCore >= 3.5.0` are unchanged. Install: `pod 'CloudXDigitalTurbineAdapter', '~> 8.4.10.1'`.
### Fixed
* **Privacy manifest included** — The adapter framework now ships its own Apple privacy manifest.
***
## InMobi adapter 11.4.1.1 - 2026-10-14
### Changed
* **Rewarded callback timing** — `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) can now fire as soon as the ad network grants the reward, instead of when the ad closes, when CloudX enables it. Takes effect with CloudXCore 3.11.0 or later.
* **No integration change required** — InMobiSDK 11.4.1 and `CloudXCore >= 3.5.0` are unchanged. Install: `pod 'CloudXInMobiAdapter', '~> 11.4.1.1'`.
### Fixed
* **Privacy manifest included** — The adapter framework now ships its own Apple privacy manifest.
***
## Magnite adapter 1.0.4.1 - 2026-10-14
### Changed
* **Withdrawn** — Can crash on devices when consent is set before initialization. Use 1.0.4.2 instead.
* **Rewarded callback timing** — `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) can now fire as soon as the ad network grants the reward, instead of when the ad closes, when CloudX enables it. Takes effect with CloudXCore 3.11.0 or later.
* **No integration change required** — MagniteSDK 1.0.4 and `CloudXCore >= 3.9.0` are unchanged. Install: `pod 'CloudXMagniteAdapterV2', '~> 1.0.4.1'`.
***
## Meta adapter 6.22.0.1 - 2026-10-14
### Changed
* **Rewarded callback timing** — `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) can now fire as soon as the ad network grants the reward, instead of when the ad closes, when CloudX enables it. Takes effect with CloudXCore 3.11.0 or later.
* **No integration change required** — Meta Audience Network SDK 6.22.0 and `CloudXCore >= 3.5.0` are unchanged. Install: `pod 'CloudXMetaAdapter', '~> 6.22.0.1'`.
### Fixed
* **Privacy manifest included** — The adapter framework now ships its own Apple privacy manifest.
***
## MobileFuse adapter 1.11.0.2 - 2026-10-14
### Changed
* **Rewarded callback timing** — `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) can now fire as soon as the ad network grants the reward, instead of when the ad closes, when CloudX enables it. Takes effect with CloudXCore 3.11.0 or later.
* **No integration change required** — MobileFuse SDK 1.11.0 and `CloudXCore >= 3.5.0` are unchanged. Install: `pod 'CloudXMobileFuseAdapter', '~> 1.11.0.2'`.
### Fixed
* **Privacy manifest included** — The adapter framework now ships its own Apple privacy manifest.
***
## Moloco adapter 4.11.1.1 - 2026-10-14
### Changed
* **Rewarded callback timing** — `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) can now fire as soon as the ad network grants the reward, instead of when the ad closes, when CloudX enables it. Takes effect with CloudXCore 3.11.0 or later.
* **No integration change required** — MolocoSDKiOS 4.11.1 and `CloudXCore >= 3.5.0` are unchanged. Install: `pod 'CloudXMolocoAdapter', '~> 4.11.1.1'`.
***
## Pangle adapter 8.3.0.6.1 - 2026-10-14
### Changed
* **Rewarded callback timing** — `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) can now fire as soon as the ad network grants the reward, instead of when the ad closes, when CloudX enables it. Takes effect with CloudXCore 3.11.0 or later.
* **No integration change required** — Pangle Ads-Global 8.3.0.6 and `CloudXCore >= 3.5.0` are unchanged. Install: `pod 'CloudXPangleAdapter', '~> 8.3.0.6.1'`.
### Fixed
* **Privacy manifest included** — The adapter framework now ships its own Apple privacy manifest.
***
## TaurusX adapter 1.18.2.1 - 2026-10-14
### Changed
* **Rewarded callback timing** — `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) can now fire as soon as the ad network grants the reward, instead of when the ad closes, when CloudX enables it. Takes effect with CloudXCore 3.11.0 or later.
* **No integration change required** — TaurusX SDK 1.18.2 and `CloudXCore >= 3.5.0` are unchanged. Install: `pod 'CloudXTaurusXAdapter', '~> 1.18.2.1'`.
### Fixed
* **Privacy manifest included** — The adapter framework now ships its own Apple privacy manifest.
* Corrected the TaurusX adapter framework bundle version to prevent App Store validation errors.
***
## Unity Ads adapter 4.20.1.1 - 2026-10-14
### Changed
* **Rewarded callback timing** — `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) can now fire as soon as the ad network grants the reward, instead of when the ad closes, when CloudX enables it. Takes effect with CloudXCore 3.11.0 or later.
* **Privacy manifest updated** — The adapter's privacy manifest no longer declares data collection or required-reason API use. The Unity Ads SDK still ships its own manifest.
* **No integration change required** — UnityAds 4.20.1 and `CloudXCore >= 3.5.0` are unchanged. Install: `pod 'CloudXUnityAdsAdapter', '~> 4.20.1.1'`.
***
## Verve adapter 3.9.0.1 - 2026-10-14
### Changed
* **Rewarded callback timing** — `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) can now fire as soon as the ad network grants the reward, instead of when the ad closes, when CloudX enables it. Takes effect with CloudXCore 3.11.0 or later.
* **No integration change required** — HyBid 3.9.0 and `CloudXCore >= 3.5.0` are unchanged. Install: `pod 'CloudXVerveAdapter', '~> 3.9.0.1'`.
### Fixed
* **Privacy manifest included** — The adapter framework now ships its own Apple privacy manifest.
***
## Vungle adapter 7.7.6.1 - 2026-10-14
### Changed
* **Rewarded callback timing** — `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) can now fire as soon as the ad network grants the reward, instead of when the ad closes, when CloudX enables it. Takes effect with CloudXCore 3.11.0 or later.
* **Vungle error codes** — Creative-download failures now report `CLXErrorCodeAdapterInternalError` (600) instead of `CLXErrorCodeAdapterInvalidLoadState` (602), and HTTP failures report `CLXErrorCodeAdapterServerError` (612). Update any handling of 602.
* **Dependencies unchanged** — VungleAds 7.7.6 and `CloudXCore >= 3.5.0` stay the same. Install: `pod 'CloudXVungleAdapter', '~> 7.7.6.1'`.
### Fixed
* **Privacy manifest included** — The adapter framework now ships its own Apple privacy manifest.
***
## Magnite adapter 1.0.4.0 - 2026-10-12
### Changed
* **Withdrawn** — Can crash on devices when consent is set before initialization. Use 1.0.4.2 instead.
* **Certified with MagniteSDK 1.0.4** — `CloudXCore >= 3.9.0` and iOS 13.0 requirements are unchanged. Install: `pod 'CloudXMagniteAdapterV2', '~> 1.0.4.0'`.
***
## Moloco adapter 4.11.1.0 - 2026-10-08
### Changed
* **Certified with MolocoSDKiOS 4.11.1** — Updates the bundled MolocoSDKiOS from 4.10.0 to 4.11.1. No integration change is required; `CloudXCore >= 3.5.0` and iOS 13.0 are unchanged. Install: `pod 'CloudXMolocoAdapter', '~> 4.11.1.0'`.
***
## Mintegral adapter 8.1.6.1 - 2026-10-06
### Fixed
* Corrected the Mintegral adapter framework bundle version to prevent App Store validation errors. MintegralAdSDK remains 8.1.6. Install: `pod 'CloudXMintegralAdapter', '~> 8.1.6.1'`.
* **Privacy manifest included** — The adapter framework now ships its own Apple privacy manifest.
***
## \[3.11.0] - 2026-10-13
Install: `pod 'CloudXCore', '~> 3.11'`
### Added
* **Rewarded callback timing** — CloudX can now control when rewarded ads deliver the reward: when the ad network grants it, or when the ad closes. Granting at grant time means a user who never closes the ad still gets the reward. This takes effect with the adapter updates shipping next.
* **Share from the Mediation Debugger** — The Mediation Debugger can share a plain-text report for support requests.
* **Startup warm-up** — Part of initialization now runs at app launch, including one request to the CloudX init host that carries no identifiers. Opt out with `CloudXStartupWarmDisabled` in Info.plist.
### Changed
* **Rewarded ads pay the reward configured for the ad unit.** Rewarded ads now pay the reward amount and currency set on the ad unit in CloudX, instead of the value the ad network reports.
* **Banners without a configured refresh interval now refresh every 30 seconds.**
### Fixed
* **Full-screen HTML ads no longer render at about a third of the screen.**
* **Built-in native templates keep their title and body text readable in dark mode.**
***
## \[3.10.0] - 2026-09-24
Install: `pod 'CloudXCore', '~> 3.10'`
### Added
* **Mediation Debugger** — Call `+[CloudXCore showMediationDebugger]` after initialization to see each adapter's status, privacy signals and ad units, load a test ad for any ad unit, and request test mode for the next launch. Returns `NO` until `CLXSDKInitializedNotification` has posted.
* **App Store surfaces on fullscreen video** — CloudX-rendered video ads can open the in-app App Store sheet on skip or close, or show Apple's App Store overlay during playback. Users stay in your app.
* **Multi-card end cards** — CloudX-rendered fullscreen video ads can show a sequence of end cards. Closing a card advances to the next one before the ad closes.
### Changed
* **CloudX-rendered fullscreen video clicks open the App Store inside your app** instead of leaving it. Click callbacks now fire only for real user clicks.
* **Ads honor the advertiser's expiry** — Showing an expired fullscreen ad fails with `CLXErrorCodeAdapterAdExpired` (616); check `isReady` before showing. A native ad that was already shown no longer receives an expiry callback.
* **Revenue callbacks now fire in test mode** — They were previously withheld on test devices; now they fire so you can verify your revenue wiring. Test-mode callbacks report `revenue` 0 and `revenuePrecision` `"undefined"`.
* **`CLXAd.revenue` reports realized impression revenue** when the serving network provides it, with a matching `revenuePrecision`. Otherwise the auction estimate is reported with precision `"estimated"`.
* **CloudXCore privacy manifest updated** — It now declares file-timestamp API use and crash-data collection.
### Fixed
* **Calling initialize twice no longer starts two initializations.** A second call waits for the first attempt to finish.
* **CloudX-rendered ads can no longer open an advertiser page or report a click without a user tap.**
* **The built-in native template no longer shows an empty media area or a blank button** when the ad does not supply them.
* **With the ad cache enabled, a native ad destroyed before it was shown is reused** by the next load while it is still valid.
* **Improved fill from demand partners that rejected every request after the first in a session.**
***
## BIGO Ads adapter 6.1.0.0 - 2026-09-23
### Changed
* **Certified with BigoADS 6.1.0** — `CloudXCore >= 3.9.0` and iOS 13.0 requirements are unchanged. Install: `pod 'CloudXBigoAdapter', '~> 6.1.0.0'`.
### Fixed
* The adapter xcframework now includes its Apple privacy manifest.
***
## BIGO Ads adapter 5.3.0.0 - 2026-09-17
### Added
* **First public BIGO Ads adapter release** — Supports Banner, MREC, Interstitial, and Rewarded with BigoADS 5.3.0. Requires `CloudXCore >= 3.9.0`. Install: `pod 'CloudXBigoAdapter', '~> 5.3.0.0'`.
***
## Unity Ads adapter 4.20.1.0 - 2026-09-15
### Changed
* **Certified with UnityAds 4.20.1** — `CloudXCore >= 3.5.0` and iOS 13.0 requirements are unchanged. Install: `pod 'CloudXUnityAdsAdapter', '~> 4.20.1.0'`.
***
## Unity Ads adapter 4.20.0.0 - 2026-09-15
### Changed
* **Certified with UnityAds 4.20.0** — `CloudXCore >= 3.5.0` and iOS 13.0 requirements are unchanged. Install: `pod 'CloudXUnityAdsAdapter', '~> 4.20.0.0'`.
***
## Google Waterfall adapter 13.9.0.0 - 2026-09-14
### Changed
* **Certified with Google Mobile Ads SDK 13.9.0** — `CloudXCore >= 3.7.0` and iOS 13.0 requirements are unchanged. Install: `pod 'CloudXGoogleWaterfallAdapter', '~> 13.9.0.0'`.
***
## Google Waterfall adapter 13.8.0.0 - 2026-09-14
### Changed
* **Certified with Google Mobile Ads SDK 13.8.0** — `CloudXCore >= 3.7.0` and iOS 13.0 requirements are unchanged. Install: `pod 'CloudXGoogleWaterfallAdapter', '~> 13.8.0.0'`.
***
## Google Waterfall adapter 13.7.0.0 - 2026-09-14
### Changed
* **Certified with Google Mobile Ads SDK 13.7.0** — `CloudXCore >= 3.7.0` and iOS 13.0 requirements are unchanged. Install: `pod 'CloudXGoogleWaterfallAdapter', '~> 13.7.0.0'`.
### Fixed
* Manually integrated adapters now enforce the existing CloudXCore 3.7.0 minimum.
***
## \[3.9.1] - 2026-09-14
Install: `pod 'CloudXCore', '~> 3.9'`
### Changed
* **Global ad-revenue delegates and MMP connectors no longer receive duplicate mediation-stack revenue** when the server marks a bidder as SDK-reported. Per-ad revenue delegates and CloudX impression events are unchanged.
### Fixed
* **Rewarded and other fullscreen callbacks now use the placement and custom data from the accepted show.** Consecutive rewarded ads no longer reuse missing or stale attribution.
***
## Digital Turbine adapter 8.4.10.0 - 2026-09-14
### Changed
* **Certified with Fyber Marketplace SDK 8.4.10** — Requires iOS 15.0+ and Xcode 26.1+; `CloudXCore >= 3.5.0` is unchanged. Includes improved click handling. Install: `pod 'CloudXDigitalTurbineAdapter', '~> 8.4.10.0'`.
***
## Moloco adapter 4.10.0.0 - 2026-09-14
### Changed
* **Certified with MolocoSDKiOS 4.10.0** — Updates the bundled MolocoSDKiOS from 4.9.1 to 4.10.0. No integration change is required; `CloudXCore >= 3.5.0` and iOS 13.0 are unchanged. Install: `pod 'CloudXMolocoAdapter', '~> 4.10.0.0'`.
***
## Moloco adapter 4.9.1.0 - 2026-09-14
### Changed
* **Certified with MolocoSDKiOS 4.9.1** — Updates the bundled MolocoSDKiOS from 4.9.0 to 4.9.1. No integration change is required; `CloudXCore >= 3.5.0` and iOS 13.0 are unchanged. Install: `pod 'CloudXMolocoAdapter', '~> 4.9.1.0'`.
***
## Moloco adapter 4.9.0.0 - 2026-09-14
### Changed
* **Certified with MolocoSDKiOS 4.9.0** — Updates the bundled MolocoSDKiOS from 4.8.1 to 4.9.0. No integration change is required; `CloudXCore >= 3.5.0` and iOS 13.0 are unchanged. Install: `pod 'CloudXMolocoAdapter', '~> 4.9.0.0'`.
***
## Moloco adapter 4.8.1.0 - 2026-09-14
### Changed
* **Certified with MolocoSDKiOS 4.8.1** — Updates the bundled MolocoSDKiOS from 4.8.0 to 4.8.1. No integration change is required; `CloudXCore >= 3.5.0` and iOS 13.0 are unchanged. Install: `pod 'CloudXMolocoAdapter', '~> 4.8.1.0'`.
***
## Pangle adapter 8.3.0.6.0 - 2026-09-14
### Changed
* **Certified with Ads-Global 8.3.0.6** — Updates the bundled Ads-Global from 8.2.1.0 to 8.3.0.6. No integration change is required; `CloudXCore >= 3.5.0` and iOS 13.0 are unchanged. Install: `pod 'CloudXPangleAdapter', '~> 8.3.0.6.0'`.
***
## Pangle adapter 8.2.1.0.0 - 2026-09-14
### Changed
* **Certified with Ads-Global 8.2.1.0** — Updates the bundled Ads-Global from 8.2.0.9 to 8.2.1.0. No integration change is required; `CloudXCore >= 3.5.0` and iOS 13.0 are unchanged. Install: `pod 'CloudXPangleAdapter', '~> 8.2.1.0.0'`.
***
## Pangle adapter 8.2.0.9.0 - 2026-09-14
### Changed
* **Certified with Ads-Global 8.2.0.9** — Updates the bundled Ads-Global from 8.2.0.8 to 8.2.0.9. No integration change is required; `CloudXCore >= 3.5.0` and iOS 13.0 are unchanged. Install: `pod 'CloudXPangleAdapter', '~> 8.2.0.9.0'`.
***
## Pangle adapter 8.2.0.8.0 - 2026-09-14
### Changed
* **Certified with Ads-Global 8.2.0.8** — Updates the bundled Ads-Global from 8.2.0.7 to 8.2.0.8. No integration change is required; `CloudXCore >= 3.5.0` and iOS 13.0 are unchanged. Install: `pod 'CloudXPangleAdapter', '~> 8.2.0.8.0'`.
***
## \[3.9.0] - 2026-09-10
Install: `pod 'CloudXCore', '~> 3.9'`
### Breaking Changes
* **A third simultaneous `load()` on one ad unit is now rejected** — Two concurrent publisher-triggered loads per ad unit is the new default ceiling. A third fails through `didFailToLoadAd:`. Contact CloudX to raise the limit for your account.
* **`load()` during a fullscreen ad no longer fails** — A load requested while an interstitial, rewarded or app-open ad is on screen now waits and starts when that ad is dismissed. Remove any retry logic that handled the old immediate failure.
* **Manual banner `load()` is ignored while auto-refresh is running** — With auto-refresh on and an ad already showing, `load()` runs no auction and fires no delegate callback; a warning is logged instead. Call `stopAutoRefresh` first to drive refresh yourself.
### Added
* **Video creatives now render inline in banner and MREC slots** — Inline video plays in the ad slot through a dedicated renderer. It starts muted and plays only while the slot is genuinely visible. Auto-refresh pauses until playback finishes.
* **Optional ad caching and warm preload** — A won but unshown ad can survive its ad object being destroyed and serve to the next object for the same ad unit, and an impression can start fetching the next ad. Both are off unless CloudX enables them for your account.
* **`shouldPrepareViewForInteractionOnMainThread` on `CLXNativeAd`** — Override it on a custom native ad to declare whether view preparation must run on the main thread. It defaults to `YES`, which matches existing behaviour.
### Changed
* **Faster first bid on a cold start** — Bid tokens are now collected as each adapter finishes initializing, rather than when the first auction runs, so the first ad request returns sooner. Measured 230–272 ms faster time-to-first-bid.
* **Auto-refresh disabled by CloudX stays disabled** — When auto-refresh is turned off for an ad unit on the CloudX side, `startAutoRefresh()` no longer re-enables it. `load()` still works, and pausing or resuming with `stopAutoRefresh()` is unaffected.
* **Banners refresh only while actually visible** — A banner that is hidden, fully transparent, zero-sized or outside the visible window stops requesting new ads until it is on screen again. Expect fewer load requests and a higher impression rate per load.
* **The generated native template view is sized from the creative** — A native template view now derives its height from the creative's aspect ratio and the width you give it, not a fixed 320x250. Read the height from `-sizeThatFits:`; a hardcoded height clips the ad.
* **A fixed-size native template is still available** — `+[CLXNativeAdView viewFromAd:withTemplate:]` with `medium_template` is unchanged and still returns exactly 320x250.
### Fixed
* **Banner auto-refresh resumes after a brief interruption** — Control Center, an incoming call or a Face ID prompt could stop a banner's auto-refresh for the rest of the session. Refresh now resumes when your app becomes active again.
* **Fullscreen ads present correctly when a modal is already open** — `showFromViewController:` now resolves the topmost view controller instead of failing. Ads are never presented over system UI such as alerts or share sheets, and the rejection reason is reported.
* **Test-mode sessions no longer report revenue** — A test session fills at placeholder prices, so those figures are no longer sent to your revenue callback or forwarded to your attribution provider. Live sessions are unaffected.
* **Your requested native template now reaches the auction** — The native template you request is sent in the bid request, so bidders can return a creative that matches it. Previously the request was dropped before it left the SDK.
***
## Unity Ads adapter 4.17.0.0 - 2026-09-11
### Added
* **Compatibility-line release for Unity Ads 4.17.0** — The 4.19.0.0 adapter rebuilt against Unity Ads 4.17.0, same feature set, no source change. Install: `pod 'CloudXUnityAdsAdapter', '4.17.0.0'`; requires `CloudXCore >= 3.5.0`.
***
## Vungle adapter 7.7.1.0 - 2026-09-11
### Added
* **Compatibility-line release for Vungle Ads SDK 7.7.1** — The 7.7.6.0 adapter rebuilt against Vungle Ads SDK 7.7.1, same feature set, no source change. Install: `pod 'CloudXVungleAdapter', '7.7.1.0'`; requires `CloudXCore >= 3.5.0`.
***
## Vungle adapter 7.7.6.0 - 2026-09-08
### Changed
* **Certified with VungleAds 7.7.6** — Updates the bundled VungleAds SDK from 7.7.4 to 7.7.6. No integration change is required; `CloudXCore >= 3.5.0` and iOS 13.0 are unchanged. Install: `pod 'CloudXVungleAdapter', '~> 7.7.6.0'`.
***
## Mintegral adapter 8.1.6.0 - 2026-09-08
### Changed
* **Certified with MintegralAdSDK 8.1.6** — Updates the bundled MintegralAdSDK from 8.1.5 to 8.1.6. No integration change is required; `CloudXCore >= 3.5.0` and iOS 13.0 are unchanged. Install: `pod 'CloudXMintegralAdapter', '~> 8.1.6.0'`.
***
## Pangle adapter 8.1.0.6.0 - 2026-09-07
### Added
* **Compatibility-line release for Pangle Ads-Global 8.1.0.6** — The 8.2.0.7.0 adapter rebuilt against Ads-Global 8.1.0.6, same feature set, no source change. Install: `pod 'CloudXPangleAdapter', '8.1.0.6.0'`; requires `CloudXCore >= 3.5.0`.
***
## Pangle adapter 8.1.0.7.0 - 2026-09-07
### Added
* **Compatibility-line release for Pangle Ads-Global 8.1.0.7** — The 8.2.0.7.0 adapter rebuilt against Ads-Global 8.1.0.7, same feature set, no source change. Install: `pod 'CloudXPangleAdapter', '8.1.0.7.0'`; requires `CloudXCore >= 3.5.0`.
***
## Moloco adapter 4.7.0.0 - 2026-09-05
### Added
* **Compatibility-line release for MolocoSDKiOS 4.7.0** — The 4.8.0.0 adapter rebuilt against MolocoSDKiOS 4.7.0, same feature set, no source change. Install: `pod 'CloudXMolocoAdapter', '4.7.0.0'`; requires `CloudXCore >= 3.5.0`.
***
## Google Waterfall adapter 12.12.0.0 - 2026-09-02
### Added
* **Compatibility-line release for Google Mobile Ads SDK 12.12.0 (12.12.0)** — The 12.14.0.3 adapter rebuilt against Google Mobile Ads SDK 12.12.0, same feature set, no source change. Install: `pod 'CloudXGoogleWaterfallAdapter', '12.12.0.0'`; requires `CloudXCore >= 3.7.0`.
***
## TaurusX adapter 1.18.2.0 - 2026-08-28
### Changed
* **Certified with TaurusX SDK 1.18.2** — Updates the bundled TaurusX SDK from 1.18.1. No integration change is required; `CloudXCore >= 3.5.0` and iOS 13.0 are unchanged. Install: `pod 'CloudXTaurusXAdapter', '~> 1.18.2.0'`.
***
## Mintegral adapter 8.1.3.0 - 2026-08-28
### Added
* **Backport of the 8.1.5.0 adapter for MintegralAdSDK 8.1.3** — Compatibility-line release with the same Banner, MREC, Interstitial, Rewarded, Native and App Open support. Install: `pod 'CloudXMintegralAdapter', '8.1.3.0'`; requires `CloudXCore >= 3.5.0`.
***
## \[3.8.0] - 2026-08-24
Install: `pod 'CloudXCore', '~> 3.8'`
### Added
* **Video in banner and MREC ads now starts muted** — Video creatives in banner and MREC placements begin playback muted; a creative can only unmute in response to a user tap on its own controls.
* **Google Ad Manager Prebid integration** — Now available as `pod 'CloudXGAMPrebid', '~> 1.0'`, letting CloudX demand compete as prebid line items in your Google Ad Manager auction for banner, MREC, interstitial, rewarded, and native. Requires CloudXCore 3.8.0.
### Fixed
* **Revenue and click callbacks always delivered** — Ads with incomplete bid metadata could serve and earn while the revenue and click callbacks were silently skipped. These callbacks now always fire, so your ILRD matches CloudX reporting.
* **Calling `load()` on a visible banner no longer leaks the replaced ad** — Publisher-driven banner/MREC refresh via `load()` leaked the previous ad's web view and media resources for the rest of the session. The replaced ad now tears down correctly.
* **Video impressions tracked when viewability measurement fails to start** — Some VAST video ads rendered and played but were never counted as impressions. They now bill and fire their tracking beacons correctly.
* **Long tracking URLs no longer dropped** — VAST tracking beacons with URLs up to 10KB now fire; previously URLs over 2KB were silently dropped.
* **Faster handling of concurrent network responses** — Network completions are no longer processed one at a time, so one slow response can no longer delay other finished requests during init and bidding.
***
## MobileFuse adapter 1.11.0.1 - 2026-08-24
### Fixed
* **Reading the MobileFuse SDK version no longer blocks the calling thread** — Removes a stall on the bid-token path. MobileFuseSDK 1.11.0 and `CloudXCore >= 3.5.0` are unchanged. Install: `pod 'CloudXMobileFuseAdapter', '~> 1.11.0.1'`.
***
## Google Waterfall adapter 12.14.0.3 - 2026-08-26
### Changed
* **Google adapter startup no longer delays CloudX SDK initialization (12.14.0)** — Same change as 13.6.0.4, for Google Mobile Ads SDK 12.14.0. Install: `pod 'CloudXGoogleWaterfallAdapter', '12.14.0.3'`; requires `CloudXCore >= 3.7.0`.
***
## Google Waterfall adapter 13.6.0.4 - 2026-08-26
### Changed
* **Google adapter startup no longer delays CloudX SDK initialization** — The Google Mobile Ads SDK now starts in the background instead of holding up your `initialize` call. A missing `GADApplicationIdentifier` in your Info.plist still fails initialization straight away.
* **An ad requested in the moment right after initialization may no-fill once** — Google demand is ready as soon as its background start finishes. A request landing before then returns a fast no-fill for that one request instead of waiting, and the next request is served normally.
* Requires Google Mobile Ads SDK 13.6.0 exactly; requires `CloudXCore` 3.7.0 or newer. Install: `pod 'CloudXGoogleWaterfallAdapter', '~> 13.6.0.4'`.
***
## Meta adapter 6.20.1.0 - 2026-08-28
### Changed
* **Audience Network 6.20.1 compatibility line** — For apps pinned to Audience Network 6.20.1; keeps iOS 13. Native video options and AdChoices close reporting are unavailable at this version. Install: `pod 'CloudXMetaAdapter', '6.20.1.0'`; requires `CloudXCore >= 3.5.0`.
***
## Vungle adapter 7.6.3.0 - 2026-08-28
### Changed
* **VungleAds 7.6.3 compatibility line (7.6.3)** — For apps whose mediation stack pins VungleAds 7.6.3. No behavioral difference from the current line. Install: `pod 'CloudXVungleAdapter', '7.6.3.0'`; requires `CloudXCore >= 3.5.0`.
***
## Unity Ads adapter 4.16.3.0 - 2026-08-28
### Changed
* **UnityAds 4.16.3 compatibility line** — For apps pinned to UnityAds 4.16.3. `CLXUnityAdsRewarded` grants its reward on show completion, and ad expiry callbacks are unavailable. Install: `pod 'CloudXUnityAdsAdapter', '4.16.3.0'`; requires `CloudXCore >= 3.5.0`.
***
## Mintegral adapter 8.0.3.0 - 2026-08-28
### Changed
* **MintegralAdSDK 8.0.3 compatibility line (8.0.3)** — For apps whose mediation stack pins Mintegral 8.0.3. No behavioral difference from the current line. Install: `pod 'CloudXMintegralAdapter', '8.0.3.0'`; requires `CloudXCore >= 3.5.0`.
***
## Google Waterfall adapter 13.6.0.3 - 2026-08-21
### Changed
* **Fullscreen ads prefetch through Google's ad-preloading API** — Interstitial, rewarded, and app-open ads are preloaded by the Google Mobile Ads SDK, not the adapter; `CLXGoogleWaterfallFullscreenLoader` is removed. Banner, MREC, and native are unchanged, and no publisher action is needed.
* Requires Google Mobile Ads SDK 13.6.0 exactly; requires `CloudXCore` 3.7.0 or newer. Install: `pod 'CloudXGoogleWaterfallAdapter', '~> 13.6.0.3'`.
***
## InMobi adapter 11.4.1.0 - 2026-08-18
### Changed
* **Certified with InMobiSDK 11.4.1** — Improves audio handling for MRAID 3 creatives, so an ad no longer interrupts audio your app is already playing. Requires `CloudXCore >= 3.5.0`, unchanged. Install: `pod 'CloudXInMobiAdapter', '~> 11.4.1.0'`.
***
## Meta adapter 6.22.0.0 - 2026-08-12
### Changed
* **Requires iOS 15** — Certified with Meta Audience Network 6.22.0, which requires iOS 15.0. Apps that still support iOS 13 or 14 should stay on 6.21.1.0. Install: `pod 'CloudXMetaAdapter', '~> 6.22.0.0'`.
* **New Meta ad formats** — Audience Network 6.22 adds eCommerce and catalog formats, chained ads on iOS, and playable improvements. Meta serves these; no integration change is needed beyond updating the adapter.
***
## \[3.7.0] - 2026-08-11
Install: `pod 'CloudXCore', '~> 3.7'`
### Added
* **AdMob and Google Ad Manager bids in the Trusted Arbiter** — New `CLXArbiterBid` factories `adMob(adUnitId:)` and `gam(adUnitId:)`, no price required: CloudX prices the bid from revenue reported via `reportRevenueData(_:)`. Optional per-impression USD override.
* **`CLXRevenuePlatformGAM`** — Report Google Ad Manager revenue through `reportRevenueData(_:)`; GAM paid events now also feed arbiter bid pricing.
### Changed
* **Adapter-facing `CLXBidRoute` native routing** — Headers used by CloudX adapters gained native routing support (`CLXBidRoute`, `CLXNativeParsedAdKey`). Internal to CloudX adapters; no publisher action needed.
### Fixed
* **Core-only integrations initialize again** — Apps integrating `CloudXCore` with no adapter pods failed to initialize (error 201) and could not run auctions. Both now work.
* **Banner and MREC views no longer leak** — Auto-refreshing inline ad views could be retained for the rest of the session, causing unbounded memory growth in long-running apps. They now release correctly.
* **Releasing an ad without `destroy()` now cleans up** — Dropping your last reference to an ad object without calling `destroy()` now tears down the ad and its resources; previously they leaked silently.
* **Rare crash during ad cleanup fixed** — A narrow internal race could abort the process while an ad's resources were being released.
* **Portrait native creatives render at their reported aspect ratio** — Vertical (e.g. 9:16) creatives in the SDK native template no longer appear letterboxed in a landscape box. The template view for a portrait creative is now taller (320x548 instead of 320x250).
* **No-bid auctions no longer log at ERROR** — A normal no-bid outcome now logs at DEBUG, so error logs and crash-tooling alerts only reflect genuine failures.
***
## Google Waterfall adapter 13.6.0.2 - 2026-08-11
### Added
* **Google Ad Manager demand** — `CLXGoogleWaterfallPlacementTypeGam` placements load via the GAM request surface (banner, MREC, interstitial); revenue measured from realized GAM revenue history. Install: `pod 'CloudXGoogleWaterfallAdapter', '~> 13.6.0.2'`; requires `CloudXCore >= 3.7.0`.
### Changed
* **Realized-price bookkeeping moved into `CloudXCore`** — The adapter's `CLXGoogleWaterfallRealizedPriceStore` header is removed; `CloudXCore` 3.7.0 now keeps this history using the same storage keys. No publisher action needed.
***
## Google Waterfall adapter 12.14.0.2 - 2026-08-11
### Added
* **Google Ad Manager demand (12.14.0)** — Same support as 13.6.0.2 (`CLXGoogleWaterfallPlacementTypeGam` placements; `CLXGoogleWaterfallRealizedPriceStore` now in core), for Google Mobile Ads SDK 12.14.0. Install: `pod 'CloudXGoogleWaterfallAdapter', '12.14.0.2'`; requires `CloudXCore >= 3.7.0`.
***
## TaurusX adapter 1.18.1.0 - 2026-08-03
### Added
* **TaurusX adapter** — New `CloudXTaurusXAdapter` serving Banner, MREC, Interstitial, Rewarded, and Native. Install: `pod 'CloudXTaurusXAdapter', '~> 1.18.1.0'`. Backed by TaurusX SDK 1.18.1; requires `CloudXCore >= 3.5.0`.
***
## Pangle adapter 8.2.0.7.0 - 2026-07-30
### Changed
* **Pangle** `CloudXPangleAdapter` → `~> 8.2.0.7.0` (Ads-Global 8.2.0.7). Install: `pod 'CloudXPangleAdapter', '~> 8.2.0.7.0'`; requires `CloudXCore >= 3.5.0`.
***
## Google Waterfall adapter 12.14.0.1 - 2026-07-29
### Fixed
* **Prefetched Google ads could be shown after expiring (12.14.0)** — Backport of the 13.6.0.1 fix for Google Mobile Ads SDK 12.14.0; fills now expire and refresh before stale, preventing blank renders. Install: `pod 'CloudXGoogleWaterfallAdapter', '12.14.0.1'`; requires `CloudXCore >= 3.5.0`.
***
## \[3.6.0] - 2026-07-28
Install: `pod 'CloudXCore', '~> 3.6'`
### Added
* **Report impression-level revenue from your own mediation platform** — Forward paid events to CloudX with `reportRevenueData(_:)`. AdMob, InMobi, and TopOn are built in.
* **App-wide ad-revenue delegate** — Receive impression-level revenue for every CloudX-won impression from one `CLXAdRevenueDelegate` instead of per ad. Register with `addAdRevenueDelegate(_:)`.
* **AppsFlyer ad-revenue connector** — Forward CloudX impression revenue to AppsFlyer. Install: `pod 'CloudXAppsFlyerConnector', '~> 6.15.0.0'`. Requires `AppsFlyerFramework >= 6.15.0`.
* **Adjust ad-revenue connector** — Forward CloudX impression revenue to Adjust. Install: `pod 'CloudXAdjustConnector', '~> 5.0.0.0'`. Requires `Adjust >= 5.0.0`.
### Fixed
* **Banner and MREC creatives could render blank on the CloudX renderer** — Creatives could fail on first load and after refresh, surfacing as blank impressions. They now render correctly.
* **HTML rewarded creatives were rejected on the CloudX renderer** — The renderer accepted only VAST rewarded creatives, so HTML rewarded ads failed to load. HTML rewarded creatives now render.
### Removed
* **Internal headers removed from the `CloudXCore.h` umbrella** — Internal implementation headers were removed from the umbrella; none was linkable, so no integration is affected. Report revenue through `reportRevenueData(_:)` and the public revenue types.
***
## Google Waterfall adapter 13.6.0.1 - 2026-07-21
### Fixed
* **Prefetched Google ads could be shown after they expired** — Prefetched fills now expire and refresh in the background before going stale, preventing blank renders. Install: `pod 'CloudXGoogleWaterfallAdapter', '~> 13.6.0.1'`. Requires `CloudXCore >= 3.5.0`.
***
## Off-line adapter releases (-core34) - 2026-07-16
Off-line builds of three adapters for publishers still on `CloudXCore` 3.4.x. Each works with `CloudXCore` 3.4.5 or 3.4.6 and is **not** compatible with `CloudXCore` 3.5.x. Pin the exact version shown — these versions are never picked up by `pod update` automatically.
* **Google Waterfall** — `pod 'CloudXGoogleWaterfallAdapter', '13.0.0.0-core34'`. Backed by Google Mobile Ads SDK 13.0.0. Requires `CloudXCore >= 3.4.6, < 3.5`.
* **Unity Ads** — `pod 'CloudXUnityAdsAdapter', '4.16.5.0-core34'`. Backed by UnityAds 4.16.5. Requires `CloudXCore >= 3.4.5, < 3.5`. Reward is granted on completed ad watch; UnityAds 4.16.5 provides no ad-expiry callback, so expired creatives are not signaled.
* **Moloco** — `pod 'CloudXMolocoAdapter', '4.3.0.0-core34'`. Backed by MolocoSDKiOS 4.3.0. Requires `CloudXCore >= 3.4.5, < 3.5`.
***
## MobileFuse adapter 1.11.0.0 - 2026-07-08
### Added
* **MobileFuse adapter** — New `CloudXMobileFuseAdapter` serving Banner, MREC, Interstitial, and Rewarded. Install: `pod 'CloudXMobileFuseAdapter', '~> 1.11.0.0'`. Backed by MobileFuse SDK 1.11.0; requires `CloudXCore >= 3.5.0`.
***
## \[3.5.0] - 2026-07-07
Adapters now version independently of `CloudXCore`. Each adapter pod uses `.` (e.g. `pod 'CloudXVungleAdapter', '~> 7.7.4.0'`) and is tied to one exact network SDK version per release. `CloudXCore` continues on SDK semver (`pod 'CloudXCore', '~> 3.5'`).
### Breaking Changes
* **Adapter pods no longer share the `CloudXCore` version.** Existing pins on the old unified line (`~> 3.4`, `~> 3.x`) will not resolve new adapter releases. Update each adapter pin independently, for example `pod 'CloudXMintegralAdapter', '~> 8.1.5.0'`.
* **`CloudXMagniteAdapterV2` replaces `CloudXMagniteAdapter` for independent versioning.** Install `pod 'CloudXMagniteAdapterV2', '~> 1.0.0.0'` to move to the new version line; the legacy pod remains on the old line. No source changes — the import name is unchanged.
* **`CloudXRenderer` is no longer a separate pod.** Its functionality is built into `CloudXCore`; remove any direct `CloudXRenderer` dependency from your Podfile.
### Added
* **Trusted Arbiter Custom Bids** — Added mediator-agnostic custom bid inputs with `CLXArbiterBid.custom(...)`, `CLXArbiterPlatform.custom`, and `CLXArbiterPrecision`.
* **App Open ad format** — New `CLXAppOpen` ad class for full-screen ads shown while your app loads or returns to the foreground. Create with `createAppOpen(adUnitId:)`. Supported on the Google Waterfall, Mintegral, Pangle, and Vungle adapters.
* **VAST video interstitials and rewarded on the CloudX renderer** — The CloudX renderer now serves VAST video interstitial and rewarded creatives, with Open Measurement (OMID) video measurement. Rollout is controlled server-side; no integration change is required.
* **HTML rewarded on the CloudX renderer** — HTML rewarded creatives now render through the CloudX renderer, completing the fullscreen HTML support introduced for interstitials in 3.4.5. Server-side rollout; no integration change required.
* **Native ads on Digital Turbine, Mintegral, Pangle, Verve, and Google Waterfall** — These adapters now serve Native creatives, standalone and native-in-banner / native-in-MREC.
* **Google Waterfall — Interstitial and Rewarded** — `CloudXGoogleWaterfallAdapter` now serves fullscreen Interstitial and Rewarded creatives, in addition to Banner, MREC, Native, and App Open.
* **In-app App Store sheet for install-ad clickthroughs** — Tapping an install-campaign ad now opens an in-app App Store product sheet instead of leaving your app, for CloudX-rendered creatives that declare the advertised app.
* **Fullscreen HTML interstitial clickthrough** — Fullscreen HTML interstitials rendered by the CloudX renderer now navigate on tap.
* **`CloudXMagniteAdapterV2` 1.0.0.1** — the new independent-versioned Magnite pod. Install: `pod 'CloudXMagniteAdapterV2', '~> 1.0.0.0'`. Backed by MagniteSDK 1.0.0.
### Changed — Adapter releases
* **Vungle** `CloudXVungleAdapter` → `~> 7.7.4.0` (VungleAds 7.7.4).
* **Mintegral** `CloudXMintegralAdapter` → `~> 8.1.5.0` (MintegralAdSDK 8.1.5).
* **Unity Ads** `CloudXUnityAdsAdapter` → `~> 4.19.0.0` (UnityAds 4.19.0).
* **Verve** `CloudXVerveAdapter` → `~> 3.9.0.0` (HyBid 3.9.0).
* **Digital Turbine** `CloudXDigitalTurbineAdapter` → `~> 8.4.8.0` (Fyber Marketplace SDK 8.4.8).
* **Google Waterfall** `CloudXGoogleWaterfallAdapter` → `~> 13.6.0.0` (Google Mobile Ads SDK 13.6.0 — a major-version upgrade from 12.x).
* **InMobi** `CloudXInMobiAdapter` → `~> 11.3.0.0` (InMobiSDK 11.3.0).
* **Meta** `CloudXMetaAdapter` → `~> 6.21.1.0` (FBAudienceNetwork 6.21.1).
* **Moloco** `CloudXMolocoAdapter` → `~> 4.8.0.0` (MolocoSDKiOS 4.8.0).
* **Pangle** `CloudXPangleAdapter` → `~> 7.9.1.3.0` (Ads-Global 7.9.1.3).
### Fixed
* **Some bidders were not billed for certain banner impressions** — Native ads rendered in a banner slot (native-in-banner) could be counted without the bidder billing notice, so the bidder was never billed. Impressions are now billed correctly in all cases.
* **Native ads in a banner or MREC slot could report their impression too early** — A native ad in a banner or MREC slot could fire its impression before the ad view was actually visible, especially when preloading off-screen. Impressions now report at the correct time.
* **Stuck fullscreen ad after a rendering crash** — If the web content behind a fullscreen ad crashed after the ad loaded, the user could be left on a stuck fullscreen. The ad now closes automatically and delivers the close callback.
* **Native ads in banner slots could miss impressions with compact layouts** — Fixed an issue where a native ad rendered in a banner slot with the compact template could display without registering an impression.
### Removed
* **`CloudXRenderer` standalone pod** — merged into `CloudXCore`. See Breaking Changes above.
***
## \[3.4.6] - 2026-06-29
### Changed
* **`CloudXMolocoAdapter` now supports the full MolocoSDK 4.x line** — The `MolocoSDKiOS` requirement was widened from `~> 4.6.0` to `>= 4.6.0, < 5.0`, so you can adopt newer 4.x Moloco SDK releases. This resolves a CocoaPods dependency conflict that could block integration.
### Fixed
* **Rare crash when an ad finished loading** — Fixed a rare crash that could occur when a banner or fullscreen (interstitial / rewarded) ad finished loading. Affected ads now load and report their result reliably.
* **Duplicate banner impressions after a no-fill refresh** — A banner whose auto-refresh returned no bid could re-display the previous creative and count extra impressions. A no-bid refresh now shows nothing and reports no impression.
***
## \[3.4.5] - 2026-06-12
### Added
* **Full native ad support across InMobi, Meta, Vungle, and Moloco** — With InMobi native and Vungle standalone native added in this release, all four demand partners now serve native ads in every form CloudX supports: standalone (via `CLXNativeAdLoader`) and native-in-banner / native-in-MREC.
* **InMobi native ads** — `CloudXInMobiAdapter` now serves Native creatives, both standalone (via `CLXNativeAdLoader`) and native-in-banner / native-in-MREC. Requires `InMobiSDK >= 11.2.0` (the adapter's minimum was raised from `11.0.0`).
* **Vungle standalone native ads** — `CloudXVungleAdapter` now serves standalone Native creatives via `CLXNativeAdLoader` (native-in-banner / native-in-MREC was already supported).
* **HTML interstitials on the CloudX renderer** — The CloudX renderer now supports fullscreen HTML interstitial creatives with MRAID 3.0, alongside the existing HTML banner / MREC support. Rollout is controlled server-side; no integration change is required.
### Changed
* **Slow ad network SDKs no longer delay CloudX SDK initialization** — A slow network SDK no longer blocks init; CloudX completes without it and the network joins later auctions once ready. Networks not yet initialized are excluded from auctions they cannot serve.
### Fixed
* **Digital Turbine adapter no longer conflicts with another mediation SDK initializing the Fyber SDK** — If another mediation SDK initializes the shared Fyber SDK first, the CloudX adapter now defers instead of re-initializing. For smooth startup, initialize the other SDK first, then CloudX.
* **Some valid HTML banner creatives were rejected by the renderer** — Fixed an issue where HTML banner / MREC creatives containing failed tracker pixels were misclassified as broken and not displayed. These creatives now render normally.
* **Native ad reliability improvements on Meta and Moloco** — Meta native ads now validate the loaded ad and register all asset views for impression and click tracking, not just the CTA. Moloco native ads are no longer delivered when the underlying ad is not renderable.
### Removed
* **Adapter-integration APIs** — Removed `clx_isFlexibleSize`, `+isInitialized`, and per-instance `sdkVersion` from the adapter-facing surface. These had no function for app integrations; standard publisher integrations are unaffected.
***
## \[3.4.4] - 2026-06-02
### Fixed
* **Digital Turbine adapter failed to link on Fyber Marketplace SDK below 8.4.0** — The adapter binary failed to link on a Fyber SDK below `8.4.0`. It now links across the full `8.x` line — Banner, MREC, Interstitial, and Rewarded work on Fyber `8.0.0`+; native fill remains on `8.4.0`+.
* **Google Waterfall — occasional lost fills on slow mediation responses** — A Google Waterfall ad could be discarded when its mediation cascade responded slowly, even though it ultimately filled. Slow-but-successful responses are now delivered, improving fill reliability.
***
## \[3.4.3] - 2026-06-01
### Added
* **Pangle Adapter** — New `CloudXPangleAdapter` supporting Banner/MREC, Interstitial, and Rewarded ad formats. Install: `pod 'CloudXPangleAdapter', '~> 3.4.3'`. Backed by the Pangle (ByteDance) SDK `Ads-Global ~> 7.9.0`.
### Changed
* **DigitalTurbine adapter — broader Fyber Marketplace SDK compatibility** — `CloudXDigitalTurbineAdapter` now accepts `Fyber_Marketplace_SDK >= 8.0.0` (previously `>= 8.4.0`). Banner, MREC, Interstitial, and Rewarded work across the full 8.x line; native fill remains on Fyber SDK 8.4.0+.
### Fixed
* **App Store rejection from the `itms-services` URL scheme** — removed the scheme from the SDK binary; store redirects are unaffected.
* **SDK init could fail for some accounts** — fixed initialization failing for accounts without an organization identifier.
***
## \[3.4.2] - 2026-05-29
### Added
* **Google Waterfall Adapter** — New `CloudXGoogleWaterfallAdapter` (Banner, MREC). Install via CocoaPods (`pod 'CloudXGoogleWaterfallAdapter', '~> 3.4.2'`) or Swift Package Manager. Backed by `Google-Mobile-Ads-SDK 12.14.0`; requires a `GADApplicationIdentifier` in your Info.plist.
***
## \[3.4.1] - 2026-05-27
### Added
* **Digital Turbine Adapter** — New `CloudXDigitalTurbineAdapter` (Banner, MREC, Interstitial, Rewarded, Native). Install: `pod 'CloudXDigitalTurbineAdapter', '~> 3.4.1'`. Backed by `Fyber_Marketplace_SDK >= 8.4.0, < 9.0`.
### Fixed
* **Duplicate `didLoadAd` / `didShowAd` callbacks on banner refresh** — When a partner adapter SDK fired its load or impression callback more than once, publishers could receive duplicates. The first callback is now canonical and re-fires are dropped, so one load and one impression emit per ad.
***
## \[3.4.0] - 2026-05-22
### Added
* **Verve Adapter** — New `CloudXVerveAdapter` (Banner, MREC, Interstitial, Rewarded, Native). Install: `pod 'CloudXVerveAdapter', '~> 3.4.0'`. Backed by HyBid 3.8.0.
* **Moloco Adapter** — New `CloudXMolocoAdapter` (Banner, MREC, Interstitial, Rewarded, Native). Install: `pod 'CloudXMolocoAdapter', '~> 3.4.0'`. Backed by MolocoSDKiOS `~> 4.6.0`.
* **Non-SDK CloudX-rendered HTML banner and MREC with MRAID 3.0** — HTML banner and MREC creatives can now be served through the CloudX renderer, with MRAID 3.0 support.
* **Native-in-banner and native-in-MREC support** — Native creatives can be served into banner or MREC slots on Meta, Vungle, and Moloco.
* **Trusted Arbiter** — Added public APIs for comparing CloudX bids with supported third-party bids from Unity LevelPlay and PubMatic, and selecting the winning platform through CloudX arbitration.
* **`CLXAd.adValues` property** — New read-only `NSDictionary` exposing SDK-defined loaded-ad metadata. Values may be absent depending on ad format and network.
### Changed
* **UnityAds adapter version range** — Widened to accept Unity Ads 4.x patch releases (`>= 4.17.0, < 5.0`).
* **Publisher delegate main-queue threading is now documented contract** — All `CLXAdDelegate` callbacks (and protocols that extend it) and `CLXAdRevenueDelegate` callbacks deliver on the main queue and may fire inline relative to the SDK call that triggered them. No runtime change.
### Fixed
* **Native ad memory leak during long sessions** — Long-lived publisher integrations that load many native ads no longer accumulate retained observers across native ad refresh cycles.
***
## \[3.2.0] - 2026-04-22
### Added
* **Magnite Adapter** — New `CloudXMagniteAdapter` (Banner, MREC, Interstitial, Rewarded). Install: `pod 'CloudXMagniteAdapter', '~> 3.2.0'`.
* **Richer dashboard telemetry** — Full telemetry overhaul. More robust event capture, new events, and increased observability.
***
## \[3.1.0] - 2026-04-16
### Added
* **Meta Reels & Native Ads** — Meta Reels (9:16 vertical video) and other Meta native ad formats are now supported via `CLXPublisherNative`. This release supports Meta native ads only; additional bidder support is coming in a future release.
* **Per-Adapter Initialization Timeout** — SDK initialization now supports per-adapter timeouts for more predictable startup behavior.
### Fixed
* **Xcode 26 Compatibility** — Resolved a build warning when compiling with Xcode 26.
***
## \[2.2.9] - 2026-04-09
### Fixed
* **Unity Ads Adapter** — Fixed initialization, privacy consent forwarding, rewarded callbacks, and bid token error handling for improved reliability across regions.
***
## \[2.2.8] - 2026-04-03
### Fixed
* **Crash Fix** — Fixed a crash that could occur during concurrent ad event tracking on background threads
***
## \[2.2.7] - 2026-04-01
### Fixed
* **Deferred Banner Display** — Fixed an issue where banners created before SDK initialization completed could fail to display after initialization succeeded
* **Vungle Adapter Reliability** — Fixed an issue where Vungle rewarded and interstitial ads could crash at runtime due to a method being incorrectly stripped during linking
***
## \[2.2.6] - 2026-03-30
### Added
* **Privacy Consent for Unity Ads** — Unity Ads adapter now supports GDPR and CCPA privacy consent forwarding
* **`isAdReady` Support** — Fullscreen ad adapters now support `isAdReady` for reliably querying ad availability before calling show
### Fixed
* **Banner Visibility Accuracy** — Fixed an issue where setting `banner.hidden = YES` did not pause ad refresh. Hidden banners could continue loading ads without being impression-eligible, potentially impacting CPMs.
* **Improved Dependency Compatibility** — Widened third-party SDK version constraints (VungleAds, FBAudienceNetwork, InMobiSDK) to prevent CocoaPods dependency conflicts when integrating alongside other mediation SDKs
* **Fullscreen Ad Reliability** — Fixed an issue where ad lifecycle callbacks could be silently lost in rare scenarios
* **iOS 16 Crash Fix** — Fixed a crash on iOS 16 devices related to session tracking
***
## \[2.2.4] - 2026-03-26
### Changed
* **Server-Driven Location Controls** — Location coordinate sharing in bid requests is now controlled via the CloudX dashboard (account-scoped). No SDK code changes required for publishers.
* **Improved Bid Request Data** — Content language is now included in bid requests for improved ad targeting
***
## \[2.2.3] - 2026-03-19
### Added
* **Unity Ads Adapter** — Unity Ads adapter now available for banner, interstitial, and rewarded ads
### Fixed
* **Unity Ads Initialization** — Fixed an issue where the Unity Ads adapter could fail to initialize correctly in some configurations
***
## \[2.2.2] - 2026-03-16
### Added
* **Manual Privacy API** — New `setHasUserConsent:` and `setDoNotSell:` methods for publisher-controlled privacy consent
* **Mintegral Adapter** — Mintegral adapter now available as a standard release
### Fixed
* **Improved Error Visibility** — Increased error visibility for no-bid scenarios, making it easier to diagnose fill rate issues
* **Fullscreen Ad Loading** — Fixed an issue where loading a fullscreen ad while another was showing could silently fail
* **Fullscreen Failure Callbacks** — Fixed missing ad unit ID in fullscreen ad failure callbacks
* **Renderer Impression Tracking** — Fixed viewability tracking accuracy in CloudX Renderer
### Changed
* **Mintegral SDK** — Upgraded from 8.0.7 to 8.0.8
***
## \[2.0.0] - 2026-02-04
This release replaces placement names with **Ad Unit IDs** from the CloudX dashboard. Update your `createBannerWithAdUnitId:`, `createMRECWithAdUnitId:`, `createInterstitialWithAdUnitId:`, and `createRewardedWithAdUnitId:` calls to use the ad unit ID instead of a placement name.
### Added
* Rewarded ads with `createRewardedWithAdUnitId:delegate:` and `CLXRewardedDelegate`
* InMobi adapter (SDK 11.1) with support for banner, MREC, interstitial, and rewarded ads
* `CLXAd.networkPlacement` property for network-specific placement ID
### Breaking Changes
* Renamed `placement` parameter to `adUnitId` in `createBannerWithAdUnitId:`, `createMRECWithAdUnitId:`, `createInterstitialWithAdUnitId:`, `createRewardedWithAdUnitId:`
* Renamed `CLXAd.placement` to `adUnitId`
* Renamed `CLXAd.bidderName` to `networkName`
* Renamed `CLXErrorCodeInvalidPlacement` to `CLXErrorCodeInvalidAdUnit`
* Changed `bannerAdView:didFailWithError:` to include ad unit ID in error
* Removed `testMode` parameter from `initializeSDKWithAppKey:completion:` - test mode is now server-controlled via dashboard
### Changed
* Meta Audience Network SDK updated from 6.17.0 to 6.21.0
* Vungle SDK updated from 7.4.2 to 7.6.0
### Fixed
* Fixed IFA (Identifier for Advertisers) collection
* Fixed country/geo-targeting data collection
***
## \[1.3.0] - 2025-12-14
### Added
* **Banner Refresh Retry** - Banners now automatically retry loading after failure when hidden
### Fixed
* **App Extension Compatibility** - SDK now works correctly in App Extensions (no UIApplication calls)
* **Rewarded Delegate Callbacks** - Fixed callback ordering bug
* **Symbol Collisions** - All category methods now prefixed with `clx_` to prevent conflicts
### Changed
* **CloudXCore now distributed as Dynamic Framework** - Enables crash symbolication for SDK issues
***
## \[1.2.1] - 2025-12-04
### Added
* **Visual Debugger Button** - New debugging tool for development and QA
* **High-ROI Key-Value Targeting Examples** - Enhanced demo apps with targeting signal examples
### Fixed
* Corrected vendored\_frameworks paths in podspecs
***
## \[1.2.0] - 2025-11-26
### 🚀 First Official Release
Initial release of the CloudX iOS SDK with support for banner, MREC, and interstitial ads.
---
# Integration
> AdMob/GAM estimated adapter for CloudX iOS SDK. Serves AdMob waterfall and Google Ad Manager demand for Banner, MREC, Interstitial, Rewarded, Native, and App Open placements.
> **Warning:** AdMob/GAM estimated is in controlled beta, and Google has not reviewed or endorsed it. See the [publisher setup guide](/en/networks/google-estimated) for details and access.
The Google adapter serves both **AdMob estimated** and **Google Ad Manager estimated** demand — AdMob waterfall and Google Ad Manager, respectively. This page covers iOS-specific integration details only.
> **Info:** GMA Next-Gen SDK support is Android-only for now — Google has not shipped an iOS next-gen SDK. On iOS, this adapter is backed by the Google Mobile Ads SDK.
For dashboard configuration, see the [AdMob/GAM estimated publisher setup guide](/en/networks/google-estimated).
> **Note:** AdMob estimated was previously called Google Waterfall. You will still see `googleWaterfall` in adapter artifact names.
Pricing and the Google-side eCPM floor are dashboard and console decisions, not app-side ones. See the "Pricing: automatic or manual" and "Set an eCPM floor: Google Optimized or Manual" sections of the [AdMob/GAM estimated publisher setup guide](/en/networks/google-estimated).
The adapter supports **Banner (320×50)**, **MREC (300×250)**, **Interstitial**, **Rewarded**, **Native**, and **App Open** ad formats for both demand sources.
## Which Google demand you get
The adapter serves two independent Google demand sources:
* **AdMob estimated** — AdMob waterfall demand.
* **Google Ad Manager estimated** — Google Ad Manager demand.
Which one runs is a dashboard decision, not a code one. You enable the ad source per placement in the CloudX dashboard; there is no separate artifact, no extra dependency, and no app-side change. Both sources may run in the same app, each with its own mapping and CPM target, and both may bid in the same auction.
Ad Manager ad unit ids take the form `/NNNNNNN/placement/name`, unlike AdMob's plain ad unit id.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXGoogleWaterfallAdapter', '~> 13.9.0.0'
```
```bash
pod install --repo-update
```
### Swift Package Manager
Add the CloudX Google Waterfall adapter package to your project:
* Package URL: `https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-googlewaterfall.git`
* Exact package version: `13090000.0.0`
* Product: `CloudXGoogleWaterfallAdapter`
The package installs CloudX Core and Google Mobile Ads as dependencies. Add `$(inherited) -ObjC` to the app target's Other Linker Flags.
### Manual
1. Download `CloudXGoogleWaterfallAdapter.xcframework.zip` from [Releases](https://github.com/cloudx-io/cloudx-ios/releases)
2. Unzip and drag `CloudXGoogleWaterfallAdapter.xcframework` into your Xcode project
3. Add the Google Mobile Ads SDK (`Google-Mobile-Ads-SDK 13.9.0`) manually.
## Info.plist Configuration
### Google Application Identifier (Required)
The Google Mobile Ads SDK requires your AdMob app ID in Info.plist. Without it, the adapter fails initialization for the whole session and never requests a Google ad:
```xml Info.plist
GADApplicationIdentifierca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY
```
Replace the value with your own AdMob app ID from the AdMob console.
### SKAdNetwork IDs (Required for iOS 14.5+)
Add Google's SKAdNetwork identifiers so Google can attribute installs. The primary Google identifier is:
```xml Info.plist
SKAdNetworkItemsSKAdNetworkIdentifiercstr6suwn9.skadnetwork
```
Google publishes the complete, up-to-date SKAdNetwork identifier list in its [Google Mobile Ads documentation](https://developers.google.com/admob/ios/quick-start). Include the full list to maximize attribution coverage.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## Project Configuration
**Linker Flags:** Add `$(inherited) -ObjC` to Other Linker Flags in Build Settings. Swift Package Manager does not set this app-target flag automatically.
## Compatibility
| Adapter version | Network SDK | Minimum CloudXCore |
| --------------- | ------------------------------- | ------------------ |
| `13.9.0.0` | Google Mobile Ads SDK `13.9.0` | `3.7.0` |
| `13.8.0.0` | Google Mobile Ads SDK `13.8.0` | `3.7.0` |
| `13.7.0.0` | Google Mobile Ads SDK `13.7.0` | `3.7.0` |
| `13.6.0.4` | Google Mobile Ads SDK `13.6.0` | `3.7.0` |
| `13.6.0.3` | Google Mobile Ads SDK `13.6.0` | `3.7.0` |
| `13.6.0.2` | Google Mobile Ads SDK `13.6.0` | `3.7.0` |
| `13.6.0.1` | Google Mobile Ads SDK `13.6.0` | `3.5.0` |
| `12.14.0.3` | Google Mobile Ads SDK `12.14.0` | `3.7.0` |
| `12.14.0.2` | Google Mobile Ads SDK `12.14.0` | `3.7.0` |
| `12.14.0.1` | Google Mobile Ads SDK `12.14.0` | `3.5.0` |
| `12.12.0.0` | Google Mobile Ads SDK `12.12.0` | `3.7.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes AdMob estimated or Google Ad Manager estimated demand. No manual adapter registration is required.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Changelog
> AdMob estimated adapter release history for CloudX iOS SDK
## \[13.9.0.0] - 2026-09-14
* Certified with Google Mobile Ads SDK 13.9.0.
* Requires CloudXCore >= 3.7.0.
## \[13.8.0.0] - 2026-09-14
* Certified with Google Mobile Ads SDK 13.8.0.
* Requires CloudXCore >= 3.7.0.
## \[13.7.0.0] - 2026-09-14
* Certified with Google Mobile Ads SDK 13.7.0.
* Requires CloudXCore >= 3.7.0.
* Fixed: manually integrated adapters now enforce the existing CloudXCore 3.7.0 minimum.
## \[13.6.0.4] - 2026-08-26
* Changed: Google adapter startup no longer delays CloudX SDK initialization. The Google Mobile Ads SDK now starts in the background instead of holding up your `initialize` call. A missing `GADApplicationIdentifier` in your Info.plist still fails initialization straight away.
* Changed: an ad requested in the moment right after initialization may no-fill once. A request landing before Google's background start finishes returns a fast no-fill for that one request instead of waiting, and the next request is served normally.
* Certified with Google Mobile Ads SDK 13.6.0. Requires CloudXCore >= 3.7.0.
## \[13.6.0.3] - 2026-08-21
* Changed: fullscreen ads now prefetch through Google's ad-preloading API — interstitial, rewarded, and app-open ads are preloaded by the Google Mobile Ads SDK, not the adapter. Banner, MREC, and native are unchanged, and no publisher action is needed.
* Certified with Google Mobile Ads SDK 13.6.0. Requires CloudXCore >= 3.7.0.
## \[13.6.0.2] - 2026-08-11
* Added: Google Ad Manager demand. `CLXGoogleWaterfallPlacementTypeGam` placements load via the GAM request surface (banner, MREC, interstitial); revenue measured from realized GAM revenue history.
* Changed: realized-price bookkeeping moved into `CloudXCore`. The adapter's `CLXGoogleWaterfallRealizedPriceStore` header is removed; `CloudXCore` 3.7.0 now keeps this history using the same storage keys. No publisher action needed.
* Certified with Google Mobile Ads SDK 13.6.0. Requires CloudXCore >= 3.7.0.
## \[13.6.0.1] - 2026-07-21
* Fixed: prefetched ads could be served after Google's documented ad-expiry windows, causing blank or failed renders. Prefetched fills now expire per format — 4 hours for App Open, 1 hour for all other formats (`CLXGoogleWaterfallFillTtlMsForFormat`) — and are proactively refreshed in the background shortly before expiry.
* Certified with Google Mobile Ads SDK 13.6.0. Requires CloudXCore >= 3.5.0.
## \[13.6.0.0] - 2026-07-08
* Certified with Google Mobile Ads SDK 13.6.0.
* Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open.
* First independently versioned release. Requires CloudXCore >= 3.5.0.
## \[12.14.0.3] - 2026-08-26
* Changed: Google adapter startup no longer delays CloudX SDK initialization. Same change as 13.6.0.4, for Google Mobile Ads SDK 12.14.0. A request landing before Google's background start finishes no-fills once.
* Certified with Google Mobile Ads SDK 12.14.0. Requires CloudXCore >= 3.7.0. Prefer `~> 13.6.0.4` unless you must stay on 12.14.0.
## \[12.14.0.2] - 2026-08-11
* Added: Google Ad Manager demand (12.14.0). Same support as 13.6.0.2 (`CLXGoogleWaterfallPlacementTypeGam` placements; `CLXGoogleWaterfallRealizedPriceStore` now in core), for Google Mobile Ads SDK 12.14.0.
* Certified with Google Mobile Ads SDK 12.14.0. Requires CloudXCore >= 3.7.0. Prefer `~> 13.6.0.2` unless you must stay on 12.14.0.
## \[12.14.0.1] - 2026-07-29
* Fixed: prefetched ads could be served after Google's documented ad-expiry windows, causing blank or failed renders. Prefetched fills now expire per format — 4 hours for App Open, 1 hour for all other formats (`CLXGoogleWaterfallFillTtlMsForFormat`) — and are proactively refreshed in the background shortly before expiry. Backport of the 13.6.0.1 fix to the 12.14.0 compatibility line.
* Certified with Google Mobile Ads SDK 12.14.0. Requires CloudXCore >= 3.5.0. Prefer `~> 13.6.0.1` unless you must stay on 12.14.0.
## \[12.12.0.0] - 2026-09-02
* Added: compatibility-line release for Google Mobile Ads SDK 12.12.0. Same feature set as 12.14.0.3 (Google Ad Manager demand, realized-price history in core, fill expiry, startup off the SDK init gate), built against Google Mobile Ads SDK 12.12.0; no adapter source changed. Fullscreen ads still prefetch through the adapter's own loader, since Google's ad-preloading API is not available on 12.x.
* Certified with Google Mobile Ads SDK 12.12.0. Requires CloudXCore >= 3.7.0. Prefer `~> 13.6.0.4` unless you must stay on 12.12.0.
## \[12.14.0.0] - 2026-07-08
* Compatibility-line release for apps pinned to Google Mobile Ads SDK 12.14.0.
* Requires CloudXCore >= 3.5.0. Prefer `~> 13.6.0.0` unless you must stay on 12.14.0.
---
# Integration
> BIGO Ads adapter for CloudX iOS SDK. Supports Banner, MREC, Interstitial, and Rewarded.
For CloudX dashboard mapping and BIGO Ads-side ID setup, see the [BIGO Ads bidder guide](/en/networks/bigo-ads). This page covers iOS-specific integration details only.
The iOS adapter supports Banner (320x50), MREC (300x250), Interstitial, and Rewarded. The adapter does not support Native or App Open (called Splash on the BIGO Ads platform) yet.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXBigoAdapter', '~> 6.1.0.0'
```
```bash
pod install --repo-update
```
### Manual
1. Download `CloudXBigoAdapter.xcframework.zip` from [Releases](https://github.com/cloudx-io/cloudx-ios/releases).
2. Unzip and add `CloudXBigoAdapter.xcframework` to your Xcode project.
3. Add `BigoADS` (`= 6.1.0`) manually.
## Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
BIGO Ads requires its `SKAdNetworkItems` in your app's Info.plist. The list is long and changes over time, so copy the current block from the [BIGO Ads iOS integration guide](https://www.bigossp.com/guide/sdk/ios/integration) on the BIGO Ads platform (sign-in required) rather than from this page.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
**Deployment Target:** Ensure your project's minimum deployment target is iOS 13.0 or higher.
## Banner refresh
Leave auto-refresh off for your BIGO Ads slots on the BIGO Ads platform. CloudX owns the refresh cycle, and a second refresh loop inside the BIGO Ads SDK renders creatives CloudX never auctioned.
## Compatibility
| Adapter version | Network SDK | Minimum CloudX SDK |
| --------------- | --------------- | ------------------ |
| `6.1.0.0` | BigoADS `6.1.0` | `3.9.0` |
| `5.3.0.0` | BigoADS `5.3.0` | `3.9.0` |
`CloudXCore` 3.9.0 is the first version whose ad network registry lists BIGO Ads, so an earlier core never discovers this adapter.
The adapter is discovered automatically when your server-provisioned CloudX configuration includes BIGO Ads demand. No manual adapter registration is required.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Changelog
> BIGO Ads adapter release history for CloudX iOS SDK
## \[6.1.0.0] - 2026-09-23
* Certified with BigoADS 6.1.0.
* The adapter xcframework now includes its Apple privacy manifest.
* Requires CloudXCore >= 3.9.0. Adapter behavior is unchanged.
## \[5.3.0.0] - 2026-09-17
* Initial release.
* Certified with BigoADS 5.3.0.
* Supports Banner, MREC, Interstitial, and Rewarded (server bidding only).
* Requires CloudXCore >= 3.9.0.
---
# Integration
> Digital Turbine (Fyber Marketplace) adapter for CloudX iOS SDK. Supports Banner, MREC, Interstitial, Rewarded, and Native.
For CloudX dashboard mapping and Digital Turbine-side ID setup, see the [Digital Turbine bidder guide](/en/networks/digitalturbine). This page covers iOS-specific integration details only.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXDigitalTurbineAdapter', '~> 8.4.10.1'
```
> **Warning:** Adapter 8.4.10.1 requires iOS 15.0+ and Xcode 26.1+. Raise your app’s deployment target before installing it. CloudXCore’s minimum remains unchanged.
>
> For apps staying on older versions, the [known issue](#known-issues) applies to adapter 8.4.8.0; affected apps should pin `~> 8.4.7.0`.
```bash
pod install --repo-update
```
### Swift Package Manager
Add the adapter package to your project:
* Package URL: `https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-digitalturbine.git`
* Exact package version: `8041001.0.0`
* Product: `CloudXDigitalTurbineAdapter`
The package installs CloudX Core and Digital Turbine as dependencies. Add `$(inherited) -ObjC` to the app target's Other Linker Flags.
### Manual
1. Download `CloudXDigitalTurbineAdapter.xcframework.zip` from [Releases](https://github.com/cloudx-io/cloudx-ios/releases).
2. Unzip and drag `CloudXDigitalTurbineAdapter.xcframework` into your Xcode project.
3. Add `Fyber_Marketplace_SDK` (`= 8.4.10`) manually.
## Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
Include the SKAdNetwork IDs provided by Digital Turbine for your integration. Refer to the current `Fyber_Marketplace_SDK` documentation for the latest `SKAdNetworkItems` list.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## Native Ads
The adapter does not expose the main image asset directly: `ad.nativeAd.mainImage` is not populated. Media content — image or video — renders through the adapter-provided media view, which CloudX attaches to the container bound with the view binder's `mediaContentViewTag`. Bind a visible media container as described in [Native ads](/en/ios/ad-formats/native).
## Compatibility
| Adapter version | Network SDK | Minimum CloudXCore |
| --------------- | ------------------------------ | ------------------ |
| `8.4.10.1` | Fyber Marketplace SDK `8.4.10` | `3.5.0` |
| `8.4.10.0` | Fyber Marketplace SDK `8.4.10` | `3.5.0` |
| `8.4.8.0` | Fyber Marketplace SDK `8.4.8` | `3.5.0` |
| `8.4.7.0` | Fyber Marketplace SDK `8.4.7` | `3.5.0` |
| `8.4.6.0` | Fyber Marketplace SDK `8.4.6` | `3.5.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes this network's demand. No manual adapter registration is required.
## Known issues
Live issues only. Resolved entries move to the adapter changelog.
### Digital Turbine is not served on the iOS Simulator
**Affected:** every `CloudXDigitalTurbineAdapter` version, on the iOS Simulator only. Physical devices are not affected.
**Symptom:** the Fyber Marketplace SDK crashes intermittently on the Simulator, with `EXC_BAD_ACCESS` while it resolves Swift type metadata for one of its own generic types. To keep the Simulator usable, CloudX omits Digital Turbine from the bidder list it provisions to Simulator sessions, so the adapter is never initialized there and Digital Turbine cannot serve an ad on the Simulator. The auction may still solicit a Digital Turbine bid for the session, but that bid cannot render. The dashboard shows the same note in the Digital Turbine bidder configuration.
**Status:** test Digital Turbine on a physical iOS device. We have reported the crash to Digital Turbine; this entry will move to the changelog once a fixed Fyber SDK is certified.
### Fyber Marketplace SDK 8.4.8 can crash Objective-C apps after SDK init
**Affected:** `CloudXDigitalTurbineAdapter` 8.4.8.0 (Fyber Marketplace SDK 8.4.8) in apps whose main target is Objective-C, or that integrate CloudXCore as a dynamic framework. Swift apps have not reproduced it.
**Symptom:** `EXC_BAD_ACCESS` a few seconds after `[CloudXCore initializeSDKWithAppKey:…]`, on the queue `com.Fyber.SDK.Marketplace.core.config`, inside the Fyber SDK's remote-configuration decoding. It reproduces on every launch.
**Workaround:** pin the previous adapter line, which uses Fyber Marketplace SDK 8.4.7 and is not affected — set the `CloudXDigitalTurbineAdapter` version in your Podfile to `~> 8.4.7.0`. Run `pod install --repo-update` and confirm `Podfile.lock` shows `Fyber_Marketplace_SDK (8.4.7)`. We have reported the issue to Digital Turbine; this entry will move to the changelog once a fixed Fyber SDK is certified.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Changelog
> Digital Turbine adapter release history for CloudX iOS SDK
## \[8.4.10.1] - 2026-10-14
* Adds support for a server-configured setting that fires `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudXCore 3.11.0 or later; with older versions it still fires at close.
* The adapter framework now ships its own Apple privacy manifest.
* Fyber Marketplace SDK (stays 8.4.10) and CloudXCore >= 3.5.0 are unchanged.
## \[8.4.10.0] - 2026-09-14
* Certified with Fyber Marketplace SDK 8.4.10.
* Requires CloudXCore >= 3.5.0.
* Requires iOS 15.0+ and Xcode 26.1+.
## \[8.4.8.0] - 2026-07-08
* Certified with Fyber Marketplace SDK 8.4.8.
* Supports Banner, MREC, Interstitial, Rewarded, and Native.
* First independently versioned release. Requires CloudXCore >= 3.5.0.
* **Known issue (2026-08-28):** Fyber Marketplace SDK 8.4.8 can crash Objective-C apps, or apps that integrate CloudXCore as a dynamic framework, a few seconds after SDK init. Pin `~> 8.4.7.0` until a fixed Fyber SDK is certified. Details in the [integration guide](/en/ios/adapters/digitalturbine/overview#known-issues).
## \[8.4.7.0] - 2026-07-08
* Compatibility-line release for apps pinned to Fyber Marketplace SDK 8.4.7.
* Requires CloudXCore >= 3.5.0. Recommended for Objective-C apps and apps that integrate CloudXCore as a dynamic framework while the 8.4.8.0 known issue is open.
## \[8.4.6.0] - 2026-08-12
* Compatibility-line release for apps pinned to Fyber Marketplace SDK 8.4.6.
* Supports Banner, MREC, Interstitial, Rewarded, and Native.
* Requires CloudXCore >= 3.5.0. Prefer `~> 8.4.8.0` unless you must stay on 8.4.6.
---
# Integration
> InMobi adapter for CloudX iOS SDK. Supports Banner, MREC, Interstitial, Rewarded, and Native.
For CloudX dashboard mapping and InMobi-side ID setup, see the [InMobi bidder guide](/en/networks/inmobi). This page covers iOS-specific integration details only.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXInMobiAdapter', '~> 11.5.0.0'
```
```bash
pod install --repo-update
```
### Manual
1. Download `CloudXInMobiAdapter.xcframework.zip` from [Releases](https://github.com/cloudx-io/cloudx-ios/releases)
2. Unzip and drag `CloudXInMobiAdapter.xcframework` into your Xcode project
3. Add `InMobiSDK` (`= 11.5.0`) manually.
## Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
### Click to expand InMobi SKAdNetwork IDs
```xml Info.plist
SKAdNetworkItemsSKAdNetworkIdentifier4pfyvq9l8r.skadnetworkSKAdNetworkIdentifieryclnxrl5pm.skadnetworkSKAdNetworkIdentifierv72qych5uu.skadnetworkSKAdNetworkIdentifiertl55sbb4fm.skadnetworkSKAdNetworkIdentifiert38b2kh725.skadnetworkSKAdNetworkIdentifierprcb7njmu6.skadnetworkSKAdNetworkIdentifierppxm28t8ap.skadnetworkSKAdNetworkIdentifiermlmmfzh3r3.skadnetworkSKAdNetworkIdentifierklf5c3l5u5.skadnetworkSKAdNetworkIdentifierhs6bdukanm.skadnetworkSKAdNetworkIdentifierc6k4g5qg8m.skadnetworkSKAdNetworkIdentifier9t245vhmpl.skadnetworkSKAdNetworkIdentifier9rd848q2bz.skadnetworkSKAdNetworkIdentifier7ug5zh24hu.skadnetworkSKAdNetworkIdentifier737z793b9f.skadnetworkSKAdNetworkIdentifier6xzpu9s2p8.skadnetworkSKAdNetworkIdentifier5lm9lj6jb7.skadnetworkSKAdNetworkIdentifier4fzdc2evr5.skadnetworkSKAdNetworkIdentifier4468km3ulz.skadnetworkSKAdNetworkIdentifier3rd42ekr43.skadnetworkSKAdNetworkIdentifier2u9pt9hc89.skadnetwork
```
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
**Bitcode:** InMobi SDK does not support Bitcode. Set Enable Bitcode to `NO`.
## Native Ads
The adapter does not expose the main image asset directly: `ad.nativeAd.mainImage` is not populated. Media content — image or video — renders through the adapter-provided media view, which CloudX attaches to the container bound with the view binder's `mediaContentViewTag`. Bind a visible media container as described in [Native ads](/en/ios/ad-formats/native).
## Compatibility
| Adapter version | Network SDK | Minimum CloudXCore |
| --------------- | ------------------ | ------------------ |
| `11.5.0.0` | InMobiSDK `11.5.0` | `3.5.0` |
| `11.4.1.1` | InMobiSDK `11.4.1` | `3.5.0` |
| `11.4.1.0` | InMobiSDK `11.4.1` | `3.5.0` |
| `11.3.0.0` | InMobiSDK `11.3.0` | `3.5.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes this network's demand. No manual adapter registration is required.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Changelog
> InMobi adapter release history for CloudX iOS SDK
## \[11.5.0.0] - 2026-10-15
* Updated to InMobi SDK 11.5.0. The SDK pin is exact, so an app that pins InMobi SDK 11.4.1 through another integration cannot install this version alongside it.
* Requires CloudXCore >= 3.5.0.
## \[11.4.1.1] - 2026-10-14
* Adds support for a server-configured setting that fires `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudXCore 3.11.0 or later; with older versions it still fires at close.
* The adapter framework now ships its own Apple privacy manifest.
* InMobiSDK (stays 11.4.1) and CloudXCore >= 3.5.0 are unchanged.
## \[11.4.1.0] - 2026-08-18
* Certified with InMobiSDK 11.4.1.
* Improved audio handling for MRAID 3 creatives. Resolves cases where an ad could interrupt audio your app was already playing.
* Requires CloudXCore >= 3.5.0 and iOS 13.0 or later, both unchanged from 11.3.0.0.
## \[11.3.0.0] - 2026-07-08
* Certified with InMobiSDK 11.3.0.
* Supports Banner, MREC, Interstitial, Rewarded, and Native.
* First independently versioned release. Requires CloudXCore >= 3.5.0.
---
# Integration
> Magnite adapter for CloudX iOS SDK. Supports Banner, MREC, Interstitial, Rewarded, and Native.
For CloudX dashboard mapping and Magnite-side ID setup, see the [Magnite bidder guide](/en/networks/magnite). This page covers iOS-specific integration details only.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXMagniteAdapterV2', '~> 1.0.4.2'
```
```bash
pod install --repo-update
```
> **Note:** `CloudXMagniteAdapterV2` is the current Magnite pod, versioned as `.`. The legacy `CloudXMagniteAdapter` pod remains available on the old unified line (`~> 3.4.6`) for apps not yet on CloudXCore 3.5.0. The framework and import name is `CloudXMagniteAdapter` in both pods — no source changes are needed to migrate.
### Manual
1. Download `CloudXMagniteAdapter.xcframework.zip` from [Releases](https://github.com/cloudx-io/cloudx-ios/releases).
2. Unzip and drag `CloudXMagniteAdapter.xcframework` into your Xcode project.
3. Add `MagniteSDK` (`= 1.0.4`) manually.
## Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
Include the SKAdNetwork IDs provided by Magnite for your integration. Refer to the current Magnite SDK documentation for the latest `SKAdNetworkItems` list.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## Compatibility
| Adapter version | Network SDK | Minimum CloudXCore |
| --------------- | ------------------ | ------------------ |
| `1.0.4.2` | MagniteSDK `1.0.4` | `3.9.0` |
| `1.0.0.2` | MagniteSDK `1.0.0` | `3.9.0` |
| `1.0.0.1` | MagniteSDK `1.0.0` | `3.5.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes this network's demand. No manual adapter registration is required.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Changelog
> Magnite adapter release history for CloudX iOS SDK
## \[1.0.4.2] - 2026-10-15
* Fixed a launch crash on devices when an app sets user consent or do-not-sell before CloudX finishes initializing (1.0.4.0 and 1.0.4.1). The adapter now holds those values and applies them once Magnite initializes.
* 1.0.4.0 and 1.0.4.1 are withdrawn. MagniteSDK (stays 1.0.4) and CloudXCore >= 3.9.0 are unchanged.
## \[1.0.4.1] - 2026-10-14
* Withdrawn: can crash on devices when consent is set before initialization. Use 1.0.4.2.
* Adds support for a server-configured setting that fires `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudXCore 3.11.0 or later; with older versions it still fires at close.
* MagniteSDK (stays 1.0.4) and CloudXCore >= 3.9.0 are unchanged.
## \[1.0.4.0] - 2026-10-12
* Withdrawn: can crash on devices when consent is set before initialization. Use 1.0.4.2.
* Certified with MagniteSDK 1.0.4.
* Requires CloudXCore >= 3.9.0.
## \[1.0.0.2] - 2026-09-16
* Added standalone Native auction and waterfall support with MagniteSDK 1.0.0. Requires CloudXCore >= 3.9.0.
* Fixed Interstitial and Rewarded callbacks so a synchronous show rejection does not emit `didDisplay`.
## \[1.0.0.1] - 2026-07-08
* Certified with MagniteSDK 1.0.0.
* Ships as `CloudXMagniteAdapterV2` (framework/import name remains `CloudXMagniteAdapter`).
* Packaging fix over 1.0.0.0 — use `~> 1.0.0.1`. Requires CloudXCore >= 3.5.0.
---
# Integration
> Meta Audience Network adapter for CloudX iOS SDK. Supports Banner, MREC, Interstitial, Rewarded, Native, Native Banner, and Native MREC.
For CloudX dashboard mapping and Meta-side ID setup, see the [Meta Audience Network bidder guide](/en/networks/meta-audience-network). This page covers iOS-specific integration details only.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXMetaAdapter', '~> 6.22.0.1'
```
```bash
pod install --repo-update
```
### Swift Package Manager
Add the adapter package to your project:
* Package URL: `https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-meta.git`
* Exact package version: `6220001.0.0`
* Product: `CloudXMetaAdapter`
The package installs CloudX Core and Meta Audience Network as dependencies. Add `$(inherited) -ObjC` to the app target's Other Linker Flags.
### Manual
1. Download `CloudXMetaAdapter.xcframework.zip` from [Releases](https://github.com/cloudx-io/cloudx-ios/releases)
2. Unzip and drag `CloudXMetaAdapter.xcframework` into your Xcode project
3. Add `FBAudienceNetwork` (`= 6.22.0`) manually.
## Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14.5+)
Both Meta SKAdNetwork IDs are required for Meta to make bids:
```xml Info.plist
SKAdNetworkItemsSKAdNetworkIdentifierv9wttpbfk9.skadnetworkSKAdNetworkIdentifiern38lu8286q.skadnetwork
```
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
**Bitcode:** Meta SDK does not support Bitcode. Set Enable Bitcode to `NO`.
## Native Ads
### Impression tracking requirements
Meta requires a specific asset to be rendered and visible on screen before it counts an impression, depending on the Meta placement format:
| Meta placement format | Required asset | Required view |
| --------------------- | -------------- | ------------- |
| Native | Media view | `FBMediaView` |
| Native Banner | App icon | `UIImageView` |
> **Warning:** Meta does not record impressions if these rendering requirements are not met. The ad still renders, so the failure is silent: you serve the creative but earn no Meta revenue.
See Meta's [Native Ads](https://developers.facebook.com/docs/audience-network/setting-up/ad-setup/ios/native) and [Native Banner Ads](https://developers.facebook.com/docs/audience-network/setting-up/ad-setup/ios/native-banner) guides for the full asset rendering requirements.
### Mapping Meta placements to CloudX ad units
| Meta placement format | CloudX ad unit | CloudX API | Who renders the required asset |
| --------------------- | --------------- | ----------------------------------------- | --------------------------------------------- |
| Native | Native | `createNativeAdLoader(adUnitIdentifier:)` | Your layout, via the binder's media container |
| Native | MREC (300×250) | `createMREC(adUnitId:)` | CloudX-provided layout |
| Native Banner | Banner (320×50) | `createBanner(adUnitId:)` | CloudX-provided layout |
For standalone Meta Native ads, bind a visible media container with the view binder's `mediaContentViewTag`. CloudX attaches Meta's `FBMediaView` to that container. If a Meta Native Banner creative can serve in your Native ad unit, also bind an icon `UIImageView` with `iconImageViewTag` — Meta registers Native Banner impressions against the icon view. See [Native ads](/en/ios/ad-formats/native) for the complete layout and loading flow.
Native Banner and MREC creatives served through `CLXBannerAdView` use CloudX-provided layouts, so no native binder is required in publisher code.
## Compatibility
| Adapter version | Network SDK | Minimum CloudXCore |
| --------------- | ------------------------------ | ------------------ |
| `6.22.0.1` | Meta Audience Network `6.22.0` | `3.5.0` |
| `6.22.0.0` | Meta Audience Network `6.22.0` | `3.5.0` |
| `6.21.1.0` | Meta Audience Network `6.21.1` | `3.5.0` |
| `6.21.0.0` | Meta Audience Network `6.21.0` | `3.5.0` |
| `6.20.1.0` | Meta Audience Network `6.20.1` | `3.5.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes this network's demand. No manual adapter registration is required.
## Known issues
Live issues only. Resolved entries move to the adapter changelog.
### Meta SDK 6.21.0 crashes in apps using SceneDelegate
**Affected:** Meta Audience Network SDK 6.21.0 in apps with the scene-based lifecycle (iOS 13+ `SceneDelegate`).
**Symptom:** crash during banner ad display:
```
*** -[NSProxy doesNotRecognizeSelector:observeScreenOrientationChanges:] called!
```
This is an internal Meta SDK bug: `FBDisplayAdController` creates an `NSProxy` for orientation observation that is not properly configured.
**Workaround:** use the old-style `AppDelegate`-only pattern (without `SceneDelegate`). This is the only reliable fix.
1. Delete `SceneDelegate.h` and `SceneDelegate.m` from your project.
2. Remove the entire `UIApplicationSceneManifest` section from your `Info.plist`.
3. Add the required keys to `Info.plist`:
```xml Info.plist
UIMainStoryboardFileMainUILaunchStoryboardNameLaunchScreen
```
4. Update your `AppDelegate`:
```objc Objective-C
// AppDelegate.h
@interface AppDelegate : UIResponder
@property (strong, nonatomic) UIWindow *window;
@end
// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.rootViewController = [[YourRootViewController alloc] init];
[self.window makeKeyAndVisible];
return YES;
}
```
```swift Swift
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = YourRootViewController()
window?.makeKeyAndVisible()
return true
}
}
```
After removing `SceneDelegate`, Xcode logs `CLIENT OF UIKIT REQUIRES UPDATE: This process does not adopt UIScene lifecycle.` This warning is expected and does not affect functionality.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Changelog
> Meta Audience Network adapter release history for CloudX iOS SDK
## \[6.22.0.1] - 2026-10-14
* Adds support for a server-configured setting that fires `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudXCore 3.11.0 or later; with older versions it still fires at close.
* The adapter framework now ships its own Apple privacy manifest.
* Meta Audience Network SDK (stays 6.22.0) and CloudXCore >= 3.5.0 are unchanged.
## \[6.22.0.0] - 2026-08-12
* Certified with Meta Audience Network SDK 6.22.0.
* Requires iOS 15.0. Apps that still support iOS 13 or 14 should stay on 6.21.1.0.
* Supports Banner, MREC, Interstitial, Rewarded, and Native. Requires CloudXCore >= 3.5.0.
## \[6.21.1.0] - 2026-07-08
* Certified with Meta Audience Network SDK 6.21.1.
* Supports Banner, MREC, Interstitial, Rewarded, and Native.
* First independently versioned release. Requires CloudXCore >= 3.5.0.
## \[6.21.0.0] - 2026-08-12
* Compatibility-line release for apps pinned to Meta Audience Network SDK 6.21.0.
* Supports Banner, MREC, Interstitial, Rewarded, and Native.
* Requires iOS 13.0 or later at this version.
* Meta Audience Network SDK 6.21.0 has a known banner display issue in apps that use a scene-based lifecycle (`SceneDelegate`); those apps should prefer a newer adapter version.
* Requires CloudXCore >= 3.5.0. Prefer `~> 6.21.1.0` unless you must stay on 6.21.0.
## \[6.20.1.0] - 2026-08-28
* Certified with Meta Audience Network SDK 6.20.1.
* Compatibility-line release for apps pinned to Meta Audience Network SDK 6.20.1. Use 6.22.0.0 if you are not tied to that version.
* Requires iOS 13.0.
* Native video options and AdChoices close reporting are unavailable on this network SDK version.
* Supports Banner, MREC, Interstitial, Rewarded, and Native. Requires CloudXCore >= 3.5.0.
---
# Integration
> Mintegral adapter for CloudX iOS SDK. Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open.
For CloudX dashboard mapping and Mintegral-side ID setup, see the [Mintegral bidder guide](/en/networks/mintegral). This page covers iOS-specific integration details only.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXMintegralAdapter', '~> 8.1.7.0'
```
```bash
pod install --repo-update
```
### Swift Package Manager
Add the adapter package to your project:
* Package URL: `https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-mintegral.git`
* Exact package version: `8010601.0.0`
* Product: `CloudXMintegralAdapter`
The package requires CloudXCore 3.9.1 or later and Mintegral Ad SDK 8.1.6. Add `$(inherited) -ObjC` to the app target's Other Linker Flags.
### Manual
1. Download `CloudXMintegralAdapter.xcframework.zip` from [Releases](https://github.com/cloudx-io/cloudx-ios/releases)
2. Unzip and drag `CloudXMintegralAdapter.xcframework` into your Xcode project
3. Add `MintegralAdSDK` (`= 8.1.7`) manually, including the `BidBannerAd`, `BidNewInterstitialAd`, `BidRewardVideoAd`, `BidNativeAdvancedAd`, and `BidSplashAd` subspecs.
## Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14.5+)
Mintegral's primary SKAdNetwork ID is required for attribution:
```xml Info.plist
SKAdNetworkItemsSKAdNetworkIdentifierkbd757ywx3.skadnetwork
```
> **Note:** Mintegral publishes a full list of partner SKAdNetwork IDs at [dev.mintegral.com/skadnetworkids.json](https://dev.mintegral.com/skadnetworkids.json). Adding these additional IDs improves attribution accuracy across Mintegral's demand partners.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings. This is required because CloudXCore is a dynamic framework and Objective-C categories must be loaded at runtime.
**Deployment Target:** Ensure your project's minimum deployment target is iOS 13.0 or higher.
## Mintegral SDK Frameworks
The Mintegral adapter requires several system frameworks. When using CocoaPods, these are configured automatically. For manual integration, ensure the following frameworks are linked:
* Foundation, UIKit, AdSupport, CoreGraphics, CoreTelephony
* SystemConfiguration, AVFoundation, CoreMedia
* QuartzCore, StoreKit, WebKit
* AppTrackingTransparency (weak-linked)
## Native Ads
The adapter does not expose the main image asset directly: `ad.nativeAd.mainImage` is not populated. Media content — image or video — renders through the adapter-provided media view, which CloudX attaches to the container bound with the view binder's `mediaContentViewTag`. Bind a visible media container as described in [Native ads](/en/ios/ad-formats/native).
## Compatibility
The table lists CocoaPods requirements. Swift Package Manager requires CloudXCore 3.9.1 or later.
| Adapter version | Network SDK | Minimum CloudXCore |
| --------------- | ---------------------- | ------------------ |
| `8.1.7.0` | MintegralAdSDK `8.1.7` | `3.5.0` |
| `8.1.6.1` | MintegralAdSDK `8.1.6` | `3.5.0` |
| `8.1.6.0` | MintegralAdSDK `8.1.6` | `3.5.0` |
| `8.1.5.0` | MintegralAdSDK `8.1.5` | `3.5.0` |
| `8.1.3.0` | MintegralAdSDK `8.1.3` | `3.5.0` |
| `8.0.9.0` | MintegralAdSDK `8.0.9` | `3.5.0` |
| `8.0.7.0` | MintegralAdSDK `8.0.7` | `3.5.0` |
| `8.0.3.0` | MintegralAdSDK `8.0.3` | `3.5.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes this network's demand. No manual adapter registration is required.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Changelog
> Mintegral adapter release history for CloudX iOS SDK
## \[8.1.7.0] - 2026-10-15
* Certified with Mintegral SDK 8.1.7.
* Requires CloudXCore >= 3.5.0.
## \[8.1.6.1] - 2026-10-06
### Fixed
* Corrected the adapter framework bundle version to prevent App Store validation errors. MintegralAdSDK remains 8.1.6; adapter behavior and dependency requirements are unchanged.
* The adapter framework now ships its own Apple privacy manifest.
## \[8.1.6.0] - 2026-09-08
* Certified with MintegralAdSDK 8.1.6.
* No adapter API changes; supported formats are unchanged.
* Requires CloudXCore >= 3.5.0.
## \[8.1.5.0] - 2026-07-08
* Certified with MintegralAdSDK 8.1.5.
* Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open.
* First independently versioned release. Requires CloudXCore >= 3.5.0.
## \[8.1.3.0] - 2026-08-28
* Compatibility-line release for apps pinned to MintegralAdSDK 8.1.3.
* Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open.
* Requires CloudXCore >= 3.5.0. Prefer `~> 8.1.5.0` unless you must stay on 8.1.3.
## \[8.0.9.0] - 2026-07-08
* Compatibility-line release for apps pinned to MintegralAdSDK 8.0.9.
* Requires CloudXCore >= 3.5.0. Prefer `~> 8.1.5.0` unless you must stay on 8.0.9.
## \[8.0.7.0] - 2026-08-12
* Compatibility-line release for apps pinned to MintegralAdSDK 8.0.7.
* Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open.
* Requires CloudXCore >= 3.5.0. Prefer `~> 8.1.5.0` unless you must stay on 8.0.7.
## \[8.0.3.0] - 2026-08-28
* Certified with MintegralAdSDK 8.0.3.
* Compatibility-line release for apps pinned to MintegralAdSDK 8.0.3. Use 8.1.5.0 if you are not tied to that version.
* Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open. Requires CloudXCore >= 3.5.0.
---
# Integration
> MobileFuse adapter for CloudX iOS SDK. Supports Banner, MREC, Interstitial, and Rewarded.
For publisher onboarding and CloudX dashboard setup, see the [MobileFuse bidder guide](/en/networks/mobilefuse).
This page covers iOS-specific integration details for the MobileFuse adapter.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXMobileFuseAdapter', '~> 1.12.0.0'
```
```bash
pod install --repo-update
```
### Manual
1. Download `CloudXMobileFuseAdapter.xcframework.zip` from [Releases](https://github.com/cloudx-io/cloudx-ios/releases)
2. Unzip and drag `CloudXMobileFuseAdapter.xcframework` into your Xcode project
3. Add `MobileFuseSDK` (`= 1.12.0`) manually.
## Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
Include the SKAdNetwork IDs published by MobileFuse for your integration. Refer to the current MobileFuse SDK documentation for the latest `SKAdNetworkItems` list.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## Compatibility
| Adapter version | Network SDK | Minimum CloudXCore |
| --------------- | ----------------------- | ------------------ |
| `1.12.0.0` | MobileFuse SDK `1.12.0` | `3.5.0` |
| `1.11.0.2` | MobileFuse SDK `1.11.0` | `3.5.0` |
| `1.11.0.1` | MobileFuse SDK `1.11.0` | `3.5.0` |
| `1.11.0.0` | MobileFuse SDK `1.11.0` | `3.5.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes this network's demand. No manual adapter registration is required.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Changelog
> MobileFuse adapter release history for CloudX iOS SDK
## \[1.12.0.0] - 2026-10-15
* Updated to MobileFuse SDK 1.12.0, which fixes a crash risk during bidding-token generation.
* Requires CloudXCore >= 3.5.0.
## \[1.11.0.2] - 2026-10-14
* Adds support for a server-configured setting that fires `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudXCore 3.11.0 or later; with older versions it still fires at close.
* The adapter framework now ships its own Apple privacy manifest.
* MobileFuse SDK (stays 1.11.0) and CloudXCore >= 3.5.0 are unchanged.
## \[1.11.0.1] - 2026-08-24
* Reading the MobileFuse SDK version no longer blocks the calling thread, removing a stall on the bid-token path.
* MobileFuse SDK (stays 1.11.0) and CloudXCore >= 3.5.0 are unchanged.
## \[1.11.0.0] - 2026-07-08
* Initial release.
* Certified with MobileFuse SDK 1.11.0.
* Supports Banner, MREC, Interstitial, and Rewarded.
* Requires CloudXCore >= 3.5.0.
---
# Integration
> Moloco adapter for CloudX iOS SDK. Supports Banner, MREC, Interstitial, Rewarded, and Native.
Contact your CloudX account manager to confirm availability and complete publisher onboarding.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXMolocoAdapter', '~> 4.11.1.1'
```
```bash
pod install --repo-update
```
### Manual
1. Download `CloudXMolocoAdapter.xcframework.zip` from [Releases](https://github.com/cloudx-io/cloudx-ios/releases).
2. Unzip and drag `CloudXMolocoAdapter.xcframework` into your Xcode project.
3. Add `MolocoSDKiOS` (`= 4.11.1`) manually.
## Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
Include the SKAdNetwork IDs provided by Moloco for your integration. Refer to the current Moloco SDK documentation for the latest `SKAdNetworkItems` list.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## Compatibility
| Adapter version | Network SDK | Minimum CloudXCore |
| --------------- | --------------------- | ------------------ |
| `4.11.1.1` | MolocoSDKiOS `4.11.1` | `3.5.0` |
| `4.11.1.0` | MolocoSDKiOS `4.11.1` | `3.5.0` |
| `4.10.0.0` | MolocoSDKiOS `4.10.0` | `3.5.0` |
| `4.9.1.0` | MolocoSDKiOS `4.9.1` | `3.5.0` |
| `4.9.0.0` | MolocoSDKiOS `4.9.0` | `3.5.0` |
| `4.8.1.0` | MolocoSDKiOS `4.8.1` | `3.5.0` |
| `4.8.0.0` | MolocoSDKiOS `4.8.0` | `3.5.0` |
| `4.7.0.0` | MolocoSDKiOS `4.7.0` | `3.5.0` |
| `4.3.0.0` | MolocoSDKiOS `4.3.0` | `3.5.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes this network's demand. No manual adapter registration is required.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Changelog
> Moloco adapter release history for CloudX iOS SDK
## \[4.11.1.1] - 2026-10-14
* Adds support for a server-configured setting that fires `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudXCore 3.11.0 or later; with older versions it still fires at close.
* MolocoSDKiOS (stays 4.11.1) and CloudXCore >= 3.5.0 are unchanged.
## \[4.11.1.0] - 2026-10-08
* Certified with MolocoSDKiOS 4.11.1.
* Requires CloudXCore >= 3.5.0.
## \[4.10.0.0] - 2026-09-14
* Certified with MolocoSDKiOS 4.10.0.
* Requires CloudXCore >= 3.5.0.
## \[4.9.1.0] - 2026-09-14
* Certified with MolocoSDKiOS 4.9.1.
* Requires CloudXCore >= 3.5.0.
## \[4.9.0.0] - 2026-09-14
* Certified with MolocoSDKiOS 4.9.0.
* Requires CloudXCore >= 3.5.0.
## \[4.8.1.0] - 2026-09-14
* Certified with MolocoSDKiOS 4.8.1.
* Requires CloudXCore >= 3.5.0.
## \[4.8.0.0] - 2026-07-08
* Certified with MolocoSDKiOS 4.8.0.
* Supports Banner, MREC, Interstitial, Rewarded, and Native.
* First independently versioned release. Requires CloudXCore >= 3.5.0.
## \[4.7.0.0] - 2026-09-05
* Compatibility-line release for apps pinned to MolocoSDKiOS 4.7.0.
* Supports Banner, MREC, Interstitial, Rewarded, and Native.
* Requires CloudXCore >= 3.5.0. Prefer `~> 4.8.0.0` unless you must stay on 4.7.0.
## \[4.3.0.0] - 2026-08-12
* Compatibility-line release for apps pinned to MolocoSDKiOS 4.3.0.
* Supports Banner, MREC, Interstitial, Rewarded, and Native.
* Requires CloudXCore >= 3.5.0. Prefer `~> 4.8.0.0` unless you must stay on 4.3.0.
---
# Integration
> Pangle (ByteDance) adapter for CloudX iOS SDK. Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open.
Contact your CloudX account manager to confirm availability and complete publisher onboarding.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXPangleAdapter', '~> 8.3.0.6.1'
```
```bash
pod install --repo-update
```
### Manual
1. Download `CloudXPangleAdapter.xcframework.zip` from [Releases](https://github.com/cloudx-io/cloudx-ios/releases).
2. Unzip and drag `CloudXPangleAdapter.xcframework` into your Xcode project.
3. Add the Pangle SDK (`Ads-Global` `= 8.3.0.6`) manually.
## Dependency compatibility
`GoogleMobileAdsMediationPangle 8.2.1.0.0` requires `Ads-Global = 8.2.1.0`. If your app uses that adapter, keep `CloudXPangleAdapter 8.2.1.0.0` until both adapters support the same Pangle SDK version.
## Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
Include the SKAdNetwork IDs provided by Pangle for your integration. Refer to the current `Ads-Global` (Pangle) documentation for the latest `SKAdNetworkItems` list.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## Native Ads
The adapter does not expose the main image asset directly: `ad.nativeAd.mainImage` is not populated. Media content — image or video — renders through the adapter-provided media view, which CloudX attaches to the container bound with the view binder's `mediaContentViewTag`. Bind a visible media container as described in [Native ads](/en/ios/ad-formats/native).
## Compatibility
| Adapter version | Network SDK | Minimum CloudXCore |
| --------------- | -------------------- | ------------------ |
| `8.3.0.6.1` | Ads-Global `8.3.0.6` | `3.5.0` |
| `8.3.0.6.0` | Ads-Global `8.3.0.6` | `3.5.0` |
| `8.2.1.0.0` | Ads-Global `8.2.1.0` | `3.5.0` |
| `8.2.0.9.0` | Ads-Global `8.2.0.9` | `3.5.0` |
| `8.2.0.8.0` | Ads-Global `8.2.0.8` | `3.5.0` |
| `8.2.0.7.0` | Ads-Global `8.2.0.7` | `3.5.0` |
| `8.1.0.7.0` | Ads-Global `8.1.0.7` | `3.5.0` |
| `8.1.0.6.0` | Ads-Global `8.1.0.6` | `3.5.0` |
| `7.9.1.3.0` | Ads-Global `7.9.1.3` | `3.5.0` |
| `7.8.5.8.0` | Ads-Global `7.8.5.8` | `3.5.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes this network's demand. No manual adapter registration is required.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Changelog
> Pangle adapter release history for CloudX iOS SDK
## \[8.3.0.6.1] - 2026-10-14
* Adds support for a server-configured setting that fires `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudXCore 3.11.0 or later; with older versions it still fires at close.
* The adapter framework now ships its own Apple privacy manifest.
* Pangle Ads-Global (stays 8.3.0.6) and CloudXCore >= 3.5.0 are unchanged.
## \[8.3.0.6.0] - 2026-09-14
* Certified with Pangle Ads-Global 8.3.0.6.
* Requires CloudXCore >= 3.5.0.
## \[8.2.1.0.0] - 2026-09-14
* Certified with Pangle Ads-Global 8.2.1.0.
* Requires CloudXCore >= 3.5.0.
## \[8.2.0.9.0] - 2026-09-14
* Certified with Pangle Ads-Global 8.2.0.9.
* Requires CloudXCore >= 3.5.0.
## \[8.2.0.8.0] - 2026-09-14
* Certified with Pangle Ads-Global 8.2.0.8.
* Requires CloudXCore >= 3.5.0.
## \[8.2.0.7.0] - 2026-07-30
* Certified with Pangle Ads-Global 8.2.0.7.
* Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open.
* Requires CloudXCore >= 3.5.0.
## \[8.1.0.7.0] - 2026-09-07
* Compatibility-line release for apps pinned to Pangle Ads-Global 8.1.0.7.
* Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open.
* Same adapter code as 8.2.0.7.0; only the Ads-Global pin changes.
* Requires CloudXCore >= 3.5.0. Prefer `~> 8.2.0.7.0` unless you must stay on 8.1.0.7.
## \[8.1.0.6.0] - 2026-09-07
* Compatibility-line release for apps pinned to Pangle Ads-Global 8.1.0.6.
* Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open.
* Same adapter code as 8.2.0.7.0; only the Ads-Global pin changes.
* Requires CloudXCore >= 3.5.0. Prefer `~> 8.2.0.7.0` unless you must stay on 8.1.0.6.
## \[7.9.1.3.0] - 2026-07-08
* Certified with Pangle Ads-Global 7.9.1.3.
* Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open.
* First independently versioned release. Requires CloudXCore >= 3.5.0.
## \[7.8.5.8.0] - 2026-08-11
* Compatibility-line release for apps pinned to Pangle Ads-Global 7.8.5.8.
* Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open.
* Requires CloudXCore >= 3.5.0. Prefer `~> 8.2.0.7.0` unless you must stay on 7.8.5.8.
---
# Integration
> TaurusX adapter for CloudX iOS SDK. Supports Banner, MREC, Interstitial, Rewarded, and Native.
Contact your CloudX account manager to confirm availability and complete publisher onboarding.
This page covers iOS-specific integration details for the TaurusX adapter.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXTaurusXAdapter', '~> 1.18.2.1'
```
```bash
pod install --repo-update
```
### Manual
1. Download `CloudXTaurusXAdapter.xcframework.zip` from [Releases](https://github.com/cloudx-io/cloudx-ios/releases)
2. Unzip and drag `CloudXTaurusXAdapter.xcframework` into your Xcode project
3. Add `TaurusxAdsSDK` (`= 1.18.2`) manually.
## Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
Include the SKAdNetwork IDs published by TaurusX for your integration. Refer to the current TaurusX SDK documentation for the latest `SKAdNetworkItems` list.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## Native Ads
Media content renders through the adapter-provided media view, which CloudX attaches to your bound media container. Bind a visible media container as described in [Native ads](/en/ios/ad-formats/native).
## Compatibility
| Adapter version | Network SDK | Minimum CloudXCore |
| --------------- | -------------------- | ------------------ |
| `1.18.2.1` | TaurusX SDK `1.18.2` | `3.5.0` |
| `1.18.2.0` | TaurusX SDK `1.18.2` | `3.5.0` |
| `1.18.1.0` | TaurusX SDK `1.18.1` | `3.5.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes this network's demand. No manual adapter registration is required.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Changelog
> TaurusX adapter release history for CloudX iOS SDK
## \[1.18.2.1] - 2026-10-14
* Adds support for a server-configured setting that fires `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudXCore 3.11.0 or later; with older versions it still fires at close.
* The adapter framework now ships its own Apple privacy manifest.
* Corrected the adapter framework bundle version to prevent App Store validation errors.
* TaurusX SDK (stays 1.18.2) and CloudXCore >= 3.5.0 are unchanged.
## \[1.18.2.0] - 2026-08-28
* Certified with TaurusX SDK 1.18.2.
* No adapter API changes; supported formats are unchanged.
* Requires CloudXCore >= 3.5.0.
## \[1.18.1.0] - 2026-08-03
* Initial release.
* Certified with TaurusX SDK 1.18.1.
* Supports Banner, MREC, Interstitial, Rewarded, and Native.
* Requires CloudXCore >= 3.5.0.
---
# Integration
> Unity Ads adapter for CloudX iOS SDK. Supports Banner, MREC, Interstitial, and Rewarded.
For CloudX dashboard mapping and Unity Ads-side ID setup, see the [Unity Ads bidder guide](/en/networks/unity-ads). This page covers iOS-specific integration details only.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXUnityAdsAdapter', '~> 4.20.1.1'
```
```bash
pod install --repo-update
```
### Swift Package Manager
Add the adapter package to your project:
* Package URL: `https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-unityads.git`
* Exact package version: `4200101.0.0`
* Product: `CloudXUnityAdsAdapter`
The package installs CloudX Core and Unity Ads as dependencies. Add `$(inherited) -ObjC` to the app target's Other Linker Flags.
### Manual
1. Download `CloudXUnityAdsAdapter.xcframework.zip` from [Releases](https://github.com/cloudx-io/cloudx-ios/releases)
2. Unzip and drag `CloudXUnityAdsAdapter.xcframework` into your Xcode project
3. Add `UnityAds` (`= 4.20.1`) manually.
> **Warning:** UnityAds 4.20.1 is built with a newer Swift toolchain. Building with Xcode 16.x can fail to link with `Undefined symbol: _swift_coroFrameAlloc`. Build with Xcode 26.1 or later.
## Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14.5+)
Unity Ads' primary SKAdNetwork ID is required for attribution:
```xml Info.plist
SKAdNetworkItemsSKAdNetworkIdentifier4dzt52r2t5.skadnetworkSKAdNetworkIdentifierbvpn9ufa9b.skadnetwork
```
> **Note:** Unity publishes a full list of SKAdNetwork IDs in their documentation. Adding these additional IDs improves attribution accuracy across Unity's demand partners.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings. This is required because CloudXCore is a dynamic framework and Objective-C categories must be loaded at runtime.
**Deployment Target:** Ensure your project's minimum deployment target is iOS 13.0 or higher.
## Unity Ads SDK Frameworks
The Unity Ads adapter requires several system frameworks. When using CocoaPods, these are configured automatically. For manual integration, ensure the following frameworks are linked:
* Foundation, UIKit, WebKit, AVFoundation, CoreMedia
* AudioToolbox, CFNetwork, CoreGraphics, CoreTelephony
* SystemConfiguration, StoreKit
## Compatibility
| Adapter version | Network SDK | Minimum CloudXCore |
| --------------- | ----------------- | ------------------ |
| `4.20.1.1` | UnityAds `4.20.1` | `3.5.0` |
| `4.20.1.0` | UnityAds `4.20.1` | `3.5.0` |
| `4.20.0.0` | UnityAds `4.20.0` | `3.5.0` |
| `4.19.0.0` | UnityAds `4.19.0` | `3.5.0` |
| `4.18.1.0` | UnityAds `4.18.1` | `3.5.0` |
| `4.17.0.0` | UnityAds `4.17.0` | `3.5.0` |
| `4.16.6.0` | UnityAds `4.16.6` | `3.5.0` |
| `4.16.3.0` | UnityAds `4.16.3` | `3.5.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes this network's demand. No manual adapter registration is required.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Changelog
> Unity Ads adapter release history for CloudX iOS SDK
## \[4.20.1.1] - 2026-10-14
* Adds support for a server-configured setting that fires `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudXCore 3.11.0 or later; with older versions it still fires at close.
* The adapter's privacy manifest no longer declares data collection or required-reason API use.
* UnityAds (stays 4.20.1) and CloudXCore >= 3.5.0 are unchanged.
## \[4.20.1.0] - 2026-09-15
* Certified with UnityAds 4.20.1.
* Requires CloudXCore >= 3.5.0. Adapter behavior is unchanged.
## \[4.20.0.0] - 2026-09-15
* Certified with UnityAds 4.20.0.
* Requires CloudXCore >= 3.5.0. Adapter behavior is unchanged.
## \[4.19.0.0] - 2026-07-08
* Certified with UnityAds 4.19.0.
* Supports Banner, MREC, Interstitial, and Rewarded.
* Requires Xcode 26 or later (UnityAds 4.19.0 Swift toolchain).
* First independently versioned release. Requires CloudXCore >= 3.5.0.
## \[4.18.1.0] - 2026-07-08
* Compatibility-line release for apps pinned to UnityAds 4.18.1.
* Requires CloudXCore >= 3.5.0. Prefer `~> 4.19.0.0` unless you must stay on 4.18.1.
## \[4.17.0.0] - 2026-09-11
* Certified with UnityAds 4.17.0.
* Compatibility-line release for apps pinned to UnityAds 4.17.0. Use 4.19.0.0 if you are not tied to that version.
* Supports Banner, MREC, Interstitial, and Rewarded, with ad-expiry callbacks available.
* Same adapter API surface as 4.19.0.0. Requires CloudXCore >= 3.5.0.
## \[4.16.6.0] - 2026-08-12
* Compatibility-line release for apps pinned to UnityAds 4.16.6.
* Supports Banner, MREC, Interstitial, and Rewarded.
* Ad-expiry callbacks are not available at this network SDK version.
* Rewarded completion is granted only when the ad is watched to completion.
* Requires CloudXCore >= 3.5.0. Prefer `~> 4.19.0.0` unless you must stay on 4.16.6.
## \[4.16.3.0] - 2026-08-28
* Certified with UnityAds 4.16.3.
* Compatibility-line release for apps pinned to UnityAds 4.16.3. Use 4.19.0.0 if you are not tied to that version.
* Ad expiry callbacks are unavailable on this network SDK version, and a rewarded grant follows the show completion state.
* Supports Banner, MREC, Interstitial, and Rewarded. Requires CloudXCore >= 3.5.0.
---
# Integration
> Verve adapter for CloudX iOS SDK. Supports Banner, MREC, Interstitial, Rewarded, and Native.
Contact your CloudX account manager to confirm availability and complete publisher onboarding.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXVerveAdapter', '~> 3.9.0.1'
```
```bash
pod install --repo-update
```
### Manual
1. Download `CloudXVerveAdapter.xcframework.zip` from [Releases](https://github.com/cloudx-io/cloudx-ios/releases).
2. Unzip and drag `CloudXVerveAdapter.xcframework` into your Xcode project.
3. Add `HyBid` (`= 3.9.0`) manually.
## Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
Include the SKAdNetwork IDs provided by Verve for your integration. Refer to the current HyBid SDK documentation for the latest `SKAdNetworkItems` list.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## Compatibility
| Adapter version | Network SDK | Minimum CloudXCore |
| --------------- | ------------- | ------------------ |
| `3.9.0.1` | HyBid `3.9.0` | `3.5.0` |
| `3.9.0.0` | HyBid `3.9.0` | `3.5.0` |
| `3.8.0.0` | HyBid `3.8.0` | `3.5.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes this network's demand. No manual adapter registration is required.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Changelog
> Verve adapter release history for CloudX iOS SDK
## \[3.9.0.1] - 2026-10-14
* Adds support for a server-configured setting that fires `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudXCore 3.11.0 or later; with older versions it still fires at close.
* The adapter framework now ships its own Apple privacy manifest.
* HyBid (stays 3.9.0) and CloudXCore >= 3.5.0 are unchanged.
## \[3.9.0.0] - 2026-07-08
* Certified with HyBid 3.9.0.
* Supports Banner, MREC, Interstitial, Rewarded, and Native.
* First independently versioned release. Requires CloudXCore >= 3.5.0.
## \[3.8.0.0] - 2026-07-08
* Compatibility-line release for apps pinned to HyBid 3.8.0.
* Requires CloudXCore >= 3.5.0. Prefer `~> 3.9.0.0` unless you must stay on 3.8.0.
---
# Integration
> Vungle (Liftoff) adapter for CloudX iOS SDK. Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open.
For CloudX dashboard mapping and Vungle-side ID setup, see the [Vungle bidder guide](/en/networks/vungle). This page covers iOS-specific integration details only.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXVungleAdapter', '~> 7.7.6.1'
```
```bash
pod install --repo-update
```
### Swift Package Manager
Add the adapter package to your project:
* Package URL: `https://github.com/cloudx-io/cloudx-ios-swift-package-adapter-vungle.git`
* Exact package version: `7070601.0.0`
* Product: `CloudXVungleAdapter`
The package installs CloudX Core and Vungle Ads as dependencies. Add `$(inherited) -ObjC` to the app target's Other Linker Flags.
### Manual
1. Download `CloudXVungleAdapter.xcframework.zip` from [Releases](https://github.com/cloudx-io/cloudx-ios/releases)
2. Unzip and drag `CloudXVungleAdapter.xcframework` into your Xcode project
3. Add `VungleAds` (`= 7.7.6`) manually.
## Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
### Click to expand Vungle SKAdNetwork IDs
```xml Info.plist
SKAdNetworkItemsSKAdNetworkIdentifiercstr6suwn9.skadnetworkSKAdNetworkIdentifier4fzdc2evr5.skadnetworkSKAdNetworkIdentifierv72qych5uu.skadnetworkSKAdNetworkIdentifierludvb6z3bs.skadnetworkSKAdNetworkIdentifierwg4vff78zm.skadnetworkSKAdNetworkIdentifier737z793b9f.skadnetworkSKAdNetworkIdentifieryclnxrl5pm.skadnetworkSKAdNetworkIdentifiert38b2kh725.skadnetworkSKAdNetworkIdentifier7ug5zh24hu.skadnetworkSKAdNetworkIdentifier9rd848q2bz.skadnetworkSKAdNetworkIdentifiern6fk4nfna4.skadnetworkSKAdNetworkIdentifierkbd757ywx3.skadnetworkSKAdNetworkIdentifier9t245vhmpl.skadnetworkSKAdNetworkIdentifier4468km3ulz.skadnetworkSKAdNetworkIdentifier2u9pt9hc89.skadnetworkSKAdNetworkIdentifierm8dbw4sv7c.skadnetworkSKAdNetworkIdentifierc6k4g5qg8m.skadnetworkSKAdNetworkIdentifiermlmmfzh3r3.skadnetworkSKAdNetworkIdentifier578prtvx9j.skadnetworkSKAdNetworkIdentifier4dzt52r2t5.skadnetworkSKAdNetworkIdentifiere5fvkxwrpn.skadnetworkSKAdNetworkIdentifier8s468mfl3y.skadnetworkSKAdNetworkIdentifierav6w8kgt66.skadnetworkSKAdNetworkIdentifierklf5c3l5u5.skadnetworkSKAdNetworkIdentifierppxm28t8ap.skadnetworkSKAdNetworkIdentifier424m5254lk.skadnetworkSKAdNetworkIdentifieruw77j35x4d.skadnetworkSKAdNetworkIdentifier4pfyvq9l8r.skadnetworkSKAdNetworkIdentifierzmvfpc5aq8.skadnetwork
```
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
**Bitcode:** Vungle SDK does not support Bitcode. Set Enable Bitcode to `NO`.
## Native Ads
The adapter does not expose the main image asset directly: `ad.nativeAd.mainImage` is not populated. Media content — image or video — renders through the adapter-provided media view, which CloudX attaches to the container bound with the view binder's `mediaContentViewTag`. Bind a visible media container as described in [Native ads](/en/ios/ad-formats/native).
## Compatibility
| Adapter version | Network SDK | Minimum CloudXCore |
| --------------- | ----------------- | ------------------ |
| `7.7.6.1` | VungleAds `7.7.6` | `3.5.0` |
| `7.7.6.0` | VungleAds `7.7.6` | `3.5.0` |
| `7.7.4.0` | VungleAds `7.7.4` | `3.5.0` |
| `7.7.1.0` | VungleAds `7.7.1` | `3.5.0` |
| `7.7.0.0` | VungleAds `7.7.0` | `3.5.0` |
| `7.6.3.0` | VungleAds `7.6.3` | `3.5.0` |
| `7.6.2.0` | VungleAds `7.6.2` | `3.5.0` |
The adapter is discovered automatically when your server-provisioned CloudX configuration includes this network's demand. No manual adapter registration is required.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Changelog
> Vungle adapter release history for CloudX iOS SDK
## \[7.7.6.1] - 2026-10-14
* Adds support for a server-configured setting that fires `didRewardUserForAd:withReward:` (Swift `didRewardUser(for:with:)`) as soon as the ad network grants the reward, instead of when the ad closes. Takes effect with CloudXCore 3.11.0 or later; with older versions it still fires at close.
* Creative-download failures now report `CLXErrorCodeAdapterInternalError` (600) instead of `CLXErrorCodeAdapterInvalidLoadState` (602), and HTTP failures report `CLXErrorCodeAdapterServerError` (612). Update any handling of 602.
* The adapter framework now ships its own Apple privacy manifest.
* VungleAds (stays 7.7.6) and CloudXCore >= 3.5.0 are unchanged.
## \[7.7.6.0] - 2026-09-08
* Certified with VungleAds 7.7.6.
* No adapter API changes; supported formats are unchanged.
* Requires CloudXCore >= 3.5.0.
## \[7.7.4.0] - 2026-07-08
* Certified with VungleAds 7.7.4.
* Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open.
* First independently versioned release. Requires CloudXCore >= 3.5.0.
## \[7.7.1.0] - 2026-09-11
* Certified with VungleAds 7.7.1.
* Compatibility-line release for apps pinned to VungleAds 7.7.1. Use 7.7.6.0 if you are not tied to that version.
* Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open. No adapter API changes relative to 7.7.6.0.
* Requires CloudXCore >= 3.5.0.
## \[7.7.0.0] - 2026-08-12
* Compatibility-line release for apps pinned to VungleAds 7.7.0.
* Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open.
* Requires CloudXCore >= 3.5.0. Prefer `~> 7.7.4.0` unless you must stay on 7.7.0.
## \[7.6.3.0] - 2026-08-28
* Certified with VungleAds 7.6.3.
* Compatibility-line release for apps pinned to VungleAds 7.6.3. Use 7.7.4.0 if you are not tied to that version.
* Supports Banner, MREC, Interstitial, Rewarded, Native, and App Open. Requires CloudXCore >= 3.5.0.
## \[7.6.2.0] - 2026-07-08
* Compatibility-line release for apps pinned to VungleAds 7.6.2.
* Requires CloudXCore >= 3.5.0. Prefer `~> 7.7.4.0` unless you must stay on 7.6.2.
---
# Integration
> Adjust ad-revenue connector for CloudX iOS SDK. Forwards CloudX ad revenue to Adjust with no publisher glue code.
Connectors pass data between the CloudX SDK and another product. The Adjust connector forwards CloudX ad revenue to your mobile measurement partner (MMP) so ad revenue appears alongside your other Adjust data and feeds ROAS measurement. Add the dependency and revenue reporting is automatic — the connector registers itself with the CloudX SDK at load time, joining the same global ad-revenue delegate list that `CloudXCore.addAdRevenueDelegate(_:)` uses.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXAdjustConnector', '~> 5.0.0.0'
```
```bash
pod install --repo-update
```
No code changes are required. The connector never initializes Adjust; it uses your existing Adjust setup and app token.
### Manual registration
The pod sets `-ObjC` on your target so the connector's `+load` runs in the host app and it self-registers. If your build does not link with `-ObjC`, register it once before or after initializing the CloudX SDK:
```objc Objective-C
#import
CloudXAdjustConnectorRegister();
```
```swift Swift
CloudXAdjustConnectorRegister()
```
## Requirements
* CloudXCore `3.6.0` or newer — the first version that ships the global ad-revenue delegate API the connector attaches through.
* Your app already integrates and initializes the Adjust SDK with your own app token. The connector never bundles or initializes Adjust; CocoaPods dedupes the dependency against your app's existing `Adjust` pod, so your runtime version is the one used.
* A supported Adjust SDK version: `Adjust` `5.0.0` or newer (the v5 line). There is no upper bound.
## What gets reported
The connector creates one Adjust `ADJAdRevenue` event per CloudX-won impression, across all CloudX ad formats — banner, MREC, interstitial, rewarded, native, and app open. Each event carries the winning ad network, the CloudX revenue estimate for the impression in USD, and callback parameters with the CloudX ad unit, format (`ad_type`), and placement.
The `ad_type` format names match the CloudX Android connector exactly, so grouping Adjust revenue by `ad_type` gives one bucket per format across both platforms.
> **Note:** Revenue is reported on Adjust's generic `publisher_sdk` source — Adjust has confirmed this is the supported path until a dedicated CloudX source ships in a future Adjust release. Revenue lands in Adjust and feeds ROAS identically; it just appears under the generic publisher source rather than a named CloudX one. When the named source ships, historical revenue stays under `publisher_sdk`, so a report spanning the cutover must union both sources.
> **Warning:** If your app already builds `ADJAdRevenue` events from a CloudX ad-revenue delegate, remove that code when you add this connector. Otherwise every impression is reported to Adjust twice.
## Finding CloudX revenue in the Adjust dashboard
Adjust doesn't have a dedicated CloudX ad revenue source yet, so there's nothing CloudX-specific to configure in the Adjust dashboard. Once the connector is in your build, CloudX ad revenue arrives on its own.
To see it, open Adjust's ad revenue metrics with the Ad Revenue Sources filter left empty, which shows all sources. For now, CloudX revenue is included in those totals under a generic source rather than its own CloudX label.
Impressions won in the CloudX auction by AppLovin MAX or the Google waterfall aren't forwarded, because those SDKs already report that revenue to Adjust.
> **Warning:** Adjust's partner page for CloudX (**Partners → CloudX**) is the demand side: it is for attributing installs from user-acquisition campaigns you buy on CloudX. The tracking link there, and the related "Dynamic ad network data access" setting in your app's Data management tab, have no effect on ad revenue reporting. Setting them up will not make CloudX ad revenue appear, and you do not need either unless you run UA on CloudX.
If Adjust shows no ad revenue at all, from CloudX or any other source, check these before investigating the connector:
* The `CloudXAdjustConnector` pod is in the build you are measuring, not only in your local project.
* Ad revenue reporting is enabled on your Adjust account. Your Adjust account manager can confirm this.
* If the build you're testing initializes Adjust in sandbox mode, switch Datascape to sandbox data to see it.
## Compatibility
| Connector version | Adjust SDK | Minimum CloudXCore |
| ----------------- | ------------------- | ------------------ |
| `5.0.0.0` | `Adjust` `>= 5.0.0` | `3.6.0` |
## Reporting revenue yourself instead
If you would rather control the reporting call, skip the connector and register your own ad-revenue delegate directly:
```swift Swift
final class MyAdRevenueDelegate: NSObject, CLXAdRevenueDelegate {
func didPayRevenue(for ad: CLXAd) {
// Forward to Adjust, or any other destination, using your own mapping.
}
}
CloudXCore.addAdRevenueDelegate(myAdRevenueDelegate)
```
```objc Objective-C
[CloudXCore addAdRevenueDelegate:myAdRevenueDelegate];
```
`+[CloudXCore addAdRevenueDelegate:]` is the stable public surface; `removeAdRevenueDelegate:` unregisters. Use either the connector or your own delegate — not both.
## Support
For support, contact [mobile@cloudx.io](mailto:mobile@cloudx.io)
---
# Changelog
> Adjust connector release history for CloudX iOS SDK
## \[5.0.0.0] - 2026-08-04
* Initial release of the CloudX Adjust ad-revenue connector. Forwards CloudX ad revenue to Adjust as `ADJAdRevenue` events with no publisher glue code — add the pod and revenue reporting is automatic, firing once per CloudX-won impression across all ad formats.
* Self-registers at load time; call `CloudXAdjustConnectorRegister()` manually if your build does not link with `-ObjC`.
* Reports on Adjust's generic `publisher_sdk` source until a dedicated CloudX source ships in a future Adjust release; revenue feeds ROAS identically either way.
* Requires `Adjust` 5.0.0 or newer, with no upper bound. The publisher integrates and initializes Adjust themselves; the connector never bundles or initializes it.
* Requires CloudXCore >= 3.6.0.
* **Removed pre-release symbols** — `CloudXAdjustConnectorVersionNumber` and `CloudXAdjustConnectorVersionString` were dropped before first publication; `CLXAdjustConnectorVersion` reports the connector version. No consumer impact.
---
# Integration
> AppsFlyer ad-revenue connector for CloudX iOS SDK. Forwards CloudX ad revenue to AppsFlyer with no publisher glue code.
Connectors pass data between the CloudX SDK and another product. The AppsFlyer connector forwards CloudX ad revenue to your mobile measurement partner (MMP) so ad revenue appears alongside your other AppsFlyer data. Add the dependency and revenue reporting is automatic — the connector registers itself with the CloudX SDK at load time, joining the same global ad-revenue delegate list that `CloudXCore.addAdRevenueDelegate(_:)` uses.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXAppsFlyerConnector', '~> 6.15.0.0'
```
```bash
pod install --repo-update
```
No code changes are required. The connector never initializes AppsFlyer; it uses your existing AppsFlyer setup and dev key.
### Manual registration
The pod sets `-ObjC` on your target so the connector's `+load` runs in the host app and it self-registers. If your build does not link with `-ObjC`, register it once before or after initializing the CloudX SDK:
```objc Objective-C
#import
CloudXAppsFlyerConnectorRegister();
```
```swift Swift
CloudXAppsFlyerConnectorRegister()
```
## Requirements
* CloudXCore `3.6.0` or newer — the first version that ships the global ad-revenue delegate API the connector attaches through.
* Your app already integrates and initializes the AppsFlyer SDK with your own dev key. The connector never bundles or initializes AppsFlyer; CocoaPods dedupes the dependency against your app's existing `AppsFlyerFramework`, so your runtime version is the one used.
* A supported AppsFlyer SDK version: `AppsFlyerFramework` `6.15.0` or newer. There is no upper bound — AppsFlyer's integrated ad-revenue API is stable across the 6.15+ and 7.x lines.
## What gets reported
The connector calls AppsFlyer's `logAdRevenue` once per CloudX-won impression, across all CloudX ad formats — banner, MREC, interstitial, rewarded, native, and app open. Each call carries the winning ad network, the CloudX revenue estimate for the impression in USD, and the CloudX ad unit, format, and placement.
> **Warning:** If your app already calls `AppsFlyerLib.shared().logAdRevenue` from a CloudX ad-revenue delegate, remove that code when you add this connector. Otherwise every impression is reported to AppsFlyer twice.
## Compatibility
| Connector version | AppsFlyer SDK | Minimum CloudXCore |
| ----------------- | -------------------------------- | ------------------ |
| `6.15.0.0` | `AppsFlyerFramework` `>= 6.15.0` | `3.6.0` |
## Reporting revenue yourself instead
If you would rather control the reporting call, skip the connector and register your own ad-revenue delegate directly:
```swift Swift
final class MyAdRevenueDelegate: NSObject, CLXAdRevenueDelegate {
func didPayRevenue(for ad: CLXAd) {
// Forward to AppsFlyer, or any other destination, using your own mapping.
}
}
CloudXCore.addAdRevenueDelegate(myAdRevenueDelegate)
```
```objc Objective-C
[CloudXCore addAdRevenueDelegate:myAdRevenueDelegate];
```
`+[CloudXCore addAdRevenueDelegate:]` is the stable public surface; `removeAdRevenueDelegate:` unregisters. Use either the connector or your own delegate — not both.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Changelog
> AppsFlyer connector release history for CloudX iOS SDK
## \[6.15.0.0] - 2026-08-03
* Initial release of the CloudX AppsFlyer ad-revenue connector. Forwards CloudX ad revenue to AppsFlyer via `logAdRevenue` with no publisher glue code — add the pod and revenue reporting is automatic, firing once per CloudX-won impression across all ad formats.
* Self-registers at load time; call `CloudXAppsFlyerConnectorRegister()` manually if your build does not link with `-ObjC`.
* Requires `AppsFlyerFramework` 6.15.0 or newer, with no upper bound. The publisher integrates and initializes AppsFlyer themselves; the connector never bundles or initializes it.
* Requires CloudXCore >= 3.6.0.
* **Removed pre-release symbols** — `CloudXAppsFlyerConnectorVersionNumber` and `CloudXAppsFlyerConnectorVersionString` were dropped before first publication; `CLXAppsFlyerConnectorVersion` reports the connector version. No consumer impact.
---
# Integration
> Singular ad-revenue connector for CloudX iOS SDK. Forwards CloudX ad revenue to Singular with no publisher glue code.
Connectors pass data between the CloudX SDK and another product. The Singular connector forwards CloudX ad revenue to your mobile measurement partner (MMP) so ad revenue appears alongside your other Singular data and feeds ROAS measurement. Add the dependency and revenue reporting is automatic — the connector registers itself with the CloudX SDK at load time, joining the same global ad-revenue delegate list that `CloudXCore.addAdRevenueDelegate(_:)` uses.
## Installation
### CocoaPods
```ruby Podfile
pod 'CloudXSingularConnector', '~> 12.6.0.0'
```
```bash
pod install --repo-update
```
No code changes are required. The connector never initializes Singular; it uses your existing Singular setup and SDK key.
### Manual registration
The pod sets `-ObjC` on your target so the connector's `+load` runs in the host app and it self-registers. If your build does not link with `-ObjC`, register it once before or after initializing the CloudX SDK:
```objc Objective-C
#import
CloudXSingularConnectorRegister();
```
```swift Swift
CloudXSingularConnectorRegister()
```
## Requirements
* CloudXCore `3.6.0` or newer — the first version that ships the global ad-revenue delegate API the connector attaches through.
* Your app already integrates and initializes the Singular SDK with your own SDK key and secret. The connector never bundles or initializes Singular; CocoaPods dedupes the dependency against your app's existing `Singular-SDK` pod, so your runtime version is the one used.
* A supported Singular SDK version: `Singular-SDK` `12.6.0` or newer. There is no upper bound. `12.6.0` is the floor because it introduced the correctly spelled ad-revenue initializer the connector calls.
## What gets reported
The connector sends one `[Singular adRevenue:]` event per CloudX-won impression, across all CloudX ad formats — banner, MREC, interstitial, rewarded, native, and app open. Each event carries:
| Singular field | Value |
| ----------------- | --------------------------------------------------------------------- |
| Ad platform | `CloudX` |
| Currency | `USD` |
| Revenue | The CloudX revenue estimate for the impression |
| Revenue precision | `estimated` |
| Network name | The winning ad network, or `CloudX` when the network is not reported |
| Ad unit id | The CloudX ad unit id |
| Ad type | `BANNER`, `MREC`, `INTERSTITIAL`, `REWARDED`, `NATIVE`, or `APP_OPEN` |
| Ad placement name | The CloudX placement |
| Ad unit name | The network placement |
The ad type names match the CloudX Android connector exactly, so grouping Singular revenue by ad type gives one bucket per format across both platforms.
Optional fields are omitted when CloudX reports them empty, so an event may arrive without an ad unit id, ad placement name, or ad unit name. The Android connector always sends the ad unit id, so a report joining the two platforms on that field will show gaps on iOS only.
> **Note:** Revenue appears in Singular under the ad platform `CloudX`. No Singular-side configuration is needed beyond having ad-revenue attribution enabled on your Singular account.
> **Warning:** If your app already sends `[Singular adRevenue:]` events from a CloudX ad-revenue delegate, remove that code when you add this connector. Otherwise every impression is reported to Singular twice.
## Compatibility
| Connector version | Singular SDK | Minimum CloudXCore |
| ----------------- | -------------------------- | ------------------ |
| `12.6.0.0` | `Singular-SDK` `>= 12.6.0` | `3.6.0` |
## Reporting revenue yourself instead
If you would rather control the reporting call, skip the connector and register your own ad-revenue delegate directly:
```swift Swift
final class MyAdRevenueDelegate: NSObject, CLXAdRevenueDelegate {
func didPayRevenue(for ad: CLXAd) {
// Forward to Singular, or any other destination, using your own mapping.
}
}
CloudXCore.addAdRevenueDelegate(myAdRevenueDelegate)
```
```objc Objective-C
[CloudXCore addAdRevenueDelegate:myAdRevenueDelegate];
```
`+[CloudXCore addAdRevenueDelegate:]` is the stable public surface; `removeAdRevenueDelegate:` unregisters. Use either the connector or your own delegate — not both.
## Support
For support, contact [mobile@cloudx.io](mailto:mobile@cloudx.io)
---
# Changelog
> Singular connector release history for CloudX iOS SDK
## \[12.6.0.0] - 2026-08-27
* Initial release of the CloudX Singular ad-revenue connector. Forwards CloudX ad revenue to Singular as `[Singular adRevenue:]` events with no publisher glue code — add the pod and revenue reporting is automatic, firing once per CloudX-won impression across all ad formats.
* Self-registers at load time; call `CloudXSingularConnectorRegister()` manually if your build does not link with `-ObjC`.
* Revenue is reported under the ad platform `CloudX` in USD, with the winning network, ad unit id, ad type, placement name, and network placement attached.
* Requires `Singular-SDK` 12.6.0 or newer, with no upper bound. The publisher integrates and initializes Singular themselves; the connector never bundles or initializes it.
* Requires CloudXCore >= 3.6.0.
---
# 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.
> **Info:** Prefer AI-assisted setup? Point your coding agent at the CloudX agent package — see [AI-assisted integration](/en/ai-integration).
## Installation
The CloudX Unity SDK is distributed as a `.unitypackage` file.
1. Download `CloudXSdk-4.10.0.unitypackage` from the [CloudX Unity SDK 4.10.0 release](https://github.com/cloudx-io/cloudx-unity/releases/tag/v4.10.0)
2. In Unity, go to **Assets > Import Package > Custom Package**
3. Select the downloaded `.unitypackage` file
4. Import all assets when prompted
> **Info:** The External Dependency Manager will automatically resolve Android and iOS dependencies.
## 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](/en/unity/adapters/meta) section for per-adapter setup instructions, including iOS Info.plist requirements.
> **Note:** At least one adapter is required for the SDK to serve ads.
## Initialization
Initialize the SDK before loading any ads. You can optionally configure user and app properties before initialization.
```csharp
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}");
}
}
```
> **Note:** Subscribe to `CloudXInitializationCallbacks` events before calling `Initialize()` to receive initialization results.
`CloudXSdkConfiguration.AdUnitConfigurations` maps each of your ad unit IDs to its session configuration, where `IsOrchestratorEnabled` says whether Orchestrator owns loading for that unit. A missing entry means the configuration is unavailable, not that the unit is disabled. The map is supplied on Android; on iOS and in the Editor it is empty.
## 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](/en/unity/ad-formats/banner-mrec)
Display ads at a fixed screen position with optional refresh control.
* [Interstitial ads](/en/unity/ad-formats/interstitial)
Load and show full-screen interstitial placements.
* [Rewarded ads](/en/unity/ad-formats/rewarded)
Reward users after completed rewarded ad views.
* [App Open ads](/en/unity/ad-formats/app-open)
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.
```csharp
// 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.
> **Warning:** A callback that uses Unity APIs (GameObjects, UI, coroutines) must run on the Unity main thread. Only set `false` if you marshal to the main thread yourself.
#### 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.
```csharp
using System;
using System.Collections.Concurrent;
using CloudX;
using UnityEngine;
using UnityEngine.UI;
public class AdEventPump : MonoBehaviour
{
[SerializeField] private Text revenueLabel;
private readonly ConcurrentQueue _pending = new ConcurrentQueue();
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();
}
}
}
```
> **Warning:** `Update()` may not run while a fullscreen ad is in front, so anything you enqueue from a callback raised during the ad may still run only after the ad closes. Do the time-sensitive part (reporting, bookkeeping) inline on the native thread and enqueue only the Unity-side work.
#### 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: ` 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.
```csharp
// 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`.
```csharp
// 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](/en/cli/exports#cloudx-export-requests) and [impression](/en/cli/exports#cloudx-export-impressions) 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](https://unifiedid.com/docs/ref-info/ref-tokens) |
| `euid.eu` | EUID advertising token, not the refresh token. Do not decrypt it. [EUID docs](https://euid.eu/docs/ref-info/ref-tokens) |
| `liveramp.com` | LiveRamp ATS envelope, not a RampID. [LiveRamp docs](https://developers.liveramp.com/authenticatedtraffic-api/docs/pass-mobile-envelopes-to-the-bidstream) |
| `id5-sync.com` | ID5 universal UID. Do not pass `0`. [ID5 docs](https://wiki.id5.io/docs/mobile-in-app-integration) |
| `intentiq.com` | Intent IQ ID (IIQ ID) from your Intent IQ integration. [Intent IQ docs](https://docs.prebid.org/dev-docs/modules/userid-submodules/intentiq.html) |
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.
```csharp
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);
```
> **Warning:** Use `SetUserKeyValue` only. Do not put these tokens in `SetAppKeyValue`. Key names must match exactly.
> **Note:** Do not set a key until you have a value. Setting it again replaces the previous value. If a value expires without a replacement, set that key to an empty string to stop sending its EID. `ClearAllKeyValues()` clears all user and app key-values and the hashed user ID. Identity tokens can be set whenever you have them, including after `Initialize`.
### Revenue Tracking
All ad formats provide revenue callbacks through the `OnAdRevenuePaid` event. The `CloudXAd` object contains revenue information:
```csharp
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}");
Debug.Log($"Creative Id: {ad.CreativeId}");
Debug.Log($"Mediator: {ad.MediatorName}");
Debug.Log($"Mediator Ad Unit: {ad.MediatorAdUnitId}");
// Track revenue in your analytics
TrackRevenue(ad.Revenue, ad.NetworkName);
};
```
`CloudXAd.CreativeId` is the creative identifier for creative-level issue reporting. It is `null` when the demand source does not report a creative id, and is distinct from the `CreativeId` you pass in [publisher-reported revenue data](#publisher-reported-revenue-data), which describes an impression won outside the CloudX ad flow.
`CloudXAd.MediatorName` and `CloudXAd.MediatorAdUnitId` report which mediator served the ad and the ad unit id that mediator used, which is distinct from the winning network in `NetworkName`. A CloudX-won ad reports `cloudx` and its own CloudX ad unit id. Both are `null` when no mediator is known, and on iOS and in the Editor.
> **Note:** By default `OnAdRevenuePaid` for fullscreen ads (interstitial, app open, rewarded) is delivered on a background thread, not the Unity main thread — the game is paused while a fullscreen ad is showing, so this is what lets the revenue signal reach you at impression time instead of after the ad closes. Banner and MREC revenue arrives on the main thread. Set `CloudXSdk.InvokeEventsOnUnityMainThread = true` if your fullscreen revenue handler uses Unity APIs — see [Callback Threading](#callback-threading).
### 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.
* [Adjust connector](/en/unity/connectors/adjust)
* [AppsFlyer connector](/en/unity/connectors/appsflyer)
* [Singular connector](/en/unity/connectors/singular)
Use either a connector or your own `OnAdRevenuePaid` forwarding code, not both.
### Mediation Debugger
Open the Mediation Debugger to check an integration on the device: every installed adapter with its version and status (completed, incomplete, missing, or not configured), the SDK configuration, privacy state, and your ad units. You can also load and show a test ad per ad unit and share the whole report as text.
```csharp
using CloudX;
bool opened = CloudXSdk.ShowMediationDebugger();
```
Call it after `OnSdkInitialized` reports success. Before that it returns `false` and opens nothing, as it does in the Editor and on any platform other than Android and iOS. The screen updates live while open. It is meant for development and QA builds; wire it to a debug menu or a gesture.
**Note:** A test mode request from the debugger takes effect on the next initialization, after you restart the app. The server decides whether that session runs in test mode.
### 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.
```csharp
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.
```csharp
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.
```csharp
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.
```csharp
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.
```csharp
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,
));
}
```
---
# Sample App
> Run the public CloudX Unity demo app and point it at your own app
The [cloudx-io/cloudx-unity](https://github.com/cloudx-io/cloudx-unity) repository, which hosts the SDK releases, is also a runnable Unity demo project. It opens on a screen that picks one of three flows. General shows banner, MREC, interstitial and rewarded ads. First Look loads CloudX first and falls back to AdMob, as described in [First Look](/en/unity/integrations/first-look). Arbiter/TPA loads CloudX and AdMob in parallel and lets [Trusted Arbiter](/en/unity/trusted-arbiter) pick the one to show. It ships with CloudX demo dashboard IDs, so it runs without an account.
## Use your own CloudX app
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.
---
# First Look
> Give CloudX the first chance to fill an ad placement, then fall back to your existing mediation setup
First Look gives CloudX the first chance to fill a placement while preserving your existing mediation setup as the fallback path. Start with one placement, verify load and show behavior, then expand to more placements.
These examples use an AdMob fallback through the [Google Mobile Ads Unity Plugin](https://developers.google.com/admob/unity/quick-start). The same pattern applies to other fallback mediators: load CloudX first, load the fallback only if CloudX does not load, and keep the game flow moving when neither source is ready.
There are two shapes to the rule, and this page covers one controller for each:
* **[Banner](#banner)** — an inline ad stays on screen and is never consumed, so the controller needs an explicit pass cycle to give CloudX another first look. MREC follows this controller exactly.
* **[Interstitial](#interstitial)** — a fullscreen ad is consumed by being shown, so the SDKs' own "is an ad ready" answers go false on their own and the next `Load()` starts at CloudX again. Rewarded follows this controller exactly.
> **Info:** Start CloudX and Google Mobile Ads initialization before you create a controller, and create it once **CloudX** has answered — initialized, failed, or past a timeout of your own. You do not have to wait for Google Mobile Ads as well: it queues loads issued while it is still initializing. Keep your existing pacing, retry, and placement timing rules around it.
>
> Google Mobile Ads raises its callbacks on a background thread. Both controllers wrap every Google callback in `MobileAdsEventExecutor.ExecuteInUpdate` (from `GoogleMobileAds.Common`), so controller state and the events your UI subscribes to stay on the Unity main thread, like the CloudX callbacks. Touching Unity APIs off the main thread throws. Keep that wrapping if you adapt them.
>
> Both controllers keep the fallback lazy: AdMob is loaded only after CloudX reports a load failure. Do not parallel-load the two sources unless CloudX recommends a different rollout strategy for your game.
>
> One consequence to expect: a CloudX load failure is not raised as `AdLoadFailed`, because it is not terminal — it is what triggers the fallback. `AdLoadFailed` fires only when the fallback fails too, which is the case actually worth retrying.
## Banner
A banner is not the interstitial with different method names. A fullscreen ad is consumed by being shown, so its readiness checks go false on their own and the next `Load()` starts at CloudX again — the [interstitial](#interstitial) below rests on that. An inline ad is never consumed: CloudX banners report load and click, with no show or close callback. Left alone, the first fill owns the placement until the scene is destroyed, so a single CloudX no-fill hands the slot to your fallback for the rest of the session.
The controller solves that with a **pass cycle**. One pass is one ad opportunity: CloudX first, AdMob only if CloudX fails, winner displayed. Displaying the winner ends the pass and raises `PassSpent`; you start the next one, and it begins at CloudX again.
* [FirstLookBannerController.cs](https://github.com/cloudx-io/cloudx-unity/blob/main/Assets/Scripts/FirstLook/FirstLookBannerController.cs)
Which SDK fills a pass. The complete controller, commented at the lines that matter.
* [FirstLookBannerCycle.cs](https://github.com/cloudx-io/cloudx-unity/blob/main/Assets/Scripts/FirstLook/FirstLookBannerCycle.cs)
When the next pass starts. The scene's half of the contract, as a `MonoBehaviour`.
* [FirstLookSource.cs](https://github.com/cloudx-io/cloudx-unity/blob/main/Assets/Scripts/FirstLook/FirstLookSource.cs)
The `CloudX` / `AdMob` enum every event reports.
Copy all three. Between them they are the whole flow.
They live in the [CloudX Unity demo app](https://github.com/cloudx-io/cloudx-unity), which is the version under CI and verified on device, so it is the one that gets fixed when something is wrong. Read them there rather than from a copy on this page — a copy would drift, and it would be the stale one.
### Why the host is a separate file
The controller is a plain class with no clock — no `Update`, no coroutine, no `Invoke` — so it cannot decide *when* the next pass starts. `FirstLookBannerCycle` is that clock, and the three rules it keeps are the ones an integration gets wrong:
* **Start the next pass on `PassSpent`, after a cooldown.** 30 seconds is a normal banner interval. Starting it immediately is a request loop, because the new ad renders into the visible slot and ends the next pass at once.
* **Cancel that pending pass when the banner is hidden.** Otherwise a hidden slot keeps requesting for the rest of the session.
* **Do not retry a failed load while it is hidden.** Cancelling alone does not cover this: a load already in flight when the player hides completes after it. A fill is harmless — it is banked for the next `Show()`. A failure is not: its retry would start the requests up again, and by then `CancelInvoke` has nothing left to cancel.
Take the cycle component as it is and you get all three; the cooldown, the backoff and the hide handling are implemented and commented there. Which SDK wins a pass, and how a fill is banked until the slot is shown, is the controller's side and is commented in that file.
> **Warning:** **Disable Automatic refresh on your AdMob banner ad unit.**
>
> The Google Mobile Ads Unity Plugin has no refresh API — a `BannerView` loads once, and whether it refreshes afterwards is decided solely by the ad unit's **Automatic refresh** setting in the AdMob console. Leave it on and AdMob swaps the creative on its own schedule, outside the pass cycle. Set it to **Disabled** on every unit you use as a First Look fallback.
>
> CloudX banner auto-refresh is the opposite: it is opt-out. Showing a banner starts it unless the ad unit was first passed to `StopBannerAutoRefresh`, which is why the controller calls that before `CreateBanner` and never calls `StartBannerAutoRefresh`. The pass cycle owns reloading; an SDK refresh timer would compete with it.
### Using it in your scene
Add `FirstLookBannerCycle` to a `GameObject`, then in the Inspector:
* drag that component into the host's `banner` field,
* fill in `cloudXBannerAdUnitId` and `adMobBannerAdUnitId` — all three are serialized fields and start empty, and `Begin` needs both ids,
* point your button's `OnClick` at `OnBannerButton`.
Then call `BeginFirstLook` once CloudX has answered — initialized, failed, or past a timeout of your own. Everything else — cooldown, backoff, cancelling on hide — is inside the cycle.
You do not need to wait for Google Mobile Ads as well. It queues loads issued while it is still initializing, and the fallback is lazy in any case, so waiting on it would only delay the first pass.
```csharp BannerButton.cs
using UnityEngine;
public class BannerButton : MonoBehaviour
{
[SerializeField] private FirstLookBannerCycle banner;
[SerializeField] private string cloudXBannerAdUnitId;
[SerializeField] private string adMobBannerAdUnitId;
void Start()
{
banner.AdShown += source => Debug.Log($"First Look banner shown: {source}");
}
/*
* Call this from your CloudX initialization result - initialized, failed,
* or past your own timeout - passing whether CloudX actually came up.
* Not from Start(): that runs when the object is enabled, which says
* nothing about whether the SDK has answered yet.
*
* Begin preloads one pass, so an ad is ready the first time the player
* asks for one.
*/
public void BeginFirstLook(bool cloudXAvailable)
{
banner.Begin(cloudXBannerAdUnitId, adMobBannerAdUnitId, cloudXAvailable);
}
// Hook this to your button.
public void OnBannerButton() => banner.Toggle();
}
```
Pass `cloudXAvailable: false` if CloudX initialization failed; the cycle then serves the fallback directly instead of waiting for load callbacks that will never arrive.
The demo does the same in [FirstLookScreen.cs](https://github.com/cloudx-io/cloudx-unity/blob/main/Assets/Scripts/FirstLook/FirstLookScreen.cs), calling `Begin` from the CloudX initialization result rather than from `Start`. It differs in one detail that is not a requirement: it adds the cycle with `AddComponent` at runtime instead of keeping it in the scene, because it builds the whole banner flow only once initialization has answered. Its ad unit ids are compile-time constants — the value it waits for is `cloudXAvailable`.
> **Tip:** MREC needs the same set of files, with the MREC calls substituted in the controller — the cycle makes no SDK calls of its own, so it is unchanged: `CreateMrec`, `LoadMrec`, `ShowMrec`, `HideMrec`, `DestroyMrec`, `StopMrecAutoRefresh`, the `CloudXAdsCallbacks.Mrec` callback group, and `AdSize.MediumRectangle` with `AdPosition.Bottom` on the AdMob side. Two details catch people out: the placement setter is `SetMRecPlacement` with a capital R, against the lowercase r in every lifecycle method, and `CloudXAdViewConfiguration` for an MREC takes a position only — passing a vertical configuration throws.
## Interstitial
The simpler of the two, because showing consumes the ad: no flag has to be cleared and no cycle has to be driven. Rewarded is this controller with the rewarded calls substituted, plus the reward callback.
* [FirstLookInterstitialController.cs](https://github.com/cloudx-io/cloudx-unity/blob/main/Assets/Scripts/FirstLook/FirstLookInterstitialController.cs)
The complete controller, commented at the lines that matter. Copy it as is.
* [FirstLookSource.cs](https://github.com/cloudx-io/cloudx-unity/blob/main/Assets/Scripts/FirstLook/FirstLookSource.cs)
The `CloudX` / `AdMob` enum every event reports. Copy it too.
Call `Show()` at the placement moment. If it returns `false`, neither CloudX nor the fallback had a ready ad, so continue the game flow without showing an ad.
```csharp
if (!firstLookInterstitial.Show())
{
ContinueToNextScene();
}
```
After a show, close, or terminal failure, call `Load()` again when your game is ready to prepare the next placement opportunity. Call `Dispose()` from the owning `MonoBehaviour` when it is destroyed.
## Check your integration
The controller decides the order. What it cannot decide is whether your app key, your ad unit ids, your dashboard configuration and — for the banner — your scene wiring are right, and those live in your project. When one of them is wrong the symptom is silent: the fallback serves, ads appear, and everything looks healthy.
One check covers it. Log the source, which every event already carries:
```csharp
banner.AdLoaded += (source) => Debug.Log($"First Look banner loaded: {source}");
```
Run with your real ad unit ids and look for `CloudX` on a fill. If you only ever see `AdMob`, CloudX is not filling at all — check the app key, the ad unit ids and the dashboard configuration before you look at the controller.
For a banner, watch for longer than one cooldown: a fresh CloudX attempt should precede *every* AdMob fill, not just the first. If CloudX is asked once and never again, `PassSpent` is not wired, or something other than hiding is cancelling the pending pass.
To exercise the fallback on purpose — to confirm your AdMob unit is configured before you ship — point the CloudX ad unit id at a string that is not in your dashboard, so every CloudX load fails and the fallback has to serve.
> **Tip:** Rewarded needs the same controller with the rewarded calls substituted: `IsRewardedReady`, `LoadRewarded`, `ShowRewarded`, `DestroyRewarded`, the `CloudXAdsCallbacks.Rewarded` callback group, and `RewardedAd` on the AdMob side. Add one event of your own for the reward itself, fed by `CloudXAdsCallbacks.Rewarded.OnAdRewarded` (which carries `reward.Amount` and `reward.Label`) and by the callback argument of the AdMob `Show(reward => ...)` overload (`reward.Amount` and `reward.Type`). Grant the reward from that event, not from `AdClosed` — a user who dismisses early closes the ad without earning it.
---
# Trusted Arbiter
> Compare CloudX bids with supported third-party bids in Unity games
Trusted Arbiter compares a loaded CloudX ad with bids from other platforms you mediate yourself and returns the platform to show. Unity SDK 4.1.0 introduced CloudX, Unity LevelPlay and PubMatic bids. Custom bids require 4.4.1 or later, and AdMob and Google Ad Manager bids require 4.5.0 or later.
## Why use Trusted Arbiter?
When your app loads ads from several platforms, it needs a way to choose which ad to show. Trusted Arbiter provides a shared comparison API for supported bids.
### Less comparison logic to maintain
Submit the supported bids to Trusted Arbiter and use the returned platform to select the ad. Your app continues to manage partner SDK integration, ad loading, and display.
### Use available bid values
Trusted Arbiter can compare actual bid values where available. Some demand uses estimated prices. See the pricing guidance below for each supported input.
`CloudXSdk.Arbiter` requires `CloudXSdk.Initialize` to have completed. Build one bid per loaded ad and hand them to it:
```csharp
var bids = new List
{
new CloudXArbiterBid.CloudX(cloudXAd),
new CloudXArbiterBid.AdMob(AdUnitId: adMobAdUnitId),
};
CloudXArbiterResult nextWinner = null;
CloudXSdk.Arbiter(bids, result =>
{
/* Store fullscreen results now and show them later at the placement. */
nextWinner = result.Platform == CloudXArbiterPlatform.None
? null
: result;
});
```
For fullscreen formats, store the result and show it later from the placement callback. Banner and MREC integrations may render the selected view when the arbiter callback returns. The constants are `CloudX`, `AdMob`, `Gam`, `LevelPlay`, `PubMatic`, `Custom` and `None`.
> **Note:** The arbitration itself always produces one callback. The native SDK owns the timeout and fallback, and the per-call timeout comes from your server-provided init configuration. On timeout, error or an unavailable service, it returns the highest locally comparable USD bid among the inputs you supplied. A single supplied bid wins immediately without a service call. Do not add your own timer or compare revenue yourself.
>
> With the default callback setting, or with `CloudXSdk.InvokeEventsOnUnityMainThread = true`, the callback runs on the Unity main thread. The examples on this page assume one of those settings. If you set it to `false`, marshal the entire callback to the Unity main thread before reading or writing controller state or calling Unity APIs. See [Callback Threading](/en/unity/integration#callback-threading). `result.Platform` is `CloudXArbiterPlatform.None` when no valid bids reached the arbiter. With several valid bids, it is also `None` when the fallback found no candidate carrying a locally comparable price. A single valid bid always wins, with or without a comparable price. Treat `None` as "no winner to show", not as an error.
Trusted Arbiter must be enabled for your app in the CloudX dashboard. Until it is, every call is answered by the local fallback, which compares only bids carrying a locally comparable price. A Google bid without a manual price has no locally comparable price, so it loses against priced bids but still wins when it is the only valid bid. A Google bid with `ManualRevenuePerImpressionUSD` competes normally.
## Demo App
The [CloudX Unity demo app](https://github.com/cloudx-io/cloudx-unity) runs this page's cycle for interstitials. CloudX and AdMob load in parallel, both fills become bids, `CloudXSdk.Arbiter` picks the winner, and the winner is shown from a stored result so the show path makes no network call.
* [ArbiterInterstitialController.cs](https://github.com/cloudx-io/cloudx-unity/blob/main/Assets/Scripts/Arbiter/ArbiterInterstitialController.cs)
The whole load, arbitrate and show cycle, with both SDKs' calls in one file. Rewarded follows this file with the rewarded calls substituted.
* [ArbiterScreen.cs](https://github.com/cloudx-io/cloudx-unity/blob/main/Assets/Scripts/Arbiter/ArbiterScreen.cs)
Brings both SDKs up and creates the controller after CloudX answers. This is demo-only layout; take the rules, not the file.
* [DemoConfig.cs](https://github.com/cloudx-io/cloudx-unity/blob/main/Assets/Scripts/DemoConfig.cs)
App key and ad unit ids per platform. This is the first file to edit when you run the demo yourself.
The demo ships Google's public AdMob test units, which report revenue of 0.0. CloudX discards zero prices from its pricing history, so the AdMob bid has no comparable historical price while you test. Point the demo at a paying unit to watch prices compete.
## When to Run the Arbiter
Run the arbiter when your candidate ads finish loading, never on the show path. With several candidates and the feature enabled, `CloudXSdk.Arbiter` reaches the service, and a user who reaches an ad placement must never wait for that network call.
Load every candidate in parallel. Once they have all settled, loaded or failed, run the arbiter and store the result. At the placement, show the stored winner immediately. Run the cycle again after the ad is hidden, after it fails to display or when a candidate expires.
```csharp
private CloudXArbiterResult _nextWinner;
private int _arbiterRound;
/*
* Run this as soon as both candidates have settled, ahead of the placement.
* Only an ad that actually loaded becomes a bid.
*/
private void PrepareWinner(
CloudXAd cloudXAd,
string adMobAdUnitId)
{
var bids = new List();
if (cloudXAd != null && CloudXSdk.IsInterstitialReady(cloudXAd.AdUnitId))
{
bids.Add(new CloudXArbiterBid.CloudX(cloudXAd));
}
if (adMobInterstitial?.CanShowAd() == true)
{
bids.Add(new CloudXArbiterBid.AdMob(AdUnitId: adMobAdUnitId));
}
PrepareWinner(bids);
}
private void PrepareWinner(IReadOnlyList bids)
{
/*
* Rounds can overlap when the placement consumes one candidate while an
* earlier arbitration is still out. Only the newest round may store a
* result, or an old result can point at an ad that has already been shown.
*/
var round = ++_arbiterRound;
_nextWinner = null;
if (bids.Count == 0)
{
BeginLoadCycle();
return;
}
CloudXSdk.Arbiter(bids, result =>
{
if (round != _arbiterRound)
{
return;
}
/* None is not a winner and must not be stored as one. */
_nextWinner = result.Platform == CloudXArbiterPlatform.None
? null
: result;
});
}
/* Run this at the placement. There is no network call here. */
private void ShowAd(string cloudXAdUnitId)
{
var winner = _nextWinner;
_nextWinner = null;
/* Retire any round that still describes this placement's candidates. */
_arbiterRound++;
if (winner == null)
{
BeginLoadCycle();
return;
}
switch (winner.Platform)
{
case CloudXArbiterPlatform.CloudX
when CloudXSdk.IsInterstitialReady(cloudXAdUnitId):
CloudXSdk.ShowInterstitial(cloudXAdUnitId);
break;
case CloudXArbiterPlatform.AdMob
when adMobInterstitial?.CanShowAd() == true:
adMobInterstitial.Show();
break;
default:
Debug.Log($"Winner {winner.Platform} is unsupported or no longer loaded");
BeginLoadCycle();
break;
}
}
```
`BeginLoadCycle` is your app's existing cycle entry point: it applies your retry backoff, loads missing candidates in parallel, reuses candidates that still hold a fill, then calls `PrepareWinner` after they have all settled. If the placement arrives before a winner is stored, continue without an ad while that next cycle runs, or show the single candidate that loaded and let its hidden callback begin the next cycle. That is an acceptable degraded path, never the primary one.
A `None` round leaves the loaded candidates holding their fills. Holding a fill starts nothing, so no hidden callback or display failure arrives to start the next cycle. The null branch above explicitly starts that next cycle instead of waiting for a callback that will not come.
There is nothing asynchronous to catch around `CloudXSdk.Arbiter`: it invokes its callback exactly once, and an exception thrown by the callback is caught and logged by the SDK. Google Mobile Ads callbacks are different. Wrap every Google callback body that mutates controller state, calls CloudX, or uses Unity APIs in `MobileAdsEventExecutor.ExecuteInUpdate` so it runs on the Unity main thread.
## Supported Formats
The arbiter is format-agnostic. It accepts any loaded CloudX ad, and the field mapping is the same for every format.
* **Fullscreen: interstitial, rewarded and app open.** Prepare the winner before the placement, as above.
* **View: banner and MREC.** Render the winner when the arbiter callback returns. See [Banner and MREC](#banner-and-mrec).
## AdMob and Google Ad Manager
CloudX compares a loaded CloudX ad with a loaded AdMob or Google Ad Manager ad. AdMob and Ad Manager are separate demand sources, so both may bid in one arbitration.
Google demand does not normally reveal the loaded ad's price before it is shown. CloudX prices the bid automatically from your historical Google performance, so the bid needs no price. The revenue reporting below feeds that history, which is why it is required. See [how AdMob/GAM estimated pricing works](/en/networks/google-estimated).
```csharp
var adManagerAdUnitId = "/21775744923/example/interstitial";
var adMobNetworkName = "admob";
var bids = new List();
if (cloudXAd != null && CloudXSdk.IsInterstitialReady(cloudXAdUnitId))
{
bids.Add(new CloudXArbiterBid.CloudX(cloudXAd));
}
if (adMobInterstitial?.CanShowAd() == true)
{
var loadedAdapter = adMobInterstitial
.GetResponseInfo()
?.GetLoadedAdapterResponseInfo();
adMobNetworkName = string.IsNullOrWhiteSpace(loadedAdapter?.AdSourceName)
? "admob"
: loadedAdapter.AdSourceName;
bids.Add(new CloudXArbiterBid.AdMob(
AdUnitId: adMobAdUnitId,
NetworkName: adMobNetworkName));
}
if (adManagerInterstitial?.CanShowAd() == true)
{
bids.Add(new CloudXArbiterBid.Gam(
AdUnitId: adManagerAdUnitId));
}
PrepareWinner(bids);
private void ShowGoogleArbitrationWinner(string cloudXAdUnitId)
{
var winner = _nextWinner;
_nextWinner = null;
_arbiterRound++;
switch (winner?.Platform)
{
case CloudXArbiterPlatform.CloudX
when CloudXSdk.IsInterstitialReady(cloudXAdUnitId):
CloudXSdk.ShowInterstitial(cloudXAdUnitId);
break;
case CloudXArbiterPlatform.AdMob
when adMobInterstitial?.CanShowAd() == true:
adMobInterstitial.Show();
break;
case CloudXArbiterPlatform.Gam
when adManagerInterstitial?.CanShowAd() == true:
adManagerInterstitial.Show();
break;
default:
Debug.Log($"Winner {winner?.Platform} is unsupported or no longer loaded");
BeginLoadCycle();
break;
}
}
```
A winning Google bid reports `CloudXArbiterPlatform.AdMob` or `CloudXArbiterPlatform.Gam`, so you do not need to inspect `PlatformName` to distinguish them. A blank ad unit id still builds a bid, but it has no usable identity, is never priced, and is rejected by the server.
### Report Google Paid Events Back to CloudX
Reporting Google's impression-level revenue is required for AdMob and Ad Manager arbitration. It is not optional analytics. Without these events CloudX does not learn what Google demand paid, and later estimates degrade.
This example uses the Google Mobile Ads Unity plugin, `com.google.ads.mobile`. `AdValue.Value` is in micros on both platforms, so divide it by 1,000,000. The plugin also requires the AdMob application ID declared natively; complete [AdMob Application ID Required](/en/unity/adapters/google-estimated#admob-application-id-required). An Android app taking only Ad Manager demand can declare `AD_MANAGER_APP` instead.
```csharp
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 ReportGooglePaidEvent(
CloudXRevenuePlatform platform,
AdValue adValue,
string adFormat,
string adUnitId,
string networkName) =>
CloudXSdk.ReportRevenueData(new CloudXRevenueData(
Platform: platform,
Revenue: adValue.Value / 1_000_000.0,
AdFormat: adFormat,
CurrencyCode: adValue.CurrencyCode,
Precision: ToCloudXRevenuePrecision(adValue.Precision),
NetworkName: networkName,
AdUnitId: adUnitId));
adMobInterstitial.OnAdPaid += adValue => MobileAdsEventExecutor.ExecuteInUpdate(() =>
{
var accepted = ReportGooglePaidEvent(
CloudXRevenuePlatform.AdMob,
adValue,
"interstitial",
adMobAdUnitId,
adMobNetworkName);
Debug.Log($"CloudX accepted revenue: {accepted}");
});
adManagerInterstitial.OnAdPaid += adValue => MobileAdsEventExecutor.ExecuteInUpdate(() =>
{
var accepted = ReportGooglePaidEvent(
CloudXRevenuePlatform.Gam,
adValue,
"interstitial",
adManagerAdUnitId,
"gam");
Debug.Log($"CloudX accepted GAM revenue: {accepted}");
});
```
Report the same ad unit id you passed to the bid so CloudX attributes the realized price correctly. `ReportRevenueData` returns `false` when the data is dropped. See [Publisher-Reported Revenue Data](/en/unity/integration#publisher-reported-revenue-data) for every field.
A `true` return does not mean the price entered CloudX's history. That history keeps only positive USD amounts carrying an ad unit id. A zero report is accepted and then ignored for pricing, which is why Google's test units do not make AdMob bids price-comparable.
## Manual Input with Pre-Bid ILRD
Some AdMob accounts expose impression-level revenue before the ad is shown. This is a legacy, account-gated capability, so check with your Google account team. An exact per-impression price known before show is the one case where your own price is better than CloudX's estimate.
> **Warning:** Supply a manual price only when it comes from pre-bid ILRD. Average eCPMs, guesses and post-impression paid events degrade arbitration. Leave `ManualRevenuePerImpressionUSD` unset in those cases.
```csharp
var adMobBid = new CloudXArbiterBid.AdMob(
AdUnitId: adMobAdUnitId,
NetworkName: adMobNetworkName,
ManualRevenuePerImpressionUSD: preBidAdValue.Value / 1_000_000.0);
```
`ManualRevenuePerImpressionUSD` is revenue for one impression in USD, not a CPM. An `AdValue.Value` of `5000` is `0.005`; do not divide by 1,000 again. Convert non-USD values before passing them.
* `0.0` is a real price and means the bid is worth nothing.
* Negative and non-finite values are treated as absent and logged.
* A blank ad unit id drops the manual price because the bid cannot be validated.
## Banner and MREC
Auto-refresh conflicts with arbitration because it can replace an ad after the arbiter selected the winner. Disable CloudX auto-refresh in the dashboard, stop it before creating the view, and never call `StartBannerAutoRefresh`. The Google Mobile Ads Unity plugin has no refresh API, so set **Automatic refresh** to **Disabled** for the AdMob ad unit in the console.
```csharp
CloudXSdk.StopBannerAutoRefresh(adUnitId);
CloudXSdk.CreateBanner(adUnitId, new CloudXAdViewConfiguration(CloudXAdViewConfiguration.AdViewPosition.BottomCenter));
CloudXSdk.LoadBanner(adUnitId);
```
Show only the winning bid with `ShowBanner` and keep the others hidden with `HideBanner`. Create the Google `BannerView`, call `Hide()` before its first `LoadAd`, and show it only after an AdMob win.
After the winner's impression, load a new fill from that network only. Keep losing ads that still hold a fill, request only unfilled networks, and rerun the arbiter after all responses return. Refresh the displayed ad every 20 to 30 seconds; shorter intervals reduce CPM performance.
MREC uses the same flow with `CreateMrec`, `LoadMrec`, `ShowMrec`, `HideMrec`, `StopMrecAutoRefresh` and the `CloudXAdsCallbacks.Mrec` callback group.
## Other Mediation Platforms
`CloudXArbiterBid.LevelPlay` takes Unity LevelPlay's own values. In LevelPlay Unity SDK 8.x, the fields are camelCase and `revenue` is a nullable `double`:
```csharp
var levelPlayBid = new CloudXArbiterBid.LevelPlay(
NetworkName: levelPlayAdInfo.adNetwork,
Revenue: levelPlayAdInfo.revenue ?? 0,
Precision: levelPlayAdInfo.precision);
```
`CloudXArbiterBid.PubMatic` takes the price from the OpenWrap bid object:
```csharp
var pubMaticBid = new CloudXArbiterBid.PubMatic(
Price: pubMaticPrice,
PartnerName: pubMaticPartnerName);
```
## Custom Bid Inputs
Use `CloudXArbiterBid.Custom` for a platform that has no dedicated bid type.
```csharp
var customBid = new CloudXArbiterBid.Custom(
PlatformName: "my_mediation_platform",
NetworkName: "winning_demand_source",
RevenuePerImpressionUSD: 0.00125,
Precision: "EXACT",
Extras: new Dictionary
{
["ad_unit"] = "third-party-ad-unit-id",
});
```
When it wins, `result.Platform` is `CloudXArbiterPlatform.Custom` and `result.PlatformName` carries the supplied `PlatformName`. `RevenuePerImpressionUSD` is revenue for one impression in USD, not CPM. `Precision` is a string token such as `"EXACT"`, `"ESTIMATED"`, `"PUBLISHER_DEFINED"` or `"UNDEFINED"`. A bid missing a non-blank platform name, revenue or precision is dropped and logged.
## The Result
| Property | Type | Description |
| -------------- | ------------------------------------- | -------------------------------------------------------------------------------- |
| `Platform` | `CloudXArbiterPlatform` | The winning platform, or `None`. |
| `PlatformName` | `string` | The concrete platform name; carries your platform name for a winning custom bid. |
| `BidId` | `string?` | Identifier of the selected bid, or `null` when there is no winner. |
| `Id` | `string` | Identifier of this arbiter request. |
| `Extras` | `IReadOnlyDictionary` | Arbiter metadata for the selected bid. |
---
# Banner & MREC Ads
> Integrate CloudX banner and MREC ads in Unity games
## Banner Ads (320x50)
Standard banner ads displayed at a fixed screen position with auto-refresh functionality.
```csharp
using CloudX;
using UnityEngine;
public class BannerExample : MonoBehaviour
{
private const string BannerAdUnitId = "banner_main";
void Start()
{
// Subscribe to callbacks
CloudXAdsCallbacks.Banner.OnAdLoadSuccess += OnBannerLoaded;
CloudXAdsCallbacks.Banner.OnAdLoadFailed += OnBannerLoadFailed;
CloudXAdsCallbacks.Banner.OnAdClicked += OnBannerClicked;
CloudXAdsCallbacks.Banner.OnAdRevenuePaid += OnBannerRevenuePaid;
// Create, load, and show banner
var config = new CloudXAdViewConfiguration(CloudXAdViewConfiguration.AdViewPosition.BottomCenter);
CloudXSdk.CreateBanner(BannerAdUnitId, config);
CloudXSdk.LoadBanner(BannerAdUnitId);
CloudXSdk.ShowBanner(BannerAdUnitId);
}
void OnApplicationFocus(bool hasFocus)
{
if (hasFocus)
{
CloudXSdk.ShowBanner(BannerAdUnitId);
}
else
{
CloudXSdk.HideBanner(BannerAdUnitId);
}
}
private void OnBannerLoaded(CloudXAd ad)
{
Debug.Log($"Banner loaded: {ad.AdUnitId}");
}
private void OnBannerLoadFailed(string adUnitId, CloudXError error)
{
Debug.LogError($"Banner failed to load: {adUnitId} - {error}");
}
private void OnBannerClicked(CloudXAd ad)
{
Debug.Log($"Banner clicked: {ad.AdUnitId}");
}
private void OnBannerRevenuePaid(CloudXAd ad)
{
Debug.Log($"Banner revenue: ${ad.Revenue} from {ad.NetworkName}");
}
}
```
### Auto-Refresh Control
Banner ads auto-refresh by default. To control refresh manually:
```csharp
CloudXSdk.StopBannerAutoRefresh(BannerAdUnitId); // Stop auto-refresh
CloudXSdk.LoadBanner(BannerAdUnitId); // Manually load
CloudXSdk.StartBannerAutoRefresh(BannerAdUnitId); // Re-enable auto-refresh
```
### Placement and Custom Data
```csharp
CloudXSdk.SetBannerPlacement(BannerAdUnitId, "home_screen");
CloudXSdk.SetBannerCustomData(BannerAdUnitId, "custom_value");
```
> **Note:** Starting with `2.2.6`, if you want `placement` and `customData` to be included on the first banner request, call `SetBannerPlacement(...)` and `SetBannerCustomData(...)` before `CreateBanner(...)`.
## Vertical Banner Ads (320x50, rotated)
A vertical banner is a standard 320x50 banner rotated 90 degrees and pinned flush against the left or right screen edge. It is vertically centered on that edge, and the top of the creative faces the screen center. When a display cutout (notch, camera hole, Dynamic Island) occupies that edge, which is common in landscape, the banner is automatically inset past it so the creative is never obscured.
This format is particularly well suited for landscape games that don't have the vertical space for a top or bottom banner: instead of eating into the gameplay area's height, the banner uses the short screen edges.
To create a vertical banner, construct the `CloudXAdViewConfiguration` with an `AdViewVerticalPosition` instead of an `AdViewPosition`. Loading, showing, callbacks (`CloudXAdsCallbacks.Banner`), auto-refresh control, and placement/custom data work similarly to horizontal banners.
```csharp
// Pin the banner flush against the left screen edge (rotated clockwise)
var config = new CloudXAdViewConfiguration(
CloudXAdViewConfiguration.AdViewVerticalPosition.Left);
CloudXSdk.CreateBanner(BannerAdUnitId, config);
CloudXSdk.LoadBanner(BannerAdUnitId);
CloudXSdk.ShowBanner(BannerAdUnitId);
```
`AdViewVerticalPosition.Left` pins the banner flush against the left screen edge, rotated clockwise; `AdViewVerticalPosition.Right` pins it against the right screen edge, rotated counter-clockwise.
> **Note:** Vertical banner positions are available starting with `4.5.1`.
> **Warning:** Vertical positions are supported only for banner ads. MREC ads require a horizontal `CloudXAdViewConfiguration.AdViewPosition`; passing a vertical configuration to `CloudXSdk.CreateMrec(...)` throws an `ArgumentException`.
## MREC Ads (300x250)
Medium Rectangle ads work the same way as banners but with a larger size.
```csharp
using CloudX;
using UnityEngine;
public class MrecExample : MonoBehaviour
{
private const string MrecAdUnitId = "mrec_main";
void Start()
{
// Subscribe to callbacks
CloudXAdsCallbacks.Mrec.OnAdLoadSuccess += OnMrecLoaded;
CloudXAdsCallbacks.Mrec.OnAdLoadFailed += OnMrecLoadFailed;
CloudXAdsCallbacks.Mrec.OnAdClicked += OnMrecClicked;
CloudXAdsCallbacks.Mrec.OnAdRevenuePaid += OnMrecRevenuePaid;
// Create, load, and show MREC
var config = new CloudXAdViewConfiguration(CloudXAdViewConfiguration.AdViewPosition.Centered);
CloudXSdk.CreateMrec(MrecAdUnitId, config);
CloudXSdk.LoadMrec(MrecAdUnitId);
CloudXSdk.ShowMrec(MrecAdUnitId);
}
void OnApplicationFocus(bool hasFocus)
{
if (hasFocus)
{
CloudXSdk.ShowMrec(MrecAdUnitId);
}
else
{
CloudXSdk.HideMrec(MrecAdUnitId);
}
}
private void OnMrecLoaded(CloudXAd ad)
{
Debug.Log($"MREC loaded: {ad.AdUnitId}");
}
private void OnMrecLoadFailed(string adUnitId, CloudXError error)
{
Debug.LogError($"MREC failed to load: {adUnitId} - {error}");
}
private void OnMrecClicked(CloudXAd ad)
{
Debug.Log($"MREC clicked: {ad.AdUnitId}");
}
private void OnMrecRevenuePaid(CloudXAd ad)
{
Debug.Log($"MREC revenue: ${ad.Revenue} from {ad.NetworkName}");
}
}
```
### Placement and Custom Data
```csharp
CloudXSdk.SetMRecPlacement(MrecAdUnitId, "home_screen");
CloudXSdk.SetMRecCustomData(MrecAdUnitId, "custom_value");
```
> **Note:** Starting with `2.2.6`, if you want `placement` and `customData` to be included on the first MREC request, call `SetMRecPlacement(...)` and `SetMRecCustomData(...)` before `CreateMrec(...)`.
### Auto-Refresh Control
MREC ads also auto-refresh by default. Use the same refresh control methods as banners:
```csharp
CloudXSdk.StopMrecAutoRefresh(MrecAdUnitId);
CloudXSdk.LoadMrec(MrecAdUnitId);
CloudXSdk.StartMrecAutoRefresh(MrecAdUnitId);
```
---
# Interstitial Ads
> Integrate CloudX interstitial ads in Unity games
Full-screen ads shown between content transitions.
```csharp
using CloudX;
using UnityEngine;
public class InterstitialExample : MonoBehaviour
{
private const string InterstitialAdUnitId = "interstitial_main";
void Start()
{
// Subscribe to callbacks
CloudXAdsCallbacks.Interstitial.OnAdLoadSuccess += OnInterstitialLoaded;
CloudXAdsCallbacks.Interstitial.OnAdLoadFailed += OnInterstitialLoadFailed;
CloudXAdsCallbacks.Interstitial.OnAdShowSuccess += OnInterstitialShown;
CloudXAdsCallbacks.Interstitial.OnAdShowFailed += OnInterstitialShowFailed;
CloudXAdsCallbacks.Interstitial.OnAdHidden += OnInterstitialHidden;
CloudXAdsCallbacks.Interstitial.OnAdClicked += OnInterstitialClicked;
CloudXAdsCallbacks.Interstitial.OnAdRevenuePaid += OnInterstitialRevenuePaid;
// Load interstitial
CloudXSdk.LoadInterstitial(InterstitialAdUnitId);
}
public void ShowInterstitial()
{
if (CloudXSdk.IsInterstitialReady(InterstitialAdUnitId))
{
// Show with optional placement and custom data
CloudXSdk.ShowInterstitial(InterstitialAdUnitId, "level_complete", "custom_data");
}
else
{
Debug.LogWarning("Interstitial not ready yet");
}
}
private void OnInterstitialLoaded(CloudXAd ad)
{
Debug.Log($"Interstitial loaded: {ad.AdUnitId}");
}
private void OnInterstitialLoadFailed(string adUnitId, CloudXError error)
{
Debug.LogError($"Interstitial failed to load: {adUnitId} - {error}");
}
private void OnInterstitialShown(CloudXAd ad)
{
Debug.Log($"Interstitial shown: {ad.AdUnitId}");
}
private void OnInterstitialShowFailed(CloudXAd ad, CloudXError error)
{
Debug.LogError($"Interstitial failed to show: {ad.AdUnitId} - {error}");
// Reload for next time
CloudXSdk.LoadInterstitial(InterstitialAdUnitId);
}
private void OnInterstitialHidden(CloudXAd ad)
{
Debug.Log($"Interstitial hidden: {ad.AdUnitId}");
// Reload for next time
CloudXSdk.LoadInterstitial(InterstitialAdUnitId);
}
private void OnInterstitialClicked(CloudXAd ad)
{
Debug.Log($"Interstitial clicked: {ad.AdUnitId}");
}
private void OnInterstitialRevenuePaid(CloudXAd ad)
{
Debug.Log($"Interstitial revenue: ${ad.Revenue} from {ad.NetworkName}");
}
}
```
---
# Rewarded Ads
> Integrate CloudX rewarded ads in Unity games
Reward users for watching video ads.
```csharp
using CloudX;
using UnityEngine;
public class RewardedExample : MonoBehaviour
{
private const string RewardedAdUnitId = "rewarded_main";
void Start()
{
// Subscribe to callbacks
CloudXAdsCallbacks.Rewarded.OnAdLoadSuccess += OnRewardedLoaded;
CloudXAdsCallbacks.Rewarded.OnAdLoadFailed += OnRewardedLoadFailed;
CloudXAdsCallbacks.Rewarded.OnAdShowSuccess += OnRewardedShown;
CloudXAdsCallbacks.Rewarded.OnAdShowFailed += OnRewardedShowFailed;
CloudXAdsCallbacks.Rewarded.OnAdHidden += OnRewardedHidden;
CloudXAdsCallbacks.Rewarded.OnAdClicked += OnRewardedClicked;
CloudXAdsCallbacks.Rewarded.OnAdRewarded += OnUserRewarded;
CloudXAdsCallbacks.Rewarded.OnAdRevenuePaid += OnRewardedRevenuePaid;
// Load rewarded ad
CloudXSdk.LoadRewarded(RewardedAdUnitId);
}
public void ShowRewardedAd()
{
if (CloudXSdk.IsRewardedReady(RewardedAdUnitId))
{
// Show with optional placement and custom data
CloudXSdk.ShowRewarded(RewardedAdUnitId, "bonus_coins", "custom_data");
}
else
{
Debug.LogWarning("Rewarded ad not ready yet");
}
}
private void OnRewardedLoaded(CloudXAd ad)
{
Debug.Log($"Rewarded ad loaded: {ad.AdUnitId}");
}
private void OnRewardedLoadFailed(string adUnitId, CloudXError error)
{
Debug.LogError($"Rewarded ad failed to load: {adUnitId} - {error}");
}
private void OnRewardedShown(CloudXAd ad)
{
Debug.Log($"Rewarded ad shown: {ad.AdUnitId}");
}
private void OnRewardedShowFailed(CloudXAd ad, CloudXError error)
{
Debug.LogError($"Rewarded ad failed to show: {ad.AdUnitId} - {error}");
// Reload for next time
CloudXSdk.LoadRewarded(RewardedAdUnitId);
}
private void OnRewardedHidden(CloudXAd ad)
{
Debug.Log($"Rewarded ad hidden: {ad.AdUnitId}");
// Reload for next time
CloudXSdk.LoadRewarded(RewardedAdUnitId);
}
private void OnRewardedClicked(CloudXAd ad)
{
Debug.Log($"Rewarded ad clicked: {ad.AdUnitId}");
}
private void OnUserRewarded(CloudXAd ad, CloudXReward reward)
{
Debug.Log($"User earned reward: {reward.Amount} {reward.Label}");
// Grant reward to user here
GrantRewardToUser();
}
private void OnRewardedRevenuePaid(CloudXAd ad)
{
Debug.Log($"Rewarded ad revenue: ${ad.Revenue} from {ad.NetworkName}");
}
private void GrantRewardToUser()
{
// Your reward logic here
Debug.Log("Granting reward to user!");
}
}
```
---
# App Open Ads
> Integrate CloudX app open ads in Unity games
Full-screen ads shown when users open your app or return it to the foreground.
```csharp
using CloudX;
using UnityEngine;
public class AppOpenExample : MonoBehaviour
{
private const string AppOpenAdUnitId = "app_open_main";
void Start()
{
// Subscribe to callbacks
CloudXAdsCallbacks.AppOpen.OnAdLoadSuccess += OnAppOpenLoaded;
CloudXAdsCallbacks.AppOpen.OnAdLoadFailed += OnAppOpenLoadFailed;
CloudXAdsCallbacks.AppOpen.OnAdShowSuccess += OnAppOpenShown;
CloudXAdsCallbacks.AppOpen.OnAdShowFailed += OnAppOpenShowFailed;
CloudXAdsCallbacks.AppOpen.OnAdHidden += OnAppOpenHidden;
CloudXAdsCallbacks.AppOpen.OnAdClicked += OnAppOpenClicked;
CloudXAdsCallbacks.AppOpen.OnAdRevenuePaid += OnAppOpenRevenuePaid;
// Load app open ad
CloudXSdk.LoadAppOpen(AppOpenAdUnitId);
}
public void ShowAppOpen()
{
if (CloudXSdk.IsAppOpenReady(AppOpenAdUnitId))
{
// Show with optional placement and custom data
CloudXSdk.ShowAppOpen(AppOpenAdUnitId, "app_foreground", "custom_data");
}
else
{
Debug.LogWarning("App open ad not ready yet");
}
}
private void OnAppOpenLoaded(CloudXAd ad)
{
Debug.Log($"App open ad loaded: {ad.AdUnitId}");
}
private void OnAppOpenLoadFailed(string adUnitId, CloudXError error)
{
Debug.LogError($"App open ad failed to load: {adUnitId} - {error}");
}
private void OnAppOpenShown(CloudXAd ad)
{
Debug.Log($"App open ad shown: {ad.AdUnitId}");
}
private void OnAppOpenShowFailed(CloudXAd ad, CloudXError error)
{
Debug.LogError($"App open ad failed to show: {ad.AdUnitId} - {error}");
// Reload for next time
CloudXSdk.LoadAppOpen(AppOpenAdUnitId);
}
private void OnAppOpenHidden(CloudXAd ad)
{
Debug.Log($"App open ad hidden: {ad.AdUnitId}");
// Reload for next time
CloudXSdk.LoadAppOpen(AppOpenAdUnitId);
}
private void OnAppOpenClicked(CloudXAd ad)
{
Debug.Log($"App open ad clicked: {ad.AdUnitId}");
}
private void OnAppOpenRevenuePaid(CloudXAd ad)
{
Debug.Log($"App open ad revenue: ${ad.Revenue} from {ad.NetworkName}");
}
}
```
---
# Changelog
> Unity SDK release history
## \[4.10.0] - 2026-10-15
### Added
* Rewarded ads can pay out as soon as the ad network grants the reward, instead of when the ad closes. CloudX enables this per account, and it needs the updated rewarded adapters this release ships.
### Fixed
* (iOS) A vertical banner in landscape no longer sits inset from a screen edge with no notch or Dynamic Island. It is now flush with that edge and centered on the screen, inset only past a cutout on its own edge. A 180 degree rotation moves it correctly.
### Integration Notes
* `CloudXAdsCallbacks.Rewarded.OnAdRewarded` still arrives when the Unity player resumes, so under the default callback threading it lands when the ad closes. See [Callback threading](/en/unity/integration#callback-threading).
* (iOS) Rewarded ads now pay the reward amount and currency configured on the ad unit in CloudX, not the value the ad network reports.
* (iOS) A banner with no configured refresh interval now refreshes every 30 seconds.
* (iOS) Part of initialization now runs at app launch, including one request to the CloudX init host that carries no identifiers. Opt out with `CloudXStartupWarmDisabled` in Info.plist.
* (Android) The Magnite adapter now depends on kotlin-stdlib 2.4.0. Apps that enable it must compile with Kotlin 2.3 or newer.
### SDK Updates
* Updated Android SDK from 4.9.0 to 4.10.0 — see [Android SDK changelog](/en/android/changelog#4100---2026-10-13) for details
* Updated iOS SDK from 3.10.0 to 3.11.0 — see [iOS SDK changelog](/en/ios/changelog#3110---2026-10-13) for details
* Updated every bundled adapter pin in `CloudXDependencies.xml` to the version tested with this release. Each adapter page lists its new version.
## \[4.9.0] - 2026-10-07
### Added
* `CloudXSdk.ShowMediationDebugger()` opens the Mediation Debugger, an in-app screen listing every installed adapter with its version and status, your ad units and the SDK configuration. It returns `false` before initialization finishes. Intended for development and QA builds.
### Integration Notes
* A banner or MREC that fails after it loaded now reports `OnAdLoadFailed`. While auto-refresh is stopped, nothing replaces it on its own; call `Load()` to request a new ad.
### SDK Updates
* Updated Android SDK from 4.8.2 to 4.9.0 — see [Android SDK changelog](/en/android/changelog#490---2026-10-07) for details
* Updated the Android InMobi adapter from 11.4.1.0 to 11.5.0.0.
* Updated the Android Moloco adapter from 4.12.0.0 to 4.13.0.0.
* Updated the Android Unity Ads adapter from 4.20.1.0 to 4.21.0.0.
* Updated the Android Vungle adapter from 7.7.8.0 to 7.7.9.0.
* Updated the iOS Mintegral adapter from 8.1.6.0 to 8.1.6.1.
* Updated the iOS Moloco adapter from 4.10.0.0 to 4.11.0.0.
## \[4.8.2] - 2026-09-30
### SDK Updates
* Updated Android SDK from 4.8.0 to 4.8.2 — see [Android SDK changelog](/en/android/changelog#482---2026-09-29) for details
* Updated iOS SDK from 3.9.1 to 3.10.0 — see [iOS SDK changelog](/en/ios/changelog#3100---2026-09-24) for details
* Updated the Android Google Waterfall adapter from 25.4.0.0 to 25.5.0.0. It requires Android API 24 or later.
* Updated the Android AdMob adapter from 1.4.0.0 to 1.5.0.0.
* Updated the Android TaurusX adapter from 1.18.3.0 to 1.19.1.0.
* Added the iOS BIGO Ads adapter 6.1.0.0 to the optional adapter lines in `CloudXDependencies.xml`.
There are no changes to the Unity C# API in this release. The Unity SDK does not expose a Native ad API.
## \[4.8.1] - 2026-09-23
### Added
* `CloudXAd.MediatorName` and `CloudXAd.MediatorAdUnitId` report which mediator and mediator ad unit served an ad. CloudX-won ads report `cloudx` and their CloudX ad unit id. Supplied on Android; `null` on iOS.
* `CloudXSdkConfiguration.AdUnitConfigurations` maps each ad unit ID to its `IsOrchestratorEnabled` state once initialization completes. A missing entry means unavailable, not disabled. Supplied on Android; empty on iOS.
* The Unity plugin now ships a privacy manifest for iOS builds, so apps that include CloudX declare its Required Reason API use to App Store review.
### SDK Updates
* Updated the Android AdMob adapter from 1.3.0.4 to 1.4.0.0.
* Android SDK remains at 4.8.0.
## \[4.8.0] - 2026-09-22
### Fixed
* Vertical banners on Android now stay inside the app window's visible region, so they no longer disappear on devices that crop the window to that region.
### SDK Updates
* Updated Android SDK from 4.7.0 to 4.8.0 — see [Android SDK changelog](/en/android/changelog#480---2026-09-21) for details
* Updated the Android BIGO Ads adapter from 6.0.1.0 to 6.1.0.0.
* Updated the Android InMobi adapter from 11.4.0.1 to 11.4.1.0.
* Updated the Android Mintegral adapter from 17.1.71.1 to 17.1.81.0.
* Updated the Android Pangle adapter from 8.3.0.3.0 to 8.3.0.4.0.
* Updated the Android Verve adapter from 3.9.0.1 to 3.9.2.0.
* Updated the Android Vungle adapter from 7.7.7.0 to 7.7.8.0.
There are no changes to the Unity C# API in this release. The Unity SDK does not expose a Native ad API.
## \[4.7.2] - 2026-09-16
### SDK Updates
* Updated the Android and iOS Magnite adapter pins from 1.0.0.1 to 1.0.0.2.
* Updated the Android Unity Ads adapter from 4.19.0.1 to 4.20.1.0.
* Updated the iOS Unity Ads adapter from 4.19.0.0 to 4.20.1.0.
* Updated the iOS Google Waterfall adapter from 13.7.0.0 to 13.9.0.0.
* Android SDK remains at 4.7.0. iOS CloudXCore remains at 3.9.1.
There are no changes to the Unity C# API in this release. The Unity SDK does not expose a Native ad API.
## \[4.7.1] - 2026-09-15
### Fixed
* Rewarded and other fullscreen callbacks on iOS now use the placement and custom data from each accepted show. Consecutive rewarded ads no longer use missing or stale custom data.
### SDK Updates
* Updated iOS SDK from 3.9.0 to 3.9.1 — see [iOS SDK changelog](/en/ios/changelog#391---2026-09-14) for details.
* Android SDK remains at 4.7.0.
* Updated Android adapters: Moloco 4.11.0.0 to 4.12.0.0, Google Waterfall 25.2.0.5 to 25.4.0.0, and Pangle 8.2.0.4.0 to 8.3.0.3.0.
* Updated iOS adapters: Moloco 4.8.0.0 to 4.10.0.0, Google Waterfall 13.6.0.4 to 13.7.0.0, Digital Turbine 8.4.8.0 to 8.4.10.0, and Pangle 8.2.0.7.0 to 8.3.0.6.0.
## \[4.7.0] - 2026-09-11
### SDK Updates
* Updated Android SDK from 4.6.0 to 4.7.0 — see [Android SDK changelog](/en/android/changelog#470---2026-09-09) for details
* Updated iOS SDK from 3.8.0 to 3.9.0 — see [iOS SDK changelog](/en/ios/changelog#390---2026-09-10) for details
* Added the BIGO Ads adapter for Android. Uncomment `io.cloudx:adapter-bigo:6.0.1.0` in `Assets/CloudXSdk/Editor/CloudXDependencies.xml` to enable it. See [BIGO Ads](/en/unity/adapters/bigo).
* Updated the Android MobileFuse adapter from 1.11.0.1 to 1.12.0.0.
* Updated the iOS Mintegral adapter from 8.1.5.0 to 8.1.6.0.
* Updated the iOS Vungle adapter from 7.7.4.0 to 7.7.6.0.
## \[4.6.1] - 2026-09-01
### What's New
* Added `CloudXSdk.InvokeEventsOnUnityMainThread` to control which thread CloudX callbacks arrive on. Set it to `true` for the Unity main thread, `false` for the native callback thread, or leave it unset for the default. See [Callback Threading](/en/unity/integration#callback-threading).
### Fixes
* Ad callbacks are no longer lost. A handler that throws is logged with the event name and the failing handler instead of silently stopping the remaining handlers.
* Showing an interstitial, rewarded, or app open ad that is not ready now reports `OnAdShowFailed` instead of doing nothing.
* A banner or MREC refresh that fails after a successful first load now reaches `OnAdLoadFailed`.
* `ReportRevenueData` now logs why a payload was rejected, so an invalid revenue value, ad format, or platform name can be corrected.
### SDK Updates
* Updated the iOS TaurusX adapter from 1.18.1.0 to 1.18.2.0.
## \[4.6.0] - 2026-08-26
### Fixes
* iOS banners and MRECs now render as soon as they are shown, instead of staying blank until the next refresh.
### SDK Updates
* Updated Android SDK from 4.5.1 to 4.6.0 — see [Android SDK changelog](/en/android/changelog#460---2026-08-23) for details
* Updated iOS SDK from 3.7.0 to 3.8.0 — see [iOS SDK changelog](/en/ios/changelog#380---2026-08-24) for details
* Updated Android Google Waterfall, AdMob, and Magnite adapter pins.
* Updated iOS Google Waterfall, InMobi, and MobileFuse adapter pins.
## \[4.5.1] - 2026-08-18
### What's New
* Added support for Vertical Banner, so landscape apps with limited vertical space can also include banners. Use `CloudXAdViewConfiguration.AdViewVerticalPosition.Left` and `.Right` for left- and right-edge banner slots.
### Integration Notes
* Apps using iOS Meta adapter `6.22.0.0` must target iOS 15 or newer. Apps supporting iOS 13 or 14 should stay on `6.21.1.0`.
### SDK Updates
* Updated Android SDK from 4.5.0 to 4.5.1 — see [Android SDK changelog](/en/android/changelog#451---2026-08-18) for details
* Updated Android Mintegral, Google Waterfall, Digital Turbine, and AdMob adapter pins.
* Updated iOS Meta adapter from 6.21.1.0 to 6.22.0.0.
## \[4.5.0] - 2026-08-11
### What's New
* Added AdMob and Google Ad Manager bids to Trusted Arbiter with `CloudXArbiterBid.AdMob(...)` and `CloudXArbiterBid.Gam(...)`. CloudX can price them from reported revenue.
* Added `CloudXRevenuePlatform.Gam` so apps can forward Google Ad Manager paid events through `CloudXSdk.ReportRevenueData(...)`.
* Added Google Ad Manager demand support to the Google Waterfall Unity adapter; CloudX configuration selects AdMob or GAM per placement.
* Added TaurusX adapter support for Android and iOS.
* Added optional Adjust and AppsFlyer ad-revenue connectors so Unity apps can forward CloudX-won impression revenue to their MMP without Unity C# glue code.
### Integration Notes
* Android projects using Meta adapter `6.22.0.0` must compile with Android API 36 because Meta Audience Network `6.22.0` depends on `androidx.browser:1.9.0`.
### SDK Updates
* Updated Android SDK from 4.4.0 to 4.5.0 — see [Android SDK changelog](/en/android/changelog#450---2026-08-10) for details
* Updated iOS SDK from 3.6.0 to 3.7.0 — see [iOS SDK changelog](/en/ios/changelog#370---2026-08-11) for details
* Updated Google Waterfall adapter pins to Android 25.2.0.2 and iOS 13.6.0.2.
* Updated Android Meta adapter to 6.22.0.0 and optional Android AdMob adapter to 1.3.0.1.
## \[4.4.1] - 2026-08-03
### What's New
* Added custom trusted-arbiter bids with `CloudXArbiterBid.Custom` for mediation platforms without a dedicated bid type.
* Added `CloudXArbiterPlatform.Custom` and `CloudXArbiterResult.PlatformName` so custom arbiter winners can identify the originating mediator.
### Migration Notes
* `CloudXArbiterResult` now includes `PlatformName`; code that directly constructs or deconstructs this record must include the new value.
### Changed
* Regenerated CloudX SDK asset GUIDs to avoid import collisions in Unity projects; assembly definition GUIDs are unchanged.
* Updated the Unity demo arbiter verification button to run CloudX, LevelPlay, PubMatic, and custom bid scenarios.
### SDK Updates
* Updated the iOS Pangle adapter from 7.9.1.3.0 to 8.2.0.7.0.
## \[4.4.0] - 2026-07-29
### What's New
* Added App Open ads through `CloudXSdk.LoadAppOpen`, `CloudXSdk.ShowAppOpen`, `CloudXSdk.IsAppOpenReady`, `CloudXSdk.DestroyAppOpen`, and `CloudXAdsCallbacks.AppOpen`.
* Added publisher revenue reporting with `CloudXSdk.ReportRevenueData(CloudXRevenueData data)`, including AdMob, InMobi, TopOn, and custom platform names.
### Fixed
* Fixed Unity main-thread callback dispatch so native callbacks that enqueue Unity work no longer deadlock callback delivery.
### SDK Updates
* Updated Android SDK from 4.1.7 to 4.4.0 — see [Android SDK changelog](/en/android/changelog#440---2026-07-28) for details
* Updated iOS CloudXCore from 3.4.6 to 3.6.0
* Updated iOS Google Waterfall adapter from 3.4.6 to 13.6.0.1
* Updated Unity demo Android adapter pins: Vungle 7.7.7.0, InMobi 11.4.0.1, Mintegral 17.1.71.0, Moloco 4.11.0.0, Verve 3.9.0.1, Google Waterfall 25.2.0.1, Digital Turbine 8.4.7.0, and Pangle 8.2.0.4.0.
## \[4.1.7] - 2026-07-02
### What's New
* Added APIs for per-request bid floor overrides so publishers can send request-scoped floor controls through the SDK bid pipeline
### SDK Updates
* Updated Android SDK from 4.1.5 to 4.1.7 — see [Android SDK changelog](/en/android/changelog#417---2026-06-25) for details
* Updated iOS SDK from 3.4.5 to 3.4.6 — see [iOS SDK changelog](/en/ios/changelog#346---2026-06-29) for details
## \[4.1.5] - 2026-06-20
### SDK Updates
* Updated Android SDK from 4.1.3 to 4.1.5 — see [Android SDK changelog](/en/android/changelog#415---2026-06-19) for details
* Updated iOS SDK from 3.4.2 to 3.4.5 — see [iOS SDK changelog](/en/ios/changelog#345---2026-06-12) for details
* Added Pangle adapter support for Android and iOS
* Added MobileFuse adapter support for Android
## \[4.1.3] - 2026-06-01
### SDK Updates
* Updated Android SDK from 4.1.1 to 4.1.3 — see [Android SDK changelog](/en/android/changelog#413---2026-05-31) for details
* Updated iOS SDK from 3.4.1 to 3.4.2 — see [iOS SDK changelog](/en/ios/changelog#342---2026-05-29) for details
* Added Google Waterfall and Digital Turbine adapter support for Android and iOS
## \[4.1.0] - 2026-05-27
### What's New
* Added `CloudXSdk.Arbiter(bids, onCompleted)` for running a trusted-arbiter auction from Unity
* Added `SetBannerExtraParameter(adUnitId, key, value)`, `SetMRecExtraParameter(adUnitId, key, value)`, `SetInterstitialExtraParameter(adUnitId, key, value)`, and `SetRewardedExtraParameter(adUnitId, key, value)` to pass network-specific parameters to each ad format
### SDK Updates
* Updated Android SDK from 4.0.0 to 4.1.1 — see [Android SDK changelog](/en/android/changelog#411---2026-05-26) for details
* Updated iOS SDK from 3.2.0 to 3.4.1 — see [iOS SDK changelog](/en/ios/changelog#341---2026-05-27) for details
* Added Moloco and Verve adapter support for Android and iOS
## \[4.0.0] - 2026-04-28
### What's New
* Added Unity Package Manager (UPM) consumer support so the SDK can be referenced from internal UPM packages. Runtime and editor scripts now ship with assembly definitions (`CloudX.Sdk` and `CloudX.Sdk.Editor`).
* Removed the runtime dependency on `Newtonsoft.Json`. The SDK no longer requires `com.unity.nuget.newtonsoft-json` to be present in your project.
### SDK Updates
* Updated Android SDK from 3.2.0 to 4.0.0 — see [Android SDK changelog](/en/android/changelog#400---2026-04-27) for details
## \[3.2.0] - 2026-04-24
### What's New
* Added `DestroyInterstitial(adUnitId)` and `DestroyRewarded(adUnitId)` to the Unity API so fullscreen ads can be explicitly destroyed and cleaned up
### SDK Updates
* Updated Android SDK from 3.1.0 to 3.2.0 — see [Android SDK changelog](/en/android/changelog#320---2026-04-24) for details
* Updated iOS SDK from 3.1.0 to 3.2.0 — see [iOS SDK changelog](/en/ios/changelog#320---2026-04-22) for details
* Added Magnite adapter support for Android and iOS
## \[3.1.0] - 2026-04-17
### What's New
* Revenue-paid callbacks are now delivered immediately and may run off the Unity main thread on Android and iOS
### Integration Notes
* Do not touch Unity scene or UI APIs directly inside `OnAdRevenuePaid`. Dispatch back to the main thread first if needed.
### SDK Updates
* Updated Android SDK from 3.0.0 to 3.1.0 — see [Android SDK changelog](/en/android/changelog#310---2026-04-17) for details
* Updated iOS SDK from 2.2.8 to 3.1.0 — see [iOS SDK changelog](/en/ios/changelog#310---2026-04-16) for details
## \[3.0.0] - 2026-04-06
### SDK Updates
* Updated Android SDK from 2.2.7 to 3.0.0 — see [Android SDK changelog](/en/android/changelog#300---2026-04-06) for details
* Updated iOS SDK from 2.2.7 to 2.2.8 — see [iOS SDK changelog](/en/ios/changelog#228---2026-04-03) for details
## \[2.2.7] - 2026-04-02
### What's New
* Added first-party metrics telemetry coverage across Android SDK call sites through the updated Android SDK
* Fixed iOS banner display after deferred initialization and improved Vungle adapter runtime reliability through the updated iOS SDK
### Integration Notes
* The Unity integration flow and banner/MREC placement/custom-data ordering guidance introduced in `2.2.6` remain unchanged in this release
### SDK Updates
* Updated Android SDK from 2.2.6 to 2.2.7 — see [Android SDK changelog](/en/android/changelog#227---2026-04-02) for details
* Updated iOS SDK from 2.2.6 to 2.2.7 — see [iOS SDK changelog](/en/ios/changelog#227---2026-04-01) for details
## \[2.2.6] - 2026-04-01
### What's New
* Added `isAdReady` support across fullscreen adapters through the updated Android and iOS SDKs
* Added Unity Ads privacy consent forwarding through the updated iOS SDK
### Integration Notes
* For Unity banner and MREC ads, call `SetBannerPlacement(...)` / `SetBannerCustomData(...)` and `SetMrecPlacement(...)` / `SetMrecCustomData(...)` before `CreateBanner(...)` or `CreateMrec(...)` if you want those values applied to the first ad request
### SDK Updates
* Updated Android SDK from 2.2.4 to 2.2.6 — see [Android SDK changelog](/en/android/changelog#226---2026-04-01) for details
* Updated iOS SDK from 2.2.4 to 2.2.6 — see [iOS SDK changelog](/en/ios/changelog#226---2026-03-30) for details
## \[2.2.4] - 2026-03-27
### What's New
* Added `SetHasUserConsent(bool?)` and `SetDoNotSell(bool?)` for manual privacy overrides before initialization
* Added automatic `NSUserTrackingUsageDescription` injection during the iOS post-process step when the app has not defined one yet
### Fixes
* Fixed Android banner view initialization so newly created banner views start hidden until you show them
* Fixed iOS interstitial and rewarded show-failure callbacks so `OnAdShowFailed` now includes both the failed `CloudXAd` and the corresponding `CloudXError`, matching the Unity callback contract
* Updated the Unity demo app to initialize banner and MREC views once and retry fullscreen ads after both load failures and show failures
### SDK Updates
* Updated Android SDK from 2.2.3 to 2.2.4 — see [Android SDK changelog](/en/android/changelog#224---2026-03-26) for details
* Updated iOS SDK from 2.2.3 to 2.2.4 — see [iOS SDK changelog](/en/ios/changelog#224---2026-03-26) for details
## \[2.2.3] - 2026-03-23
### SDK Updates
* Updated Android SDK from 2.2.2 to 2.2.3 — see [Android SDK changelog](/en/android/changelog#223---2026-03-20) for details
* Updated iOS SDK from 2.2.2 to 2.2.3 — see [iOS SDK changelog](/en/ios/changelog#223---2026-03-19) for details
* Added Unity Ads adapter support for Android and iOS
## \[2.2.2] - 2026-03-17
### SDK Updates
* Updated Android SDK from 2.2.1 to 2.2.2 — see [Android SDK changelog](/en/android/changelog#222---2026-03-17) for details
* Updated iOS SDK from 2.0.0 to 2.2.2 — see [iOS SDK changelog](/en/ios/changelog#222---2026-03-16) for details
* Lowered iOS minimum deployment target from 15.0 to 13.0
* Added Mintegral adapter support (Android + iOS)
## \[2.2.1] - 2026-03-11
### SDK Updates
* Updated Android SDK from 2.2.0 to 2.2.1 — see [Android SDK changelog](/en/android/changelog#221---2026-03-11) for details
* Added Mintegral adapter and Maven repository for Android
## \[2.2.0] - 2026-03-10
### SDK Updates
* Updated Android SDK from 2.0.1 to 2.2.0 — see [Android SDK changelog](/en/android/changelog#220---2026-03-10) for details
## \[2.0.1] - 2026-03-04
This patch release adds full compatibility with Unity Engine 2022.3.
### Compatibility
* Added support for Unity Engine 2022.3 LTS
### SDK Updates
* Updated Android SDK from 2.0.0 to 2.0.1 — see [Android SDK changelog](/en/android/changelog#201---2026-03-04) for details
## \[2.0.0] - 2026-02-05
### Breaking Changes
* Banner and MREC ads no longer auto-load after creation. You must now call `LoadBanner()` or `LoadMrec()` explicitly after `CreateBanner()` or `CreateMrec()`
* `CloudXAd` property renames:
* `PlacementName` is now `AdUnitId`
* `BidderName` is now `NetworkName`
### What's New
* Added placement and custom data support for all ad formats:
* `SetBannerPlacement(adUnitId, placement)` and `SetBannerCustomData(adUnitId, customData)`
* `SetMRecPlacement(adUnitId, placement)` and `SetMRecCustomData(adUnitId, customData)`
* `ShowInterstitial(adUnitId, placement?, customData?)` now accepts optional placement and customData parameters
* `ShowRewarded(adUnitId, placement?, customData?)` now accepts optional placement and customData parameters
* New `CloudXAd` properties: `AdFormat`, `Placement`, `NetworkPlacement`
### SDK Updates
* Updated Android SDK from 0.12.1 to 2.0.0
* Updated Android adapters from 0.12.1 to 2.0.0
* Updated iOS CloudXCore from 1.3.0 to 2.0.0
* Updated iOS CloudXRenderer from 1.3.0 to 2.0.0
* Updated iOS adapters from 1.3.0 to 2.0.0
## \[0.12.1] - 2026-01-15
### What's New
* Added support for iOS platform
### Performance Improvements
* Android: BidToken is now generated on a background thread for improved performance
### SDK Updates
* Updated Android SDK from 0.12.0 to 0.12.1
### Improvements
* Renamed internal `UnityMainThreadDispatcher` class to avoid namespace collisions with third-party packages
## \[0.12.0] - 2025-12-15
Initial release of CloudX Unity SDK
---
# BIGO Ads
> BIGO Ads integration for CloudX Unity SDK. Supports Banner, MREC, Interstitial, and Rewarded.
For dashboard configuration, see the [BIGO Ads bidder guide](/en/networks/bigo-ads).
Uncomment the BIGO Ads lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
Requires CloudX Android SDK 4.7.0 or newer and CloudX iOS SDK 3.9.0 or newer, which the CloudX Unity SDK 4.7.0 package already pins. The package lists the iOS line from Unity SDK 4.8.2. With an earlier package, add it to the `` section yourself.
The Android BIGO Ads SDK resolves from Maven Central, so no additional repository is required.
After modifying the file, go to **Assets > External Dependency Manager > Android Resolver > Force Resolve**.
## iOS Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
Include the SKAdNetwork IDs provided by BIGO Ads for your integration. The list is long and changes over time, so copy the current `SKAdNetworkItems` block from the [BIGO Ads iOS integration guide](https://www.bigossp.com/guide/sdk/ios/integration) on the BIGO Ads platform (sign-in required).
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## iOS Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## iOS Banner Refresh
Leave auto-refresh off for your BIGO Ads slots on the BIGO Ads platform. CloudX owns the refresh cycle, and a second refresh loop inside the BIGO Ads SDK renders creatives CloudX never auctioned.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Digital Turbine
> Digital Turbine (Fyber Marketplace) integration for CloudX Unity SDK. Supports Banner, MREC, Interstitial, Rewarded, and Native. Android also supports Native Banner and Native MREC.
For dashboard configuration, see the [Digital Turbine publisher setup guide](/en/networks/digitalturbine).
Uncomment the Digital Turbine lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
> **Warning:** The iOS adapter 8.4.10.1 requires iOS 15.0+ and Xcode 26.1+. Set the iOS target minimum version to 15.0 or later in Unity Player Settings.
> **Note:** Native support reflects the underlying Android and iOS adapters. There is no dedicated Unity Native ad format guide.
After modifying the file, go to **Assets > External Dependency Manager > Android Resolver > Force Resolve**.
## iOS Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
Include the SKAdNetwork IDs provided by Digital Turbine for your integration. Refer to the current `Fyber_Marketplace_SDK` documentation for the latest `SKAdNetworkItems` list.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## iOS Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# AdMob/GAM estimated
> AdMob/GAM estimated adapter for CloudX Unity SDK. Serves AdMob waterfall and Google Ad Manager demand for Banner, MREC, Interstitial, Rewarded, Native, and App Open.
> **Warning:** AdMob/GAM estimated is in controlled beta, and Google has not reviewed or endorsed it. See the [publisher setup guide](/en/networks/google-estimated) for details and access.
The Google adapter serves both **AdMob estimated** and **Google Ad Manager estimated** demand — AdMob waterfall and Google Ad Manager, respectively. It supports **Banner (320×50)**, **MREC (300×250)**, **Interstitial**, **Rewarded**, **Native**, and **App Open** ad formats for both demand sources.
> **Info:** The GMA Next-Gen SDK is Android-only for now, and it is not yet wired into the Unity plugin — Unity uses the legacy Google Mobile Ads SDK on both platforms. Google has not shipped an iOS next-gen SDK.
For dashboard configuration, see the [AdMob/GAM estimated publisher setup guide](/en/networks/google-estimated).
> **Note:** AdMob estimated was previously called Google Waterfall. You will still see `googleWaterfall` in adapter artifact names.
Pricing and the Google-side eCPM floor are dashboard and console decisions, not app-side ones. See the "Pricing: automatic or manual" and "Set an eCPM floor: Google Optimized or Manual" sections of the [AdMob/GAM estimated publisher setup guide](/en/networks/google-estimated).
## Which Google demand you get
The adapter serves two independent Google demand sources:
* **AdMob estimated** — AdMob waterfall demand.
* **Google Ad Manager estimated** — Google Ad Manager demand.
Which one runs is a dashboard decision, not a code one. You enable the ad source per placement in the CloudX dashboard; there is no separate artifact, no extra dependency, and no app-side change. Both sources may run in the same app, each with its own mapping and CPM target, and both may bid in the same auction.
Google Ad Manager demand requires Unity SDK 4.5.1 or later.
Ad Manager ad unit ids take the form `/NNNNNNN/placement/name`, unlike AdMob's plain ad unit id.
Uncomment the Google adapter lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
On Android, adapter 25.5.0.0 requires **Minimum API Level 24 (Android 7.0)** in Unity Player Settings. Keep adapter 25.4.0.0 if your app must support API 23.
> **Note:** Native support reflects the underlying Android and iOS adapters. There is no dedicated Unity Native ad format guide.
After modifying the file, go to **Assets > External Dependency Manager > Android Resolver > Force Resolve**.
## AdMob Application ID (Required)
The Google Mobile Ads SDK requires your AdMob app ID. Without it the adapter fails initialization on both platforms and never requests a Google ad. Use the app ID from your AdMob console. Android apps that only use Google Ad Manager may declare the boolean `com.google.android.gms.ads.AD_MANAGER_APP` meta-data instead of an app ID.
### Android
Add the meta-data to your `Assets/Plugins/Android/AndroidManifest.xml`, inside ``:
```xml AndroidManifest.xml
```
### iOS
Add `GADApplicationIdentifier` to the generated `Info.plist`:
```xml Info.plist
GADApplicationIdentifierca-app-pub-XXXXXXXXXXXXXXXX~YYYYYYYYYY
```
## iOS Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14.5+)
Add Google's SKAdNetwork identifiers so Google can attribute installs. Google publishes the complete, up-to-date list in its [Google Mobile Ads documentation](https://developers.google.com/admob/ios/quick-start). Include the full list to maximize attribution coverage.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## iOS Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# InMobi
> InMobi integration for CloudX Unity SDK. Supports Banner, MREC, Interstitial, Rewarded, and Native. Android also supports Native Banner and Native MREC.
For CloudX dashboard mapping and InMobi-side ID setup, see the [InMobi bidder guide](/en/networks/inmobi).
Uncomment the InMobi lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
> **Note:** Native support reflects the underlying Android and iOS adapters. There is no dedicated Unity Native ad format guide.
After modifying the file, go to **Assets > External Dependency Manager > Android Resolver > Force Resolve**.
## iOS Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
### Click to expand InMobi SKAdNetwork IDs
```xml Info.plist
SKAdNetworkItemsSKAdNetworkIdentifier4pfyvq9l8r.skadnetworkSKAdNetworkIdentifieryclnxrl5pm.skadnetworkSKAdNetworkIdentifierv72qych5uu.skadnetworkSKAdNetworkIdentifiertl55sbb4fm.skadnetworkSKAdNetworkIdentifiert38b2kh725.skadnetworkSKAdNetworkIdentifierprcb7njmu6.skadnetworkSKAdNetworkIdentifierppxm28t8ap.skadnetworkSKAdNetworkIdentifiermlmmfzh3r3.skadnetworkSKAdNetworkIdentifierklf5c3l5u5.skadnetworkSKAdNetworkIdentifierhs6bdukanm.skadnetworkSKAdNetworkIdentifierc6k4g5qg8m.skadnetworkSKAdNetworkIdentifier9t245vhmpl.skadnetworkSKAdNetworkIdentifier9rd848q2bz.skadnetworkSKAdNetworkIdentifier7ug5zh24hu.skadnetworkSKAdNetworkIdentifier737z793b9f.skadnetworkSKAdNetworkIdentifier6xzpu9s2p8.skadnetworkSKAdNetworkIdentifier5lm9lj6jb7.skadnetworkSKAdNetworkIdentifier4fzdc2evr5.skadnetworkSKAdNetworkIdentifier4468km3ulz.skadnetworkSKAdNetworkIdentifier3rd42ekr43.skadnetworkSKAdNetworkIdentifier2u9pt9hc89.skadnetwork
```
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## iOS Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
**Bitcode:** InMobi SDK does not support Bitcode. Set Enable Bitcode to `NO`.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Magnite
> Magnite integration for CloudX Unity SDK. Supports Banner, MREC, Interstitial, and Rewarded.
For CloudX dashboard mapping and Magnite-side ID setup, see the [Magnite bidder guide](/en/networks/magnite).
Uncomment the Magnite lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
> **Warning:** Unity SDK 4.10.0 ships this line with version `1.0.4.1`, which can crash on iOS devices when consent is set before initialization. Change it to `1.0.4.2` when you uncomment it.
> **Note:** If your Unity Android build compiles Kotlin (for example, through a custom Gradle template or another plugin), it must use Kotlin 2.3 or later with the Magnite adapter, because Magnite SDK 1.0.1 and later depend on kotlin-stdlib 2.4.0.
> **Note:** The underlying iOS and Android Magnite adapters support Native. The current Unity SDK does not expose a Native ad API.
After modifying the file, go to **Assets > External Dependency Manager > Android Resolver > Force Resolve**.
## iOS Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
Include the SKAdNetwork IDs provided by Magnite for your integration. Refer to the current Magnite SDK documentation for the latest `SKAdNetworkItems` list.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## iOS Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Meta Audience Network
> Meta Audience Network integration for CloudX Unity SDK. Supports Banner, MREC, Interstitial, Rewarded, Native, Native Banner, and Native MREC.
For CloudX dashboard mapping and Meta-side ID setup, see the [Meta Audience Network bidder guide](/en/networks/meta-audience-network).
Uncomment the Meta lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
> **Warning:** Android adapter `6.22.0.1` brings in Meta Audience Network `6.22.0`, which depends on `androidx.browser:1.9.0` and requires your app to compile against Android API 36. Set `compileSdk = 36`; `targetSdk` does not need to change.
> **Warning:** iOS adapter `6.22.0.1` brings in Meta Audience Network `6.22.0`, which requires iOS 15.0. Apps that still support iOS 13 or 14 should keep the iOS line pinned to `6.21.1.0`.
> **Note:** Native support reflects the underlying Android and iOS adapters. There is no dedicated Unity Native ad format guide.
After modifying the file, go to **Assets > External Dependency Manager > Android Resolver > Force Resolve**.
## iOS Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14.5+)
Both Meta SKAdNetwork IDs are required for Meta to make bids:
```xml Info.plist
SKAdNetworkItemsSKAdNetworkIdentifierv9wttpbfk9.skadnetworkSKAdNetworkIdentifiern38lu8286q.skadnetwork
```
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## iOS Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
**Bitcode:** Meta SDK does not support Bitcode. Set Enable Bitcode to `NO`.
## Meta SDK 6.21.0 SceneDelegate Crash
> **Warning:** Meta Audience Network SDK 6.21.0 has an internal bug that causes crashes when used in apps with `SceneDelegate` (iOS 13+ scene-based lifecycle).
See the [iOS Meta adapter](/en/ios/adapters/meta/overview#meta-sdk-6210-crashes-in-apps-using-scenedelegate) for the full workaround.
## Android Configuration
The Meta adapter supports CloudX native ads, including native video creatives. The adapter brings in Meta Audience Network transitively; you do not need to declare `com.facebook.android:audience-network-sdk` separately unless your app uses Meta APIs directly.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Mintegral
> Mintegral integration for CloudX Unity SDK. Supports Banner, MREC, Interstitial, Rewarded, and App Open. iOS also supports Native.
For CloudX dashboard mapping and Mintegral-side ID setup, see the [Mintegral bidder guide](/en/networks/mintegral).
Uncomment the Mintegral lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
> **Note:** Native support reflects the underlying iOS adapter. There is no dedicated Unity Native ad format guide.
The Mintegral Maven repository is already declared in `CloudXDependencies.xml` and resolved automatically by the External Dependency Manager:
```
https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea
```
After modifying the file, go to **Assets > External Dependency Manager > Android Resolver > Force Resolve**.
## iOS Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14.5+)
Mintegral's primary SKAdNetwork ID is required for attribution:
```xml Info.plist
SKAdNetworkItemsSKAdNetworkIdentifierkbd757ywx3.skadnetwork
```
> **Note:** Mintegral publishes a full list of partner SKAdNetwork IDs at [dev.mintegral.com/skadnetworkids.json](https://dev.mintegral.com/skadnetworkids.json). Adding these additional IDs improves attribution accuracy across Mintegral's demand partners.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## iOS Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
**Deployment Target:** Ensure your project's minimum deployment target is iOS 13.0 or higher.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# MobileFuse
> MobileFuse integration for CloudX Unity SDK. Supports Banner, MREC, Interstitial, and Rewarded. Android also supports Native, Native Banner, and Native MREC.
For dashboard configuration, see the [MobileFuse publisher setup guide](/en/networks/mobilefuse).
Uncomment the MobileFuse lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
> **Note:** Native support reflects the underlying Android adapter. There is no dedicated Unity Native ad format guide.
The MobileFuse SDK resolves from Maven Central, so no additional repository is required.
After modifying the file, go to **Assets > External Dependency Manager > Android Resolver > Force Resolve**.
## iOS Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
Include the SKAdNetwork IDs published by MobileFuse for your integration. Refer to the current MobileFuse SDK documentation for the latest `SKAdNetworkItems` list.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## iOS Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Moloco
> Moloco integration for CloudX Unity SDK. Supports Banner, MREC, Interstitial, Rewarded, and Native. Android also supports leader/tablet Banner, Native Banner, and Native MREC.
Contact your CloudX account manager to confirm availability and complete publisher onboarding.
Uncomment the Moloco lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
> **Note:** Native support reflects the underlying Android and iOS adapters. There is no dedicated Unity Native ad format guide.
After modifying the file, go to **Assets > External Dependency Manager > Android Resolver > Force Resolve**.
## iOS Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
Include the SKAdNetwork IDs provided by Moloco for your integration. Refer to the current Moloco SDK documentation for the latest `SKAdNetworkItems` list.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## iOS Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Pangle
> Pangle (ByteDance) integration for CloudX Unity SDK. Supports Banner, MREC, Interstitial, Rewarded, and App Open. iOS also supports Native.
Contact your CloudX account manager to confirm availability and complete publisher onboarding.
Uncomment the Pangle lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
> **Note:** Native support reflects the underlying iOS adapter. There is no dedicated Unity Native ad format guide.
The Pangle Maven repository is already declared in `CloudXDependencies.xml` and resolved automatically by the External Dependency Manager:
```
https://artifact.bytedance.com/repository/pangle
```
After modifying the file, go to **Assets > External Dependency Manager > Android Resolver > Force Resolve**.
## iOS Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
Include the SKAdNetwork IDs provided by Pangle for your integration. Refer to the current Pangle (Ads-Global) SDK documentation for the latest `SKAdNetworkItems` list.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## iOS Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# TaurusX
> TaurusX integration for CloudX Unity SDK. Supports Banner, MREC, Interstitial, Rewarded, and Native.
Contact your CloudX account manager to confirm availability and complete publisher onboarding.
Uncomment the TaurusX lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
> **Note:** Native support reflects the underlying Android and iOS adapters. There is no dedicated Unity Native ad format guide.
The TaurusX Maven repository is required for Android dependency resolution:
```
https://artifact.taurusx.com/artifactory/taurusx-sdk/
```
After modifying the file, go to **Assets > External Dependency Manager > Android Resolver > Force Resolve**.
## iOS Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
Include the SKAdNetwork IDs published by TaurusX for your integration. Refer to the current TaurusX SDK documentation for the latest `SKAdNetworkItems` list.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## iOS Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Unity Ads
> Unity Ads integration for CloudX Unity SDK. Supports Banner, MREC, Interstitial, and Rewarded.
For CloudX dashboard mapping and Unity Ads-side ID setup, see the [Unity Ads bidder guide](/en/networks/unity-ads).
Uncomment the Unity Ads lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
After modifying the file, go to **Assets > External Dependency Manager > Android Resolver > Force Resolve**.
## iOS Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14.5+)
Unity Ads' primary SKAdNetwork IDs are required for attribution:
```xml Info.plist
SKAdNetworkItemsSKAdNetworkIdentifier4dzt52r2t5.skadnetworkSKAdNetworkIdentifierbvpn9ufa9b.skadnetwork
```
> **Note:** Unity publishes a full list of SKAdNetwork IDs in their documentation. Adding these additional IDs improves attribution accuracy across Unity's demand partners.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## iOS Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
**Deployment Target:** Ensure your project's minimum deployment target is iOS 13.0 or higher.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Verve
> Verve (HyBid) integration for CloudX Unity SDK. Supports Banner, MREC, Interstitial, Rewarded, and Native. Android also supports Native Banner and Native MREC.
Contact your CloudX account manager to confirm availability and complete publisher onboarding.
Uncomment the Verve lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
> **Note:** Native support reflects the underlying Android and iOS adapters. There is no dedicated Unity Native ad format guide.
The Verve Maven repository is already declared in `CloudXDependencies.xml` and resolved automatically by the External Dependency Manager:
```
https://verve.jfrog.io/artifactory/verve-gradle-release
```
After modifying the file, go to **Assets > External Dependency Manager > Android Resolver > Force Resolve**.
## iOS Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
Include the SKAdNetwork IDs provided by Verve for your integration. Refer to the current HyBid SDK documentation for the latest `SKAdNetworkItems` list.
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## iOS Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Vungle
> Vungle (Liftoff) integration for CloudX Unity SDK. Supports Banner, MREC, Interstitial, Rewarded, App Open, and Native. Android also supports Native Banner and Native MREC.
For CloudX dashboard mapping and Vungle-side ID setup, see the [Vungle bidder guide](/en/networks/vungle).
Uncomment the Vungle lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
> **Note:** Native support reflects the underlying Android and iOS adapters. There is no dedicated Unity Native ad format guide.
After modifying the file, go to **Assets > External Dependency Manager > Android Resolver > Force Resolve**.
## iOS Info.plist Configuration
### SKAdNetwork IDs (Required for iOS 14+)
### Click to expand Vungle SKAdNetwork IDs
```xml Info.plist
SKAdNetworkItemsSKAdNetworkIdentifiercstr6suwn9.skadnetworkSKAdNetworkIdentifier4fzdc2evr5.skadnetworkSKAdNetworkIdentifierv72qych5uu.skadnetworkSKAdNetworkIdentifierludvb6z3bs.skadnetworkSKAdNetworkIdentifierwg4vff78zm.skadnetworkSKAdNetworkIdentifier737z793b9f.skadnetworkSKAdNetworkIdentifieryclnxrl5pm.skadnetworkSKAdNetworkIdentifiert38b2kh725.skadnetworkSKAdNetworkIdentifier7ug5zh24hu.skadnetworkSKAdNetworkIdentifier9rd848q2bz.skadnetworkSKAdNetworkIdentifiern6fk4nfna4.skadnetworkSKAdNetworkIdentifierkbd757ywx3.skadnetworkSKAdNetworkIdentifier9t245vhmpl.skadnetworkSKAdNetworkIdentifier4468km3ulz.skadnetworkSKAdNetworkIdentifier2u9pt9hc89.skadnetworkSKAdNetworkIdentifierm8dbw4sv7c.skadnetworkSKAdNetworkIdentifierc6k4g5qg8m.skadnetworkSKAdNetworkIdentifiermlmmfzh3r3.skadnetworkSKAdNetworkIdentifier578prtvx9j.skadnetworkSKAdNetworkIdentifier4dzt52r2t5.skadnetworkSKAdNetworkIdentifiere5fvkxwrpn.skadnetworkSKAdNetworkIdentifier8s468mfl3y.skadnetworkSKAdNetworkIdentifierav6w8kgt66.skadnetworkSKAdNetworkIdentifierklf5c3l5u5.skadnetworkSKAdNetworkIdentifierppxm28t8ap.skadnetworkSKAdNetworkIdentifier424m5254lk.skadnetworkSKAdNetworkIdentifieruw77j35x4d.skadnetworkSKAdNetworkIdentifier4pfyvq9l8r.skadnetworkSKAdNetworkIdentifierzmvfpc5aq8.skadnetwork
```
### App Tracking Transparency (iOS 14+)
```xml Info.plist
NSUserTrackingUsageDescriptionThis identifier will be used to deliver personalized ads to you.
```
## iOS Project Configuration
**Linker Flags:** Add `-ObjC` to Other Linker Flags in Build Settings.
**Bitcode:** Vungle SDK does not support Bitcode. Set Enable Bitcode to `NO`.
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Adjust
> Adjust ad-revenue connector for CloudX Unity SDK. Forwards CloudX ad revenue to Adjust with no publisher glue code.
The Adjust connector forwards CloudX ad revenue to your mobile measurement partner (MMP) so revenue appears alongside your other Adjust data and feeds ROAS measurement. Add the connector dependencies and revenue reporting is automatic; no Unity C# call is required.
The connector does not initialize Adjust. Your app must already integrate and initialize the Adjust SDK with your own app token.
## Install
Uncomment the Adjust connector lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
Then run External Dependency Manager for Unity (EDM4U) resolution for Android and iOS.
## Requirements
* CloudX Unity SDK `4.5.0` or newer.
* Your app already integrates and initializes Adjust.
* Android apps include `com.adjust.sdk:adjust-android` `5.0.0` or newer.
* iOS apps include the `Adjust` pod `5.0.0` or newer.
## What gets reported
The connector reports once per CloudX-won impression across CloudX ad formats. Each event includes the winning ad network, the CloudX revenue estimate in USD, and CloudX ad unit, format, and placement values.
> **Note:** Revenue is reported on Adjust's generic `publisher_sdk` source — Adjust has confirmed this is the supported path until a dedicated CloudX source ships in a future Adjust release. Revenue lands in Adjust and feeds ROAS identically; it just appears under the generic publisher source rather than a named CloudX one. When the named source ships, historical revenue stays under `publisher_sdk`, so a report spanning the cutover must union both sources.
> **Warning:** If your app already reports CloudX ad revenue to Adjust from `OnAdRevenuePaid`, remove that code when you add this connector. Otherwise each CloudX impression is reported to Adjust twice.
## Finding CloudX revenue in the Adjust dashboard
Adjust doesn't have a dedicated CloudX ad revenue source yet, so there's nothing CloudX-specific to configure in the Adjust dashboard. Once the connector is in your build, CloudX ad revenue arrives on its own.
To see it, open Adjust's ad revenue metrics with the Ad Revenue Sources filter left empty, which shows all sources. For now, CloudX revenue is included in those totals under a generic source rather than its own CloudX label.
Impressions won in the CloudX auction by AppLovin MAX or the Google waterfall aren't forwarded, because those SDKs already report that revenue to Adjust.
> **Warning:** Adjust's partner page for CloudX (**Partners → CloudX**) is the demand side: it is for attributing installs from user-acquisition campaigns you buy on CloudX. The tracking link there, and the related "Dynamic ad network data access" setting in your app's Data management tab, have no effect on ad revenue reporting. Setting them up will not make CloudX ad revenue appear, and you do not need either unless you run UA on CloudX.
If Adjust shows no ad revenue at all, from CloudX or any other source, check these before investigating the connector:
* The connector lines in `CloudXDependencies.xml` are uncommented and EDM4U resolution has run, so the connector is in the build you are measuring.
* Ad revenue reporting is enabled on your Adjust account. Your Adjust account manager can confirm this.
* If the build you're testing initializes Adjust in sandbox mode, switch Datascape to sandbox data to see it.
---
# AppsFlyer
> AppsFlyer ad-revenue connector for CloudX Unity SDK. Forwards CloudX ad revenue to AppsFlyer with no publisher glue code.
The AppsFlyer connector forwards CloudX ad revenue to your mobile measurement partner (MMP) so revenue appears alongside your other AppsFlyer data. Add the connector dependencies and revenue reporting is automatic; no Unity C# call is required.
The connector does not initialize AppsFlyer. Your app must already integrate and initialize the AppsFlyer SDK with your own dev key.
## Install
Uncomment the AppsFlyer connector lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml`:
```xml
```
Then run External Dependency Manager for Unity (EDM4U) resolution for Android and iOS.
## Requirements
* CloudX Unity SDK `4.5.0` or newer.
* Your app already integrates and initializes AppsFlyer.
* Android apps include `com.appsflyer:af-android-sdk` `6.15.0` up to, but not including, `7.0.0`.
* iOS apps include `AppsFlyerFramework` `6.15.0` or newer.
## What gets reported
The connector reports once per CloudX-won impression across CloudX ad formats. Each event includes the winning ad network, the CloudX revenue estimate in USD, and CloudX ad unit, format, and placement values.
> **Warning:** If your app already reports CloudX ad revenue to AppsFlyer from `OnAdRevenuePaid`, remove that code when you add this connector. Otherwise each CloudX impression is reported to AppsFlyer twice.
---
# Singular
> Singular ad-revenue connector for CloudX Unity SDK. Forwards CloudX ad revenue to Singular with no publisher glue code.
The Singular connector forwards CloudX ad revenue to your mobile measurement partner (MMP) so revenue appears alongside your other Singular data and feeds ROAS measurement. Add the connector dependencies and revenue reporting is automatic; no Unity C# call is required.
The connector does not initialize Singular. Your app must already integrate and initialize the Singular SDK with your own SDK key.
## Install
Uncomment the Singular connector lines in `Assets/CloudXSdk/Editor/CloudXDependencies.xml` — on packages that ship them, see below if yours does not:
```xml
```
Then run External Dependency Manager for Unity (EDM4U) resolution for Android and iOS.
On CloudX Unity SDK `4.5.1` or earlier, `CloudXDependencies.xml` does not include these entries yet — add them yourself, then run EDM4U resolution:
```xml
https://maven.singular.net
```
Singular publishes its Android SDK to its own Maven repository rather than Maven Central. From the next Unity SDK release onward `CloudXDependencies.xml` declares that repository for you, so uncommenting the connector line is all that is needed.
## Requirements
* CloudX Unity SDK `4.5.0` or newer.
* Your app already integrates and initializes Singular.
* Android apps include `com.singular.sdk:singular_sdk` `12.0.0` or newer.
* iOS apps include the `Singular-SDK` pod `12.6.0` or newer.
## What gets reported
The connector reports once per CloudX-won impression across CloudX ad formats. Each event is recorded under the ad platform `CloudX` and includes the winning ad network, the CloudX revenue estimate in USD, and the CloudX ad unit id, ad type, placement name, and network placement.
> **Warning:** If your app already reports CloudX ad revenue to Singular from `OnAdRevenuePaid`, remove that code when you add this connector. Otherwise each CloudX impression is reported to Singular twice.
---
# Overview
> Overview of CloudX React Native SDK setup and core features
[](https://www.npmjs.com/package/cloudx-react-native)
The CloudX React Native SDK enables monetization of your React Native apps with banner, MREC, interstitial, rewarded, App Open, and native ads on iOS and Android. It supports both the New Architecture (Fabric) and the legacy architecture (Paper).
> **Info:** Prefer AI-assisted setup? Point your coding agent at the CloudX agent package — see [AI-assisted integration](/en/ai-integration).
## Manual Installation
Requires React Native 0.70+, React 18.0+, iOS 13.0+, and Android API 23+.
```bash
npm install cloudx-react-native
```
### iOS Setup
Add ad network adapter pods to your `ios/Podfile`:
> **Warning:** Including Digital Turbine adapter 8.4.10.1 requires an app deployment target of iOS 15.0+ and Xcode 26.1+. These higher requirements apply only when you include this adapter.
```ruby
platform :ios, '15.0'
target 'YourApp' do
# ... existing config ...
# CloudX ad network adapters (add as needed)
pod 'CloudXBigoAdapter', '~> 6.1.0.0' # BigoADS 6.1.0
pod 'CloudXMetaAdapter', '~> 6.22.0.1' # FBAudienceNetwork 6.22.0
pod 'CloudXVungleAdapter', '~> 7.7.6.1' # VungleAds 7.7.6
pod 'CloudXInMobiAdapter', '~> 11.5.0.0' # InMobiSDK 11.5.0
pod 'CloudXMintegralAdapter', '~> 8.1.7.0' # MintegralAdSDK 8.1.7
pod 'CloudXUnityAdsAdapter', '~> 4.20.1.1' # UnityAds 4.20.1
pod 'CloudXMagniteAdapterV2', '~> 1.0.4.2' # MagniteSDK 1.0.4
pod 'CloudXMobileFuseAdapter', '~> 1.12.0.0' # MobileFuseSDK 1.12.0
pod 'CloudXMolocoAdapter', '~> 4.11.1.1' # MolocoSDKiOS 4.11.1
pod 'CloudXVerveAdapter', '~> 3.9.0.1' # HyBid 3.9.0
pod 'CloudXDigitalTurbineAdapter', '~> 8.4.10.1' # Fyber Marketplace SDK 8.4.10
pod 'CloudXGoogleWaterfallAdapter', '~> 13.9.0.0' # Google Mobile Ads SDK 13.9.0
pod 'CloudXPangleAdapter', '~> 8.3.0.6.1' # Ads-Global (Pangle / ByteDance) 8.3.0.6
pod 'CloudXTaurusXAdapter', '~> 1.18.2.1' # TaurusX SDK 1.18.2
end
```
Then install pods:
```bash
cd ios && pod install
```
> **Note:** The `CloudXCore` pod is automatically included as a dependency of the `cloudx-react-native` npm package. You only need to add the adapter pods you want.
> **Note:** Do not add `CloudXRenderer` to your Podfile. Its functionality is built into `CloudXCore` on the current iOS SDK line.
#### App Transport Security
The CloudX SDK does not require disabling App Transport Security. If a
mediated network serves assets over plain HTTP from a specific host, scope an
exception to that domain in your `Info.plist` instead of allowing arbitrary
loads app-wide (a blanket `NSAllowsArbitraryLoads` weakens your app's
transport security and can draw App Store review scrutiny):
```xml
NSAppTransportSecurityNSExceptionDomainsexample-ad-network.comNSExceptionAllowsInsecureHTTPLoadsNSIncludesSubdomains
```
### Android Setup
Add CloudX SDK and adapter dependencies to your `android/app/build.gradle`:
```groovy
dependencies {
// CloudX Android SDK
implementation "io.cloudx:sdk:4.10.0"
// Adapters for ad networks (add as needed)
implementation "io.cloudx:adapter-bigo:6.1.0.0" // BIGO Ads SDK 6.1.0
implementation "io.cloudx:adapter-digitalturbine:8.4.7.2" // Digital Turbine Marketplace SDK 8.4.7
implementation "io.cloudx:adapter-googlewaterfall:25.5.0.0" // Google Mobile Ads SDK 25.5.0
implementation "io.cloudx:adapter-inmobi:11.5.0.1" // InMobi SDK 11.5.0
implementation "io.cloudx:adapter-magnite:1.0.2.1" // Magnite Ads SDK 1.0.2
implementation "io.cloudx:adapter-meta:6.22.0.1" // Meta Audience Network 6.22.0
implementation "io.cloudx:adapter-mintegral:17.1.81.1" // Mintegral SDK 17.1.81
implementation "io.cloudx:adapter-mobilefuse:1.12.0.1" // MobileFuse SDK 1.12.0
implementation "io.cloudx:adapter-moloco:4.13.0.1" // Moloco SDK 4.13.0
implementation "io.cloudx:adapter-pangle:8.3.0.4.2" // Pangle SDK 8.3.0.4
implementation "io.cloudx:adapter-taurusx:1.19.1.1" // TaurusX SDK 1.19.1
implementation "io.cloudx:adapter-unityads:4.21.0.2" // Unity Ads SDK 4.21.0
implementation "io.cloudx:adapter-verve:3.9.3.0" // Verve HyBid SDK 3.9.3
implementation "io.cloudx:adapter-vungle:7.7.9.1" // Vungle SDK 7.7.9
}
```
> **Note:** At least one adapter is required for the SDK to serve ads.
> **Note:** Use `io.cloudx:adapter-googlewaterfall:25.5.0.0` when your app uses the Google Mobile Ads SDK and needs Google Ad Manager demand. If your Android app uses Google's GMA Next-Gen SDK for AdMob demand, use `io.cloudx:adapter-admob:1.5.0.0` instead. Do not include both Google adapter artifacts in one app.
> **Note:** Android apps that include `io.cloudx:adapter-meta:6.22.0.1` must compile with Android API 36 because Meta Audience Network 6.22.0 depends on `androidx.browser:1.9.0`. Set `compileSdk = 36`; `targetSdk` does not need to change.
> **Note:** Apps that include the Magnite adapter must compile with Kotlin 2.3 or later, because Magnite SDK 1.0.1 and later depend on kotlin-stdlib 2.4.0.
If you integrate the Mintegral adapter, also declare its Maven repository in `android/build.gradle`:
```groovy
allprojects {
repositories {
maven { url "https://dl-maven-android.mintegral.com/repository/mbridge_android_sdk_oversea" }
}
}
```
### Optional 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 React Native JavaScript glue code. Add the connector's native dependency to your `ios/Podfile` and `android/app/build.gradle`, as shown on its page.
* [Adjust connector](/en/react-native/connectors/adjust)
* [AppsFlyer connector](/en/react-native/connectors/appsflyer)
* [Singular connector](/en/react-native/connectors/singular)
Use either a connector or your own `onAdRevenuePaid` forwarding code, not both.
## Initialization
Initialize the SDK before loading any ads:
```typescript
import { CloudX, CloudXLogLevel } from 'cloudx-react-native';
// Optional: enable verbose logging (development only)
CloudX.setMinLogLevel(CloudXLogLevel.VERBOSE);
// Initialize with your app key
const result = await CloudX.initialize('YOUR_APP_KEY');
if (result.success) {
console.log('CloudX initialized');
} else {
console.error('CloudX init failed:', result.message);
}
```
`CloudX.initialize()` returns a `CloudXInitializationResult` with:
* `success: boolean` — whether initialization succeeded
* `message?: string` — additional details, populated on failure
Other initialization utilities:
```typescript
const initialized = await CloudX.isInitialized();
const version = await CloudX.getVersion();
const tablet = await CloudX.isTablet();
```
## Ad Formats
CloudX React Native supports banner, MREC, interstitial, rewarded, App Open, and native ad integrations. Use the format-specific guides for implementation details:
* [Banner & MREC ads](/en/react-native/ad-formats/banner-mrec)
Create programmatic overlay display placements with optional refresh control.
* [Interstitial ads](/en/react-native/ad-formats/interstitial)
Load and show full-screen interstitial placements.
* [Rewarded ads](/en/react-native/ad-formats/rewarded)
Reward users after completed rewarded ad views.
* [App Open ads](/en/react-native/ad-formats/app-open)
Show full-screen placements during app launch and foreground moments.
* [Native ads](/en/react-native/ad-formats/native)
Render ads inside your content from a bundled template or your own layout.
### Ad Information (CloudXAdInfo)
Most ad event callbacks receive a `CloudXAdInfo` object:
| Property | Type | Description |
| ------------------ | ------------------------ | -------------------------------------------------------------------------------- |
| `adUnitId` | `string` | The ad unit ID |
| `adFormat` | `string` | Ad format: `BANNER`, `MREC`, `INTERSTITIAL`, `REWARDED`, `APP_OPEN`, or `NATIVE` |
| `networkName` | `string` | Name of the winning ad network |
| `networkPlacement` | `string \| null` | Network-specific placement ID |
| `creativeId` | `string \| null` | Creative identifier for creative-level issue reporting |
| `placement` | `string \| null` | Custom placement set by your integration |
| `revenue` | `number` | Revenue value in USD |
| `adValues` | `Record` | SDK-provided ad metadata, used by features such as Trusted Arbiter |
`creativeId` is `null` when the demand source does not report a creative id. It is distinct from the `creativeId` you pass in [`reportRevenueData`](#publisher-reported-revenue-data), which describes an impression won outside the CloudX ad flow.
### Error Handling
All SDK errors are returned as error objects in callbacks and rejected promises:
| Property | Type | Description |
| --------- | ----------------- | -------------------------- |
| `code` | `CloudXErrorCode` | Error category |
| `message` | `string \| null` | Human-readable description |
#### Error Code Categories
| Range | Category | Common Codes |
| ------- | -------------- | ------------------------------------------------------- |
| 0 | General | `internalError` |
| 100-199 | Network | `networkError`, `networkTimeout`, `networkNoConnection` |
| 200-299 | Initialization | `notInitialized`, `sdkDisabled`, `invalidAppKey` |
| 300-399 | Ad Loading | `noFill`, `invalidAdUnit`, `adsDisabled` |
| 400-499 | Display | `adNotReady`, `adAlreadyShowing` |
| 600-699 | Adapter | `adapterNoFill`, `adapterTimeout` |
See `CloudXErrorCode` export for the full list of error codes.
## Advanced Features
### Debug Logging
```typescript
import { CloudX, CloudXLogLevel } from 'cloudx-react-native';
// Enable verbose logging (call before initialize)
CloudX.setMinLogLevel(CloudXLogLevel.VERBOSE);
// Available levels: VERBOSE, DEBUG, INFO, WARN, ERROR, NONE
```
**Log Levels:** `VERBOSE` \< `DEBUG` \< `INFO` \< `WARN` \< `ERROR` \< `NONE`
Native SDK logs appear in the iOS and Android platform logs. JavaScript-side calls and event handling can also be logged through your app's normal React Native logging.
### Mediation Debugger
Open the Mediation Debugger to check an integration on the device: every installed adapter with its version and status, the SDK configuration, privacy state, and your ad units. You can also load a test ad for each ad unit and request test mode for the next launch.
```typescript
import { CloudX } from 'cloudx-react-native';
const opened = await CloudX.showMediationDebugger();
```
Call it after `CloudX.initialize()` succeeds. Before that, it resolves `false` and opens nothing. It is meant for development and QA builds; wire it to a debug menu or a gesture.
> **Note:** A test mode request from the debugger takes effect on the next initialization, after you restart the app. The server decides whether that session runs in test mode.
### Impression-Level Revenue Tracking
Set an `addAdRevenuePaidListener` callback on any ad format to receive impression-level revenue events. The `CloudXAdInfo` object includes the revenue value in USD and the winning network name.
```typescript
CloudXInterstitialAd.addAdRevenuePaidListener((adInfo) => {
trackRevenue(adInfo.revenue, adInfo.networkName, adInfo.adUnitId);
});
```
Works with banner, MREC, interstitial, rewarded, and App Open. Native ads report revenue through the `onAdRevenuePaid` prop on `CloudXNativeAdView`.
To forward paid events from another mediation or reporting platform into CloudX, call `CloudX.reportRevenueData()`. Revenue is a currency-unit number, not micros.
```typescript
import { CloudX, CloudXRevenuePlatform, CloudXRevenuePrecision } from 'cloudx-react-native';
const accepted = await CloudX.reportRevenueData({
platform: CloudXRevenuePlatform.ADMOB,
revenue: adValue.valueMicros / 1_000_000.0,
adFormat: 'INTERSTITIAL',
currencyCode: adValue.currencyCode,
precision: CloudXRevenuePrecision.ESTIMATED,
networkName: 'admob',
adUnitId: 'interstitial_ad_unit',
});
```
Use `CloudXRevenuePlatform.GAM` for Google Ad Manager paid events. Use `CloudXRevenuePlatform.custom('TopOn')`, `CloudXRevenuePlatform.custom('InMobi')`, or another non-empty platform name for mediation platforms without a JavaScript constant.
### Publisher-Reported Revenue Data
If your app receives impression-level revenue callbacks from AdMob, Google Ad Manager, or another provider outside the CloudX ad flow, forward those events to CloudX after initialization:
| Field | Required | Description |
| ------------------------- | -------- | ----------------------------------------------------------------------------------------------------------- |
| `platform` | Yes | `CloudXRevenuePlatform.ADMOB`, `CloudXRevenuePlatform.GAM`, 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`, `PUBLISHER_DEFINED`, 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 |
`CloudX.reportRevenueData()` resolves `true` when the event is accepted into the CloudX revenue pipeline. It resolves `false` when the payload is invalid, the platform name is blank, the SDK is not initialized, or revenue tracking is unavailable.
#### AdMob paid events
Google does not provide an official React Native AdMob plugin. This example uses [`react-native-google-mobile-ads`](https://docs.page/invertase/react-native-google-mobile-ads), a third-party React Native wrapper around the official Google Mobile Ads Android and iOS SDKs.
```tsx
const toCloudXRevenuePrecision = (precision) => {
switch (precision) {
case RevenuePrecisions.PRECISE:
return CloudXRevenuePrecision.EXACT;
case RevenuePrecisions.ESTIMATED:
return CloudXRevenuePrecision.ESTIMATED;
case RevenuePrecisions.PUBLISHER_PROVIDED:
return CloudXRevenuePrecision.PUBLISHER_DEFINED;
case RevenuePrecisions.UNKNOWN:
default:
return CloudXRevenuePrecision.UNDEFINED;
}
};
const reportAdMobPaidEvent = (event, adFormat, adUnitId) =>
CloudX.reportRevenueData({
platform: CloudXRevenuePlatform.ADMOB,
revenue: event.value,
adFormat,
currencyCode: event.currency,
precision: toCloudXRevenuePrecision(event.precision),
adUnitId,
});
const AdMobBanner = () => (
{
reportAdMobPaidEvent(event, 'banner', ADMOB_BANNER_UNIT_ID);
}}
/>
);
```
For Google Ad Manager paid events, keep the same revenue and precision mapping and set `platform: CloudXRevenuePlatform.GAM`.
#### Custom platform events
React Native has JavaScript constants for AdMob and Google Ad Manager. Use `custom` for providers that do not have a CloudX JavaScript SDK constant, such as InMobi, TopOn, TradPlus, and Nimbus. 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.
```tsx
const reportCustomRevenueEvent = (
providerName,
revenue,
adFormat,
currencyCode,
adUnitId,
placementId,
) =>
CloudX.reportRevenueData({
platform: CloudXRevenuePlatform.custom(providerName),
revenue,
adFormat,
currencyCode,
precision: CloudXRevenuePrecision.PUBLISHER_DEFINED,
adUnitId,
thirdPartyAdPlacementId: placementId,
});
```
### Test Mode
Test mode is **server-controlled** via device whitelisting. This provides better security and control over which devices receive test ads.
**To enable test mode:**
1. Initialize the SDK with verbose logging enabled.
2. Find your device advertising ID in the iOS or Android platform logs.
3. Add the advertising ID to your device whitelist on the CloudX server dashboard.
**Note:** Test mode is determined by the server, so you don't need to change any code between development and production builds.
### Privacy Compliance
The CloudX SDK supports GDPR and CCPA privacy compliance by reading standard IAB privacy strings from platform storage (`NSUserDefaults` on iOS, `SharedPreferences` on Android). These values are typically set automatically by your Consent Management Platform (CMP), such as Google UMP, OneTrust, or Sourcepoint.
#### How It Works
The native SDKs automatically detect user location and read consent signals:
1. **EU Users (GDPR)**: Checks TCF v2 consent for purposes 1 and 2 per the [IAB Global Vendor List](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework) and vendor consent ([CloudX Vendor ID: 1510](https://vendor-list.consensu.org/v3/vendor-list.json))
2. **US Users (CCPA)**: Checks for sale/sharing opt-out signals
3. **Other Regions**: No restrictions applied
When consent is denied or the user opts out, the SDK removes PII from ad requests:
* Advertising ID (IDFA/GAID) is cleared
* Geo coordinates (lat/lon) are removed
* User key-values are not sent
* Hashed user ID is removed from bidder-bound requests and suppressed from impression reporting
#### Supported Privacy Keys
| Key | Standard | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `IABGPP_HDR_GppString` | [GPP](https://github.com/InteractiveAdvertisingBureau/Global-Privacy-Platform) | Global Privacy Platform string (modern) |
| `IABGPP_GppSID` | GPP | Section IDs (e.g., "2" for EU, "7" for US-National, "8" for US-CA) |
| `IABTCF_TCString` | [TCF v2](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework) | GDPR consent string (legacy) |
| `IABTCF_gdprApplies` | TCF v2 | Whether GDPR applies (1 = yes, 0 = no) |
| `IABUSPrivacy_String` | [US Privacy](https://github.com/InteractiveAdvertisingBureau/USPrivacy) | CCPA privacy string (legacy, e.g., "1YNN") |
> **Note**: The SDK prioritizes GPP (modern standard) over legacy TCF/US Privacy strings when both are available.
#### Manual Privacy API
If you manage user consent yourself (without a CMP), you can set GDPR and CCPA privacy status directly. These can be called before or after SDK initialization.
```typescript
import { CloudX } from 'cloudx-react-native';
// GDPR consent: true, false, or null to defer to CMP
CloudX.setHasUserConsent(true);
// CCPA do-not-sell: true, false, or null to defer to CMP
CloudX.setDoNotSell(true);
```
> **Note:** When both manual values and CMP signals are present, CMP signals (GPP/TCF/US Privacy) take priority. Manual values act as a fallback when no CMP is integrated.
### User Targeting
```typescript
import { CloudX } from 'cloudx-react-native';
// Set hashed user ID for targeting
CloudX.setHashedUserID('hashed-user-id');
// Set custom user key-value pairs
CloudX.setUserKeyValue('age', '25');
CloudX.setUserKeyValue('gender', 'male');
CloudX.setUserKeyValue('location', 'US');
// Set custom app key-value pairs
CloudX.setAppKeyValue('app_version', '1.0.0');
CloudX.setAppKeyValue('user_level', 'premium');
// Clear all custom key-values
CloudX.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](/en/cli/exports#cloudx-export-requests) and [impression](/en/cli/exports#cloudx-export-impressions) 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.
#### Per-load extra parameters
React Native SDK 3.4.4 and later can attach network or server-side configuration to an individual ad load. Values may be strings, numbers, booleans, `null`, arrays, or nested objects.
For programmatic ads, call the format's `setExtraParameters` method before the method that starts the load:
```typescript
import {
CloudXBannerAd,
CloudXMRECAd,
CloudXInterstitialAd,
CloudXRewardedAd,
CloudXAdPosition,
} from 'cloudx-react-native';
const extraParameters = {
requestId: 'request-456',
bidFloor: 0.5,
testRequest: false,
keywords: ['sports', 'scores'],
context: {
screen: 'home',
refreshIndex: null,
},
};
// Call before createAd() for programmatic banner and MREC ads.
CloudXBannerAd.setExtraParameters(BANNER_AD_UNIT_ID, extraParameters);
CloudXBannerAd.createAd(BANNER_AD_UNIT_ID, CloudXAdPosition.BOTTOM_CENTER);
CloudXMRECAd.setExtraParameters(MREC_AD_UNIT_ID, extraParameters);
CloudXMRECAd.createAd(MREC_AD_UNIT_ID, CloudXAdPosition.CENTERED);
// Call before loadAd() for interstitial and rewarded ads.
CloudXInterstitialAd.setExtraParameters(INTERSTITIAL_AD_UNIT_ID, extraParameters);
CloudXInterstitialAd.loadAd(INTERSTITIAL_AD_UNIT_ID);
CloudXRewardedAd.setExtraParameters(REWARDED_AD_UNIT_ID, extraParameters);
CloudXRewardedAd.loadAd(REWARDED_AD_UNIT_ID);
```
For interstitial and rewarded ads, values are captured when `loadAd()` runs; later changes apply to the next load. For programmatic banner and MREC ads, set the values before `createAd()` to include them in the first auction. Changes made after creation apply to future refreshes.
For component placements, pass the values through the `extraParameters` prop on `CloudXBannerView`, `CloudXMRECView`, or `CloudXNativeAdView` (SDK 3.10.1 and later) instead of calling the programmatic setter:
```tsx
import { CloudXBannerView, CloudXMRECView, CloudXNativeAdView } from 'cloudx-react-native';
```
See [Banner & MREC ads](/en/react-native/ad-formats/banner-mrec#component-props) and [Native ads](/en/react-native/ad-formats/native) for the complete component APIs.
#### 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](https://unifiedid.com/docs/ref-info/ref-tokens) |
| `euid.eu` | EUID advertising token, not the refresh token. Do not decrypt it. [EUID docs](https://euid.eu/docs/ref-info/ref-tokens) |
| `liveramp.com` | LiveRamp ATS envelope, not a RampID. [LiveRamp docs](https://developers.liveramp.com/authenticatedtraffic-api/docs/pass-mobile-envelopes-to-the-bidstream) |
| `id5-sync.com` | ID5 universal UID. Do not pass `0`. [ID5 docs](https://wiki.id5.io/docs/mobile-in-app-integration) |
| `intentiq.com` | Intent IQ ID (IIQ ID) from your Intent IQ integration. [Intent IQ docs](https://docs.prebid.org/dev-docs/modules/userid-submodules/intentiq.html) |
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.
```typescript
CloudX.setUserKeyValue('uidapi.com', uid2Token);
CloudX.setUserKeyValue('euid.eu', euidToken);
CloudX.setUserKeyValue('liveramp.com', liveRampEnvelope);
CloudX.setUserKeyValue('id5-sync.com', id5Id);
CloudX.setUserKeyValue('intentiq.com', intentIQId);
```
> **Warning:** Use `setUserKeyValue` only. Do not put these tokens in `setAppKeyValue`. Key names must match exactly.
> **Note:** Do not set a key until you have a value. Setting it again replaces the previous value. If a value expires without a replacement, set that key to an empty string to stop sending its EID. `clearAllKeyValues()` clears all user and app key-values and the hashed user ID.
### App Tracking Transparency (iOS)
Request tracking authorization on iOS 14+:
```typescript
import { requestTrackingAuthorization, getTrackingAuthorizationStatus, CloudXATTStatus } from 'cloudx-react-native';
const status = await requestTrackingAuthorization();
if (status === CloudXATTStatus.AUTHORIZED) {
console.log('Tracking authorized');
}
```
> **Note:** Call `requestTrackingAuthorization()` before `CloudX.initialize()` for best ad targeting results.
ATT status values: `AUTHORIZED`, `DENIED`, `RESTRICTED`, `NOT_DETERMINED`, `NOT_REQUIRED`.
On Android or iOS \< 14, `requestTrackingAuthorization()` returns `NOT_REQUIRED`.
### Visual Debugging
Enable visual debugging overlays to see ad unit boundaries and network info:
```typescript
CloudX.setVisualDebuggingEnabled(true);
```
## Support
For support, contact [support@cloudx.io](mailto:support@cloudx.io)
---
# Sample App
> Run the public CloudX React Native demo app and point it at your own app
The [cloudx-io/cloudx-react-native-public](https://github.com/cloudx-io/cloudx-react-native-public) repository is a runnable demo of [First Look](/en/react-native/integrations/first-look): one banner and one interstitial, with CloudX first and Google Ad Manager as the fallback. It ships with CloudX demo dashboard IDs and Google's test ad units, so it runs without an account; its README lists the setup commands.
## Use your own CloudX app
To point it at your own CloudX app, replace the app key and ad unit IDs in `src/config/adUnits.ts`, then set the bundle identifier registered for that app. Bid requests are authorized per app key and bundle identifier, so both must match your dashboard app or the SDK gets no fill.
---
# First Look
> Give CloudX the first chance to fill an ad placement, then fall back to your existing mediation setup
First Look gives CloudX the first chance to fill a placement on every ad opportunity, while your existing mediation setup remains the fallback path. Exactly one SDK owns the placement at any moment, and CloudX gets first look again on the next cycle. Start with one placement, verify load and show behavior, then expand to more placements.
The examples on this page use Google Ad Manager (GAM) as the fallback through [`react-native-google-mobile-ads`](https://docs.page/invertase/react-native-google-mobile-ads). The same pattern applies to any other fallback mediator: load CloudX first, load the fallback only if CloudX does not fill, and keep the app flow moving when neither source is ready.
There are two shapes to the rule, and this page covers one hook for each:
* **[Banner & MREC](#banner--mrec)** — an inline ad stays on screen and is never consumed, so the hook needs an explicit refresh cycle to give CloudX another first look. MREC follows this hook exactly.
* **[Interstitial](#interstitial)** — a fullscreen ad is consumed by being shown, so the SDKs' own "is an ad ready" answers go false on their own and the next `load()` starts at CloudX again. Rewarded follows this hook exactly.
> **Info:** Initialize CloudX and Google Mobile Ads **before any ad view mounts**. Mounting is what starts a load in React Native, so a view rendered during initialization waits silently rather than failing — which is why both hooks carry a 15-second attempt timeout instead of trusting the SDKs to answer. Keep your existing pacing, frequency capping, and placement timing rules around them.
>
> Both hooks keep the fallback lazy: GAM is loaded only after CloudX reports a load failure. Do not parallel-load the two sources unless CloudX recommends a different rollout strategy for your app.
>
> One consequence to expect: a CloudX load failure is not raised as `onAdLoadFailed`, because it is not terminal — it is what triggers the fallback. `onAdLoadFailed` fires only when the fallback fails too, which is the case actually worth retrying.
## Banner & MREC
A banner is not the interstitial with different method names. A fullscreen ad is consumed by being shown, so its readiness checks go false on their own and the next `load()` starts at CloudX again — the [interstitial](#interstitial) below rests on that. An inline ad is never consumed: left alone, the first fill owns the placement until the screen unmounts, so a single CloudX no-fill hands the slot to your fallback for the rest of the session.
The hook solves that with a **refresh cycle**. One cycle is one ad opportunity: CloudX first, GAM only if CloudX misses, winner swapped in. Swapping the winner in ends the cycle and starts the clock on the next one, which begins at CloudX again.
The examples use the **component banner**, `CloudXBannerView`, not the programmatic `CloudXBannerAd`. The component renders in your view tree, so it sits in your layout rather than as a native overlay above it — and the cycle needs that form, because mounting starts the load and unmounting destroys the ad.
> **Warning:** **Set the CloudX banner ad unit's refresh rate to `0`** in the [CloudX dashboard](/en/dashboard/ad-units), and disable auto-refresh on the GAM ad unit in the Ad Manager UI (or use a non-refreshing unit).
>
> The dashboard value is the only way to turn CloudX's refresh timer off for a component placement — it cannot be done from the client. `CloudXBannerAd.stopAutoRefresh(adUnitId)` affects only the programmatic overlay ads created through that API, so calling it for a `CloudXBannerView` is a silent no-op. `Banner refresh scheduled in 30s` in the SDK log means the dashboard value did not take.
With refresh disabled on both sides, each cycle works like this:
1. Load a CloudX banner off-screen while the previous ad (if any) stays visible.
2. On fill, swap it in: show the new banner and unmount the previous one, which destroys its native view.
3. On CloudX no-fill, load the GAM banner for this cycle instead.
4. If both no-fill, retry from CloudX with an exponential delay (1, 2, 4, 8, ... seconds, capped at 64).
5. Once the new ad is swapped in, wait 30 seconds, then start the next cycle at step 1 — CloudX gets first look again. A new attempt only starts while your app is in the foreground; a load already in flight finishes normally.
This lets you prepare the next fill in the background while the current banner is displaying, swap instantly when the refresh moment arrives, and benefit from CloudX optimistic loading.
* [useFirstLookBanner.ts](https://github.com/cloudx-io/cloudx-react-native-public/blob/main/src/firstlook/useFirstLookBanner.ts)
Which SDK gets the cycle, and when the next one starts. The whole cycle, commented at the lines that matter.
* [FirstLookBannerSlot.tsx](https://github.com/cloudx-io/cloudx-react-native-public/blob/main/src/firstlook/FirstLookBannerSlot.tsx)
How that cycle is rendered: the visible slot and the hidden slot that preloads the next ad.
* [firstLookTiming.ts](https://github.com/cloudx-io/cloudx-react-native-public/blob/main/src/firstlook/firstLookTiming.ts)
The four numbers worth tuning — refresh delay, backoff ceiling, attempt timeout, close settle.
* [FirstLookSource.ts](https://github.com/cloudx-io/cloudx-react-native-public/blob/main/src/firstlook/FirstLookSource.ts)
The `'cloudx'` / `'gam'` union every callback reports.
Copy all four. Between them they are the whole flow.
They live in the [CloudX React Native demo app](https://github.com/cloudx-io/cloudx-react-native-public), which is the version verified on device, so it is the one that gets fixed when something is wrong. That is why this page links them instead of pasting them: a copy here would drift, and it would be the stale one.
### Why the slot is a separate file
The hook has no view. It tracks two attempts — the ad currently displayed and the ad currently loading off-screen — and the slot is what turns those into mounted components. Mounting is what triggers a load for both `CloudXBannerView` and GAM's `GAMBannerAd`, so the slot's structure is not cosmetic; it is how the cycle runs. Three rules live in it, and they are the ones an integration gets wrong:
* **Render the loading attempt hidden, not deferred.** Nothing loads until it mounts, so a slot that waits for the refresh moment to mount the next ad shows an empty banner for the length of a load. The hidden slot is mounted the whole time and costs nothing until it fills.
* **Hide it with `position: absolute` and `opacity: 0`, plus `pointerEvents="none"` and the accessibility flags.** Unmounting it would cancel the load; leaving it interactive would let a user tap an ad that is not on screen, which is an invalid click.
* **Key every callback to its attempt.** The displayed ad and the loading ad share one set of handlers, and the displayed ad can emit its own events. Without the attempt key, a stale event from the ad already on screen promotes an unfilled hidden attempt and blanks the slot.
Take the slot as it is and you get all three. Which SDK wins a cycle, and when the next one starts, is the hook's side and is commented in that file.
### Wiring it up
Render `` wherever the banner belongs in your layout, and pass an optional `observer` to see what the cycle is doing. When the screen unmounts, both slots unmount with it, which destroys the native ads and clears the hook's timers.
```tsx HomeScreen.tsx
import React, { useCallback, useMemo } from 'react';
import { View } from 'react-native';
import { FirstLookBannerSlot } from './firstlook/FirstLookBannerSlot';
import type { FirstLookBannerObserver } from './firstlook/useFirstLookBanner';
export function HomeScreen() {
const report = useCallback((line: string) => console.log(line), []);
/*
* The observer is optional - the cycle runs whether or not you pass one.
* The hook keeps it in a ref, so a fresh object on every render is harmless:
* an observer identity change never restarts a running cycle.
*/
const observer: FirstLookBannerObserver = useMemo(
() => ({
onAdLoaded: source => report(`Banner loaded (${source})`),
onAdLoadFailed: (source, error) =>
report(`Banner load failed (${source}): ${error}`),
onAdClicked: source => report(`Banner clicked (${source})`),
}),
[report],
);
return (
);
}
```
Render one slot per placement. Navigators keep screens mounted, so two components on the same ad unit run two independent cycles and both receive that ad unit's events — one instance's failure arms the other's fallback.
### What the observer reports
Three callbacks, each carrying the source that served the ad.
| Callback | Meaning |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `onAdLoaded(source)` | A source filled. This is also the swap: the ad goes on screen in the same handler. `source` is the answer to "is CloudX actually filling?" |
| `onAdLoadFailed(source, error)` | **Both** sources missed and the opportunity is over. Only `'gam'` is emitted today. |
| `onAdClicked(source)` | The user tapped the ad. Reporting only. |
The one to get right is **`onAdLoadFailed`**. It is not raised when CloudX alone misses, because that miss is what starts the GAM attempt — the cycle is still running, and treating it as terminal would double-book the opportunity while GAM is still loading.
Two silences to know about. A GAM banner click is never reported: [`react-native-google-mobile-ads`](https://github.com/invertase/react-native-google-mobile-ads) wires no banner click event on iOS or Android, so `onAdClicked` covers CloudX banners only (the interstitial is unaffected and reports both sources). And the cycle pauses whenever your app leaves the foreground, with no callback for it. On iOS that includes the ATT prompt, which appears at launch just as the first cycle starts.
> **Tip:** MREC needs the same four files, with `CloudXMRECView` substituted for `CloudXBannerView` in the slot, GAM's `sizes={[BannerAdSize.MEDIUM_RECTANGLE]}`, and a `300x250` slot style. The hook makes no SDK calls of its own, so it is unchanged. The dashboard refresh rate must be `0` on the MREC ad unit too.
## Interstitial
The simpler of the two, because showing consumes the ad: no slot has to be rendered and no cycle has to be driven. Rewarded is this hook with the rewarded calls substituted, plus the reward callback.
* [useFirstLookInterstitial.ts](https://github.com/cloudx-io/cloudx-react-native-public/blob/main/src/firstlook/useFirstLookInterstitial.ts)
The complete hook, commented at the lines that matter. Copy it as is.
* [firstLookTiming.ts](https://github.com/cloudx-io/cloudx-react-native-public/blob/main/src/firstlook/firstLookTiming.ts)
The attempt timeout and the post-close settle window this hook reads. Shared with the banner.
* [FirstLookSource.ts](https://github.com/cloudx-io/cloudx-react-native-public/blob/main/src/firstlook/FirstLookSource.ts)
The `'cloudx'` / `'gam'` union every callback reports. Shared with the banner.
Call `load()` when the screen is ready to prepare the placement. At the placement moment, call `show()`. If it returns `false`, neither CloudX nor GAM had a ready ad, so continue the app flow without showing an ad.
**The hook never reloads for you.** `onAdClosed`, `onAdLoadFailed` and `onAdShowFailed` each mean the opportunity is over, and each is a place `load()` belongs — without the reload on close, the placement is dead after the first impression.
```tsx GameScreen.tsx
const { isReady, load, show } = useFirstLookInterstitial(
CLOUDX_INTERSTITIAL_AD_UNIT_ID,
GAM_INTERSTITIAL_AD_UNIT_ID,
{
onAdLoaded: source => console.log(`Interstitial ready (${source})`),
// Showing consumes the ad, so without this the slot is dead after the
// first impression.
onAdClosed: () => load(),
// No close follows a miss, so nothing else would refill. Space these with
// an exponential delay rather than reloading immediately.
onAdLoadFailed: (source, error) => scheduleRetry(),
onAdShowFailed: (source, error) => scheduleRetry(),
},
);
useEffect(() => {
load();
}, [load]);
const continueToNextScreen = () => {
if (!show()) {
navigation.navigate('NextScreen');
}
};
```
### What the observer reports
Six callbacks, each carrying the source that served the ad.
| Callback | Meaning |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `onAdLoaded(source)` | A source filled. `source` is the answer to "is CloudX actually filling?" |
| `onAdLoadFailed(source, error)` | **Both** sources missed; the opportunity is over. Only `'gam'` is emitted today. |
| `onAdShown(source)` | The SDK confirmed the ad is on screen. Not inferred from `show()`. |
| `onAdShowFailed(source, error)` | A loaded ad could not be presented; the opportunity is over. Only `'gam'` today — a CloudX display failure arrives on the load path instead, which is what triggers the fallback. |
| `onAdClosed(source)` | The ad was dismissed. Call `load()` from here. |
| `onAdClicked(source)` | The user tapped the ad. Reporting only; the placement is unaffected. |
`onAdLoadFailed` follows the same terminal-only rule as the banner's: the CloudX miss is what triggers the fallback, so it is not reported.
> **Warning:** The CloudX interstitial event listeners are **singletons per event**: `addAdDisplayedEventListener` and friends replace the previous listener rather than adding to it, and there is no handle to unsubscribe an individual one. Mounting this hook twice makes the earlier copy deaf, silently. Use one instance per placement.
### Error paths
* **CloudX load fails** (no-fill or error): the hook loads the GAM interstitial for this opportunity. `useCloudXInterstitial` reports it by setting its `error` field, which is the hook's only fallback trigger.
* **CloudX show fails**: the ad is consumed and the same `error` field is set, so the hook loads GAM for this opportunity too — the fallback covers display failures, not only load failures. This includes an expired fill (`ADAPTER_AD_EXPIRED`): a CloudX interstitial held loaded but unshown for a long time can expire and fail at show, and the fallback picks it up.
* **A reload straight from the close**: from `cloudx-react-native` 3.10.0, a `load()` from `onAdClosed` waits until the ad has fully closed and then runs normally. On earlier versions the SDK could reject that load (on Android with `Cannot load while another ad is currently being displayed`), which looks like a no-fill and would hand GAM an opportunity CloudX never got. The hook still covers those versions: it waits out `CLOSE_SETTLE_MS` and retries the CloudX load rather than treating that rejection as a miss. Keep calling `load()` from `onAdClosed`.
* **GAM load also fails**: `show()` returns `false` and the app flow continues without an ad. `onAdLoadFailed('gam', ...)` is raised; space the retries with an exponential delay (1, 2, 4, 8, ... seconds) rather than retrying immediately.
* **GAM never answers at all**: after `ATTEMPT_TIMEOUT_MS` the hook raises `onAdLoadFailed('gam', ...)` so you are not left waiting. Read that message — it also tells you the GAM ad object cannot load again this session, because the plugin ignores a load while its own request is outstanding and only a close or an error clears that. Later opportunities still start at CloudX and serve normally; recovering the GAM leg means recreating the ad object.
* **GAM cannot present** (on Android, no resumed activity): the promise rejects after `show()` has already returned `true`, so it is reported as `onAdShowFailed`. The fill is not lost — the next `show()` presents the ad still in hand.
* **After a successful show and close**: call `load()` again when your app is ready to prepare the next opportunity — CloudX gets first look again.
On unmount, the CloudX hook releases its event subscriptions and destroys its interstitial instance, and the GAM event listener is unsubscribed by the effect cleanup.
### State reference
GAM is reachable only through the CloudX error path, so exactly one source is ever loading, and the next opportunity always starts back at CloudX.
| State | `isReady` | `show()` | `load()` |
| -------------- | --------- | ------------------------------------- | ------------------ |
| Idle | `false` | `false` — continue app flow | starts CloudX |
| CloudX loading | `false` | `false` — continue app flow | no-op |
| CloudX ready | `true` | shows CloudX | no-op |
| GAM loading | `false` | `false` — continue app flow | no-op |
| GAM ready | `true` | shows GAM | no-op |
| Presenting | `true` | `false` — a show is already in flight | no-op |
| Both failed | `false` | `false` — continue app flow | restarts at CloudX |
## Check your integration
The hooks decide the order. What they cannot decide is whether your app key, your ad unit ids and your dashboard configuration are right, and those live in your project. When one of them is wrong the symptom is silent: the fallback serves, ads appear, and everything looks healthy.
One check covers it. Log the source, which every callback already carries:
```tsx
onAdLoaded: source => console.log(`First Look loaded: ${source}`);
```
Run with your real ad unit ids and look for `cloudx` on a fill. If you only ever see `gam`, CloudX is not filling at all — check the app key, the ad unit ids and the dashboard configuration before you look at the hook.
For a banner, watch for longer than one refresh delay: a fresh CloudX attempt should precede *every* GAM fill, not just the first. If CloudX is asked once and never again, something is ending the cycle — an SDK refresh timer that is still on, or a second component mounted on the same ad unit.
To exercise the fallback on purpose — to confirm your GAM unit is configured before you ship — point the CloudX ad unit id at a string that is not in your dashboard, so every CloudX load fails and the fallback has to serve. The sequence should read:
```
cloudx no-fill -> gam attempt -> gam fill -> (refresh delay) -> cloudx attempt
```
With a working CloudX id, the same watch confirms the negative case that matters: no load-failure line appears between the CloudX miss and `Banner loaded (gam)`, because the CloudX miss is not terminal.
## Common mistakes
These patterns apply to any fallback mediator, not only the one used in the examples above. Most of them end the same way: two sources loading for one opportunity.
* **Loading the fallback early "so one is always ready."** Every opportunity then produces two fills, and the discarded one is wasted; most mediation SDKs expire an interstitial that is held unshown — a GAM interstitial after about an hour, with no impression for it. The only trigger for a fallback load is a CloudX failure — load or show.
* **Leaving an old preloader running.** If your app preloaded the fallback mediator at startup before you added CloudX, that code now runs in parallel with the hook even when the hook is correct. Give each ad unit exactly one owner; a fallback request at app start, before the first placement moment, means the legacy path is still live.
* **Falling back on a timer.** A timeout that loads the fallback because CloudX is "slow" fires under normal auction latency and double-books the opportunity when CloudX then fills. Fall back only from a CloudX failure; if you need a deadline, cancel and replace the attempt like the banner hook's 15-second timeout, never race two live loads.
* **Mounting one placement twice.** Navigators keep screens mounted, so two slots on one ad unit run two cycles and double the request rate. Banner views do not cross-talk — each reports through its own props — they simply compete for the placement. Two interstitial hooks on one ad unit are worse: both subscribe to that ad unit's events, so one instance's miss arms the other's fallback. Render one per placement.
* **Moving the request gates into `useState`.** The gates — the fallback-requested flag and the timers — are refs because a guard must read and set in the same tick; state updates are batched, so two events can both pass the guard. Keep them as refs, and keep the interstitial hook's ref mirror of the CloudX ready flags, which its event handlers read before the next render commits.
* **Reaching past the hook to show.** The hook's `show()` is safe to call at any time — with nothing loaded it just returns `false`. `useCloudXInterstitial`'s own `show()` is not: called with no ad loaded it sets `error`, and a CloudX error is what arms the fallback, so a speculative show during a CloudX load starts a parallel GAM load. Go through the hook.
* **Wrapping `load()` in your own retry logic.** `load()` in the render body, an effect with unstable dependencies, or a generic retry helper wrapped around it turns one failing placement into a request storm. Load from a mount effect or a user-flow moment. The banner hook already backs off internally; for the interstitial the backoff is yours to write, as `scheduleRetry` above.
---
# Trusted Arbiter
> Compare CloudX bids with supported third-party bids in React Native apps
Trusted Arbiter compares a loaded CloudX bid with supported third-party bids and returns the selected platform. React Native support is backed by the CloudX native SDKs and supports CloudX, Unity LevelPlay, PubMatic, and custom publisher-supplied bid inputs.
## Why use Trusted Arbiter?
When your app loads ads from several platforms, it needs a way to choose which ad to show. Trusted Arbiter provides a shared comparison API for supported bids.
### Less comparison logic to maintain
Submit the supported bids to Trusted Arbiter and use the returned platform to select the ad. Your app continues to manage partner SDK integration, ad loading, and display.
### Use available bid values
Trusted Arbiter can compare actual bid values where available. Some demand uses estimated prices. See the pricing guidance below for each supported input.
Use loaded ad objects or ad info objects from each SDK to populate bid values:
```typescript
import { CloudX, CloudXArbiterBid } from 'cloudx-react-native';
// cloudXAdInfo is the CloudXAdInfo object from a CloudX load callback.
// levelPlayAdInfo is the Unity LevelPlay ad info object.
// pubMaticPrice and pubMaticPartnerName come from the PubMatic/OpenWrap bid object.
const result = await CloudX.arbiter({
bids: [
CloudXArbiterBid.cloudX(cloudXAdInfo),
CloudXArbiterBid.levelPlay({
networkName: levelPlayAdInfo.adNetwork,
revenue: levelPlayAdInfo.revenue,
precision: levelPlayAdInfo.precision,
}),
CloudXArbiterBid.pubMatic({
price: pubMaticPrice,
partnerName: pubMaticPartnerName,
}),
],
});
console.log('Selected platform:', result.platform);
```
On timeout, error, or an unavailable arbiter service, the SDK falls back to the highest comparable USD bid among the supplied supported bid inputs.
## When to run the arbiter
Run the arbiter when your candidate ads finish loading — never on the show path. `CloudX.arbiter()` is a network round trip, and a user who taps a button that shows an ad must never wait on it.
**Fullscreen formats (interstitial, rewarded, app open): prepare ahead.** Load every candidate in parallel. Once they have all settled — loaded or failed — run the arbiter and store the result. At the placement, show the stored winner immediately, with no network call. Run the cycle again after the ad is shown or hidden, after it fails to display, or when a candidate expires.
```typescript
import { CloudX, CloudXAdInfo, CloudXArbiterBid, CloudXInterstitialAd, CloudXArbiterResult } from 'cloudx-react-native';
let nextWinner: CloudXArbiterResult | null = null;
// Runs as soon as the candidates have loaded, ahead of the placement.
const prepareWinner = async (cloudXAdInfo: CloudXAdInfo, adMobAdUnitId: string) => {
nextWinner = await CloudX.arbiter({
bids: [CloudXArbiterBid.cloudX(cloudXAdInfo), CloudXArbiterBid.adMob({ adUnitId: adMobAdUnitId })],
});
};
// Runs at the placement. No network call here.
const showInterstitial = (cloudXAdUnitId: string) => {
switch (nextWinner?.platform) {
case 'CLOUDX':
CloudXInterstitialAd.showAd(cloudXAdUnitId);
break;
case 'ADMOB':
// show your AdMob interstitial
break;
default:
break; // no winner prepared; continue without an ad
}
nextWinner = null;
};
```
If the placement arrives before a winner is stored, either continue without an ad or show the single candidate that did load. That is an acceptable degraded path, never the primary one.
**View formats (banner, MREC): arbitrate, then render.** Nothing user-initiated is waiting, so it is correct to attach or render the winner directly where the `await CloudX.arbiter()` call returns — the arbiter completing *is* the trigger to show. Only the winner's view or assets may ever be attached. See [Banner and MREC](#banner-and-mrec) for the view-attachment rules.
> **Note:** The arbiter call always completes. Its per-call timeout is supplied by the server in your init configuration (`arbiterTimeoutMs`), and the SDK owns both the timeout and the fallback: on timeout, error, or an unavailable service it returns a local fallback — the highest comparable USD bid among the inputs you supplied. A single supplied bid wins immediately without any service call. Do not wrap the arbiter in your own timer and do not compare revenue yourself.
>
> `result.platform` is `'NONE'` when no candidate carried a locally comparable price during fallback — for example Google bids without `manualRevenuePerImpressionUSD` — as well as when no bids were supplied. Treat `'NONE'` as "no winner to show", not as an error.
## AdMob and Google Ad Manager
CloudX compares a loaded CloudX ad with a loaded AdMob or Google Ad Manager ad. AdMob and Ad Manager are separate demand sources, so both may bid in the same arbitration. This applies to publisher-managed mediation setups, including accounts described commercially as AdMob Pro; there is no separate `AdMob Pro` SDK API.
Google demand does not normally reveal the price of a loaded ad before it is shown, so there is no pre-bid price for the arbiter to compare against CloudX's bid. CloudX prices the bid automatically from your historical Google performance, so you do not supply a price and no pre-bid pricing API is required. The revenue reporting below feeds that history. See [how AdMob/GAM estimated pricing works](/en/networks/google-estimated).
If your AdMob account exposes impression-level revenue data pre-bid, you can supply that exact price yourself instead of using the estimate — see [Manual input with pre-bid ILRD](#manual-input-with-pre-bid-ilrd) below.
### Report Google paid events back to CloudX (required)
Reporting Google's impression-level revenue is a required part of the Trusted Arbiter AdMob and Ad Manager integration, not an optional analytics extra. After you show an AdMob or Ad Manager ad that won an arbitration, forward Google's paid event into the CloudX SDK. Without those events CloudX never learns the realized price of Google demand, and the estimates it supplies to future arbitrations degrade.
Call `CloudX.reportRevenueData()` from your Google paid-event handler. Revenue is a currency-unit number, not micros — `react-native-google-mobile-ads` already reports `event.value` in currency units.
```typescript
import { CloudX, CloudXRevenuePlatform, CloudXRevenuePrecision } from 'cloudx-react-native';
// event is the paid event from your Google ad (react-native-google-mobile-ads onPaid).
const reportGooglePaidEvent = (event, adFormat, adUnitId) =>
CloudX.reportRevenueData({
platform: CloudXRevenuePlatform.ADMOB, // use CloudXRevenuePlatform.GAM for Ad Manager
revenue: event.value,
adFormat,
currencyCode: event.currency,
precision: CloudXRevenuePrecision.ESTIMATED,
networkName: 'admob',
adUnitId,
});
```
Report the ad unit id you passed to the arbiter bid so CloudX can attribute the realized price to the right ad unit. See [Publisher-Reported Revenue Data](/en/react-native/integration#publisher-reported-revenue-data) for the full field reference and the precision mapping.
Create the bid from the ad unit id of the ad you loaded:
```typescript
// networkName is optional; pass the winning ad source when you know it.
const adMobBid = CloudXArbiterBid.adMob({
adUnitId: adMobAdUnitId,
networkName: adMobNetworkName ?? 'admob',
});
// An Ad Manager ad unit id takes the form /NNNNNNN/placement/name.
const adManagerBid = CloudXArbiterBid.gam({
adUnitId: '/21775744923/example/interstitial',
});
// Runs once the candidates have loaded, ahead of the placement.
nextWinner = await CloudX.arbiter({
bids: [CloudXArbiterBid.cloudX(cloudXAdInfo), adMobBid, adManagerBid],
});
// Runs at the placement. No network call here.
const showAd = () => {
if (nextWinner?.platform === 'ADMOB') {
// show the AdMob ad
} else if (nextWinner?.platform === 'GAM') {
// show the Ad Manager ad
}
nextWinner = null;
};
```
Store the result rather than showing from where the arbiter returns — see [When to run the arbiter](#when-to-run-the-arbiter).
A winning Google bid reports its own platform — the string `'ADMOB'` or `'GAM'`. You no longer need to inspect the result's `platformName` to tell these sources apart, as you would for a [custom bid](#custom-bid-inputs).
Pass a blank ad unit id and the bid still builds rather than crashing your app, but it carries no usable identity: it is never priced and the server rejects it.
## Manual input with pre-bid ILRD
Some AdMob accounts expose impression-level revenue data pre-bid: the ad value for the loaded ad is available at load time, before the ad is shown. This is a legacy, account-gated capability, so check with your Google account team whether it is enabled for your account. An exact per-impression price that you know before show is the one case where supplying your own price beats CloudX's estimate.
> **Warning:** We recommend manually assigned prices only when they come from pre-bid ILRD. Prices from weaker sources — average eCPMs, guesses, or post-impression paid events — degrade arbitration rather than improving it. Leave `manualRevenuePerImpressionUSD` undefined in those cases and let CloudX's estimate price the bid instead.
Pass the pre-bid ad value as `manualRevenuePerImpressionUSD` and it overrides the estimate:
```typescript
const adMobBid = CloudXArbiterBid.adMob({
adUnitId: adMobAdUnitId,
manualRevenuePerImpressionUSD: preBidPricePerImpressionUSD,
});
```
The unit you receive depends on the native SDK underneath: the Google Mobile Ads SDK reports ad values in micros on Android and in currency units on iOS, so convert to a per-impression USD amount before passing it in.
How the value is treated:
* `0.0` is a real price. It means this bid is worth nothing — not that the price is missing.
* Negative and non-finite values are not prices, so they are treated as absent and logged.
* A blank ad unit id drops the manual price entirely, because a bid with no identity cannot be validated.
`manualRevenuePerImpressionUSD` is revenue for a single impression, in USD — not CPM, and a non-USD amount must be converted to USD first. See the [Android](/en/android/trusted-arbiter#manual-input-with-pre-bid-ilrd) and [iOS](/en/ios/trusted-arbiter#manual-input-with-pre-bid-ilrd) pages for the per-platform unit-conversion rules for AdMob ad values.
## Supported Formats
The arbiter is format-agnostic: it takes any loaded CloudX ad, and the same field mapping applies regardless of format.
* **Fullscreen — interstitial, rewarded, app open.** Prepare the winner ahead of the placement; see [When to run the arbiter](#when-to-run-the-arbiter).
* **View — banner, MREC.** Arbitrate, then render the winner; see [Banner and MREC](#banner-and-mrec) for the view-attachment rules.
## Banner and MREC
Auto-refresh conflicts with arbitration: a refresh can swap the ad after the arbiter has already picked a winner. Disable auto-refresh in the CloudX dashboard and call `stopAutoRefresh()` on the banner or MREC ad, and disable auto-refresh on the other arbitrated networks as well.
```typescript
CloudXBannerAd.stopAutoRefresh(adUnitId);
// CloudXMRECAd.stopAutoRefresh(adUnitId);
```
Only show the winning bid's ad with `showAd(adUnitId)`; keep the rest hidden with `hideAd(adUnitId)` (or never shown).
Recommended refresh flow: after the winner's impression, load a new fill from the winning network only; retain non-winning ads that already have a fill and re-request just the unfilled networks; rerun the arbiter once responses return. Refresh the displayed ad every 20-30 seconds — shorter intervals decrease CPM performance.
## Custom Bid Inputs
Use `CloudXArbiterBid.custom()` when you want Trusted Arbiter to compare CloudX with a third-party platform that does not have a dedicated bid factory.
```typescript
const customBid = CloudXArbiterBid.custom({
platformName: 'my_mediation_platform',
networkName: 'winning_demand_source',
revenuePerImpressionUSD: 0.00125,
precision: 'EXACT',
extras: { ad_unit: 'third-party-ad-unit-id' },
});
const result = await CloudX.arbiter({
bids: [CloudXArbiterBid.cloudX(cloudXAdInfo), customBid],
});
console.log('Selected platform:', result.platform);
```
When a custom bid wins, `result.platform` is `'CUSTOM'` and `result.platformName` contains the `platformName` supplied on the bid. Pass `revenuePerImpressionUSD` as revenue for one impression in USD, not CPM. Use `'EXACT'`, `'ESTIMATED'`, `'PUBLISHER_DEFINED'`, or `'UNDEFINED'` for `precision` to describe that revenue value. The React Native bid object requires `networkName`; pass `''` if your source does not provide a winning-network name. A custom bid missing a non-blank `platformName`, revenue, or precision is dropped with a warning and does not compete.
---
# Banner & MREC Ads
> Integrate CloudX banner and MREC ads in React Native apps
## Component Banner
Use `CloudXBannerView` when the ad should participate in your React Native layout. The component renders as a normal view, so parent layout, spacing, safe areas, overlays, and z-ordering work the same way as the rest of your app UI.
```typescript
import React from 'react';
import { View } from 'react-native';
import { CloudXBannerView } from 'cloudx-react-native';
const AD_UNIT_ID = 'home_banner';
export function HomeScreen() {
return (
{
console.log('Banner loaded from', adInfo.networkName);
}}
onAdLoadFailed={(error) => {
console.log('Banner failed:', error.code, error.message);
}}
onAdClicked={(adInfo) => {
console.log('Banner clicked:', adInfo.adUnitId);
}}
onAdRevenuePaid={(adInfo) => {
console.log('Banner revenue:', adInfo.revenue);
}}
/>
);
}
```
`CloudXBannerView` defaults to `320x50` on phones and `728x90` on tablets. You can pass normal React Native `ViewProps`, including `style`, `testID`, and accessibility props.
The component owns the native ad lifecycle. Do not call `CloudXBannerAd.createAd()`, `showAd()`, or `destroyAd()` for a component placement. If you need to change `adUnitId`, remount the component with a different React `key`.
```typescript
```
## Component MREC
Use `CloudXMRECView` for an in-tree `300x250` MREC placement. It supports the same component props and callbacks as `CloudXBannerView`.
The component owns the native ad lifecycle. Do not call `CloudXMRECAd.createAd()`, `showAd()`, or `destroyAd()` for a component placement.
```typescript
import React from 'react';
import { View } from 'react-native';
import { CloudXMRECView } from 'cloudx-react-native';
export function ArticleScreen() {
return (
{
console.log('MREC loaded from', adInfo.networkName);
}}
onAdLoadFailed={(error) => {
console.log('MREC failed:', error.code, error.message);
}}
onAdClicked={(adInfo) => {
console.log('MREC clicked:', adInfo.adUnitId);
}}
onAdRevenuePaid={(adInfo) => {
console.log('MREC revenue:', adInfo.revenue);
}}
/>
);
}
```
## Component Props
| Prop | Type | Description |
| ----------------- | --------------------------------- | ------------------------------------------------------------------- |
| `adUnitId` | `string` | Required CloudX ad unit ID. |
| `placement` | `string` or `null` | Optional placement name for reporting. |
| `customData` | `string` or `null` | Optional custom data string. |
| `extraParameters` | `CloudXExtraParameters` or `null` | Optional extra parameters for network or server-side configuration. |
| `onAdLoaded` | `(adInfo) => void` | Called when an ad loads. |
| `onAdLoadFailed` | `(error) => void` | Called when an ad load fails. |
| `onAdClicked` | `(adInfo) => void` | Called when the ad is clicked. |
| `onAdRevenuePaid` | `(adInfo) => void` | Called when impression-level revenue is available. |
`CloudXExtraParameters` accepts string, number, boolean, `null`, arrays, and nested objects.
```typescript
```
## Programmatic Overlay Banner
Use `CloudXBannerAd` when you need the existing native overlay behavior. Programmatic banners overlay your content at a fixed screen position.
```typescript
import { CloudXBannerAd, CloudXAdPosition } from 'cloudx-react-native';
const AD_UNIT_ID = 'home_banner';
CloudXBannerAd.addAdLoadedEventListener((adInfo) => {
console.log('Banner loaded from', adInfo.networkName);
});
CloudXBannerAd.addAdLoadFailedEventListener((error) => {
console.log('Banner failed:', error.code, error.message);
});
CloudXBannerAd.addAdClickedEventListener((adInfo) => {
console.log('Banner clicked');
});
CloudXBannerAd.addAdExpandedEventListener((adInfo) => {
console.log('Banner expanded');
});
CloudXBannerAd.addAdCollapsedEventListener((adInfo) => {
console.log('Banner collapsed');
});
CloudXBannerAd.addAdRevenuePaidListener((adInfo) => {
console.log('Banner revenue:', adInfo.revenue);
});
CloudXBannerAd.createAd(AD_UNIT_ID, CloudXAdPosition.BOTTOM_CENTER);
CloudXBannerAd.showAd(AD_UNIT_ID);
CloudXBannerAd.hideAd(AD_UNIT_ID);
CloudXBannerAd.destroyAd(AD_UNIT_ID);
```
Auto-refresh is enabled by default. To control it manually:
```typescript
CloudXBannerAd.stopAutoRefresh(AD_UNIT_ID);
CloudXBannerAd.startAutoRefresh(AD_UNIT_ID);
```
Use `CloudXAdPosition` to place the overlay banner:
`TOP_LEFT`, `TOP_CENTER`, `TOP_RIGHT`, `CENTER_LEFT`, `CENTERED`, `CENTER_RIGHT`, `BOTTOM_LEFT`, `BOTTOM_CENTER`, `BOTTOM_RIGHT`
For pixel-level control, use offsets:
```typescript
CloudXBannerAd.createAdWithOffsets(AD_UNIT_ID, CloudXAdPosition.TOP_CENTER, 0, 50);
CloudXBannerAd.updatePosition(AD_UNIT_ID, CloudXAdPosition.BOTTOM_CENTER);
CloudXBannerAd.updateOffsets(AD_UNIT_ID, 0, 20);
```
Optional placement and custom data for tracking:
```typescript
CloudXBannerAd.setBackgroundColor(AD_UNIT_ID, '#000000');
CloudXBannerAd.setPlacement(AD_UNIT_ID, 'home_screen');
CloudXBannerAd.setCustomData(AD_UNIT_ID, 'custom_data');
```
## Programmatic Overlay MREC
MREC overlays use `CloudXMRECAd` and a `300x250` size.
```typescript
import { CloudXMRECAd, CloudXAdPosition } from 'cloudx-react-native';
const AD_UNIT_ID = 'home_mrec';
CloudXMRECAd.addAdLoadedEventListener((adInfo) => {
console.log('MREC loaded from', adInfo.networkName);
});
CloudXMRECAd.addAdLoadFailedEventListener((error) => {
console.log('MREC failed:', error.code, error.message);
});
CloudXMRECAd.addAdClickedEventListener((adInfo) => {
console.log('MREC clicked');
});
CloudXMRECAd.addAdExpandedEventListener((adInfo) => {
console.log('MREC expanded');
});
CloudXMRECAd.addAdCollapsedEventListener((adInfo) => {
console.log('MREC collapsed');
});
CloudXMRECAd.addAdRevenuePaidListener((adInfo) => {
console.log('MREC revenue:', adInfo.revenue);
});
CloudXMRECAd.createAd(AD_UNIT_ID, CloudXAdPosition.CENTERED);
CloudXMRECAd.showAd(AD_UNIT_ID);
CloudXMRECAd.destroyAd(AD_UNIT_ID);
```
`CloudXMRECAd` has the same API surface as `CloudXBannerAd`, including positioning, offsets, background color, placement, custom data, auto-refresh control, and event listeners.
## React Hook
```typescript
import React, { useEffect } from 'react';
import { Text } from 'react-native';
import { useCloudXBanner, CloudXBannerAd, CloudXAdPosition } from 'cloudx-react-native';
function HomeScreen() {
const { isLoaded, error } = useCloudXBanner('home_banner');
useEffect(() => {
CloudXBannerAd.createAd('home_banner', CloudXAdPosition.BOTTOM_CENTER);
CloudXBannerAd.showAd('home_banner');
return () => CloudXBannerAd.destroyAd('home_banner');
}, []);
return {isLoaded ? 'Banner showing' : error?.message ?? 'Loading banner...'};
}
```
Returns: `{ isLoaded, error }`
---
# Interstitial Ads
> Integrate CloudX interstitial ads in React Native apps
Interstitial ads are full-screen ads shown at natural transition points.
```typescript
import { CloudXInterstitialAd } from 'cloudx-react-native';
const AD_UNIT_ID = 'level_complete';
CloudXInterstitialAd.addAdLoadedEventListener((adInfo) => {
console.log('Interstitial loaded from', adInfo.networkName);
});
CloudXInterstitialAd.addAdLoadFailedEventListener((error) => {
console.log('Interstitial load failed:', error.code, error.message);
});
CloudXInterstitialAd.addAdDisplayedEventListener((adInfo) => {
console.log('Interstitial displayed');
});
CloudXInterstitialAd.addAdFailedToDisplayEventListener((error) => {
console.log('Interstitial display failed:', error.code, error.message);
});
CloudXInterstitialAd.addAdClickedEventListener((adInfo) => {
console.log('Interstitial clicked');
});
CloudXInterstitialAd.addAdHiddenEventListener(() => {
CloudXInterstitialAd.loadAd(AD_UNIT_ID);
});
CloudXInterstitialAd.addAdRevenuePaidListener((adInfo) => {
console.log('Revenue:', adInfo.revenue);
});
CloudXInterstitialAd.loadAd(AD_UNIT_ID);
const isReady = await CloudXInterstitialAd.isAdReady(AD_UNIT_ID);
if (isReady) {
CloudXInterstitialAd.showAd(AD_UNIT_ID);
// Or with optional placement and custom data:
// CloudXInterstitialAd.showAd(AD_UNIT_ID, 'level_complete', 'custom_data');
} else {
console.warn('Interstitial ad not ready yet');
}
// Destroy when no longer needed.
CloudXInterstitialAd.destroyAd(AD_UNIT_ID);
```
## React Hook
```typescript
import { useCloudXInterstitial } from 'cloudx-react-native';
function GameScreen() {
const { isLoaded, isLoading, error, load, show, destroy } =
useCloudXInterstitial('level_complete');
useEffect(() => {
load();
}, [load]);
return (