> ## Documentation Index
> Fetch the complete documentation index at: https://docs.permutive.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Activating Contextual Cohorts in Google Ad Manager

> How to activate Permutive contextual cohorts in Google Ad Manager for privacy-safe ad targeting

## Overview

This guide walks you through activating Permutive contextual cohorts in Google Ad Manager (GAM) for ad targeting. The implementation calls the [Contextual API](/api/contextual/introduction) 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.

<Info>
  **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
</Info>

## Configure GAM Key-Values

Before implementing the code, configure a key-value in Google Ad Manager.

<Steps>
  <Step title="Navigate to Key-Values">
    In Google Ad Manager, navigate to **Inventory > Key-Values** and click **New key-value**.
  </Step>

  <Step title="Create the key">
    Configure the key with these settings:

    * **Name**: `prmtvctx`
    * **Display name**: Permutive Contextual
    * **Value type**: Predefined
    * **Report on values**: Include values in reporting
  </Step>
</Steps>

## Implementation

Choose the implementation for your platform. Each example calls the [Contextual API `/segment` endpoint](/api/contextual/segment) and passes the returned cohort codes to GAM.

<Tabs>
  <Tab title="Web">
    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

    <Warning>
      **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.
    </Warning>

    ```javascript theme={"dark"}
    // 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.
  </Tab>

  <Tab title="iOS">
    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.

    <Warning>
      **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.
    </Warning>

    Retrieve Permutive contextual information:

    ```swift theme={"dark"}
    import Foundation

    struct 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:

    ```swift theme={"dark"}
    // 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
    }
    ```
  </Tab>

  <Tab title="Android">
    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:

    ```kotlin theme={"dark"}
    implementation("com.google.android.gms:play-services-ads:$google_ads_version")
    implementation("com.squareup.moshi:moshi:$moshi_version")
    kapt("com.squareup.moshi:moshi-kotlin-codegen:$moshi_version")
    implementation("com.squareup.retrofit2:retrofit:$retrofit_version")
    implementation("com.squareup.retrofit2:converter-moshi:$retrofit_version")
    ```

    <Note>An annotation processor such as kapt is required for Moshi codegen.</Note>

    Once added, create a Retrofit instance with a `MoshiConverterFactory` to create the `PermutiveContextualApi` instance to pass into the class:

    ```kotlin theme={"dark"}
    val retrofit: Retrofit = Retrofit.Builder()
        .baseUrl(YOUR_BASE_URL)
        .addConverterFactory(MoshiConverterFactory.create())
        .build()
    val contextualApi: PermutiveContextualApi = retrofit.create(PermutiveContextualApi::class.java)
    ```

    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 `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.

    <Warning>
      **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.
    </Warning>

    ```kotlin theme={"dark"}
    import com.google.android.gms.ads.admanager.AdManagerAdRequest
    import com.squareup.moshi.JsonClass
    import retrofit2.Call
    import retrofit2.Callback
    import retrofit2.Response
    import retrofit2.http.Body
    import retrofit2.http.POST
    import 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_KEY
    private 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:

    ```kotlin theme={"dark"}
    package com.example.sample

    import android.os.Bundle
    import androidx.appcompat.app.AppCompatActivity
    import com.example.sample.databinding.SampleActivityBinding
    import retrofit2.Retrofit
    import retrofit2.converter.moshi.MoshiConverterFactory

    private const val BASE_URL = $YOUR_BASE_URL

    class 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
                    }
                )
            }
        }
    }
    ```
  </Tab>
</Tabs>

<Tip>
  **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.
</Tip>

## Testing

After implementing the code, verify that contextual targeting is working correctly:

<Steps>
  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Share testing link">
    Share a testing link with [Technical Services](mailto:technical-services@permutive.com) so we can help validate the deployment before releasing to production.
  </Step>
</Steps>

## Troubleshooting

<AccordionGroup>
  <Accordion title="API returns empty cohorts">
    Verify that:

    * You are using the correct API key
    * Contextual cohorts have been created and activated in the Permutive dashboard
    * The URL being passed matches content that has been classified
  </Accordion>

  <Accordion title="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
  </Accordion>

  <Accordion title="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.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Contextual API Reference" icon="code" href="/api/contextual/introduction">
    Full API documentation for the Contextual API
  </Card>

  <Card title="Creating Contextual Cohorts" icon="plus" href="/guides/signals/cohorts/contextual/creating-contextual-cohorts">
    Build audience segments based on page content
  </Card>

  <Card title="Back to Contextual Cohorts" icon="arrow-left" href="/products/signals/cohorts/contextual">
    Return to product overview
  </Card>
</CardGroup>
