Implement a Custom GMA Adapter for Android

LiveRamp's integration with Google's Secure Signals lets publishers pass RampID envelopes as signals to Google Ad Manager's (GAM) Open Bidding. On mobile applications, the Google Mobile Ads (GMA) SDK collects those signals through classes that extend Google's RtbAdapter.

This guide uses the following placeholders in code examples, which you should replace with your own names as part of the adapter creation process:

  • YourRtbAdapter: Replace with your own adapter class name (for example, AcmeMediationAdapter, PublisherRtbAdapter).
  • com.yourcompany.yourapp:Replace with your application package name or application ID.
  • YourEnvelopeManager: Replace with your envelope helper.

Prerequisites

Before starting, confirm the following are in place:

  • ATS API integration is complete. Your app retrieves, refreshes, and caches RampID envelopes independently of the SDK. The adapter only reads from this layer — it does not call LiveRamp APIs itself. For instructions, see "ATS API Implementation Guide for Mobile Publishers".
  • Google Mobile Ads (GMA) SDK is integrated. Your app already initializes MobileAds and loads ads.
  • GAM access is available. You or your ad ops team can edit yield groups and custom events to register the adapter class name and enable Secure Signals for LiveRamp. See Google's article "Share secure signals with bidders​".
  • Agree on a class name. The adapter class name registered in the Google Ad Manager UI must match with what's implemented in your code. Work with the ad ops team to be aligned on a class name before implementation.

Overall Steps

  1. Choose and register your adapter class name in GAM.
  2. Update your app dependencies.
  3. Implement the adapter class in your app.
  4. Make sure the envelope is present at ad time.
  5. Validate the adapter on device.

Migrating from the ATS Mobile SDK

If you previously used com.liveramp.ats.LRAtsMediationAdapter as part of the ATS Mobile SDK, you must:

  • Replace the adapter with your own RtbAdapter subclass.
  • Replace the com.liveramp.ats.LRAtsMediationAdapter class name in your GAM configuration with the fully qualified class name (FQCN) of your new adapter.

When you used the ATS Mobile SDK, GAM loaded LRAtsMediationAdapter from the AAR to collect RTB (real-time bidding) signals—the identity envelope string attached to your ad bid request. After you remove the SDK, you provide that adapter yourself as a normal class in your application.

See the table below for comparison:

SDK (LRAtsMediationAdapter)Your adapter
MethodLRAtsManager.getSdkStatus() == READY check in initialize()Call onInitializationSucceeded() only without checking SDK ready state
Envelope supplyEnvelope retrieval via LRAtsManager.getEnvelope(callback)From your ATS API integration in YourEnvelopeManager.getEnvelope(callback)
Signal priorityenvelope27 , then envelope (type 19), then ""Same priority in collectSignals()
ErrorAdError(101,..)AdError(101,..)
Where it's shippedLiveRamp AARYour app module; FQCN (Fully Qualified Class Name) registered in GAM

1. Choose and Register Your Adapter Class Name in GAM

GAM discovers your adapter by the fully qualified class name (FQCN) you register in the Ad Manager UI. Engineering and ad ops must agree on this name before implementation — the string in GAM and the class name in your code must match exactly (case-sensitive).

Work with your ad ops team to complete these steps in Google Ad Manager:

  1. In GAM, select AdminYield groups (or your existing yield group setup for these ad units).
  2. Add or edit the Custom event used for this RTB/identity signal.
  3. In the Class name field, enter your adapter FQCN exactly as you will define it in code. The FQCN also includes your application ID, e.g. com.yourcompany.yourapp.ads.YourRtbAdapter (replace YourRtbAdapter with the class name your team chose).
  4. Enter a Label that identifies this signal for your team.
  5. Leave Parameter empty unless your implementation requires it.
  6. Save and publish.

See Google's article "Create and manage yield groups" for more information.

⚠️

If you've used the ATS Mobile SDK, update every yield group, mediation, or custom event setting that referenced com.liveramp.ats.LRAtsMediationAdapter and replace it with your new FQCN (e.gcom.yourcompany.yourapp.ads.YourRtbAdapter) .

HandoffOwnerValue
Adapter FQCNEngineeringcom.yourcompany.yourapp.ads.YourRtbAdapter
GAM Class name fieldAd opsSame string as FQCN
Ad unitsAd opsUnits tied to this yield group

2. Implement the Adapter Class

Create a new class in your app module — not in a third-party library. The class must extend Google's RtbAdapter and use the exact FQCN you registered in GAM.

At runtime, GAM calls your adapter in two stages:

  1. Initialization: When MobileAds.initialize() is called, the GMA SDK loads your adapter by FQCN and calls initialize() once.
  2. Per-bid signal collection: When an ad loads, the GMA SDK calls collectSignals(). Your adapter reads the current envelope from your envelope manager and returns it via the signal callbacks. GAM attaches the signal to the outgoing bid request.

If collectSignals() is called before an envelope is available, return signalCallbacks.onFailure(). Do not rely on initialize() to validate envelope state.

MethodPurpose
initialize()Called once when the Mobile Ads SDK starts. Call onInitializationSucceeded() to report success. No SDK ready-state check required.
collectSignals()Called before each bid. Returns envelope27, or envelope (type 19), or "" via onSuccess. Call onFailure if no envelope is available.
getVersionInfo() / getSDKVersionInfo()Return a VersionInfo used for mediation reporting and debugging.

Signal priority

When returning the envelope string, apply this priority order:

  1. envelope27 if present
  2. envelope (type 19) if envelope27 is absent
  3. Empty string "" if neither is present
  4. On error, call signalCallbacks.onFailure(AdError(101, ...))

Kotlin

Create YourRtbAdapter.kt in your app module. Rename YourRtbAdapter, the package, and YourEnvelopeManager to match your project before use.

package com.yourcompany.yourapp.ads

import android.content.Context
import com.google.android.gms.ads.AdError
import com.google.android.gms.ads.VersionInfo
import com.google.android.gms.ads.mediation.InitializationCompleteCallback
import com.google.android.gms.ads.mediation.MediationConfiguration
import com.google.android.gms.ads.mediation.rtb.RtbAdapter
import com.google.android.gms.ads.mediation.rtb.RtbSignalData
import com.google.android.gms.ads.mediation.rtb.SignalCallbacks
import com.yourcompany.yourapp.envelope.YourEnvelopeManager

class YourRtbAdapter : RtbAdapter() {  // rename class per your requirements

    override fun initialize(
        context: Context,
        initializationCompleteCallback: InitializationCompleteCallback,
        mediationConfigurations: List<MediationConfiguration>
    ) {
        // No LRAtsManager / SDK ready check — always succeed
        initializationCompleteCallback.onInitializationSucceeded()
    }

    override fun collectSignals(
        rtbSignalData: RtbSignalData,
        signalCallbacks: SignalCallbacks
    ) {
        // Same callback pattern as LRAtsManager.getEnvelope { envelope, error -> }
        YourEnvelopeManager.getEnvelope { envelope, error ->
            if (envelope != null) {
                if (envelope.envelope27 != null) {
                    signalCallbacks.onSuccess(envelope.envelope27!!)
                } else {
                    signalCallbacks.onSuccess(envelope.envelope ?: "")
                }
            } else {
                val errorMsg = error?.message ?: "Unable to return envelope."
                signalCallbacks.onFailure(
                    AdError(101, errorMsg, "com.yourcompany.yourapp")
                )
            }
        }
    }

    override fun getSDKVersionInfo(): VersionInfo = adapterVersionInfo()

    override fun getVersionInfo(): VersionInfo = adapterVersionInfo()

    private fun adapterVersionInfo(): VersionInfo {
        // Return your adapter or app version, e.g. VersionInfo(1, 0, 0)
        return VersionInfo(1, 0, 0)
    }

Java

Create YourRtbAdapter.java in your app module. Rename the class, package, and YourEnvelopeManager to match your project before use.

package com.yourcompany.yourapp.ads;

import android.content.Context;
import androidx.annotation.NonNull;
import com.google.android.gms.ads.AdError;
import com.google.android.gms.ads.VersionInfo;
import com.google.android.gms.ads.mediation.InitializationCompleteCallback;
import com.google.android.gms.ads.mediation.MediationConfiguration;
import com.google.android.gms.ads.mediation.rtb.RtbAdapter;
import com.google.android.gms.ads.mediation.rtb.RtbSignalData;
import com.google.android.gms.ads.mediation.rtb.SignalCallbacks;
import com.yourcompany.yourapp.envelope.YourEnvelopeManager;
import java.util.List;

public class YourRtbAdapter extends RtbAdapter {

    @Override
    public void initialize(
            @NonNull Context context,
            @NonNull InitializationCompleteCallback initializationCompleteCallback,
            @NonNull List<MediationConfiguration> mediationConfigurations) {
        initializationCompleteCallback.onInitializationSucceeded();
    }

    @Override
    public void collectSignals(
            @NonNull RtbSignalData rtbSignalData,
            @NonNull SignalCallbacks signalCallbacks) {
        YourEnvelopeManager.getEnvelope((envelope, error) -> {
            if (envelope != null) {
                if (envelope.getEnvelope27() != null) {
                    signalCallbacks.onSuccess(envelope.getEnvelope27());
                } else {
                    signalCallbacks.onSuccess(
                            envelope.getEnvelope() != null ? envelope.getEnvelope() : "");
                }
            } else {
                String errorMsg = error != null ? error.getMessage() : "Unable to return envelope.";
                signalCallbacks.onFailure(new AdError(101, errorMsg, "com.yourcompany.yourapp"));
            }
        });
    }

    @NonNull
    @Override
    public VersionInfo getSDKVersionInfo() {
        return new VersionInfo(1, 0, 0);
    }

    @NonNull
    @Override
    public VersionInfo getVersionInfo() {
        return getSDKVersionInfo();
    }
}

3. Update App Dependencies

Remove the LiveRamp ATS SDK and keep only the Google Mobile Ads SDK.

Gradle

// Keep
implementation "com.google.android.gms:play-services-ads:<your-version>"

// Remove
// implementation "com.liveramp:lrats:<version>"
// implementation "com.liveramp:lrats-mediation-adapter:<version>"

ProGuard

Add a keep rule using your adapter FQCN to prevent it from being stripped in a production build:

-keep class com.yourcompany.yourapp.ads.YourRtbAdapter { *; }

Do not declare your adapter in AndroidManifest.xml or reference it in your application code. GAM loads the class by FQCN at runtime when the Mobile Ads SDK initializes and when an ad is requested.

⚠️

Do not declare your adapter in AndroidManifest.xml or reference it in your application code. GAM loads the class by FQCN at runtime when the Mobile Ads SDK initializes and when an ad is requested.


4. Make Sure Envelope is Present at Ad Time

The adapter does not call LiveRamp APIs itself to retrieve or refresh envelopes. It only reads whatever your envelope manager provides at the time GAM calls collectSignals().

Because of this, your ATS API implementation should be configured to:

  • Retrieve envelopes for consented identifiers.
  • Refresh envelopes when needed before an ad load.
  • Supply the envelope string at collectSignals().
ActionEndpoint
Retrievehttps://api.rlcdn.com/api/identity/v2/envelope/
Refreshhttps://api.rlcdn.com/api/identity/v2/envelope/refresh/

For more information, see "ATS API Implementation Guide for Mobile Publishers​".


5. Verify the Adapter on Device

After implementing the class, confirm that GAM recognizes your adapter at runtime.

MobileAds.initialize(context) { status ->
    val fqcn = "com.yourcompany.yourapp.ads.YourRtbAdapter" // your actual FQCN
    Log.d("Ads", "Adapter status: ${status.adapterStatusMap[fqcn]}")
}

// After an ad loads:
Log.d("Ads", "Mediation class: ${ad.responseInfo?.mediationAdapterClassName}")

You should see your FQCN in the output. If you see com.liveramp.ats.LRAtsMediationAdapter instead, the old SDK is still linked or GAM still points to the old class name.

Validation checklist

  • [ ] Adapter FQCN agreed on and registered in the GAM Class name field
  • [ ] Old com.liveramp.ats.LRAtsMediationAdapter removed from GAM configuration
  • [ ] LiveRamp SDK (LRAts) removed from Gradle
  • [ ] Your RtbAdapter class added to your app module with the same FQCN as registered in GAM
  • [ ] initialize() always calls onInitializationSucceeded() — no SDK ready-state check
  • [ ] collectSignals() passes envelope27envelope19"" in that priority order
  • [ ] adapterStatusMap shows your FQCN after MobileAds.initialize()
  • [ ] Envelope is available from your ATS API flow before or during ad load
  • [ ] ProGuard keep rule added for your adapter FQCN