Implement a Custom GMA Adapter for iOS

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 conform to the GADRTBAdapter protocol.

This article uses the following placeholders in code examples which you should define yourself as part of the adapter creation process:

  • YourRtbAdapter: Replace with your own adapter class name (e.g. AcmeMediationAdapter, PublisherRtbAdapter)
  • 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 identity envelopes independently. The adapter only reads from this layer and not call LiveRamp APIs itself. See “ATS API Implementation Guide for Mobile Publishers” for more information.
  • Google Mobile Ads 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 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. Implement the adapter class in your app.
  3. Update your app dependencies.
  4. Make sure envelope is present at ad time.
  5. Validate the adapter on device.

Migrating From the ATS Mobile SDK

If you have previously leveraged LRAtsMediationAdapter as part of the ATS Mobile SDK, you must:

  • Replace the adapter with your own GADRTBAdapter class.
  • Replace the LRAtsMediationAdapter class name in xcframework to the new class name in your app.

Refer to the table below to see how LRAtsMediationAdapter compares with your own adapter.

LRAtsMediationAdapterYour RTB Adapter
Method[[LRAts shared] status] == LRStatusReady in setUpWithConfigurationcompletionHandler(nil) only
Envelope supply[LRAts shared] getEnvelopeWithCompletion:YourEnvelopeManager getEnvelopeWithCompletion:
Signal priorityenvelope27 then envelope then ""Same priority in collectSignalsForRequestParameters
ErrorNSError code 101 on failureNSError code 101 on failure
Where it's shippedLiveRamp's xcframeworkYour app target; class name registered in GAM

1. Choose and Register Your Adapter Class Name in GAM

GAM discovers your adapter by the class name you register in the Ad Manager UI. Engineering and ad ops must agree on the class name before implementation, because the string in GAM and the class name in your app must match exactly.

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 class name exactly as you will define it in code—for example, YourRtbAdapter.
  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 previously used the LiveRamp ATS SDK, update every yield group, mediation or custom event settings that referenced to the old LRAtsMediationAdapter class name and replace it with your new class name.

HandoffOwnerValue
Adapter class nameEngineeringThe chosen class name. For example, YourRtbAdapter
Class name field in Google Ad ManagerAd opsSame string as the adapter class name.
Ad unitsAd opsUnits tied to this yield group

2. Implement the Adapter Class

Create a new class in your app target—not in a separate library or framework. The class must conform to Google's GADRTBAdapter protocol, using the class name you registered in GAM UI from the previous step.

At runtime, GAM calls your adapter in two stages:

  1. Initialization — When GADMobileAds.sharedInstance().start() is called, the GMA SDK loads your adapter by class name and calls setUpWithConfiguration once.
  2. Per-bid signal collection — When an ad loads, the GMA SDK calls collectSignalsForRequestParameters. Your adapter reads the current envelope and returns it via completionHandler. GAM attaches the signal to the outgoing bid request.

If collectSignalsForRequestParameters is called before an envelope is available, return an error via completionHandler. Do not use setUpWithConfiguration to validate envelope state.

For details on the methods you implement, see the table below:

MethodPurpose
+setUpWithConfiguration:completionHandler:Called once when the Mobile Ads SDK initializes to report success so GAM can use your adapter for later requests. No SDK ready-state check required.
-collectSignalsForRequestParameters:completionHandler:Called before each bid. Returns envelope27, or envelope (type 19) or “” via completionHandler. Passes an error if no envelope is available.
+adSDKVersion / +adapterVersionReturns a GADVersionNumber used for reporting and debugging.
+networkExtrasClassReturns Nil if you do not use network extras.

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 completionHandler(nil, error)

Objective-C

Create YourRTBAdapter.m in your app target. Rename YourRtbAdapter and YourEnvelopeManager in the implementation reference below to fit your project before use.

YourRtbAdapter.m (filename = your class name)

// YourRtbAdapter.h
 #import <Foundation/Foundation.h>
 #import <GoogleMobileAds/GoogleMobileAds.h>

 @interface YourRtbAdapter : NSObject <GADRTBAdapter>
 @end

 // YourRtbAdapter.m
 #import "YourRtbAdapter.h"
 #import "YourEnvelopeManager.h"

 @implementation YourRtbAdapter

 + (GADVersionNumber)adSDKVersion {
 	return (GADVersionNumber){1, 0, 0};
 }

 + (GADVersionNumber)adapterVersion {
 	return (GADVersionNumber){1, 0, 0};
 }

 + (nullable Class<GADAdNetworkExtras>)networkExtrasClass {
 	return Nil;
 }

 + (void)setUpWithConfiguration:(GADMediationServerConfiguration *)configuration
              completionHandler:(GADMediationAdapterSetUpCompletionBlock)completionHandler {
 	completionHandler(nil);
 }

 - (void)collectSignalsForRequestParameters:(GADRTBRequestParameters *)params
                          completionHandler:(GADRTBSignalCompletionHandler)completionHandler {
 	[YourEnvelopeManager getEnvelopeWithCompletion:^(YourEnvelope *envelope, NSError *error) {
     	if (envelope != nil) {
         	NSString *signal = envelope.envelope27 ?: (envelope.envelope ?: @"");
         	completionHandler(signal, nil);
     	} else {
         	NSString *errorMsg = error.localizedDescription ?: @"Unable to return envelope.";
         	NSError *customError = [NSError errorWithDomain:@"com.yourcompany.yourapp" code:101
                 userInfo:@{NSLocalizedDescriptionKey : errorMsg}];
         	completionHandler(nil, customError);
     	}
 	}];
 }

 @end

Swift

Create YourRtbAdapter.swift in your app target. Rename YourRtbAdapter and YourEnvelopeManager in the implementation reference below to fit your project before use.

import GoogleMobileAds

@objc(YourRtbAdapter)
class YourRtbAdapter: NSObject, GADRTBAdapter {

    static func setUp(with configuration: GADMediationServerConfiguration,
                      completionHandler: @escaping GADMediationAdapterSetUpCompletionBlock) {
        // No SDK ready-state check required.
        completionHandler(nil)
    }

    static func adSDKVersion() -> GADVersionNumber {
        GADVersionNumber(majorVersion: 1, minorVersion: 0, patchVersion: 0)
    }

    static func adapterVersion() -> GADVersionNumber { adSDKVersion() }

    static func networkExtrasClass() -> GADAdNetworkExtras.Type? { nil }

    func collectSignals(for params: GADRTBRequestParameters,
                        completionHandler: @escaping GADRTBSignalCompletionHandler) {
        YourEnvelopeManager.getEnvelope { envelope, error in
            if let envelope = envelope {
                completionHandler(envelope.envelope27 ?? envelope.envelope ?? "", nil)
            } else {
                let msg = error?.localizedDescription ?? "Unable to return envelope."
                completionHandler(nil, NSError(domain: "com.yourcompany.yourapp",
                                               code: 101,
                                               userInfo: [NSLocalizedDescriptionKey: msg]))
            }
        }
    }
}

3. Update App Dependencies

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

CocoaPods

# Keep
pod 'Google-Mobile-Ads-SDK', '~> 12.0'

# Remove
# pod 'LRAtsSDK'
# pod 'LRAtsSDKMediationAdapter'

Swift Package Manager

Remove any LRAts packages from your Package.swift file and Xcode project dependencies. Keep the GoogleMobileAds package.

⚠️

Do not declare your adapter in Info.plist. GAM loads the adapter by class name at runtime; no additional registration is needed.


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 layer provides at the time GAM calls collectSignalsForRequestParameters.

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

  • Retrieve envelopes for consented identifiers
  • Refresh envelopes when needed before an ad load
  • Supply the envelope string at collectSignalsForRequestParameters

Call following ATS API endpoints to retrieve and refresh endpoints:

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.

GADMobileAds.sharedInstance().start { status in
    let className = "YourRtbAdapter" // Replace with your actual class name
    if let adapterStatus = status.adapterStatusesByClassName[className] {
        print("Adapter status: \(adapterStatus.state.rawValue)")
    }
}

// After an ad loads:
print("Mediation class: \(ad.responseInfo?.adNetworkClassName ?? "none")")

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

Validation checklist

  • [ ] Adapter class name agreed on and registered in the GAM Class name field
  • [ ] Old LRAtsMediationAdapter removed from GAM configuration
  • [ ] LiveRamp SDK (LRAtsSDK / LRAtsSDKMediationAdapter) removed from CocoaPods or SPM
  • [ ] Your GADRTBAdapter class added to your app target with the same class name as registered in GAM
  • [ ] setUpWithConfiguration always calls completionHandler(nil) — no SDK ready-state check
  • [ ] collectSignalsForRequestParameters passes envelope27envelope19"" in that priority order
  • [ ] adapterStatusesByClassName shows your class name after GADMobileAds.sharedInstance().start()
  • [ ] Envelope is available from your direct API flow before or during ad load