ATS API Implementation Guide for Mobile Publishers
This article outlines the steps to implement the ATS Envelope API to retrieve and manage consented envelopes for mobile use cases.
1. Create an ATS Placement in Console
Create an ATS placement in LiveRamp Console to obtain a Placement ID that will be used in your ATS requests. During this process, you must include the bundle IDs and the countries/regions where you want to run ATS. Note that your ATS placement must be in an 'Approved' state to retrieve valid envelopes.
To learn how you can obtain a Placement ID, see "Create an ATS Placement".
2. Collect Valid Consent Strings
Based on the user's location, ATS Envelope API may require a standardized consent string to retrieve envelopes. Make sure you have a proper Consent Management Platform (CMP) in place that can provide a valid consent string.
See the consent requirement for retrieving envelopes below:
- For EU: Positive consent required and must be TCF v2.3 compatible.
- For US: Consent not required, but recommended in conjunction with the Global Privacy Platform (GPP).
- Other countries: Consent signal not required.
- For iOS devices: ATT consent required.
For more information, see "Provide a Valid Consent String".
3. Prepare the Identifiers
To improve match rate to audience data, we recommend pre-processing your data to ensure valid identifiers are provided when calling the ATS Envelope API. ATS supports the following identifier types, which you must indicate correctly via the it parameter in your call:
- Hashed email (
it=4)- SHA256 (recommended if you are only able to hash once)
- SHA1
- MD5
- Hashed phone (
it=11)- SHA1
- Custom ID (
it=15)
Before hashing, pre-process your identifiers to ensure valid identifiers are being sent. For more information on data normalization, see "Prepare the Identifiers".
Email Normalization and Hashing (iOS)
func normalizeEmail(_ email: String) -> String {
var cleaned = email.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
let parts = cleaned.split(separator: "@", omittingEmptySubsequences: false)
guard parts.count == 2 else { return cleaned }
var username = String(parts[0].split(separator: "+").first ?? parts[0])
let domain = String(parts[1])
if domain == "gmail.com" {
username = username.replacingOccurrences(of: ".", with: "")
}
return "\(username)@\(domain)"
}
// Usage: hash normalized email with SHA1, SHA256, MD5
let normalized = normalizeEmail("[email protected]")
// → "[email protected]"
let sha1 = sha1Hash(normalized)
let sha256 = sha256Hash(normalized)
let md5 = md5Hash(normalized)
Phone Number Formatting and Hashing (iOS)
func formatPhone(_ phone: String) -> String {
var digits = phone.trimmingCharacters(in: .whitespacesAndNewlines)
.replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)
digits = digits.replacingOccurrences(of: "^0+(?!$)", with: "", options: .regularExpression)
return digits
}
// Usage: hash formatted phone with SHA1
let formatted = formatPhone("+1 (555) 123-4567")
// → "15551234567"
let sha1 = sha1Hash(formatted)
4. Call the ATS Envelope API
Call the v2 ATS Envelope API endpoint pattern with the required request parameters. See "Call the ATS Envelope API" for more details on each required parameter.
Make sure to only call the LiveRamp API with a positive consent string for users in the EU.
In the
originheader of your ATS requests, you must include a bundle ID that is tied to an approved Placement ID.
Android
val client = OkHttpClient()
val url = "https://api.rlcdn.com/api/identity/v2/envelope?pid=14&it=4&iv=YOUR_HASHED_IDENTIFIER&atype=4&ct=4&cv=YOUR_TCF_STRING&gpp_sid=7&gpp=YOUR_GPP_STRING"
val request = Request.Builder()
.url(url)
.get()
.addHeader("origin", "com.example.app")
.addHeader("accept", "application/json")
.build()
val response = client.newCall(request).execute()
val body = response.body?.string()
OkHttpClient client = new OkHttpClient();
String url = "https://api.rlcdn.com/api/identity/v2/envelope?pid=14&it=4&iv=YOUR_HASHED_ID&atype=4&ct=4&cv=YOUR_TCF_STRING";
Request request = new Request.Builder()
.url(url)
.get()
.addHeader("origin", "com.example.app")
.addHeader("accept", "application/json")
.build();
Response response = client.newCall(request).execute();
String body = response.body() != null ? response.body().string() : null;
iOS
import Foundation
let baseURL = "https://api.rlcdn.com/api/identity/v2/envelope"
let pid = 14
let it = 4 // identifier type: 4=email, 11=phone, 15=custom id
let iv = "sha256_or_sha1_hex_of_email"
let atype = 4 // 4 = app
var components = URLComponents(string: baseURL)!
components.queryItems = [
URLQueryItem(name: "pid", value: "\(pid)"),
URLQueryItem(name: "it", value: "\(it)"),
URLQueryItem(name: "iv", value: iv),
URLQueryItem(name: "atype", value: "\(atype)")
]
// Add consent if needed: ct, cv, gpp_sid, gpp
var request = URLRequest(url: components.url!)
request.httpMethod = "GET"
request.setValue("com.example.app", forHTTPHeaderField: "origin")
request.setValue("application/json", forHTTPHeaderField: "accept")
URLSession.shared.dataTask(with: request) { data, response, error in
let body = data.flatMap { String(data: $0, encoding: .utf8) }
// Handle response
}.resume()
#import <Foundation/Foundation.h>
NSString *baseURL = @"https://api.rlcdn.com/api/identity/v2/envelope";
NSInteger pid = 14;
NSInteger it = 4; // 4=email, 11=phone, 15=custom id
NSString *iv = @"YOUR_HASHED_ID";
NSInteger atype = 4;
NSString *urlString = [NSString stringWithFormat:@"%@?pid=%ld&it=%ld&iv=%@&atype=%ld",
baseURL, (long)pid, (long)it, [iv stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet], (long)atype];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"GET";
[request setValue:@"com.example.app" forHTTPHeaderField:@"origin"];
[request setValue:@"application/json" forHTTPHeaderField:@"accept"];
[[NSURLSession.sharedSession dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString *body = data ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : nil;
}] resume];
Server-to-Server IntegrationsWhen retrieving or refreshing the ATS Envelopes, publishers integrating server-to-server are required to include the
X-Forwarded-Forheader to ensure that the client-side IP address is within the approved countries tied to the ATS Placement.For example:
curl --request GET \ --url 'https://api.rlcdn.com/api/identity/v2/envelope?pid=14&it=4&iv=38CFDA7F9B55B7F74B6D3D143CBB7661DA9BFEB4683473A5813B220A571A1E37&ct=4&cv=CPUCbs9PUDXghADABCENCBCoAP_AAEJAAAAADGwBAAGABPADCAY0BjYAgADAAngBhAMaAAA.YAAAAAAAA4AA' \ --header 'Origin: https://example.com' \ --header 'X-Forwarded-For: 203.0.113.195' \ --header 'accept: application/json'
Responses
For opted-in users, a successful ATS response returns HTTP 200 with one or more envelopes (depending on your integration) in JSON format.
{"envelopes":[{"type":19,"source":"envelopeLiveramp","value":"Aqs3wtOuqIRic78_s4Nqilcr0MGn0cDvKzvpEhSGVjwV8Ci0Jy73B1bkfpv03GwK2uyKbME4kSAtVFn_5sCLWstyHTnEWYurbjLXLpgtf9MfLP1AeDTUQ_0ESnD3x1pr6gYfBOOUH8BPhbfscxdaGUA-_sacPab3nzc05koLhbqrJIQC7JBraEbpOJPDAHp9f44DXIXm","err":null},{"type":25,"source":"pairIds","value":"WyJBeVhiNUF0dmsvVS8xQ1d2ejJuRVk5aFl4T1g3TVFPUTJVQk1BMFdiV1ZFbSJd","err":null}]}For opted-out users, the call will return a 204 response with no content.
For more information, see "ATS Envelope API Responses".
5. Store the Envelope with a Timestamp
Publishers can store envelopes retrieved from mobile devices using a local database or other storage mechanisms, such as UserDefaults, property lists (plist), or the Keychain on iOS, and SharedPreferences on Android.
When managing envelopes, note the following key points:
- Expiry: An envelope should be considered expired if the current time (
now) minus thecreatedAttimestamp exceeds the pre-defined Time-To-Live (TTL), e.g., 15 days (in milliseconds). - Refresh: An envelope is due for a refresh if the current time (
now) minus thelastRefreshTimeexceeds the designated refresh interval, e.g., 15 minutes (in milliseconds).
The table below lists the minimum required structure for storing envelope data. Depending on your application design, it may hold a single row or one row per user.
| Column | Type | Description |
|---|---|---|
| id | Long | Primary key (auto-increment) |
| envelope | String | The LiveRamp primary ATS envelope (type 19). |
| envelope24 | String | The Meta envelope (type 24). |
| envelope25 | String | The PAIR envelope (type 25). |
| envelope26 | String | The ATS Direct envelope (type 26). |
| envelope27 | String | The Google SSP envelope (type 27). |
| lastRefreshTime | Long | Timestamp (in milliseconds) of the most recent envelope refresh. |
| createdAt | Long | Timestamp (in milliseconds) when the envelope was first generated. |
| userId (optional) | Identifier needed if you store multiple envelopes, one for each user. |
Returning Stored EnvelopesFor troubleshooting purposes and as required by vendors, such as Prebid.js, you must implement a mock function that returns envelope to the caller from the application.
6. Implement Envelope Refresh
Refreshing envelopes is an important step to keep the match rate as high as possible for your users. Calling the Refresh Envelope endpoint will reset the envelope expiration to the maximum allowed value, after which you need to store the newly obtained envelope and update the timestamp in storage.
Android
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url("https://api.rlcdn.com/api/identity/v2/envelope/refresh?pid=14&it=19&iv=ApN4iB_v5QsHRLDYDvEPmdhbdaknzxNA3-4Al2d_JFnP6IlH9cApY45f3ohEbriGAzRGVmnizG1iE8bV9MK66Dde9wCektrTCHCD8wKmRkimgOJQyfzwWRfMljKUlcN3TE0k&ct=4&gpp_sid=7&cv=CPUCbs9P...&gpp=DBABLA~...&atype=4")
.get()
.addHeader("origin", "com.example.app")
.addHeader("accept", "application/json")
.build();
Response response = client.newCall(request).execute();
String body = response.body() != null ? response.body().string() : null;
val client = OkHttpClient()
val request = Request.Builder()
.url("https://api.rlcdn.com/api/identity/v2/envelope/refresh?pid=14&it=19&iv=ApN4iB_v5QsHRLDYDvEPmdhbdaknzxNA3-4Al2d_JFnP6IlH9cApY45f3ohEbriGAzRGVmnizG1iE8bV9MK66Dde9wCektrTCHCD8wKmRkimgOJQyfzwWRfMljKUlcN3TE0k&ct=4&gpp_sid=7&cv=CPUCbs9PUDXghADABCENCBCoAP_AAEJAAAAADGwBAAGABPADCAY0BjYAgADAAngBhAMaAAA.YAAAAAAAA4AA&gpp=DBABLA~BVQqAAAAAAA&atype=4")
.get()
.addHeader("origin", "com.example.app")
.addHeader("accept", "application/json")
.build()
val response = client.newCall(request).execute()
val body = response.body?.string()
iOS
let refreshURL = "https://api.rlcdn.com/api/identity/v2/envelope/refresh"
var components = URLComponents(string: refreshURL)!
components.queryItems = [
URLQueryItem(name: "pid", value: "14"),
URLQueryItem(name: "it", value: "19"),
URLQueryItem(name: "iv", value: "ApN4iB_v5QsHRLDYDvEPmdhbdaknzxNA3-4Al2d_JFnP6IlH9cApY45f3ohEbriGAzRGVmnizG1iE8bV9MK66Dde9wCektrTCHCD8wKmRkimgOJQyfzwWRfMljKUlcN3TE0k"),
URLQueryItem(name: "ct", value: "4"),
URLQueryItem(name: "gpp_sid", value: "7"),
URLQueryItem(name: "cv", value: "CPUCbs9PUDXghADABCENCBCoAP_AAEJAAAAADGwBAAGABPADCAY0BjYAgADAAngBhAMaAAA.YAAAAAAAA4AA"),
URLQueryItem(name: "gpp", value: "DBABLA~BVQqAAAAAAA"),
URLQueryItem(name: "atype", value: "4")
]
var request = URLRequest(url: components.url!)
request.httpMethod = "GET"
request.setValue("com.example.app", forHTTPHeaderField: "origin")
request.setValue("application/json", forHTTPHeaderField: "accept")
URLSession.shared.dataTask(with: request) { data, _, _ in
let body = data.flatMap { String(data: $0, encoding: .utf8) }
}.resume()
NSString *refreshURL = @"https://api.rlcdn.com/api/identity/v2/envelope/refresh?pid=14&it=19&iv=ApN4iB_v5QsHRLDYDvEPmdhbdaknzxNA3-4Al2d_JFnP6IlH9cApY45f3ohEbriGAzRGVmnizG1iE8bV9MK66Dde9wCektrTCHCD8wKmRkimgOJQyfzwWRfMljKUlcN3TE0k&ct=4&gpp_sid=7&cv=CPUCbs9P...&gpp=DBABLA~...&atype=4";
NSURL *url = [NSURL URLWithString:refreshURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"GET";
[request setValue:@"com.example.app" forHTTPHeaderField:@"origin"];
[request setValue:@"application/json" forHTTPHeaderField:@"accept"];
[[NSURLSession.sharedSession dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSString *body = data ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : nil;
}] resume];
When you call the Refresh Envelope API on an expired envelope; you will receive a 204 response. In this case, you'll need to discard the expired envelope and fetch another envelope with the ATS Envelope API.
7. Pass Envelopes into Monetization Workflows
Once ATS is set up, you can now configure the programmatic advertising solution to pass your envelopes into downstream workflows and monetize your inventory. LiveRamp is working with several partners that support setting identity envelopes in their SDKs, and passing them into their programmatic bidstreams.
Prebid (SDK)
You can pass envelopes a Prebid server instance from the Prebid iOS/Android SDK by adding the envelope as an ExternalUserId .
externalUserIdArray.add(new ExternalUserId("liveramp.com", "<liverampEnvelope>", null, null));externalUserIdArray.append(ExternalUserId(source: "liveramp.com", identifier: "<liverampEnvelope>"))For more information, see the Prebid SDK documentation for iOS or Android SDK.
InMobi
InMobi allows in-app publishers to pass identity envelopes into InMobi's exchange using their UnifID service. There are two ways you can pass envelopes to InMobi:
- Use the ATS Envelope API to generate the envelopes and pass them to InMobi's UnifID service using
setPublisherProvidedUnifiedId.
val unifiedIds = JSONObject().put("liveramp.com", envelope)
InMobiSdk.setPublisherProvidedUnifiedId(unifiedIds)var idDictionary = ["liveramp.com": envelope]
IMSdk.self.setPublisherProvidedUnifiedId(idDictionary)- Use InMobi’s own SDK to generate and pass envelopes. See InMobi's SDK Specifications to learn how.
Pubmatic
iF you are working with Pubmatic, you can pass envelopes through Pubmatic OpenWrap SDK to configured OpenWrap partners.
// Set the envelope here
// It MUST be liveramp.com to work
var userId = POBExternalUserId(source: "liveramp.com", andId: "<liverampEnvelope>")
// Add the ID to OW SDK
OpenWrapSDK.addExternalUserId(userId)// Set the envelope here
// It MUST be liveramp.com to work
POBExternalUserId adServerUserId = new POBExternalUserId("liveramp.com","<liverampEnvelope>");
// Add the ID to OW SDK
OpenWrapSDK.addExternalUserId(adServerUserId);See the following documentation in Pubmatic's portal to learn more (login to Pubmatic required):
Nimbus
Nimbus SDK allows you to pass envelopes to Nimbus' exchange. There are two ways you can do this:
-
Pass envelopes directly to Nimbus SDK. For example:
NimbusAdManager.addExtendedId( source = "liveramp.com", id = envelope, extensions = mapOf("rtiPartner" to "idl"), )var extendedId = NimbusExtendedId(source: "liveramp.com", id: envelope) extendedId.extensions = ["rtiPartner": NimbusCodable("idl")] /* Setting extendedIds more than once will clear the previous results and is nil by default */ NimbusAdManager.extendedIds = [extendedId] -
Use the Nimbus-LiveRamp extension to generate and pass envelopes. For more information, see Nimbus' documentation for Android and iOS.
MobileFuse
MobileFuse's SDK allows you to pass RampID Envelopes to MobileFuse's exchange - MFX.
There are two ways you can do this:
- Pass envelopes directly to MobileFuse using
MobileFuseTargetingData:
// Pass in a RampID Envelope here
[MobileFuseTargetingData setLiveRampEnvelope: @"<liverampEnvelope>"];// Pass in a RampID Envelope here
MobileFuseTargetingData.setLiveRampEnvelope(“<liverampEnvelope>”)- Use MobileFuse's SDK to generate and pass identity envelopes. See MobileFuse's documentation to learn how or reach out to your MobileFuse account manager.
AppLovin MAX (SDK)
You can pass envelopes to AppLovin MAX fto AppLovin MAX by adding the RampID Envelope as an extra parameter in AppLovinSdkSettings:
AppLovinSdkSettings settings = AppLovinSdk.getInstance( context ).getSettings();
settings.setExtraParameter( "liveramp.com", "<liverampEnvelope>" );let settings = ALSdk.shared()?.settings
settings?.setExtraParameterForKey("liveramp.com", value: "<liverampEnvelope>")For more information, see AppLovin's MAX SDK documentation on how to pass identifier tokens for iOS or Android.
Updated 1 day ago