Android SDK - Ad Revenue Tracking

Ad Revenue Attribution

Track ad revenue from mediation platforms and attribute it to the marketing campaigns that acquired your users, providing complete ROI visibility across campaign costs, in-app purchases, and advertising monetization.

Overview

What Is Ad Revenue Attribution

Ad Revenue Attribution connects mobile app advertising revenue to the user acquisition campaigns that drove app installs, enabling you to measure true campaign profitability including ad monetization.

Key Benefits:

  • Unified ROI View: See campaign costs, in-app revenue, and ad revenue in a single dashboard
  • Campaign Optimization: Send ad revenue data back to ad networks to improve bidding and targeting
  • LTV Measurement: Calculate complete user lifetime value including advertising monetization

Data Sources: Ad revenue data typically comes from your mediation platform (e.g., AdMob, AppLovin MAX, IronSource) at either the user level or impression level. Singular supports multiple integration methods to receive this data.

Learn More: See the Ad Revenue Attribution FAQ for comprehensive details on setup, reporting, and troubleshooting.


Implementation Requirements

Critical Guidelines

Required SingularAdData parameters: The constructor arguments adPlatform , currency , and revenue are all required. If any are missing, empty, or invalid, the SDK silently drops the call to Singular.adRevenue() without raising an exception or returning an error. Singular.adRevenue() also silently no-ops when the SDK is not yet initialized — always confirm Singular.init() has run before calling it.

Data Accuracy Is Critical:

  1. Currency Codes: Use three-letter ISO 4217 currency codes (e.g., USD, EUR, INR). Many mediation platforms report in USD—verify your platform's currency before implementation
  2. Validate Before Sending: Always validate revenue and currency data before calling Singular.adRevenue() . Incorrect data cannot be corrected after submission
  3. Platform Differences: Some SDKs report revenue in micros (divide by 1,000,000) while others report in standard currency units. Check your platform's documentation

Setup Steps

Follow these steps to implement ad revenue attribution:

  1. Update SDK: Ensure you're using the latest Singular SDK version
  2. Choose Integration: Select the mediation platform integration below that matches your setup
  3. Implement Callbacks: Add platform-specific paid event listeners to capture revenue data
  4. Validate Data: Test revenue reporting and verify data appears in Singular dashboard

Platform Integrations

AdMob Integration

Track ad revenue from Google AdMob using the paid event listener for impression-level revenue reporting.

Requirements:

Platform Revenue Reporting: AdMob reports revenue differently by platform. Android returns revenue in micros (e.g., $0.005 = 5000). Divide adValue.valueMicros by 1,000,000 to convert to standard currency units before sending to Singular.

Implementation

Set a paid event listener when loading ads to capture revenue data and send it to Singular.

Kotlin Java
import com.singular.sdk.Singular
import com.singular.sdk.SingularAdData
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.rewarded.RewardedAd
import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback

private const val AD_UNIT_ID = "YOUR_AD_UNIT_ID"

class AdManager(private val context: Context) {
    private var rewardedAd: RewardedAd? = null

    fun loadRewardedAd() {
        val adRequest = AdRequest.Builder().build()
        
        RewardedAd.load(context, AD_UNIT_ID, adRequest, 
            object : RewardedAdLoadCallback() {
                override fun onAdLoaded(ad: RewardedAd) {
                    rewardedAd = ad
                    
                    // Set full screen content callback
                    rewardedAd?.fullScreenContentCallback = 
                        object : FullScreenContentCallback() {
                            override fun onAdShowedFullScreenContent() {
                                Log.d("AdManager", "Rewarded ad displayed")
                            }
                            
                            override fun onAdDismissedFullScreenContent() {
                                Log.d("AdManager", "Rewarded ad dismissed")
                            }
                        }
                    
                    // Set paid event listener for revenue tracking
                    rewardedAd?.setOnPaidEventListener { adValue ->
                        // Convert revenue from micros to standard units
                        val revenue = adValue.valueMicros / 1_000_000.0
                        val currency = adValue.currencyCode
                        
                        // Validate revenue data before sending
                        if (revenue > 0 && !currency.isNullOrEmpty()) {
                            val data = SingularAdData(
                                "AdMob",
                                currency,
                                revenue
                            )
                            
                            Singular.adRevenue(data)
                            Log.d("AdManager", "Ad revenue sent: $revenue $currency")
                        } else {
                            Log.w("AdManager", "Invalid revenue: $revenue $currency")
                        }
                    }
                }
                
                override fun onAdFailedToLoad(error: LoadAdError) {
                    Log.e("AdManager", "Ad failed to load: ${error.message}")
                }
            })
    }
}

AppLovin MAX Integration

Share impression-level ad revenue using the AppLovin Impression-Level User Revenue API.

Requirements:

Implementation

Handle revenue messages through the AppLovin Communicator callback.

Kotlin Java
import com.singular.sdk.Singular
import com.singular.sdk.SingularAdData
import com.applovin.communicator.AppLovinCommunicatorMessage

override fun onMessageReceived(message: AppLovinCommunicatorMessage) {
    // Check for revenue events topic
    if (message.topic == "max_revenue_events") {
        val adData: Bundle? = message.messageData
        
        // Extract and validate revenue value
        val revenueValue = adData?.getDouble("revenue", 0.0) ?: 0.0
        
        if (revenueValue > 0) {
            val data = SingularAdData(
                "AppLovin",
                "USD",  // AppLovin typically reports in USD
                revenueValue
            )
            
            Singular.adRevenue(data)
            Log.d("AppLovin", "Ad revenue sent: $revenueValue USD")
        } else {
            Log.w("AppLovin", "Invalid revenue value: $revenueValue")
        }
    }
}

Unity LevelPlay (IronSource) Integration

Track impression-level revenue from ironSource and mediated networks using the IronSource SDK.

Requirements:

  • Implement ironSource SDK (see Getting Started Guide )
  • Enable ARM SDK Postbacks flag in your IronSource dashboard
  • Set impression data listener to receive revenue callbacks

Learn More: See IronSource Ad Revenue Documentation for complete setup details.

Implementation

Implement the impression data success callback to capture and send revenue.

Kotlin Java
import com.singular.sdk.Singular
import com.singular.sdk.SingularAdData
import com.ironsource.mediationsdk.impressionData.ImpressionData

fun onImpressionDataSuccess(impressionData: ImpressionData?) {
    // Validate impression data
    if (impressionData == null) {
        Log.d("IronSource", "No impression data available")
        return
    }
    
    // Extract and validate revenue
    val revenue = impressionData.revenue?.toDouble() ?: 0.0
    if (revenue <= 0) {
        Log.w("IronSource", "Invalid revenue: $revenue")
        return
    }
    
    // Create and send ad revenue data
    val data = SingularAdData(
        "IronSource",
        "USD",  // IronSource typically reports in USD
        revenue
    )
    
    Singular.adRevenue(data)
    Log.d("IronSource", "Ad revenue sent: $revenue USD")
}

TradPlus Integration

Capture ad revenue from TradPlus mediation using the global impression listener.

Requirements:

  • Set global impression listener via TradPlusSdk.setGlobalImpressionListener()
  • Handle onImpressionSuccess callback to receive revenue data
  • Convert eCPM from milli-units to standard currency (divide by 1000)

Implementation

Register a global impression listener to track all ad impressions and revenue.

Kotlin Java
import com.singular.sdk.Singular
import com.singular.sdk.SingularAdData
import com.tradplus.ads.mgr.GlobalImpressionManager
import com.tradplus.ads.base.bean.TPAdInfo

// Set global impression listener
TradPlusSdk.setGlobalImpressionListener(
    object : GlobalImpressionManager.GlobalImpressionListener {
        override fun onImpressionSuccess(tpAdInfo: TPAdInfo?) {
            // Validate ad info
            if (tpAdInfo == null) {
                Log.w("TradPlus", "AdInfo is null")
                return
            }
            
            // Convert eCPM from milli-units to dollars
            val ecpm = tpAdInfo.ecpm ?: run {
                Log.w("TradPlus", "eCPM is null")
                return
            }
            val revenue = ecpm.toDouble() / 1000.0
            
            // Validate revenue
            if (revenue <= 0) {
                Log.w("TradPlus", "Invalid revenue: $revenue")
                return
            }
            
            // Create and send ad revenue data
            val data = SingularAdData(
                "TradPlus",
                "USD",
                revenue
            )
            
            Singular.adRevenue(data)
            Log.d("TradPlus", "Ad revenue sent: $revenue USD")
        }
    }
)

Generic Integration (Other Platforms)

Integrate any mediation platform using the generic SingularAdData interface.

Requirements:

  • Access to impression-level revenue data from your mediation platform
  • Revenue amount in standard currency units (not micros)
  • ISO 4217 currency code (e.g., USD, EUR, INR)

Data Accuracy: Validate revenue and currency data before sending to Singular. Incorrect data cannot be corrected after submission.

Implementation

Create a SingularAdData object with platform name, currency, and revenue, then call Singular.adRevenue() .

Kotlin Java
import com.singular.sdk.Singular
import com.singular.sdk.SingularAdData

fun reportAdRevenue(adPlatform: String, currency: String, revenue: Double) {
    // Validate revenue
    if (revenue <= 0) {
        Log.w("AdRevenue", "Invalid revenue: $revenue")
        return
    }
    
    // Validate currency
    if (currency.isEmpty()) {
        Log.w("AdRevenue", "Invalid currency: $currency")
        return
    }
    
    // Create ad revenue data object
    val data = SingularAdData(
        adPlatform,
        currency,
        revenue
    )
    
    // Send to Singular
    Singular.adRevenue(data)
    Log.d("AdRevenue", "Revenue sent: $revenue $currency from $adPlatform")
}

// Example usage
reportAdRevenue("MyMediationPlatform", "USD", 0.05)

Enriching Ad Data with Additional Attributes

SingularAdData exposes chainable setters for impression-level metadata. Use these to enrich the event with placement, ad unit, mediation grouping, and precision details when your mediation platform provides them. Attributes set by these methods are surfaced in Singular reporting and downstream postbacks.

Method Description
withNetworkName(String networkName) Overrides the network name (defaults to the ad platform passed to the constructor). Use for mediation where the underlying network differs from the mediation platform.
withAdType(String adType) Ad format (for example, "Rewarded" , "Interstitial" , "Banner" ).
withAdGroupType(String adGroupType) Ad group categorization defined by your mediation platform.
withImpressionId(String impressionId) Unique identifier for the impression, used for deduplication and reconciliation.
withAdPlacementName(String adPlacementName) Human-readable placement name (for example, "level_complete" ).
withAdUnitId(String adUnitId) Platform-specific ad unit identifier (for example, "ca-app-pub-..." for AdMob).
withAdUnitName(String adUnitName) Human-readable ad unit name.
withAdGroupId(String adGroupId) Ad group identifier from the mediation platform.
withAdGroupName(String adGroupName) Human-readable ad group name.
withAdGroupPriority(String adGroupPriority) Priority assigned to the ad group in the mediation waterfall.
withPrecision(String precision) Revenue precision indicator (for example, "publisher_provided" , "estimated" , "exact" ) when supported by the platform.
withPlacementId(String placementId) Platform-specific placement identifier.
withLimitDataSharing(boolean shouldLimitDataSharing) Overrides the global data-sharing limit for this single ad event, for example when a user has opted out of personalized advertising.

Kotlin example:

val data = SingularAdData("AdMob", "USD", 0.05)
    .withAdUnitId("ca-app-pub-123456789/1234567890")
    .withAdType("Rewarded")
    .withAdPlacementName("level_complete")
    .withImpressionId("imp_abc123")
    .withPrecision("publisher_provided")

Singular.adRevenue(data)

Java example:

SingularAdData data = new SingularAdData("AdMob", "USD", 0.05)
    .withAdUnitId("ca-app-pub-123456789/1234567890")
    .withAdType("Rewarded")
    .withAdPlacementName("level_complete")
    .withImpressionId("imp_abc123")
    .withPrecision("publisher_provided");

Singular.adRevenue(data);

Testing and Validation

Verify Revenue Reporting

Test your ad revenue implementation to ensure data flows correctly to Singular.

  1. Check Logs: Verify revenue callback logs appear with correct values and currency
  2. Test Ads: Load and display test ads to trigger revenue events
  3. Dashboard Verification: Confirm revenue appears in Singular dashboard within 24 hours
  4. Data Accuracy: Validate revenue amounts match your mediation platform reports

Troubleshooting: If revenue doesn't appear in Singular, check that:

  • Ad revenue attribution is enabled in your Singular account
  • Revenue values are greater than 0
  • Currency codes are valid ISO 4217 codes
  • Platform name matches Singular's expected format