This guide walks you through activating Permutive contextual cohorts in Google Ad Manager (GAM) for ad targeting. The implementation calls the Contextual API directly (without the Permutive SDK) and passes the returned cohort codes to GAM as key-value targeting.This approach is designed for environments where user consent is not available, allowing you to target ads based on page content without processing user data.
Prerequisites:
A Permutive workspace API key
Admin access to Google Ad Manager to configure key-values
Contextual Cohorts created and activated in the Permutive Dashboard
Choose the implementation for your platform. Each example calls the Contextual API /segment endpoint and passes the returned cohort codes to GAM.
Web
iOS
Android
Add this script to the <head> of your page, as early as possible to ensure targeting is attached to the first ad call. You may need to customize this snippet to work with your setup.Replace the following placeholders:
$API_KEY — the same API key you use in your main Permutive deployment
$PAGE_PROPERTIES — the same page properties object you pass to window.permutive.addon('web', { page: {...} }), excluding any user-related properties
Personal Data: The Contextual API request must not contain any user-related data points. Make sure to omit any user-related properties that you might be recording as part of your Pageview event.
Copy
Ask AI
// new script for contextual;(function (apiKey, pageProperties) { var url = 'https://api.permutive.com/ctx/v1/segment?k=' + apiKey var request = new window.XMLHttpRequest() request.open('POST', url, true) request.onreadystatechange = callback request.send(JSON.stringify({ pageProperties: enrichClient(pageProperties), url: document.URL })) function callback () { if (request.readyState === 4 && request.status === 200) { var apiResponse try { apiResponse = JSON.parse(request.responseText) } catch (e) { return } // expose API response to main SDK window.permutive.addon('contextual', apiResponse) window.googletag = window.googletag || {} window.googletag.cmd = window.googletag.cmd || [] window.googletag.cmd.push(function () { var pubads = window.googletag.pubads() var ctxcohorts = apiResponse.gam || [] var targetingKey = 'prmtvctx' var targetingValues = ctxcohorts.concat('rts') // always include "rts" pubads.setTargeting(targetingKey, targetingValues) }) } } function getClient () { return { url: document.URL, referrer: document.referrer, type: 'web', user_agent: navigator.userAgent, domain: window.location.hostname, title: document.title } } function enrichClient (pageProps) { try { var clientObject = { client: getClient() } if (typeof pageProps === 'object' && !Array.isArray(pageProps)) { return Object.assign({}, pageProps, clientObject) } else { return clientObject } } catch (e) { return {} } }})($API_KEY, $PAGE_PROPERTIES)// existing deployment - DO NOT ADD AGAIN// window.permutive.addon('web', { page: $PAGE_PROPERTIES })
The enrichClient function adds client properties (URL, referrer, user agent, domain, title) to the payload, enabling additional targeting options.
The sample below uses Apple’s URLSession framework and JSONSerialization for serialization. You will need to adjust the code to suit your networking libraries.The PermutiveProvider struct demonstrates what data you need to make a call to the API. The implementation will likely look different in your application but the concepts will apply.Replace the following placeholders:
$API_KEY — the same API key you use in your main Permutive deployment. The snippet below assumes this is stored in a constant but please update depending on your implementation.
Page properties — the same custom properties you track as part of the Pageview event in the iOS SDK, passed dynamically to contextualTargeting. Pass this object into both the contextual API call and the existing Permutive call.
Personal Data: The Contextual API request must not contain any user-related data points. Make sure to omit any user-related properties that you might be recording as part of your Pageview event.
Retrieve Permutive contextual information:
Copy
Ask AI
import Foundationstruct PermutiveProvider { enum PermutiveError: Error { case malformedData } let apiKey: String func contextualTargeting(title: String? = nil, url: URL, referrer: URL? = nil, properties: [String: Any]? = nil, completion: @escaping (Result<[String:[String]], Error>) -> Void) { let requestBody: [String: Any] = [ "pageProperties": enrichClient(properties: properties, title: title, url: url, referrer: referrer), "url": url.absoluteString ] // Build URL with key, request body guard let requestUrl = URL(string: "https://api.permutive.com/ctx/v1/segment?k=\(apiKey)"), let requestJson = try? JSONSerialization.data(withJSONObject: requestBody) else { completion(.failure(NSError(domain: "Permutive", code: -1))) return } var request = URLRequest(url: requestUrl) request.httpMethod = "POST" request.httpBody = requestJson let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in if let error = error { completion(.failure(error)) return } do { guard let data = data, var response = try JSONSerialization.jsonObject(with: data) as? [String: [String]] else { completion(.failure(PermutiveError.malformedData)) return } // Always add rts response.forEach { (key, value) in var newValue = value newValue.append("rts") response[key] = newValue } completion(.success(response)) } catch { completion(.failure(error)) } } task.resume() } private func enrichClient(properties: [String: Any]?, title: String?, url: URL, referrer: URL?) -> [String: Any] { var enriched = properties ?? [:] enriched["client"] = [ "url": url.absoluteString, "domain": url.host, "referrer": referrer?.absoluteString, "type": "ios", "user_agent": Bundle.main.bundleIdentifier, "title": title ] return enriched }}
Enrich your GAM request with the Permutive contextual response:
Copy
Ask AI
// Import GoogleMobileAds to leverage ad.import GoogleMobileAds/// Set your project with [GoogleMobileAds](https://developers.google.com/admob/ios/quick-start) sdk./// Usage:/// Call `func contextualTargeting` with your PermutiveProvider instance to obtain the permutive contextual response.func GAMRequest(with response: [String:[String]]) -> GoogleMobileAds.GAMRequest? { guard let gamCustomTargeting = response["gam"] else { return nil } let adRequest = GoogleMobileAds.GAMRequest() adRequest.customTargeting = ["prmtvctx": gamCustomTargeting] return adRequest}
The sample below uses Retrofit for networking and Moshi for JSON serialization, but the approach will be similar for other libraries. The ContextualTargeting class demonstrates what data you need to make a call to the contextual API. You can use this snippet as-is by adding the class to your project and using the getContextualAdRequest function with the passed-in callback to receive Result<AdManagerAdRequest>. It can also be used as a guide on how to implement it within your own architecture.To use as-is, add the following dependencies if they are not already in your project:
$API_KEY — the same API key you use in your main Permutive deployment. The snippet below assumes this is stored in a constant PERMUTIVE_API_KEY but please update depending on your implementation.
Pageview properties — you may currently be passing com.permutive.android.EventProperties into Page/Event tracker calls to track custom properties. For contextual targeting, these properties should be added to a Map<String, Any> instead and combined with a client property as shown. The snippet shows an example custom property being added — please update depending on your implementation.
Personal Data: The Contextual API request must not contain any user-related data points. Make sure to omit any user-related properties that you might be recording as part of your Pageview event.
Copy
Ask AI
import com.google.android.gms.ads.admanager.AdManagerAdRequestimport com.squareup.moshi.JsonClassimport retrofit2.Callimport retrofit2.Callbackimport retrofit2.Responseimport retrofit2.http.Bodyimport retrofit2.http.POSTimport retrofit2.http.Query@JsonClass(generateAdapter = true)data class ContextualBody( // This map will correspond to the Permutive event schema val pageProperties: Map<String, Any>?, val url: String)@JsonClass(generateAdapter = true)data class ContextualResponse( val cohorts: List<String>?, val gam: List<String>?, val xandr: List<String>?)interface PermutiveContextualApi { @POST("https://api.permutive.com/ctx/v1/segment") fun getContextualCohorts( @Query("k") apiKey: String, @Body body: ContextualBody ): Call<ContextualResponse>}private const val PERMUTIVE_API_KEY = $API_KEYprivate const val CONTEXTUAL_TARGETING_KEY = "prmtvctx"class ContextualTargeting( private val customProperties: Map<String, Any>?, // Your custom properties private val contextualApi: PermutiveContextualApi, private val url: String, private val referrer: String?, private val title: String?, private val domain: String?) { fun getContextualAdRequest(onAdRequestCreated: (Result<AdManagerAdRequest>) -> Unit) { val permutivePageProperties = createPageProperties( customProperties = customProperties, url = url, referrer = referrer, title = title, domain = domain ) val requestBody = ContextualBody( pageProperties = permutivePageProperties, url = url ) contextualApi.getContextualCohorts(PERMUTIVE_API_KEY, requestBody) .enqueue( object : Callback<ContextualResponse> { override fun onResponse( call: Call<ContextualResponse>, response: Response<ContextualResponse> ) { val cohorts = response.body()?.gam ?: emptyList() createContextualAdRequest(cohorts, onAdRequestCreated) } override fun onFailure( call: Call<ContextualResponse>, t: Throwable ) { // Result.Failure passed to callback on failure onAdRequestCreated(Result.failure(t)) } } ) } private fun createContextualAdRequest( cohorts: List<String>, onAdRequestCreated: (Result<AdManagerAdRequest>) -> Unit ) { // always include "rts" val gamCohorts = cohorts.plus("rts") val adRequest = AdManagerAdRequest.Builder() .addCustomTargeting(CONTEXTUAL_TARGETING_KEY, gamCohorts) .build() // AdRequest passed into callback onAdRequestCreated(Result.success(adRequest)) } private fun createPageProperties( customProperties: Map<String, Any>?, url: String? = null, referrer: String? = null, title: String? = null, domain: String? = null ): Map<String, Any> { val client = buildMap<String, Any> { put("user_agent", (System.getProperty("http.agent") ?: BuildConfig.APPLICATION_ID)) put("type", "android") url?.let { put("url", it) } referrer?.let { put("referrer", it) } domain?.let { put("domain", it) } title?.let { put("title", it) } } return (customProperties ?: emptyMap()) + mapOf("client" to client) }}
Usage in a sample activity:
Copy
Ask AI
package com.example.sampleimport android.os.Bundleimport androidx.appcompat.app.AppCompatActivityimport com.example.sample.databinding.SampleActivityBindingimport retrofit2.Retrofitimport retrofit2.converter.moshi.MoshiConverterFactoryprivate const val BASE_URL = $YOUR_BASE_URLclass SampleActivity : AppCompatActivity() { private val retrofit: Retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(MoshiConverterFactory.create()) .build() private val contextualApi: PermutiveContextualApi = retrofit.create(PermutiveContextualApi::class.java) private val contextualTargeting = ContextualTargeting( customProperties = mapOf("SampleKey" to "sampleProperty"), contextualApi = contextualApi, url = "https://www.sample.com/article/samplearticle.html", referrer = "https://www.sample.com/article", title = "Sample Article", domain = "sample.com" ) private lateinit var binding: SampleActivityBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = SampleActivityBinding.inflate(layoutInflater) setContentView(binding.root) // load the ad with contextual targeting contextualTargeting.getContextualAdRequest { result -> result.fold( onSuccess = { request -> binding.adManagerAdView.loadAd(request) }, onFailure = { e -> // Handle failure } ) } }}
Implementation timing: Add the script as early as possible in the page <head> to ensure the Contextual API call completes before the first ad request is made.
After implementing the code, verify that contextual targeting is working correctly:
1
Test in staging
Deploy the implementation in a staging or test environment first. Load a page with the script and check your browser’s network tab to confirm the Contextual API request completes successfully and returns cohort codes.
2
Verify GAM targeting
Inspect the ad request in your browser’s developer tools to confirm that the prmtvctx key-value is present with the expected cohort codes.
3
Share testing link
Share a testing link with Technical Services so we can help validate the deployment before releasing to production.
Contextual cohorts have been created and activated in the Permutive dashboard
The URL being passed matches content that has been classified
Targeting not appearing in ad requests
Check that:
The API call completes before the ad request is made
The key-value prmtvctx is correctly configured in your ad server
CORS errors (Web)
The Permutive API supports CORS for web implementations. If you encounter CORS errors, verify that your domain is correctly configured in your Permutive workspace settings.