# Authentication Source: https://docs.permutive.com/api/authentication Authenticate and access Permutive APIs Permutive uses **API keys** for authentication. These keys take the form of version 4 UUIDs, which are to be included in the `X-API-Key` header of requests. ```curl theme={"dark"} curl https://api.permutive.app/... \ -H 'X-API-Key: f9975143-2d88-46dc-9247-1fb6c790e1cc' \ ... ``` Alternatively, customers can provide their API key as a query parameter on the `k` parameter: ```curl theme={"dark"} curl https://api.permutive.app/...?k=f9975143-2d88-46dc-9247-1fb6c790e1cc \ ... ``` ## Key types There are two types of API key: **public** and **private**. | Key type | Description | | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Public | Used when you cannot guarantee the key won't be seen by others. These are typically used on your sites & apps. | | Private | Used when you can guarantee the key won't be seen by others. These are typically used for making changes to your Permutive account, e.g. creating a new cohort. | ## Creating an API key You can manage your API keys in the [Settings › Keys](https://dash.permutive.com/settings/keys) section of the [Permutive Dashboard](https://dash.permutive.com). To create a new API key, follow these steps: 1. Navigate to the [Keys](https://dash.permutive.com/settings/keys) section of your Permutive [organization settings](https://dash.permutive.com/settings). 2. Click on the *Add Key* button. 3. Choose between a public and private key, and provide a name for your key. 4. Once you click *Add Key*, the key you have created will be listed in the Keys section. # Introduction Source: https://docs.permutive.com/api/ccs/introduction Real-time user segmentation based on events and cohort definitions The Custom Cohort Segmentation (CCS) API enables real-time user segmentation by processing events against your workspace's cohort definitions. This API is designed for client-side and server-side integrations where deploying our SDK isn't feasible, but you still need to determine a user's cohort membership in real-time. **Please note**: Client-side integrations must use a Public API key, where server-side integrations must use a Private API key. ## Overview The CCS API provides two main endpoints: Segment a user based on events and persist state for future requests. Segment a user without persisting state - ideal for testing and high-performance scenarios. ## Use Cases * **Server-side targeting**: Determine user cohorts server-side before making ad requests * **Real-time personalization**: Segment users in real-time based on their actions * **SDK-less integrations**: Integrate with Permutive without using client-side SDKs * **AMP pages**: Segment users on AMP pages where JavaScript SDK cannot run ## Authentication All endpoints require API key authentication. You can provide your API key in one of two ways: * **Query parameter**: `?k=your-api-key` * **Header**: `X-Api-Key: your-api-key` You can find your API key in the [Permutive Dashboard](https://dash.permutive.com) under Settings. ## Base URL All API requests should be made to: ``` https://api.permutive.app ``` ## Rate Limits The CCS API is designed for high-throughput scenarios. Contact your Permutive representative for specific rate limit information for your workspace. ## Batch Size Each request can include up to **10 events**. For larger batches, split your events across multiple requests. # Segment User Source: https://docs.permutive.com/api/ccs/segmentation POST /ccs/v1/segmentation This endpoint accepts a list of events for a given user and combines them with any pre-existing state for the user to determine the full list of cohorts of which they are a member. State is also persisted, to be applied to subsequent requests for the same user. Events provided to this endpoint are published to the Permutive Events API, meaning that they will be reflected in other areas of Permutive, for example Insights. This endpoint persists user state, which will be used in subsequent requests for the same user. ## User Identification You must provide exactly one of the following to identify the user: * `user_id`: The Permutive User ID (UUID) * `alias`: A single custom alias with `tag` and `id` * `aliases`: A list of prioritized aliases, each with `priority`, `tag`, and `id` ## Events Events should match the schema defined in your Permutive workspace. Each event requires: * `name`: The event name (e.g., "Pageview") * `time`: ISO 8601 timestamp * `properties`: Event properties as a JSON object Optional fields: * `view_id`: UUID to group events within a single page view * `session_id`: UUID to group events within a session ## Response The response includes: * `user_id`: The Permutive User ID * `cohorts`: List of cohort IDs the user is currently a member of * `activations`: (Optional) Map of activation platform to cohort IDs, if `activations=true` was set ## Example Usage ```bash theme={"dark"} curl -X POST "https://api.permutive.app/ccs/v1/segmentation?k=YOUR_API_KEY&activations=true" \ -H "Content-Type: application/json" \ -d '{ "user_id": "2008c38f-dece-4570-976d-87593ed001c3", "events": [ { "name": "Pageview", "time": "2024-01-15T10:30:00Z", "properties": { "url": "https://example.com/article/123", "title": "Example Article" } } ] }' ``` # Stateless Segmentation Source: https://docs.permutive.com/api/ccs/segmentation-stateless POST /ccs/v1/segmentation/stateless This endpoint accepts a list of events for a given user and optionally some pre-existing state for the user. It uses these to determine the full list of cohorts of which they are a member, and returns these along with the updated state for the user. State is not persisted, so will not be used in the next request unless it is passed back in as part of that request. It is likely that this endpoint is more performant than the stateful segmentation endpoint, since it does not need to read state for the user from a database. It is also useful for testing purposes. Events provided to this endpoint are published to the Permutive Events API, meaning that they will be reflected in other areas of Permutive, for example Insights. This endpoint does not persist state. If you need state persistence, use the [Segmentation](./segmentation) endpoint instead. ## When to Use Stateless Segmentation The stateless endpoint is ideal for: * **Testing and debugging**: Verify cohort logic without affecting production state * **High-performance scenarios**: Skip database reads/writes for faster response times * **Client-managed state**: When you want to manage user state in your own systems ## User State You can optionally provide pre-existing user state in the request. The response will include the updated state that you should store and pass back in subsequent requests. The `state` field format is internal to Permutive and should not be parsed or modified. Treat it as an opaque blob. ## Example Usage ### First Request (No State) ```bash theme={"dark"} curl -X POST "https://api.permutive.app/ccs/v1/segmentation/stateless?k=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "user_id": "2008c38f-dece-4570-976d-87593ed001c3", "events": [ { "name": "Pageview", "time": "2024-01-15T10:30:00Z", "properties": { "category": "technology" } } ] }' ``` ### Subsequent Request (With State) ```bash theme={"dark"} curl -X POST "https://api.permutive.app/ccs/v1/segmentation/stateless?k=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "user_id": "2008c38f-dece-4570-976d-87593ed001c3", "events": [ { "name": "Pageview", "time": "2024-01-15T10:35:00Z", "properties": { "category": "sports" } } ], "state": { "internal_state": { ... }, "external_state": { ... }, "cohorts": ["12345", "67890"] } }' ``` ## Response The response includes: * `user_id`: The Permutive User ID * `state`: The updated user state (including cohort memberships) Store the returned `state` and include it in your next request to maintain continuity. # Cohort query format Source: https://docs.permutive.com/api/cohorts/cohort-query-format How to define the behavior of cohorts The behavior of a cohort is defined by a 'query', which specifies the conditions a user must meet to fall into the cohort. When creating, updating or viewing cohorts, the Cohorts API uses a JSON format to represent these queries. A query for a 'football lovers' cohort might say something like 'the user has viewed at least two pages with the word "football" in the URL in the last 30 days.' This particular query would be represented in the Cohorts API as: ```json theme={"dark"} { "event": "Pageview", "frequency": { "greater_than_or_equal_to": 2 }, "where": { "property": "properties.client.url", "condition": { "contains": "football" } }, "during": { "the_last": { "value": 30, "unit": "days" } } } ``` The above query consists of a single 'clause'. It is possible to combine an arbitrary number of clauses using 'and' or 'or' logic, for example, a query specifying 'the user has viewed at least two pages with the word "football" in the URL **OR** has viewed at least one page with "London" in the title' would be represented as: ```json theme={"dark"} { "or": [ { "event": "Pageview", "frequency": { "greater_than_or_equal_to": 2 }, "where": { "property": "properties.client.url", "condition": { "contains": "football" } } }, { "event": "Pageview", "frequency": { "greater_than_or_equal_to": 1 }, "where": { "property": "properties.client.title", "condition": { "contains": "London" } } } ] } ``` The most complex supported query logic can be supplied in conjunctive normal form, or effectively an AND of ORs of clauses. For example, the query specifying that '(the user has viewed at least two pages with the word "football" in the URL **OR** has viewed at least one page with "London" in the title) **AND** the user has not visited a page on the domain example.com' would be expressed as: ```json theme={"dark"} { "and": [ { "or": [ { "event": "Pageview", "frequency": { "greater_than_or_equal_to": 2 }, "where": { "property": "properties.client.url", "condition": { "contains": "football" } } }, { "event": "Pageview", "frequency": { "greater_than_or_equal_to": 1 }, "where": { "property": "properties.client.title", "condition": { "contains": "London" } } } ] }, { "event": "Pageview", "frequency": { "equal_to": 0 }, "where": { "property": "properties.client.domain", "condition": { "equal_to": "example.com" } } } ] } ``` **Simplification**: The second half of the AND expression doesn't need to show the OR explicitly, since there is only one clause. ## Clause A clause can be either an "expression" clause, an "engagement" clause, a "transition" clause, a "cohort membership" clause, or a "connections import" clause. It is essentially a particular condition which must be met in order for a user to enter a cohort. A query is composed from one or more clauses. In the Behavior section of the Custom Cohort builder in the Permutive dashboard, a clause is represented as a single white box containing conditions on a single event type. The image below shows a cohort with two clauses: 2676 ## Expression Clause An expression clause represents conditions relating to a particular type of event, which must be met in order for the user to fall into the segment. It has the following top level fields: | Top level key | Value | Description | | :------------ | :------------------------------------------------------------------------- | :------------------------------------------------------------- | | "event" | a string value | the name of the relevant event | | "frequency" | a 'number comparison' object (see relevant subsection below) | how often/how many times the condition must be met | | "during" | a 'during' object (see relevant subsection below) | the time period within which the conditions must have been met | | "where" | An object representing conditions on the event (see `where` section below) | the conditions on the event which must be met | The 'football lovers' query above is an example of an expression clause. ## Engagement Clause An engagement clause selects users based on the time they spend active on-site (*engaged time*) and their page scroll-depth (*completion*). It has **one** of the following top level keys: * `"engaged_time"` - identify users with a total amount of engaged time over a period, regardless of how many pageviews the user has had. For example, *users with 120 seconds or more engaged time in the last 7 days across pages about dogs*. * `"engaged_completion"` - identify users with a specified maximum completion on the current page. For example, *users with at least 40% completion on the current page*. * `"engaged_views"` - identify users who have had distinct page views each with some amount of engaged time or completion. For example, *users with 3 or more page views about dogs each with more than 30 seconds' engaged time*. These keys would then point to an object with the following respective fields: | Top level key | Nested object key | Value | Description | | :-------------------- | :---------------------------------------------------------- | :------------------------------------------------------------------------- | :---------------------------------------------------------------------- | | "engaged\_time" | "seconds" | A 'number comparison' object (see relevant section below) | How long the user must have spent on the page | | | "during" (optional) | An object representing a time period (see `during` section below) | The time period within which the conditions must have been met | | | "where" (optional) | An object representing conditions on the event (see `where` section below) | The conditions on the pageview which must be met | | "engaged\_completion" | "completion" | A 'number comparison' object (see relevant section below) | The fraction of the page which must have been completed | | | "where" (optional) | An object representing conditions on the event (see `where` section below) | The conditions on the pageview which must be met | | "engaged\_views" | "times" | A 'number comparison' object (see relevant section below) | How many times the condition must have been met | | | "during" (optional) | An object representing a time period (see `during` section below) | The time period within which the conditions must have been met | | | "where" (optional) | An object representing conditions on the event (see `where` section below) | The conditions on the pageview which must be met | | | "engaged\_time" (must have EITHER this key OR "completion") | A 'number comparison' object (see relevant section below) | Condition on the number of seconds the user must have spent on the page | | | "completion" (must have EITHER this key OR "engaged\_time") | A 'number comparison' object (see relevant section below) | The fraction of the page which must have been completed | Here are some examples of engagement clause objects: ```json theme={"dark"} { "engaged_time": { "where": { "property": "properties.article.title", "condition": { "equal_to": "My Interesting Article" } }, "seconds": { "greater_than": 42 } } } ``` ```json theme={"dark"} { "engaged_completion": { "completion": { "greater_than": 0.5 } } } ``` ```json theme={"dark"} { "engaged_views": { "completion": { "greater_than": 0.3 }, "where": { "property": "properties.article.categories", "condition": { "list_contains": "sport" } }, "during": { "the_last": { "value": 2, "unit": "days" } }, "times": { "greater_than_or_equal_to": 2 } } } ``` ## Transition Clause A transition clause selects users based on whether or not they have entered or left another particular cohort. It has **one** of the following top level keys: * `"has_entered"` - the user has entered the given cohort * `"has_not_entered"` - the user has **not** entered the given cohort * `"has_exited"` - the user has exited the given cohort * `"has_not_exited"` - the user has **not** exited the given cohort Whichever one of these keys is used, the value must be an object with the following fields: | Key | Value | Description | | :------------------ | :------------------------------------------------------------------------- | :---------------------------------------------------------------------------------- | | "segment" | an integer value | the Short Cohort ID of the segment on which the condition is based | | "during" (optional) | a 'during' object (see relevant subsection below) | the time period within which the entry/exit condition must have been met | | "where" (optional) | An object representing conditions on the event (see `where` section below) | the conditions on the pageview which must hold when the entry/exit condition is met | **Cohort IDs**: Cohorts have two different types of ID, a long UUID and a short integer ID. Cohorts are addressed in API URLs by the long UUID, but when identifying a cohort in a transition clause the short integer ID must be used. Here is an example of a transition clause object: ```json theme={"dark"} { "has_entered": { "where": { "property": "properties.client.url", "condition": { "does_not_contain": "gossip" } }, "segment": 1234, "during": { "after": "2021-08-10T00:00:00Z" } } } ``` ## Cohort Membership Clause This clause represents the requirement for a user to belong to a given third party (or second party) cohort. It has **one** of the following top level keys: * `"in_third_party_segment"` - the user is in the given third party cohort * `"not_in_third_party_segment"` - the user is **not** in the given third party cohort * `"in_second_party_segment"` - the user is in the given second party cohort * `"not_in_second_party_segment"` - the user is **not** in the given second party cohort For any of these keys, the value is an object with the following fields: | Key | Value | Description | | :--------- | :----- | :-------------------------------------------------------------------------------------------- | | "provider" | string | a string identifying the second/third party data provider within Permutive platform | | "segment" | string | a string identifying the particular second/third party cohort on which the condition is based | **Provider and Cohort IDs**: The provider and cohort IDs used in Cohort Membership clauses are the identifiers used for the relevant entity within the Permutive platform. Depending on the use case, it might be necessary to request a list of these identifiers from the Support team or your Customer Success Manager. Here are some examples of cohort membership clauses: ```json theme={"dark"} { "in_third_party_segment": { "segment": "123456", "provider": "my_tpd_provider" } } ``` ```json theme={"dark"} { "not_in_second_party_segment": { "segment": "1000", "provider": "test_2nd_party" } } ``` ## Connections Import Clause A connections import clause matches users based on their membership of a Connectivity import (of either "User Profile" or "User Activity" data). You can optionally apply property filters, restrict the time window, and set frequency constraints. It has **one** of the following top level keys: * `"in_connections_import_segment"` - the user is in the given connections import segment * `"not_in_connections_import_segment"` - the user is **not** in the given connections import segment For either of these keys, the value is an object with the following fields: | Key | Value | Description | | :--------------------- | :----------------------------------------------------------- | :------------------------------------------------------------ | | "provider" | a string value | an identifier for the Connectivity import | | "filters" (optional) | a 'filters' object (see below) | conditions on properties of the connections import | | "during" (optional) | a 'during' object (see relevant subsection below) | the time period within which the condition must have been met | | "frequency" (optional) | a 'number comparison' object (see relevant subsection below) | how many times the condition must be met | **Provider IDs**: The list of provider IDs corresponding to Connectivity imports is not currently visible within the Permutive dashboard - please contact Support or your Customer Success Manager to receive a list of available IDs. ### Filters The `"filters"` field defines conditions on properties of the connections import segment. Property names reference the connections import schema directly and do not use the `properties.` prefix. A single filter condition: ```json theme={"dark"} { "property": "field_name", "condition": { ... } } ``` Multiple filter conditions can be combined with `"and"` or `"or"`: ```json theme={"dark"} { "and": [ { "property": "field_name_1", "condition": { ... } }, { "property": "field_name_2", "condition": { ... } } ] } ``` The `"condition"` object uses the same format as described in the `"where"` section below (string, integer, float, date, boolean, and list conditions are all supported). Here are some examples of connections import clause objects: ```json theme={"dark"} { "in_connections_import_segment": { "provider": "my_data_provider" } } ``` ```json theme={"dark"} { "not_in_connections_import_segment": { "provider": "my_data_provider", "filters": { "property": "interest", "condition": { "equal_to": "sports" } } } } ``` ```json theme={"dark"} { "in_connections_import_segment": { "provider": "my_data_provider", "filters": { "and": [ { "property": "education", "condition": { "equal_to": "GCSE" } }, { "property": "age", "condition": { "greater_than": 18 } } ] }, "during": { "the_last": { "value": 30, "unit": "days" } }, "frequency": { "greater_than_or_equal_to": 2 } } } ``` ## Number Comparison A 'number comparison' object is used in several different cases when a numeric property needs to be measured against a specific condition. The object consists of a single key-value, with the following available options: | Key | Value | Description | | :----------------------------- | :------------------------------------------------------------------------------ | :----------------------------------------------------------------------------------------------------------------------- | | "equal\_to" | a single number | condition is met if the property is exactly equal to the given number | | | a non-empty list of numbers | condition is met if the property is exactly equal to any one of the given numbers | | "not\_equal\_to" | a single number | condition is met if the property is not exactly equal to the given number | | | a non-empty list of numbers | condition is met if the property is not exactly equal to any of the given numbers | | "between" | an object with two members, "start" and "end", each with a single numeric value | condition is met if the property is greater than or equal to the "start" value and less than or equal to the "end" value | | "greater\_than" | a single number | condition is met if the property is greater than the given number | | "less\_than" | a single number | condition is met if the property is less than the given number | | "greater\_than\_or\_equal\_to" | a single number | condition is met if the property is greater than or equal to the given number | | "less\_than\_or\_equal\_to" | a single number | condition is met if the property is less than or equal to the given number | ## "during" This defines the time period during which the conditions must be met in order for a user to enter a cohort. It can be either a single string value, or an object with a single key pointing to a single value or a nested object. Valid values are: | Key | Nested object key | Value | Description | | :-------------- | :---------------- | :-------------------------------------------------- | :---------------------------------------------------------------------------------------------------- | | "this\_session" | N/A | N/A (string value only) | the condition must have been met during the current session | | "the\_last" | "value" | integer | taken with "unit", gives the length of time before now during which the condition must have been met | | | "unit" | "seconds"/"minutes"/"hours"/"days"/"weeks"/"months" | taken with "value", gives the length of time before now during which the condition must have been met | | "in\_interval" | "start" | timestamp | the start of the time window during which the condition must have been met | | | "end" | timestamp | the end of the time window during which the condition must have been met | | "before" | N/A | timestamp | the time before which the condition must have been met | | "after" | N/A | timestamp | the time after which the condition must have been met | | "first" | N/A | integer | the condition must have been met during the first N events of this type | | "last" | N/A | integer | the condition must have been met during the most recent N events of this type | | "current\_view" | N/A | N/A (string value only) | the condition must have been met within the current pageview | Here are examples of `"during"` conditions: ```json theme={"dark"} { "in_interval": { "start": "2021-08-11T00:00:00Z", "end": "2021-08-12T00:00:00Z" } } ``` ```json theme={"dark"} "this_session" ``` ```json theme={"dark"} {"last": 5} ``` ## "where" This defines conditions on properties of a given event which must be met in order for a user to enter a cohort. It consists of an object with two fields: * `"property"` - this is the name of the property on which the condition is tested * `"condition"` - this defines the condition to be tested **Property naming conventions**: Property names consist of string segments separated by periods. All properties start with `"properties."`, although this is hidden in the Permutive Dashboard. The full name for an event property that appears in the dashboard as `client.title` would therefore be `"properties.client.title"`. The `"condition"` can be one of the following: **Integer condition**\ This is a condition on an integer numeric value. It takes the form of a 'Number Comparison' object as described above. **Float condition**\ This is a condition on a floating point numeric value. Its form is exactly the same as a normal 'number comparison' object, except that the top level key is prefixed with `float_`, for example `"float_equal_to"`, `"float_between"`, etc, and the actual comparison value is interpreted as a floating point number rather than an integer. **Date condition**\ This is a condition on a timestamp value. Its form is exactly the same as a normal 'number comparison' object, except that the top level key is prefixed with `date_`, for example `"date_equal_to"`, `"date_between"`, etc, and the actual comparison value is a string timestamp instead of an integer. **String condition**\ This is a condition on a string property. It consists either of a single string value, or an object with a single field. Valid values are: | Key | Value | Description | | :------------------- | :------------------------ | :------------------------------------------------------------------------------------------- | | "equal\_to" | string | the property value must exactly match the provided value | | | non-empty list of strings | the property value must exactly match any one of the provided values | | "not\_equal\_to" | string | the property value must not exactly match the provided value | | | non-empty list of strings | the property value must not exactly match any of the provided values | | "contains" | string | the property value must include the provided value as a substring | | | non-empty list of strings | the property value must include at least one of the provided values as a substring | | "does\_not\_contain" | string | the property value must not include the provided value as a substring | | | non-empty list of strings | the property value must not include any of the provided values as a substring | | "is\_empty" | N/A (string value only) | the property value must be an empty string, or the event must have no value for the property | | "is\_not\_empty" | N/A (string value only) | the property value must be a non-empty string | **List condition**\ This is a condition on a list property. It consists either of a single string value, or an object with a single field. Valid values are: | Key | Value | Description | | :---------------------------------- | :------------------------ | :----------------------------------------------------------------------------------------- | | "list\_contains" | string | the list must include the provided string value | | | non-empty list of strings | the list must include at least one of the provided string values | | "list\_does\_not\_contain" | string | the list must not include the provided string value | | | non-empty list of strings | the list must not include any of the provided string values | | "list\_contains\_date" | timestamp | the list must include the provided timestamp | | "list\_does\_not\_contain\_date" | timestamp | the list must not include the provided timestamp value | | "list\_contains\_float" | float | the list must include the provided floating point numeric value | | "list\_does\_not\_contain\_float" | float | the list must not include the provided floating point numeric value | | "list\_contains\_integer" | integer | the list must include the provided integer numeric value | | "list\_does\_not\_contain\_integer" | integer | the list must not include the provided integer numeric value | | "list\_is\_empty" | N/A (string value only) | the property value must be an empty list, or the event must have no value for the property | | "list\_is\_not\_empty" | N/A (string value only) | the property value must be a non-empty list | **List summary condition**\ This is a condition on some aggregation of a property which is a list of objects. It consists of an object with four fields: * "`property`" - the property within the listed objects which is to be aggregated * `"condition"` - the condition to be applied to the specified property * `"function"` - the type of aggregation to perform on the list * `"where"` (optional) - an additional filter to apply to the listed objects before applying the aggregation **Property naming**: The `"property"` naming convention within a list summary condition is to omit the common prefix. For example, say we have a list summary condition on a property `properties.slot.targeting`, which is a list of objects with two fields, `properties.slot.targeting.key` and `properties.slot.targeting.value`. Within the list summary condition, we would refer to those two properties as `key` and `value` respectively, since the first part of the property path is implicit. The `"condition"` object takes the same form as the `"condition"` clause within a normal `"where"` object (see relevant section above). The `"function"` must be one of the following: `"any"`, `"all"`, `"sum"`, `"product"`, "max"`, `"min"`, ``"count"`, or `"mean"`. Some of these (sum, product, max, min and mean) can only be used on a numeric sub-property. The `"where"` object takes the same form as the `"where"` component of a top level clause. An example of a list summary condition is: ```json theme={"dark"} { "property": "properties.slot.targeting", "condition": { "property": "key", "function": "any", "where": { "property": "value", "condition": { "list_contains": "efgh" } }, "condition": { "contains": "abcd" } } } ``` **Boolean condition**\ This is a condition on a Boolean property. Is consists of an object with a single key, `"boolean_equal_to"`, and a value of either `"true"` or `"false"`. ## Compound `"where"` conditions Anywhere a `"where"` object is expected, it is also possible to provide a list of multiple conditions separated by either ORs or ANDs. This can only be a single list, and can only be one level deep. For example: ```json theme={"dark"} { "and": [ { "property": "properties.article.title", "condition": { "equal_to": "abcd" } }, { "property": "properties.article.tags", "condition": { "list_contains": "defg" } } ] } ``` ```json theme={"dark"} { "or": [ { "property": "properties.article.title", "condition": { "equal_to": "abcd" } }, { "property": "properties.article.tags", "condition": { "list_contains": "defg" } } ] } ``` # Create a cohort Source: https://docs.permutive.com/api/cohorts/create-cohort POST /v2/cohorts This endpoint allows the creation of a new cohort. The query definition is provided using the Cohort API JSON query definition format. The cohort will belong to the workspace which owns the provided API key. # Delete a cohort Source: https://docs.permutive.com/api/cohorts/delete-cohort DELETE /v2/cohorts/{cohortId} This endpoint allows deleting an existing cohort. Depending on the access level of the provided API key, it can delete cohorts owned by the requesting workspace, or also by child workspaces below the requesting workspace in the organization hierarchy. Lookalike-based cohorts cannot be deleted via the Cohort API. # Retrieve individual cohort Source: https://docs.permutive.com/api/cohorts/get-cohort GET /v2/cohorts/{cohortId} This endpoint returns details of a single cohort belonging either to the workspace that owns the requesting API key, or to a parent workspace if the requesting workspace inherits the cohort from that workspace. If the requested cohort is not a lookalike-based cohort, the response will include the query definition. # Retrieve all cohorts Source: https://docs.permutive.com/api/cohorts/get-cohorts GET /v2/cohorts By default this endpoint returns all cohorts belonging to the workspace to which the supplied API key belongs, plus any cohorts inherited from parent workspaces in the organization hierarchy. If the API key has the required access level, it is also possible to return segments belonging to child workspaces below the requesting workspace in the organization hierarchy. The queries defining the behaviour of the cohorts are not returned from this endpoint. To retrieve the query of a given cohort, use the 'Retrieve individual cohort' endpoint. # Introduction Source: https://docs.permutive.com/api/cohorts/introduction Programmatically manage cohorts The Cohorts API allows for the programmatic creation, mutation, and deletion of cohorts for your workspace. ## Authentication It's important to note that all requests to the Cohorts API must be authenticated with a private API key. See the [Authentication](/api/authentication) section for details on obtaining and using a private API key. The behavior of the API depends on which workspace the private API key is associated with in your organization. Details are provided with each endpoint in this documentation. There are various different access levels available. By default, a private API key will provide read-only access to cohorts defined in the workspace that owns the API key, or inherited from workspaces above this workspace in an organization hierarchy. Please contact [Technical Services](mailto:technical-services@permutive.com) if you would like to discuss different access levels to the Cohorts API. ## Cohort query format The behavior of a cohort is defined by a *query*, which specifies the conditions a user must meet to fall into the cohort. When creating, updating or viewing cohorts, the Cohorts API uses a JSON format to represent these queries. How to define the behavior of cohorts ## Endpoints The Cohorts API provides five endpoints: Create a cohort for your workspace. Retrieve a cohort by its cohort ID. Retrieve all cohorts for your workspace. Update a cohort by its cohort ID. Delete a cohort by its cohort ID. # Update a cohort Source: https://docs.permutive.com/api/cohorts/update-cohort PATCH /v2/cohorts/{cohortId} This endpoint allows updating an existing cohort. Depending on the access level of the provided API key, it can update cohorts owned by the requesting workspace, or also by child workspaces below the requesting workspace in the organization hierarchy. Lookalike-based cohorts cannot be updated via the Cohort API. Any top level fields for which no value is provided in the request body will remain unchanged. Optional fields (currently only `description`) can be deleted by explicitly specifying `null` as the value. # Introduction Source: https://docs.permutive.com/api/contextual/introduction Retrieve contextual cohort targeting values for ad server integration. ## Overview The Contextual API enables retrieval of contextual cohort targeting values without processing user data. Use it to pass targeting values to ad servers like Google Ad Manager. This API is designed to work independently of the main Permutive SDK, allowing you to retrieve contextual targeting even when user consent is not available. If you don't need to target unconsented users with your contextual signals, the Permutive SDK will call these endpoints and segment contextual cohorts automatically. ## Base URL ``` https://api.permutive.com/ctx/v1 ``` ## Authentication The API uses API key authentication. Pass your Permutive API key as a query parameter: ``` ?k=YOUR_API_KEY ``` This is the same API key used for your main Permutive SDK deployment. ## Use Cases * **Privacy-safe contextual targeting**: Target ads based on page content without user identifiers * **Cookie-less advertising**: Enable ad targeting in environments where cookies are blocked or unavailable * **Server-side targeting value retrieval**: Fetch targeting values for integration with any ad server * **Consent-free operation**: Retrieve targeting values without requiring user consent for personal data processing ## Available Endpoints | Endpoint | Method | Description | | ---------- | ------ | -------------------------------------------------------------------- | | `/segment` | POST | Retrieve contextual cohort codes for a given URL and page properties | ## Response Format All responses are returned in JSON format. Successful responses include cohort codes and activation mappings. The endpoint also returns content classification data if any classification provider is enabled in the account. ```json theme={"dark"} { "cohorts": ["abc", "def", "ghi"], "activations": { "target_dfp": ["abc", "def"], "appnexus_adserver": ["ghi"] }, "contextual_data": { "classifications": { "categories": [ { "value": "155", "confidence": 0.5, "taxonomy": "iab_3.0", "provider": "ibm_watson" } ], "keywords": [ { "value": "museum", "confidence": 0.7, "provider": "ibm_watson" } ], "entities": [ { "value": "British Museum", "confidence": 0.9, "provider": "ibm_watson" } ], "sentiment": [ { "value": "positive", "confidence": 0.9, "provider": "ibm_watson" } ], "emotion": [ { "value": "joy", "confidence": 0.4, "provider": "ibm_watson" } ], "concepts": [ { "value": "Vincent van Gogh", "confidence": 0.6, "provider": "ibm_watson" } ] } } } ``` ## Error Handling The API returns standard HTTP status codes: | Status Code | Description | | ----------- | ------------------------------------------------ | | `200` | Success | | `400` | Bad Request - Invalid request body or parameters | | `401` | Unauthorized - Invalid or missing API key | | `500` | Internal Server Error | ## Rate Limits Contact your Customer Success Manager for information about rate limits applicable to your account. ## Next Steps Learn how to retrieve contextual cohort codes using the segment endpoint. # Get Contextual Segments Source: https://docs.permutive.com/api/contextual/segment POST https://api.permutive.com/ctx/v1/segment Retrieve contextual cohort codes for a given URL and page properties. Returns cohort codes formatted for Google Ad Manager. ## Request ### Query Parameters Your Permutive API key. This is the same API key used for your main Permutive SDK deployment. ### Body Parameters The URL to retrieve contextual cohorts for. This is typically the current page URL. An object containing page properties used for contextual classification. This should match the structure of properties you track in Pageview events, but **must exclude any user-related data**. Client information for the request. The page URL The domain of the page The referrer URL The client type: `web`, `ios`, or `android` The user agent string The page title You can also include any custom page properties that you track as part of your Pageview events (e.g., `category`, `tags`, `author`). **Personal Data**: The request must not contain any user-related data points. Omit any user-related properties that you might be recording as part of your Pageview events. ## Response All contextual cohort codes for the page. This is the complete list of cohorts that matched. An object mapping activation destination names to arrays of cohort code strings. Each key is an activation destination (e.g., `target_dfp`, `appnexus_adserver`) and the value is an array of cohort codes configured for that destination. An object containing content classification data, returned when a classification provider is enabled in the account. Includes classifications such as categories, keywords, entities, sentiment, emotion, and concepts. ## Example Request ```bash cURL theme={"dark"} curl -X POST "https://api.permutive.com/ctx/v1/segment?k=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/article/sports-news", "page_properties": { "client": { "url": "https://example.com/article/sports-news", "domain": "example.com", "referrer": "https://example.com", "type": "web", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", "title": "Latest Sports News" }, "category": "sports", "tags": ["football", "premier-league"] } }' ``` ```javascript JavaScript theme={"dark"} const apiKey = 'YOUR_API_KEY'; const url = `https://api.permutive.com/ctx/v1/segment?k=${apiKey}`; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ url: document.URL, page_properties: { client: { url: document.URL, domain: window.location.hostname, referrer: document.referrer, type: 'web', user_agent: navigator.userAgent, title: document.title }, category: 'sports', tags: ['football', 'premier-league'] } }) }); const data = await response.json(); console.log(data); ``` ```swift Swift theme={"dark"} let apiKey = "YOUR_API_KEY" let requestUrl = URL(string: "https://api.permutive.com/ctx/v1/segment?k=\(apiKey)")! let requestBody: [String: Any] = [ "url": "https://example.com/article/sports-news", "page_properties": [ "client": [ "url": "https://example.com/article/sports-news", "domain": "example.com", "type": "ios", "title": "Latest Sports News" ], "category": "sports" ] ] var request = URLRequest(url: requestUrl) request.httpMethod = "POST" request.httpBody = try? JSONSerialization.data(withJSONObject: requestBody) request.setValue("application/json", forHTTPHeaderField: "Content-Type") URLSession.shared.dataTask(with: request) { data, response, error in // Handle response }.resume() ``` ```kotlin Kotlin theme={"dark"} val apiKey = "YOUR_API_KEY" val requestBody = ContextualBody( page_properties = mapOf( "client" to mapOf( "url" to "https://example.com/article/sports-news", "domain" to "example.com", "type" to "android", "title" to "Latest Sports News" ), "category" to "sports" ), url = "https://example.com/article/sports-news" ) contextualApi.getContextualCohorts(apiKey, requestBody) .enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { val cohorts = response.body() // Handle response } override fun onFailure(call: Call, t: Throwable) { // Handle error } }) ``` ## Example Response ```json Success (200) theme={"dark"} { "cohorts": ["abc", "def", "ghi"], "activations": { "target_dfp": ["abc", "def"], "appnexus_adserver": ["ghi"] }, "contextual_data": { "classifications": { "categories": [ { "value": "155", "confidence": 0.5, "taxonomy": "iab_3.0", "provider": "ibm_watson" } ], "keywords": [ { "value": "museum", "confidence": 0.7, "provider": "ibm_watson" } ], "entities": [ { "value": "British Museum", "confidence": 0.9, "provider": "ibm_watson" } ], "sentiment": [ { "value": "positive", "confidence": 0.9, "provider": "ibm_watson" } ], "emotion": [ { "value": "joy", "confidence": 0.4, "provider": "ibm_watson" } ], "concepts": [ { "value": "Vincent van Gogh", "confidence": 0.6, "provider": "ibm_watson" } ] } } } ``` ```json No matching cohorts (200) theme={"dark"} { "cohorts": [], "activations": {}, "contextual_data": {} } ``` ```json Error (400) theme={"dark"} { "error": "Invalid request body" } ``` ## Implementation Notes When passing targeting values to your ad server, always append `rts` to the array of cohort codes. This is required for proper targeting: ```javascript theme={"dark"} const targetingValues = apiResponse.activations.target_dfp.concat('rts'); ``` Make the API call as early as possible when the page loads to ensure targeting is attached to the first ad call. We recommend placing the API call in the `` of your page. Use the same API key that you use for your main Permutive SDK deployment. This ensures cohorts are correctly associated with your workspace. ## Related Full implementation guide with code samples for Google Ad Manager. Overview of the Contextual API. # Errors Source: https://docs.permutive.com/api/errors How errors are represented in API responses Permutive uses conventional HTTP response codes to indicate success or failure of an API request. In general, codes in the `2xx` range indicate success, codes in the `4xx` range indicate an error that resulted from the information provided in the request (e.g. a required parameter was missing), and codes in the `5xx` range indicate an error with Permutive's API. ## HTTP status codes You may encounter the following response codes. Any unsuccessful response will contain more information to help you identify the cause of the problem. | Response code | Description | | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | | `200`   **OK** | Everything worked as expected and the request succeeded. | | `400`   **Bad Request** | The request could not be understood by the server, usually due to malformed syntax such as a missing required parameter. | | `401`   **Unauthorized** | The client has not provided a valid Authentication HTTP header. | | `403`   **Forbidden** | The client has provided a valid Authentication header, but it does not have permission to access this resource. | | `404`   **Not Found** | The requested resource cannot be found or does not exist. | | `500`, `502`, `503`, `504`
**Internal Server Error** | The server encountered an error while processing your request and failed. Something went wrong on Permutive's end. | ## Subcodes If there has been an error (non-200 response), the body of the response will be a JSON object that describes the error in more detail. To help you better understand what went wrong and reference the problem, the API defines an error subcode and human-readable messages for every type of error that can occur. The `docs` field provides a resource where you can find out more information about this error. If you wish to contact our support team about any API errors, you can reference the `request_id` of your request. All error responses from the API follow a common format. Here's an example response to a request with an invalid API key: ```json theme={"dark"} HTTP/1.1 401 Unauthorized { "request_id": "e2cef6f6-296d-4044-b362-73fb2bce4a72", "error": { "status": "Unauthorized", "code": 2000, "message": "The API key provided is invalid.", "cause": "This key does not exist.", "docs": "https://developer.permutive.com/docs/errors/401/2000" } } ``` Every field is guaranteed, other than `cause` which is optional. **Understanding errors** If you're unclear on what an error means, or want more help please get in touch using the chat widget or by emailing [Support](mailto:support@permutive.com). # Introduction Source: https://docs.permutive.com/api/events/introduction Capture behavioral and contextual data An event is an action a user takes on one of your environments, such as a site or app. Examples include viewing a page, clicking an article, pausing a video, or scrolling down a page/view. Events are a core data abstraction within the Permutive platform, typically used to capture user behavioral and contextual data. They are one the primary building inputs to building cohorts, activating audiences, and warehousing data for subsequent analysis. ## Overview The Events API provides one endpoint: Segment a user based on events and persist state for future requests. # Track an event Source: https://docs.permutive.com/api/events/track POST /events This endpoint creates a new event for a user, persisting the event in Permutive for downstream consumption, such as for segmentation, insights, or routing to a data lake. It validates the event against the schema defined in your workspace, and generates an event ID for the event and optionally enriches the event with geo, ISP, and contextual data. ## Event schema It's important to note that Permutive enforces event schemas to ensure data consistency and validity. If an event is tracked that does not match the expected schema for the event type, it will be rejected and a `400` error will be returned. Where Permutive hasn't received an event type before, it will infer the event's schema. Event schemas are viewable in the [Permutive Dashboard](https://dash.permutive.com). To migrate an event schema, please [email support](mailto:support@permutive.com). ## Event size limit The size of an event's `properties` JSON object is limited to 950 KB to ensure platform performance. If an event exceeds this size limit, it will be rejected and a 400 error will be returned. ## Time attributes When streaming in data in real-time (e.g. from within an app), Permutive sets a time attribute for events to the current time in UTC. # Create a user ID Source: https://docs.permutive.com/api/identity/create POST /users This endpoint generates a new user ID that can be used to identify a user within Permutive. When you're not using an SDK, and you're interacting with this endpoint directly, it is your responsibility to store a user's ID. This endpoint responds with a Permtive user ID that can be used to identify a user within Permutive, to be stored by the client. # Identify a user Source: https://docs.permutive.com/api/identity/identify POST /identify Associate one or more identities with a Permutive user, allowing you to identify users in multiple ways. There are a couple of scenarios for this endpoint, depending on whether the user has been identified before: 1. **None of the provided identities have been seen before:** The API will create new identities for those provided and associate them with the Permutive user ID. This means in the future all the provided identities will map to the same Permutive user ID. 2. **Some or all of the provided identities have been seen before:** There are two sub-scenarios: * **Case A**: When all of the provided identities are associated with the same Permutive user ID, this user is returned and no further action is taken. * **Case B**: When the provided identities are associated with different Permutive user IDs, the identity with the highest priority (lowest number) takes precedence, and all other identities will then be synchronized to map to this user ID. ## Identity tags The identity tag(s) passed in the request body of the request (e.g. `email_sha256`, `appnexus`) must be from the identity tag allow-list configured in the [Settings](https://dash.permutive.com/settings) section of the Permutive dashboard. If an identity tag included in the identify call is not in the allow-list, it will be ignored and not associated with the Permutive user ID. ## Identity priorities Prioritized aliases allow customers to express which identifiers they consider most reliable or authoritative for resolving user identities. When multiple aliases are provided, priorities are used to determine the order in which aliases are attempted for future resolution to a Permutive user ID, where priority `0` has the highest priority. # Introduction Source: https://docs.permutive.com/api/identity/introduction Manage device, user, and household identities Permutive's Identity API lets media companies manage identities across their organization. ## Permutive ID By default, Permutive generates a unique user ID for each user on a device, referred to the as the *Permutive ID*. This is a first-party identity that exists within the context of the publisher's domain or app, and is therefore distinct across publishers. It does not track users across domains or devices, and it has no reliance on third-party identifiers like cookies or mobile device IDs. The Permutive ID is used on-device to store cohort state across sessions and in Permutive's cloud to generate accurate publisher-level insights. ## Identities Customers can also associate users with other identifiers, such as an email address the user has authenticated with on the publisher's site, or a third-party identity provided by a partner that the publisher has obtained user consent for. These identities can be used to: * **Unify cross-device behavior**: Connect a user's activity across multiple devices they are logged in to, enabling segmentation based on their combined behavior across sites & apps owned by the media company. * **Connect auxiliary data**: Augment user segmentation with third-party audience data provided by partners (with appropriate user consent). * **Enable identity-based activation**: Activate audiences through server-to-server integrations with partners that support identity-based targeting. The Identity API allows customers to associate and retrieve identities for users. ## Overview The Identity API provides three endpoints: Generate a new Permutive user ID. Associate one or more identifiers with a user. Retrieve identities for a user. # Retrieve identities Source: https://docs.permutive.com/api/identity/retrieve GET /users/{userId}/aliases Retrieves all identities available for a given user. **Private API key required**: You must provide a **private** API key during authentication to retrieve aliases. # Introduction Source: https://docs.permutive.com/api/introduction Learn how to start using Permutive's APIs Permutive's APIs allow media owners to collect, enrich, resolve, and segment their data in real-time, and to programmatically manage their Permutive account. For processing end-user data, our APIs are typically called via the various [SDKs](/sdks) Permutive offers for different environments, but customers are also free to directly interact with our APIs. Get started with Permutive's APIs by heading to [authentication](./authentication) and setting up an API key. ## Endpoints There are six primary Permutive APIs. They are broadly categorized into two groups: 1. **Real-time APIs**: Capture end-user actions on your sites & apps, register & resolve identities, perform segmentation, and retrieve contextual cohorts for a page. 2. **Management APIs**: Programmatically manage cohorts and second-party data taxonomies in your Permutive account. ### Real-time APIs Capture actions on your sites & apps Register & resolve identities Retrieve contextual cohorts for a page Segment custom cohorts in real-time ### Management APIs Programmatically manage your cohorts Manage your second-party data taxonomies # Get Cohort Detail Source: https://docs.permutive.com/api/mcp/get_cohort_detail **Tool name** `get_cohort_detail` Read-only · Idempotent Returns metadata for specific cohorts: name, type, state, tags, owning workspace, and which of the requested workspaces the cohort is available in. ## Example prompt > What type and state is the "Luxury Auto Intenders" cohort, and which of my workspaces can use it? ## Parameters Cohort short ids to retrieve metadata for. Obtain these from search\_cohorts. min 1 item. Workspaces to look the given cohorts up in. min 1 item. ## Returns Cohorts that were found in the requested workspaces, each with its metadata. Which of the requested workspaces this cohort can be used from. Pass any of these to follow-up tools. min 1 item. Cohort short id, matching the input. Cohort type. one of: `custom`, `lookalike`, `contextual`, `standard`, `classificationModel`, `advertiser`. Human-readable cohort name. Workspace where this cohort was originally defined. Whether the cohort is enabled or disabled. Tags assigned to this cohort by the publisher. Estimated unique users in the last 30 days, across the requested workspaces. Cohorts that weren't found in any of the requested workspaces. Cohort short id that wasn't found. # Get Organizations And Workspaces Source: https://docs.permutive.com/api/mcp/get_orgs_and_workspaces **Tool name** `get_orgs_and_workspaces` Read-only · Idempotent Returns the organizations and workspaces accessible to the authenticated user. ## Example prompt > Which organizations and workspaces do I have access to? ## Parameters This tool takes no parameters. ## Returns # Get Workspace Details Source: https://docs.permutive.com/api/mcp/get_workspace_details **Tool name** `get_workspace_details` Read-only · Idempotent Describes one or more workspaces: each one's name, the publisher domains it collects data on, the cohort tags used in it, and its usage stats (users and page views, broken down by domain, device, and country). ## Example prompt > Which domains do we serve from our UK workspace? What's the geographical coverage for the News workspace? ## Parameters Workspaces to describe. Each requested workspace is described separately in the response. min 1 item. ## Returns One entry per requested workspace, in the order requested. Publisher domains this workspace collects data on. Pass these to measure\_audiences as restrict\_to. Workspace name. Usage stats for the whole workspace over the last 30 days. Users and page views per country (ISO 3166-1 alpha-3 code), top 20 by users. ISO 3166-1 alpha-3 country code (e.g. GBR, USA). Estimated page views in this country. Estimated unique users in this country. Users and page views per device type. Device type (e.g. Desktop, Mobile, Tablet, CTV). Estimated page views on this device type. Estimated unique users on this device type. Users and page views per publisher domain, top 20 by users. Publisher domain name. Estimated page views on this domain. Estimated unique users on this domain. Estimated page views in the workspace in the last 30 days. Estimated unique users active in the workspace in the last 30 days. Cohort tags used in this workspace. The workspace these details describe. # Introduction Source: https://docs.permutive.com/api/mcp/introduction Audience intelligence, built for AI agents The Permutive MCP server is in a **testing phase with partnering customers**. Access is invitation-only and the tool interfaces may change. If you would like access, please contact your Permutive representative. ## What it is The Permutive MCP server gives AI agents a direct, structured line into your audience data. Connect an agent — such as Claude — to your Permutive account, and it can explore and reason about your cohorts in plain language: find audiences that match a brief, pull their detail, and combine them to check reach, all without you writing queries or leaving the conversation. It implements the [Model Context Protocol](https://modelcontextprotocol.io), an open standard — published by Anthropic and adopted across the major model providers and agent frameworks — for connecting AI agents to external systems. The shorthand is "USB-C for AI": instead of a bespoke integration per agent, the agent and the service agree on a common protocol. The agent reads the tool list, understands each tool from its description, and calls it — no glue code required. MCP is vendor-neutral. The server works with Claude, ChatGPT, Cursor, and any agent or framework that speaks the protocol — you are not locked into a model provider to use it. Every tool has a typed schema and a description the agent reads at runtime. No HTML parsing, no fragile screen-scraping — the agent gets a precise, typed definition of each tool's inputs and outputs. Authentication is per organization. An agent acting on your behalf sees only your data — your cohorts, domains, and activations. The same boundaries that apply in the Permutive UI apply here. ## What you can do The tools cover the full discovery-to-evaluation loop a commercial team runs when responding to a brief: * **Discover audiences** — find cohorts matching a natural-language brief ("high-income readers interested in luxury cars") across one or more workspaces. * **Understand a cohort** — look up a cohort's name, type, state, tags, and which workspaces it is available in. * **Measure reach** — estimate how many users and page views an audience reaches, with optional breakdowns by cohort, domain, device, country, and workspace. * **Compose audiences** — combine cohorts and measure the result, iterating as you refine against the brief. The tools explore and evaluate your audience data — they do not create, edit, or delete anything in your account. These bullets are a curated tour, not the full list — see the [All Tools reference](/api/mcp/tools) for every tool, with parameters, return shapes, and example prompts. ## Example use cases * A planner sizing an audience for a campaign brief, directly in their AI assistant. * An analyst comparing the reach of several cohort combinations without building a report. * Quickly checking which workspaces a cohort is available in before activating it. ## Access Alpha is a structured technical-validation stage with a small group of design partners, so there is no SLA yet. To request access, or to find out more about how it fits your workflows, **contact your Permutive representative**. Once you're granted access, they will provide the connection details and walk you through signing in. The connection acts with your Permutive account, so the tools work with the organizations and workspaces you already have access to. # List Cohorts Source: https://docs.permutive.com/api/mcp/list_cohorts **Tool name** `list_cohorts` Read-only · Idempotent Browses the cohorts available across one or more workspaces, filtered by type, state, tags, a name keyword, and a user-count range, and ordered by short id, name, or user count. Each returned cohort includes its estimated unique users over the last 30 days. Returns one page at a time. ## Example prompt > List all the enabled lookalike cohorts available across my workspaces. ## Parameters Restrict to these cohort types. Omit to list all types the caller can see. min 1 item. each one of: `custom`, `lookalike`, `contextual`, `standard`, `classificationModel`, `advertiser`. Only include cohorts with at most this many users. Omit for no upper bound. Only include cohorts with at least this many users. Omit for no lower bound. Case-insensitive substring matched against each cohort's name. Omit to match all cohorts. Opaque token from a previous response's `next_page_token`, to fetch the next page. Field to order the listing by. Defaults to short\_id. one of: `short_id`, `name`, `users`. Order direction. Defaults to ascending. one of: `asc`, `desc`. Restrict to enabled or disabled cohorts. Omit to list both. one of: `enabled`, `disabled`. Restrict to cohorts tagged with at least one of these tags. min 1 item. Workspaces whose cohorts are aggregated into a single flat list, not broken down by workspace. min 1 item. ## Returns A page of cohorts matching the query, in the requested order (short id by default). Which of the requested workspaces this cohort can be used from. Pass any of these to follow-up tools. min 1 item. Cohort type one of: `custom`, `lookalike`, `contextual`, `standard`, `classificationModel`, `advertiser`. Human-readable cohort name. Workspace where this cohort was originally defined. Stable unique cohort identifier (UUID). Short cohort identifier. Pass to get\_cohort\_detail, and as the cohort reference in measure\_audiences expressions. Whether the cohort is enabled or disabled. Estimated unique users in the last 30 days, across the requested workspaces. True when the listing was capped and some matching cohorts are missing (from this page and every other page of this query). Pass as `page_token` to fetch the next page. Absent on the last page. Expires shortly after the first page. Total cohorts matching the filters across all pages, before pagination. # Measure Audiences Source: https://docs.permutive.com/api/mcp/measure_audiences **Tool name** `measure_audiences` Read-only · Idempotent Measures the reach of an audience by combining multiple cohorts — how many users are in it and how many page views they generate. ## Example prompt > How many users and page views would I reach if I combined the luxury car cohort with the premium travel cohort, broken down by country? ## Parameters One or more audience expressions to measure, each a string in LISP S-expression form. min 1 item. Which breakdowns to include beyond total reach. each one of: `cohort_contributions`, `domain_breakdown`, `device_breakdown`, `country_breakdown`, `workspace_breakdown`. Narrow the measured population to activity matching these dimensions. ISO 3166-1 alpha-3 country codes (e.g. GBR, USA). When set, only activity in these countries is counted. Domain names (e.g. telegraph.co.uk). When set, only activity on these domains is counted. Workspaces whose user-behaviour data is aggregated across. min 1 item. ## Returns One measurement per measurable expression. Per-cohort contribution to this expression. Cohort short id. Cohort's standalone size within the restrict\_to filters. Cohort's incremental contribution of unique users to the composed expression. Users per country (ISO 3166-1 alpha-3 code) within this expression's audience. ISO 3166-1 alpha-3 country code (e.g. GBR, USA). Estimated page views in this country. Estimated unique users in this country. Users per device type within this expression's audience. Device type (e.g. Desktop, Mobile, Tablet, CTV). Estimated page views on this device type. Estimated unique users on this device type. Users per publisher domain within this expression's audience. Publisher domain name. Estimated page views on this domain. Estimated unique users on this domain. The expression that was evaluated, echoed back so results can be paired with inputs. Total reach for this expression across the requested workspaces, within the restrict\_to filters. Estimated page views in the last 30 days. Estimated unique users in the last 30 days. Users per requested workspace within this expression's audience. Estimated unique users in this workspace matching the expression. One of the requested workspace IDs. Expressions that were not measured because one or more of their cohorts aren't available in any of the requested workspaces. The expression that could not be measured, echoed back so it can be paired with your input. The cohort short ids in this expression that aren't available in any of the requested workspaces. min 1 item. # Search Cohorts Source: https://docs.permutive.com/api/mcp/search_cohorts **Tool name** `search_cohorts` Read-only · Idempotent Searches for cohorts matching a natural language query across one or more workspaces. ## Example prompt > Find cohorts of high-income readers interested in luxury cars and premium lifestyle content. ## Parameters Maximum number of cohorts to return. Defaults to 20. Only include cohorts with at most this many users. Omit for no upper bound. Only include cohorts with at least this many users. Omit for no lower bound. The keywords, persona or campaign description to match. Workspaces to search across. min 1 item. ## Returns Cohorts matching the query, ranked by relevance (most relevant first). Which of the requested workspaces this cohort can be used from. Pass any of these to follow-up tools. min 1 item. Cohort type one of: `custom`, `lookalike`, `contextual`, `standard`, `classificationModel`, `advertiser`. Human-readable cohort name. Workspace where this cohort was originally defined. Stable unique cohort identifier (UUID). Short cohort identifier. Pass to get\_cohort\_detail, and as the cohort reference in measure\_audiences expressions. A summary of how users are targeted in this cohort. May be absent for newly created cohorts. Estimated unique users in the last 30 days, across the requested workspaces. # All Tools Source: https://docs.permutive.com/api/mcp/tools Tools exposed by the Permutive MCP server | Tool | Access | Description | | -------------------------------------------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Get Cohort Detail](/api/mcp/get_cohort_detail) | Read-only | Returns metadata for specific cohorts: name, type, state, tags, owning workspace, and which of the requested workspaces the cohort is available in. | | [Get Organizations And Workspaces](/api/mcp/get_orgs_and_workspaces) | Read-only | Returns the organizations and workspaces accessible to the authenticated user. | | [Get Workspace Details](/api/mcp/get_workspace_details) | Read-only | Describes one or more workspaces: each one's name, the publisher domains it collects data on, the cohort tags used in it, and its usage stats (users and page views, broken down by domain, device, and country). | | [List Cohorts](/api/mcp/list_cohorts) | Read-only | Browses the cohorts available across one or more workspaces, filtered by type, state, tags, a name keyword, and a user-count range, and ordered by short id, name, or user count. Each returned cohort includes its estimated unique users over the last 30 days. Returns one page at a time. | | [Measure Audiences](/api/mcp/measure_audiences) | Read-only | Measures the reach of an audience by combining multiple cohorts — how many users are in it and how many page views they generate. | | [Search Cohorts](/api/mcp/search_cohorts) | Read-only | Searches for cohorts matching a natural language query across one or more workspaces. | # Requests & Responses Source: https://docs.permutive.com/api/requests-and-responses Sending & receiving calls from the API The Permutive API follows [REST](https://en.wikipedia.org/wiki/REST)ful principles. It is designed to have predictable, resource-oriented URLs and make use of built-in HTTP features such as HTTP response codes and HTTP authentication. ## Base URL The base URL for the Permutive API is ``` https://api.permutive.app/ ``` Customers who have been with Permutive longer may have deployments with a base URL of `https://api.permutive.com`. Permutive continues to support this address but we recommend and ask that new deployments use the `.app` base URL. ## Content types All data exchanged between clients and the API is **JSON**, including errors, so all API requests should set the `Content-Type` header to `application/json`. ## Field types Unless otherwise stated, these are the default data types for common categories of fields: | Field type | Format | | ------------------ | ------------------------------------------------------------------------------------- | | Resource IDs | Version 4 [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) format. | | Permutive User IDs | Version 4 [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) format. | | Session & view IDs | Version 4 [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) format. | | Timestamps | Requested & returned in [ISO8601](https://en.wikipedia.org/wiki/ISO_8601) format. | ## Error responses Please see [Errors](./errors) to understand what error types may be included in responses. # Batch update segments Source: https://docs.permutive.com/api/taxonomy/batch-update PATCH /imports/{importId}/segments Note that operation order is not guaranteed. No more than 5,000 operations can be contained in a batch. # Create a segment for an import Source: https://docs.permutive.com/api/taxonomy/create-a-segment POST /imports/{importId}/segments # Delete a segment by its code Source: https://docs.permutive.com/api/taxonomy/delete-a-segment-by-code DELETE /imports/{importId}/segments/code/{segmentCode} Deletes a segment for an import. The segment is identified by its globally unique public ID. # Delete a segment by its public ID Source: https://docs.permutive.com/api/taxonomy/delete-a-segment-by-id DELETE /imports/{importId}/segments/{segmentId} Deletes a segment for an import. The segment is identified by its code. # Get a segment by its code from an import Source: https://docs.permutive.com/api/taxonomy/get-a-segment-by-code GET /imports/{importId}/segments/code/{segmentCode} Retrieves a single segment for a given import. The segment is identified by its code. # Get a segment by its public ID from an import Source: https://docs.permutive.com/api/taxonomy/get-a-segment-by-id GET /imports/{importId}/segments/{segmentId} Retrieves a single segment for a given import. The segment is identified by its globally unique public ID (UUID) # Get all imports Source: https://docs.permutive.com/api/taxonomy/get-imports GET /imports # Get all segments for an import Source: https://docs.permutive.com/api/taxonomy/get-segments-for-import GET /imports/{importId}/segments Retrieves all segments for an import. This is a paginated endpoint and takes an optional pagination token as a query parameter. If no token is provided then it retrieves the first page of results. # Introduction Source: https://docs.permutive.com/api/taxonomy/introduction Programmatically manage second-party taxonomies A second-party taxonomy provides a mapping of segment codes to human-readable names and metadata (e.g. descriptions) for use in building Permutive cohorts. The Taxonomy API allows for the programmatic creation, mutation, and deletion of second-party taxonomies for imported data in your workspace. ## Authentication It's important to note that all requests to the Taxonomy API must be authenticated with a private API key. See the [Authentication](/api/authentication) documentation for details on obtaining and using a private API key. ## Endpoints The Taxonomy API provides 11 endpoints: Get all imports for a workspace. Retrieve a specific import by its import ID. Get all segments for a given import. Get a segments for an import by its public import ID. Get a segment for an import by its code. Create a segment for an import. Update a segment for an import by its public import ID. Update a segment for an import by its code. Batch update segments for an import. Delete a segment for an import by its public import ID. Delete a segment for an import by its code. # Retrieve an import Source: https://docs.permutive.com/api/taxonomy/retrieve-import GET /imports/{importId} # Update a segment by its code Source: https://docs.permutive.com/api/taxonomy/update-a-segment-by-code PATCH /imports/{importId}/segments/code/{segmentCode} Updates a segment for an import. The segment is identified by its code. # Update a segment by its public ID Source: https://docs.permutive.com/api/taxonomy/update-a-segment-by-id PATCH /imports/{importId}/segments/{segmentId} Updates a segment for an import. The segment is identified by its globally unique public ID. # Versioning Source: https://docs.permutive.com/api/versioning Selecting the right version of our APIs Permutive's APIs are versioned to ensure that your applications continue to work as expected as the platform evolves. All API requests require specifying an API version. The version of the API is specified in the URL at the leftmost (highest) scope of the path: ```curl theme={"dark"} curl https://api.permutive.app/{version}... \ ... ``` Version numbers follow [Semantic Versioning](https://semver.org). In short, this means that versions that have the same MAJOR number are backwards compatible. For instance, **v2.0** and **v2.1** are guaranteed to be backwards compatible, but **v2.1** adds new features and/or routes. Versions **v2.x** and **v3.x** are not guaranteed to be backwards compatible. The current version of the Permutive API is **v2.0**. Fixes, improvements, and additions for a given version of the API are made in a backwards-compatible manner to ensure each version has a stable interface across its lifetime. While we don't expect public endpoints to change greatly, keep in mind that the API is continuously under development. # Activations Source: https://docs.permutive.com/concepts/activations Delivering your audiences to ad platforms for targeting An **activation** is a cohort configured for delivery to a specific ad platform. While cohorts define the audience segments users belong to, activations control which of those cohorts are sent to which platforms for targeting. Not all cohorts need to be sent to every platform. Activations give publishers control over the distribution of their audiences, enabling: * **Platform-specific targeting** — Send different cohorts to different ad platforms based on campaign requirements. * **Data governance** — Control which platforms receive which audience segments, maintaining oversight of how audience data is used. * **Performance optimization** — Only deliver relevant cohorts to each platform, reducing noise and improving targeting efficiency. ## Cohorts vs. activations **Cohorts** represent the full set of audience segments a user belongs to within Permutive. **Activations** are the subset of those cohorts that have been configured for a specific ad platform. A single cohort can have activations across multiple platforms, or may not be activated at all if it is used only for insights or internal analysis. ## How activations work Cohort membership is computed on-device by the Permutive SDK. When a user is a member of an activated cohort, the SDK delivers the relevant activation data directly to the ad platform during the ad request—typically as key-value pairs passed to the ad server. This on-device delivery ensures that activation happens in real time, on the first pageview, for 100% of users. ## Activation types Activations are configured per platform. Common activation types include: * **Ad server activations** — Deliver cohorts to ad servers like Google Ad Manager, Xandr, or FreeWheel for direct targeting. * **Bidstream activations** — Pass cohort data into the bidstream via integrations like Prebid, making audiences available to demand-side buyers. * **Identity-based activations** — Deliver cohort data via server-to-server integrations using identifiers such as hashed emails, advertising IDs, or IP addresses, enabling activation with DSPs, SSPs, and other partners. See [Server-side identity-based activation](/integrations/server-side-identity-based-activation) for how this pattern works and which integrations use it. # Cohorts Source: https://docs.permutive.com/concepts/cohorts Securely activate your audiences at scale ## Cohort types Permutive supports several types of cohorts to address different targeting needs: * **Custom cohorts**: Audiences defined by publishers using their first-party behavioral data, such as users who have read articles in a specific category or engaged with particular content. * **Contextual & affinity cohorts**: Audiences based on page-level signals rather than user-level data. Contextual cohorts target content that matches criteria such as categories, keywords, or sentiment derived from NLP classification. Affinity cohorts extend this by leveraging insights from consented users—Permutive calculates an affinity score for each URL based on how likely users in a specific custom cohort are to engage with that content compared to the site average, allowing publishers to apply behavioral insights to contextual targeting without requiring user consent. * **Modeled cohorts**: Audiences expanded through lookalike modeling or classification models, allowing publishers to reach users who share characteristics with a seed audience. * **Standard cohorts**: Pre-built audiences based on common taxonomies (such as IAB categories) that provide consistent targeting options across publishers. # Events Source: https://docs.permutive.com/concepts/events Capturing your users' first-party behavior An **event** is an action a user takes on your site or app. Examples include viewing a page, clicking an article, or pausing a video. Events capture a publisher's user behavioral and contextual data, enabling granular segmentation of users into cohorts for real-time activation & insights, and warehousing of first-party data for subsequent analysis. For each event type they wish to capture, publishers may define an event schema that specifies the properties to collect for each event and their data types. Permutive provides a set of default event types whose schemas capture common data for publisher use-cases, such as pageviews and video views. This schema is enforced across all workspaces within an organization. # Identity Source: https://docs.permutive.com/concepts/identity A foundation for accurate audience reporting and connecting auxiliary data A user on a device is automatically allocated a unique device ID by the Permutive SDK on behalf of the publisher. This **user ID** is distinct across publishers, such that the same user who visits two different publishers' sites on their device will be assigned distinct IDs. As a first-party identity, it does not track users across domains or devices, and it has no reliance on third-party identifiers like cookies or mobile device IDs. The user ID is used in Permutive's cloud to generate accurate publisher-level audience insights, and on-device to store cohort information across sessions. ## Identifiers In addition to the automatically assigned user ID, publishers can associate users with other **identifiers**. An identifier represents a specific ID type (such as an email address or a third-party identity) and consists of: * **Tag**: The type of identifier (e.g., `email`, `uid2`, `rampid`) * **Value**: The actual ID value for the user Identifiers can be used to: * **Unify cross-device behavior**: Connect a user's activity across multiple devices they are logged in to, enabling segmentation based on their combined behavior across sites owned by the publisher. * **Connect auxiliary data**: Augment user segmentation with third-party audience data provided by partners (with appropriate user consent). * **Enable identity-based activation**: Activate audiences through server-to-server integrations with partners that support identifier-based targeting. Publishers can configure which identifier types they collect and use within their Permutive workspace through the Identity Graph. ## User group identifiers In addition to user-level identifiers, Permutive supports **user group identifiers** that represent a group of users rather than an individual. The most common example is a **household ID**. A household ID represents users residing in the same household, enabling publishers to: * **Segment at the household level**: Build cohorts based on the combined behavior of all users in a household, such as "households with 5+ video starts in the last 14 days." * **Target across devices**: Reach all devices in a household based on behavior observed on any member device—particularly valuable in CTV environments where multiple devices share a household context. * **Apply household-level frequency caps**: Control ad exposure across all devices in a household rather than per-device. Household ID graphs can be imported into Permutive via Connectivity from data warehouses such as BigQuery, Snowflake, or S3, and are managed through the Identity Graph alongside user-level identifiers. ## Identity resolution **Identity resolution** is the process of linking multiple identifiers to the same user, creating a unified view across different touchpoints. When an identifier is set via the SDK or API, Permutive checks whether that identifier is already associated with an existing user. If a match is found, the user profiles are merged so that all identifiers resolve to the same Permutive ID. When multiple identifiers are provided, they are resolved in **priority order** (lower number = higher priority), allowing publishers to control which identifiers take precedence. Enabling an identifier for resolution improves audience accuracy and cross-device reach, but it is important to consider the privacy implications for each identifier type. For guidance on evaluating these trade-offs, see [Understanding ID Resolution Privacy Considerations](/guides/signals/identity/understanding-id-resolution-privacy). ## Identity Graph The **Identity Graph** is Permutive's core identity management system that collects, stores, and links identifiers to build a unified view of users across domains, devices, and sessions. It supports both user-level and user group-level identifiers, enabling publishers to build identity relationships for audience targeting, insights, measurement, and activation. Publishers configure their identifier types and manage the Identity Graph through the Permutive dashboard. # Insights Source: https://docs.permutive.com/concepts/insights Understand your audience to sell and deliver campaigns **Insights** analyze the users visiting your sites & apps so that you can plan, optimize, and report on your direct-sold campaigns. Permutive offers various insights products that align with key stages of the campaign lifecycle: 1. **Sales / Planning**: insights products focused on helping you to find the best audience for a given campaign opportunity / brief with the ability to analyze their broader behaviors and characteristics in order to build a compelling sales narrative. 2. **Deliver / Optimization**: insights that leverage campaign delivery and performance data from ad servers (GAM, Xandr and Equativ primarily supported) to understand key characteristics of users who have engaged with the campaign and identify new targeting opportunities for improved performance. The primary metric that Permutive's Insights capabilities report on is reach. Insights are designed for publishers to respond to briefs from advertisers by demonstrating their reach for the audiences the brand cares about, for planning campaigns through reach forecasts, for optimizing campaigns mid-flight against reach objectives, and to create data-rich insights post-campaign that provide evidence to the advertiser on how their delivery performed from a reach perspective. # Overview Source: https://docs.permutive.com/concepts/overview Understand the core concepts in the Permutive platform Addressing 100% of your audience Securely activate your audiences at scale Delivering your audiences to ad platforms for targeting Understand your audience reach Capturing your users’ first-party behavior A foundation for accurate audience reporting and connecting auxiliary data Capturing, processing, and activating your audiences in real-time Connecting data to and from Permutive Configuring sites and business units across your organization # Reach Source: https://docs.permutive.com/concepts/reach Addressing 100% of your audience **Reach** sits at the heart of a publisher's value proposition. Advertisers partner with publishers to reach their consumers through the publisher's endemic and non-endemic audiences. A publisher's ability to offer reachability of their audiences is key for providing value at scale to buyers. The technology a publisher uses to make audiences available to advertisers is a limiting factor on how much of the publisher's true audience size and distribution are available for activation. If the true size of an audience is 10M users, but you are only able to provide targeting for 4M of those users, the reachability you offer for this audience is 40%. Reachability can be broken down further by environment distribution; for example, if 3M of these users interact with your site through Safari but you're unable to target them, you are unable to reach Apple consumers who should fall into this audience. ## Achieving 100% addressability Permutive's edge technology enables publishers to address **100% of their audience** regardless of browser, device, or environment. By processing data on-device rather than relying on third-party identifiers, Permutive can build and activate cohorts for every user—including those in privacy-safe environments like Safari, Firefox, and iOS where traditional ad-tech cannot reach. ## What limits reach? A number of factors can negatively impact a publisher's reach with traditional technologies: * **Reliance on tracking technologies:** Audience delivery that relies on third-party identifiers like third-party cookies or techniques like fingerprinting reduces reach because users that engage with your sites & apps in privacy-safe environments like Safari & Firefox and on iOS are invisible with respect to reporting and activation. * **First pageview targeting:** As browsing behavior has changed, passerby users have come to represent a significant portion of a publisher's traffic. Publishers must be able to understand and activate new users during their first pageview, otherwise they miss out on reaching a significant percentage of their true audience. * **Availability of data and modeling capabilities:** To sell and target non-endemic audiences, a publisher must be able to reach users who they cannot identify solely with their first-party data. A publisher's ability to import data sources and model them against their first-party user data determines the reach they can offer to brands targeting audiences that are not endemic to their properties. # SDKs Source: https://docs.permutive.com/concepts/sdks Capturing, processing, and activating your audiences in real-time Permutive **SDKs** are deployed by publishers to their sites & apps to provide a privacy-safe environment for capturing, processing, and activating user data in real-time. While a typical data platform's SDK is responsible for capturing data and sending it to the platform's API for downstream computation in the cloud, Permutive's SDKs are unique in that they are designed to process data on-device and activate audiences in addition to data collection. Combined with our API, Permutive operates a hybrid edge-cloud architecture in which our SDKs determine and activate cohorts for individual users at the edge, and our cloud provides publisher-level insights. A Permutive SDK is responsible for tracking events on-device, processing these events and other connected data for each cohort to determine a user's cohort memberships, performing activations attached to cohorts—typically attaching cohorts to an ad request—and finally for recording events and cohorts to the publisher's cloud instance of Permutive. Permutive offers SDKs for web (desktop, AMP, FB Instant Articles), mobile (iOS, Android, React Native), and CTV (Roku, Apple TV, Amazon Fire TV, and Samsung TV). # Source/Destination Source: https://docs.permutive.com/concepts/sources-destinations Connecting data to and from Permutive Publishers need to be able to connect their tools and data sources to their audience offering. There are two broad types of connections required: consuming the necessary data sources to build your audiences and pushing them to the tools you rely on. Permutive provides these capabilities through two types of connections, sources and destinations: * A **source** is a platform from which you want to capture data into Permutive. Examples of sources include on-site video players and third-party data providers. * A **destination** is a platform where you want to send data or activate audiences via Permutive. Examples of destinations include ad servers, where publishers will send cohorts for activation, and data warehouses, where they will send event data for storage & analysis. Sources and destinations connect either on-device or in the cloud according to their use-cases. For example, an ad server destination might connect on-device to guarantee cohorts are added to ad requests in real-time, whereas a data warehouse destination would be cloud-based. # Workspaces Source: https://docs.permutive.com/concepts/workspaces Organizing your deployments across sites and business units in your organization A **workspace** contains a Permutive deployment for a site or app. Publishers with multiple sites and apps can create a workspace for each site/app within their **organization**, providing a logical separation of resources (event schemas, cohorts, activations) and team permissions for each site/app. Workspaces can be grouped together under a **business unit**, with an organization able to have multiple business units. # Consent Source: https://docs.permutive.com/governance/consent How data controllers can signal user consent to Permutive ## Consent mechanisms Permutive operates as a data processor, processing personal data on behalf of our customers who act as data controllers. Under the GDPR and ePrivacy Directive, data controllers seek consent from their users to process personal data on their behalf. As a data processor, Permutive provides **consent mechanisms** for data controllers to signal to Permutive that they have obtained consent to process data for a user. Permutive provides two different consent mechanisms that customers can configure for their Permutive deployment, in addition to a mechanism for offering users the option to opt out of all tracking. These are described in the sections below. ## Consent-by-token Permutive's **consent-by-token** mechanism ensures no user data is collected or processed until a data controller has received consent from the user. Data controllers signal the user's consent via a token to the Permutive SDK. Once the Permutive SDK has been granted consent for this user, the SDK will start collecting & processing user data from this moment on only. The user can revoke this consent at any point. To configure your Permutive SDK in *consent-by-token* mode, set the `consentRequired` configuration field to true: ```javascript Web SDK theme={"dark"} ``` No user data will be tracked by the SDK until it receives a consent token for the user. Once you have obtained consent for the user, it can be passed to Permutive by calling the SDK `consent` method with the consent token: ```javascript Web SDK theme={"dark"} permutive.consent({ "opt_in": true, "token": "" }); ``` From this point on, the SDK will track user event data — or until the user wishes to opt out. ### More information on SDK configuration for different environments ## Consent-by-default If `consentRequired` is not specified or is set to `false`, Permutive assumes the data controller has consent to track their users’ data. In this configuration mode, which we call **consent-by-default**, the collection of user data starts from the first time Permutive’s SDK loads without requiring a consent token be passed by the controller for the user. ## Opt out You may choose or be required to offer users the option to opt out of tracking. All future tracking is then disabled for the user until the point they opt back in. Whether the SDK is configured to have `consentRequired` as `true` or `false`, a user can be opted out by setting the consent `opt_in` field to `false`: ```javascript Web SDK theme={"dark"} permutive.consent({ "opt_in": false }); ``` ## Transparency & Consent Framework (TCF) As a data processor, Permutive relies on consent obtained by the data controller, passed to Permutive using the consent mechanisms described above. For customers who use a consent management platform that relies on the Global Vendor List (GVL) to display their vendors to consumers, Permutive is also registered on the GVL for IAB Europe's [Transparency & Consent Framework (TCF)](https://iabeurope.eu/iab-europe-transparency-consent-framework-policies) as of TCF v2.3. Our GVL ID is **361** and we operate under **Purpose 1** (*store and/or access information on a device*) only, as is recommended for data processors by IAB Europe. # Data subject requests Source: https://docs.permutive.com/governance/data-subject-requests How Permutive complies with individuals' rights regarding personal data processing under the GDPR and other privacy laws ## Understanding data subject rights Privacy laws such as the General Data Protection Regulation (GDPR) in the EU and California Consumer Privacy Act (CCPA) in the US grant individuals rights concerning their personal data. These are collectively known as data subject rights and they endow individuals (data subjects) with legally-enforceable rights to how their personal data is collected and processed. The key rights under privacy laws include: * **Right of access**: Individuals can request confirmation of whether their personal data is being processed and, if so, obtain a copy of that data along with information about how it is used. * **Right to erasure**: Also known as the "right to be forgotten", individuals can request deletion of their personal data under certain circumstances. * **Right to object**: Individuals can object to processing of their personal data in certain circumstances, including for direct marketing purposes. ## Permutive's role as a data processor Permutive operates as a data processor, processing personal data on behalf of our customers—typically media companies and advertisers—who act as data controllers. This relationship is governed by the Data Processing Agreement (DPA) between Permutive and each customer. As the data controller, you are responsible for responding to data subject requests from your users. Permutive provides the capabilities necessary for you to fulfill these obligations where we have processed your users' data as a processor acting on your instructions. The sections below outline how to submit requests to Permutive to action data subject rights on behalf of your users. ## Right of access requests To fulfill a data subject's right of access request, compile the relevant user IDs in a list or spreadsheet and email it to [datarequest@permutive.com](mailto:datarequest@permutive.com). User IDs may be submitted as either Permutive user IDs or identifiers. Please specify which identifier type you are providing in your email. We will action your request within 14 days of receipt and send confirmation once the request is complete. ## Right to erasure requests To fulfill a data subject's right to erasure request, compile the relevant user IDs in a list or spreadsheet and email it to [datarequest@permutive.com](mailto:datarequest@permutive.com). User IDs may be submitted as either Permutive user IDs or identifiers. Please specify which identifier type you are providing in your email. We will respond to confirm the date by which your request will be actioned, which will be no later than 14 days from the date we received your email. For example, a request received on the 1st of a month will be processed by the 15th of that month at the latest. For efficiency, we batch erasure requests. Any additional requests received between your initial submission and the processing date will be included in the same batch. Requests received after the batch is processed will begin a new 14-day cycle. You will receive a separate confirmation email for each request submitted, and a final confirmation once the erasure has been successfully processed. # Security Source: https://docs.permutive.com/governance/security Permutive's security practices and assurance program ## Overview At Permutive, security isn’t just a checkbox—it's a core principle we uphold in everything we do. It is embedded across our technology, processes, and organizational culture. Our team includes security professionals who ensure robust security measures are embedded into our technology stack and processes. We continuously improve our security posture through proactive assessments, rigorous testing, and a commitment to industry best practices. Our Trust Center contains more information about our security posture. Visit the Permutive Trust Center at [https://trust.permutive.com](https://trust.permutive.com) for our SOC 2 compliance attestation, subprocessors, and live compliance status. ## Data Security * **Encryption at rest**: Customer data is encrypted at rest using industry-standard cryptographic algorithms (AES-256). * **Encryption in transit**: Data in transit between clients, services, and internal systems is protected using TLS 1.2 or higher. * **Key management**: Encryption keys are protected using hardened cloud key management services with strict access controls, separation of duties, and auditing. Permutive leverages managed key management infrastructure provided by its cloud service providers. * **Data isolation**: Customer data is logically isolated between tenants using application- and infrastructure-level access controls. ## Identity and Access Management * **Single Sign-On (SSO)**: Permutive supports enterprise authentication using SAML and OpenID Connect, enabling customers to integrate with their corporate identity providers. * **Multi-Factor Authentication (MFA)**: Multi-factor authentication is enforced through customer identity providers for privileged and administrative access. * **Role-Based Access Control (RBAC)**: Access to platform features is governed by role-based permissions aligned with the principle of least privilege. * **Just-in-time privileged access**: Privileged access to production systems is granted on a just-in-time basis using privileged access management controls, with time-bound permissions and approval workflows to minimize standing privileges. ## Platform and Infrastructure Security * **Secure cloud architecture**: Permutive operates on hardened cloud infrastructure with environment isolation, network segmentation, and controlled access pathways for administrative and production systems. * **Perimeter and network protection**: Web application and network traffic are protected using cloud-native security controls, including DDoS mitigation and application-layer filtering. * **Continuous vulnerability management**: We perform continuous vulnerability scanning and assessment across application code, cloud infrastructure, and workloads. * **Runtime and workload protection**: Cloud-native security tooling is used to monitor workloads, detect threats, and identify misconfigurations and vulnerabilities. * **Secure software development lifecycle (SDLC)**: Security testing is integrated into development pipelines, including automated code analysis and dependency scanning, with security reviews for high-risk changes. ## Compliance and Independent Assurance * **SOC 2 Type II and SOC 3**: Permutive undergoes annual independent SOC 2 Type II audits, with SOC 3 reports available publicly. * **Independent penetration testing**: External penetration tests are conducted by independent security specialists on a regular basis. * **Private bug bounty program**: Permutive operates a private vulnerability disclosure and bug bounty program to facilitate responsible reporting of security issues. * **Continuous security monitoring**: Production systems and security events are monitored continuously, with defined escalation and response procedures. ## Incident Response * **Incident response procedures**: Permutive maintains documented incident response processes with defined roles, escalation paths, and communication procedures. * **Customer notification**: Security incidents affecting customer data are communicated in accordance with contractual and regulatory obligations. ## Shared Responsibility Model Security responsibilities are shared between Permutive and its customers. Permutive is responsible for securing the underlying platform infrastructure, services, and managed components. Customers are responsible for identity management, access configuration, and the appropriate use of the platform in accordance with their security and privacy requirements. ## Learn More Access compliance reports, penetration testing documentation, security FAQs, and live compliance status in the Permutive Trust Center. For security-related inquiries, contact **[security@permutive.com](mailto:security@permutive.com)**. # Configuring Ad Server Targeting Source: https://docs.permutive.com/guides/clean-room/configuring-ad-server-targeting How to configure Clean Room targeting in Google Ad Manager or Microsoft Monetize ## Overview Once clean room audiences have been created, you need to configure targeting in your ad server. This guide covers setup for both Google Ad Manager and Microsoft Monetize. **Prerequisites:** * Permutive SDK properly configured with your ad server integration * For GAM: DFP integration enabled in your Permutive SDK * For Microsoft Monetize: AppNexus Seller Tag installed and configured ## Understanding Clean Room Targeting Values Clean room audiences use unique targeting values specific to each publisher-advertiser connection. You can find these targeting values in the Permutive Platform under **Connections** when viewing a specific advertiser connection. These values are: * Stored in the `_pcrprs` local storage key on the user's browser * Automatically populated by the Permutive SDK when a user is in a clean room audience ## Google Ad Manager (GAM) Targeting is **automatic** - no manual activation steps required. The Permutive SDK's DFP addon automatically reads targeting values from `_pcrprs` and applies them to ad requests. Ensure your Permutive SDK is configured with the DFP integration enabled. The DFP addon will automatically: * Read targeting values from the `_pcrprs` local storage key * Call Google Publisher Tag's `setTargeting()` method * Attach targeting to every ad request In GAM, go to **Delivery** > **Line Items** and create or edit a line item. Add key-value targeting using the targeting values from your advertiser connection. The key-value pairs will match what's in `_pcrprs`. Configure additional targeting criteria as needed (geography, device, etc.). Set frequency caps if desired. Associate the line item with the appropriate order and creative. Launch your campaign. ## Microsoft Monetize (formerly Xandr) Targeting requires **manual setup** in your on-page code to read from `_pcrprs` and pass values to the AppNexus Seller Tag. Ensure you have the AppNexus Seller Tag installed on your site. See the [Microsoft Monetize integration guide](/integrations/advertising/ad-servers/xandr) for details. Add the following JavaScript to read clean room targeting values and set keywords on your ad tags: ```javascript theme={"dark"} window.apntag = window.apntag || {}; window.apntag.anq = window.apntag.anq || []; window.apntag.anq.push(function () { var original = window.apntag.defineTag; window.apntag.defineTag = function (arg) { original(arg); try { if (arg.targetId) { // Read clean room targeting values from _pcrprs var kvs = window.localStorage.getItem("_pcrprs"); window.apntag.setKeywords( arg.targetId, { permutive: kvs ? JSON.parse(kvs) : [] }, { overrideKeyValue: true } ); } } catch (e) { /* Ignore */ } }; }); ``` Deploy this code before or alongside your Permutive tag. In Microsoft Monetize, create or update a line item. Set targeting to use the `permutive` key-value. Values will match the targeting values shown in your connection details. Configure additional targeting criteria as needed and launch your campaign. This setup is similar to the regular Microsoft Monetize integration but reads from `_pcrprs` (clean room values) instead of `_papns` (regular cohorts). See the full [Microsoft Monetize integration documentation](/integrations/advertising/ad-servers/xandr) for more details. ## Verification Tips * Check browser local storage for `_pcrprs` key to verify targeting values are being set * Use browser developer tools to inspect ad requests and confirm targeting key-values are present * Create test campaigns with low budget to verify targeting is working before full launch ## Next Steps Set up PMP deals in GAM Return to product overview # Configuring Frequency Control Source: https://docs.permutive.com/guides/clean-room/configuring-frequency-control How to set up frequency caps for Clean Room campaigns ## Overview Frequency control helps manage how often users see ads from clean room campaigns, preventing ad fatigue and optimizing campaign performance. **Prerequisites:** * Clean room audiences created * Publisher has configured order details with Line Item IDs in Permutive (required for frequency control to function) * Campaign running via PMPs configured in GAM ## Steps Find the clean room audience you want to configure frequency control for. Go to **Settings** > **Frequency Control**. Set maximum impressions per user per day/week/month based on your campaign goals. Frequency control is dependent on: * The campaign running via PMPs configured by the publishers in GAM * The publisher correctly registering the order details within Permutive (see [Sharing Proposal and Deal Information](/guides/clean-room/sharing-proposal-and-deal-information)) ## Next Steps Create more audiences Return to product overview # Creating Clean Room Audiences Source: https://docs.permutive.com/guides/clean-room/creating-clean-room-audiences How to create matched and modeled audiences in the Clean Room ## Overview This guide walks you through creating Clean Room audiences. Depending on your workflow: * **Publisher-driven workflows**: Publishers create audiences on behalf of advertisers * **Permutive-driven workflows**: Advertisers create their own audiences Both use the same interface described below. **Prerequisites:** * Data sources uploaded and permissioned between parties * Publisher connection established ## Steps In the left menu of your demand-side workspace, click **Cohorts**. Click **+ Add Cohort** in the top right. Enter a **Name** for your cohort (e.g., "ACME - Travel Intenders - Lookalike 70%"). Optionally add a **Description** to document the cohort's purpose. Build your **Query** using data from the available data sources: * Click the **+** button to add conditions * Select "FIRST PARTY" to access data source fields * Choose the data source (e.g., advertiser's uploaded data) * Select the specific field or segment you want to target * Add multiple conditions using "any" (OR) or "all" (AND) logic Select a **Country** to geo-lock the audience to a specific market. In the **Connections** dropdown, select which publisher connection(s) to deploy this cohort to. Click one of the buttons: * **Create Matched Cohort**: Creates an audience of users matched between the data sources. Segmentation begins immediately. * **Create Modeled Cohort**: Creates a lookalike audience by finding similar users. Requires model training (\~1 hour) followed by similarity selection before segmentation begins. ## Configuring Modeled Cohorts After you create a modeled cohort, the lookalike model will train for approximately 1 hour. Once training completes: Go back to the cohort in the Cohorts list. You'll see a curve showing the trade-off between audience similarity and reach. Choose a point on the curve based on your campaign goals: * **Higher similarity** (e.g., 90%): Smaller, more precise audience closely matching your seed data * **Lower similarity** (e.g., 70%): Larger audience with broader reach but less precise matching Confirm your selection. Segmentation will now begin and the cohort will be ready for targeting. **Important timing notes:** * **Matched cohorts** start segmenting users immediately upon creation * **Modeled cohorts** require: (1) \~1 hour for model training, (2) user selection of similarity-reach point, then (3) segmentation begins **Best practices:** * Use clear, descriptive naming conventions that include the advertiser name, audience description, and cohort type * Start with matched cohorts of at least 1,000 users for reliable targeting * For modeled cohorts, consider testing different model similarity levels (e.g., 70%, 80%, 90%) to balance reach and precision * Monitor cohort sizes and performance after creation to ensure they meet campaign requirements ## Next Steps Set up targeting in your ad server Return to product overview # Setting Up Advertiser Connections Source: https://docs.permutive.com/guides/clean-room/setting-up-advertiser-connections How to connect with advertisers in the Clean Room ## Overview This guide walks publishers through setting up a new advertiser connection in the Clean Room. Connections allow you to share your supply-side data with advertisers for audience matching and targeting. **This guide uses your supply-side dashboard.** All steps in this guide are completed in your supply-side workspace at dashboard.permutive.com. ### Understanding Connection Targets The Organization ID you enter depends on your workflow: | Workflow | Organization ID to Enter | What This Does | | -------------------- | ------------------------------------ | ---------------------------------------------------------------------------------- | | **Permutive-Driven** | Advertiser's Organization ID | Connects to the advertiser's demand-side workspace where they create audiences | | **Publisher-Driven** | Your own demand-side Organization ID | Connects your supply-side to your demand-side workspace where you create audiences | In the **publisher-driven workflow**, you're essentially connecting your two workspaces together. Your demand-side Organization ID can be found in your Clean Room dashboard under Settings. **Prerequisites:** * Clean Room enabled for your organization * For Permutive-driven: Advertiser's Organization ID (they can find this in their Permutive Settings) * For Publisher-driven: Your own demand-side Organization ID (found in your Clean Room dashboard Settings) ## Steps Access your Permutive Dashboard at [dashboard.permutive.com](https://dashboard.permutive.com). Click on the **Connections** tab in the menu bar. Click **+ Add Connection**. Enter the target Organization ID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). See [Understanding Connection Targets](#understanding-connection-targets) above to determine which ID to use for your workflow. Choose the permissions you want to grant: * **Standard Cohorts**: Allow targeting of pre-built standardized segments * **Matched Cohorts**: Allow targeting of users matched between your datasets * **Modeled Audiences**: Allow targeting of lookalike modeled audiences * **Insights**: Enable basic insights capabilities * **Advanced Insights**: Enable content read reports and advanced analytics * **Custom Cohorts**: Share specific custom cohorts for direct targeting by the advertiser Click **Create** to finalize the connection. The advertiser will now appear in your list of available connections. You can edit permissions or delete the connection at any time. ## What You'll See The Connections page shows your existing connections with their permission counts. You can edit or delete connections from here. Connections page showing list of advertiser connections When adding a new connection, enter the Organization ID and select the permissions you want to grant. New Connection form with Organization ID and permission toggles ## Next Steps Set up targeting in GAM or Microsoft Monetize Return to product overview # Sharing Proposal and Deal Information Source: https://docs.permutive.com/guides/clean-room/sharing-proposal-and-deal-information How to share GAM proposals and order details with advertisers ## Overview This guide walks publishers through sharing proposal and deal information with advertisers for Private Marketplace (PMP) deals within Google Ad Manager. This step is only required if you're setting up Private Marketplace (PMP) deals within Google Ad Manager to run clean room campaigns. For direct campaigns or non-PMP programmatic, you can skip this guide. **Prerequisites:** * Clean room audiences created and visible in your Connections page * Proposal created in your Google Ad Manager account * Advertiser has DSP access to accept proposals ## Configuring Proposal Details In the Permutive Platform, click on **Connections**. Locate the advertiser connection and view the cohorts available. In the cohort table, locate the **GAM** column and click **Configure** for the cohort you want to set up a PMP for. In the "Update GAM Configuration" dialog, enter: * **Proposal ID**: The Proposal ID from your GAM account * **Proposal Name**: The full proposal name as it appears in GAM Click **OK** to save. The proposal information is now displayed to the advertiser in the Permutive Platform. They can view the proposal details and accept it within their DSP. ## Adding Order Line Item Details Once the advertiser has accepted the proposal in their DSP and the GAM order has been created: In GAM, note down the Order Line Item IDs and floor prices for each line item. Go back to the Permutive Platform **Connections** page. Click **Configure** again for the same cohort. The dialog will now show your previously entered Proposal ID and Proposal Name. In the "Order Line Item ID" section: * Enter each 10-digit Line Item ID * Enter the corresponding Floor Price for each line item * Use **+ Add** to add additional line items * Use **Remove** to delete line items if needed Click **OK** to save. Specifying the Order Line Item IDs is **required for frequency control features to function correctly**. Without these IDs, Permutive cannot apply frequency caps across line items within the order. **Best practices:** * Use clear, descriptive proposal names that reference the clean room audience and campaign details * Keep the Permutive configuration updated if line items are added or removed from the GAM order * Coordinate with the advertiser to ensure they accept the proposal promptly in their DSP * Verify all line item IDs are correctly entered to ensure frequency control works as expected ## Next Steps Set up frequency caps for campaigns Return to product overview # Uploading Advertiser Data Source: https://docs.permutive.com/guides/clean-room/uploading-advertiser-data How to upload first-party data to Clean Room data sources ## Overview This guide walks advertisers through uploading first-party data to Clean Room data sources for matching with publisher data. **Prerequisites:** * First-party data prepared with common identifiers (hashed emails, mobile IDs, etc.) * Data taxonomy defined ## Steps In the left menu of your demand-side workspace, click **Data Sources**. Click **Add Source**. Enter a name and description for your data source: * Use a clear, descriptive name (e.g., "Lapsed customers") * Include details about the data set in the description Prepare a taxonomy file in CSV format. The taxonomy defines segment IDs and names. Click **Upload Taxonomy** and select your file. Once the taxonomy is validated, you'll be granted access to a GCS (Google Cloud Storage) bucket. Upload your raw data files to the provided GCS bucket using the following format: * **File format**: Tab-separated file (TSV) with NO headers * **Columns** (in order): `id`, `tag`, `segments` * `id`: The user identifier (e.g., hashed email, mobile ad ID, IP address) * `tag`: The identifier type - use one of: `email_sha256`, `appnexus`, or `ip_address` * `segments`: Comma-separated list of segment IDs as defined in your taxonomy file * **Example row**: `abc123def456\temail_sha256\tsegment_001,segment_042,segment_103` Data will begin processing automatically. ## Permissioning Data (Publisher-Driven Workflow) If following the publisher-driven workflow, you also need to permission the data source to the publisher's demand-side workspace: Ask the publisher for their demand-side Organization ID (found in their Settings). Input the publisher's Organization ID, set any time limits or usage restrictions if needed, and click **Grant Permission**. ### What You'll See **Advertiser's view:** When granting permission, you'll see a modal where you enter the publisher's demand-side workspace ID as the destination. Advertiser view: New Destination modal for granting permission to a publisher **Publisher's view:** After permission is granted, the publisher will see your data source appear in their demand-side workspace. The Owner column shows which advertiser provided each data source. Publisher view: Data Sources page showing permissioned advertiser data **Data preparation best practices:** * Hash emails using SHA-256 — lowercase hex encoded — before uploading (do NOT upload raw email addresses) * Normalize identifiers before hashing: lowercase emails, trim values * Validate data quality and completeness before uploading large datasets * Test with a small sample file first to verify format is correct Data processing can take several hours depending on dataset size. Monitor the data source status in the interface. ## Next Steps Build audiences from your data Return to product overview # Creating & Updating Event Schema Source: https://docs.permutive.com/guides/connectivity/events/create-update-event-schema How to add new events and event properties using your schema Google Sheet ## Overview In Permutive, an **event schema** defines the structure of data collected through events and their properties. Designing your schema is an iterative process. You are not locked into an initial configuration -- schemas can be extended over time as your data collection needs evolve. Permutive **enforces** schemas at ingestion time to ensure data consistency. Events that do not conform to the defined schema are rejected entirely. ## Schema Structure Your event schema is managed in a **Google Sheet** that is shared with your organization and the Permutive team. Below is an example of what the schema sheet looks like: Example event schema Google Sheet Each row in the sheet represents a single property within an event. The columns are: | Column | Field | Description | Editable After Production? | | :---------- | :---------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------- | | **A** | **Event Name** | The name of the event as it appears in the Dashboard and in your on-page tracking code. Do not use special characters and spaces. | No | | **B** | **Name** | The display name of the property as it appears in the Dashboard. Spaces are allowed. | Yes | | **C, D, E** | **Level 1, 2, 3** | The path to the leaf-level property, which translates directly into the JSON structure of the event payload. Do not use special characters and spaces. | No | | **F** | **Type** | The data type of the property (e.g., String, Boolean, List of Strings). | No | | **G** | **Description** | A brief explanation of what the property captures. Not required, but strongly recommended for team clarity. | Yes | | **H** | **Examples** | Sample values illustrating expected input. Not required, but strongly recommended. | Yes | ### How the Level Columns Map to JSON The Level 1, 2, and 3 columns define where a value lives in the nested JSON payload sent to Permutive. For example, the "Authors" row in the screenshot above has `article` in Level 1 and `authors` in Level 2, which translates to: ```json theme={"dark"} { "article": { "authors": ["Jane Doe", "John Smith"] } } ``` A property like "Page Type" with only `page_type` in Level 1 and no deeper nesting translates to: ```json theme={"dark"} { "page_type": "article" } ``` ### Full Event Payload Example Below is an example of a **Pageview** event custom payload based on the schema shown above: ```json theme={"dark"} { "page_type": "article", "article": { "authors": ["Jane Doe"], "title": "Understanding First-Party Data" }, "tags": ["news", "data-strategy"], "user": { "logged_in": true, "subscription": true } } ``` ## Which Events Can Be Edited? Permutive provides two categories of events: **Standard events** are collected automatically when you deploy the SDK. These include Pageview, Engagement, and Video events. Of the standard events, only the **Pageview** event schema can be extended with custom properties. You can add as many custom properties as needed to the Pageview event. **Custom events** are events you define to track behaviors specific to your data collection needs. Using the SDK function `permutive.track()`, you can track custom events: ```javascript theme={"dark"} permutive.track('EventName', { property: 'value' }); ``` Before adding a new custom event, confirm with your Customer Success Manager (CSM) that your contract allows additional custom events on the platform. ## How to Edit the Schema All schema changes are made in your **Google Sheet**. Here is how each column should be edited: **Event Name (Column A)** -- When adding a new custom event, select a new row and enter the event name. How it appears in the sheet is how it will be displayed in the Dashboard. Avoid special characters. Once changes have been pushed to production, the event name cannot be changed. **Name (Column B)** -- This is the Dashboard display name of the property. Because this value is only visible in the Dashboard, you can edit it at any time, even if it is already in production. A production update will still be needed for changes to take effect. **Level 1, 2, 3 (Columns C, D, E)** -- Enter the path to your leaf-level property. These columns are **not editable** once pushed to production. **Type (Column F)** -- The data type for the property. This is **not editable** once pushed to production. **Description (Column G)** -- Not required, but highly recommended. A brief description clarifies the property's purpose for team members building cohorts. **Examples (Column H)** -- Not required, but highly recommended. Providing examples illustrates what values are expected for the property. Be sure your property structure is entered correctly before requesting a production push. Event names, level paths, and data types cannot be changed after they are live. ## How to Push Schema Changes to Production Once you have finished editing your schema in the Google Sheet: Review all new or modified rows to ensure event names, property paths, and data types are correct. Once pushed to production, these fields cannot be changed. Contact [Support](mailto:technical-services@permutive.com) to request that your changes be pushed to production. After the update has been applied, your deployment documentation will be updated to reflect the changes. Use the *Events* menu item in the Dashboard or the Permutive Chrome Extension to verify the new properties are being collected correctly. ## Correcting Published Properties The following fields **cannot be edited** once they are pushed to production: * Event Name * Level 1, 2, 3 (property path) * Type If corrections are needed for any of these: Re-add the custom event or property in a new row using a similar but distinct name. For example, if `browserLanguage` has the wrong data type, add a new property called `browserLanguageString` with the correct type. Contact [Support](mailto:technical-services@permutive.com) with details of the changes you made and which event or property is incorrect. A member of the Technical Services team will hide the old event or property from the Dashboard so that cohorts are not accidentally built with it. Update your implementation to send data under the new property name. Verify with your team that no existing cohorts depend on the old property or event before fully deprecating it. ## Schema Enforcement and Validation Permutive enforces event schemas strictly. When an event is tracked, the payload is validated against your published schema. If the payload does not match, the **entire event is rejected** and no data from that event is collected. This means: * Every property in the payload must be defined in the schema. * Every property value must match its declared data type. * If you fire a custom event or property that has not been defined in your schema, it will be rejected and that entire event's data will not be collected. A single invalid property in an event payload will cause the entire event to be rejected -- not just the invalid property. Always ensure any custom event or property is defined in the schema before passing data to it. ## Troubleshooting There are two key ways to identify schema errors: * **Event Rejections Dashboard** -- The central way of checking for schema errors across all of your domains and platforms. Contact your CSM to get access to your event rejections dashboard. * **Permutive Chrome Extension** -- Pinpoint schema errors during your browsing experience on the web. The extension displays each event payload and flags validation errors as they occur. ### Common Validation Errors A property in the event payload is not defined in the schema. **Solution**: Add the property to your schema in the Google Sheet and request a production push, or remove the property from your tracking code. A property value does not match the expected type (e.g., sending a string where a number is expected). **Solution**: Review the Type column in your schema Google Sheet and update your tracking code to send the correct type. A required property is missing or has a `null`/`undefined` value. **Solution**: Ensure your data layer or tracking logic always provides a valid value for required fields. Consider delaying event tracking until required data is available. Property names are case-sensitive. For example, `articleTitle` and `articletitle` are treated as different properties. **Solution**: Compare property names in your tracking code against the Level columns in your schema Google Sheet character by character. If you fire a custom event or property that has not been defined in your schema, the entire event is rejected and no data is collected. **Solution**: Ensure any custom event or property is defined in the schema Google Sheet and pushed to production before data is passed to it. ### Recommended Debugging Workflow 1. **Reproduce the issue** on a page where the event should fire. 2. **Open the Permutive Chrome Extension** and check whether the event appears and whether any errors are flagged. 3. **Inspect the browser console** for network errors (HTTP 400 responses) on requests to Permutive. 4. **Compare the event payload** in the network request against your schema definition in the Google Sheet. 5. **Correct the tracking code** or **update the schema** as needed. 6. **Verify the fix** by reloading the page and confirming the event is accepted without errors. ## Quick Reference * You can always **add** new properties to your schema via the Google Sheet. * **Event names**, **level paths**, and **data types** are immutable after being pushed to production. * **Property display names**, **descriptions**, and **examples** can be edited at any time. * Events with payloads that do not match the schema are **rejected entirely**. * To correct an immutable field, create a new property in the Google Sheet and contact Support to hide the old one. * All schema changes must be pushed to production by the Permutive Technical Services team. ## Next Steps Learn more about standard and custom events in Permutive Get help with schema changes or troubleshooting # Tracking Off-Site Events with Pixels Source: https://docs.permutive.com/guides/connectivity/events/tracking-offsite-events Capture conversions, email opens, and ad impressions that happen outside your site, and attribute them to your Permutive users. ## Overview Off-site tracking lets you record events that happen away from your own pages — a conversion on a partner site, an email open, an ad impression in a video player — and attribute them to a user in Permutive. Typical use cases: * **Campaign reporting** — measure conversions driven by a campaign on partner inventory (e.g. a `PartnerConversion` event). * **Campaign optimization** — adjust targeting mid-flight to hit conversion KPIs. * **Suppression** — drop users from a targeting cohort once they convert. * **Premium targeting** — target users who visited a partner site but have not yet converted. * **Engagement audiences** — track email opens (e.g. an `EmailOpen` event) or ad views (e.g. an `AdImpression` event) to build audiences from off-site engagement. Every method below ultimately calls one endpoint — Permutive's pixel-tracking endpoint — with a small set of parameters. The differences between methods come down to two independent choices: **how you identify the user**, and **how the pixel is fired**. The rest of this guide is organized around those two choices. **Before you start** * A custom event schema for your event must exist in your workspace. See [Creating & Updating Event Schema](/guides/connectivity/events/create-update-event-schema). * Decide which identifier you'll use (see [Identifying the user](#identifying-the-user)). * If the event involves EU/UK/Swiss users, read [Consent and privacy](#consent-and-privacy) first. ## Choose your setup Find the row closest to where your event happens. This tells you the identifier and firing mechanism to use; the linked sections cover each in detail. | Where the event happens | Recommended identifier | Firing mechanism | | ------------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | | A web page you control (JS available) | Permutive user ID (`u`) or first-party ID (`i` + `it`) | [JavaScript tag](#firing-the-pixel) | | A partner web page (you supply a tag) | First-party ID, or third-party cookie sync | [JavaScript tag or tracking URL](#firing-the-pixel) | | A marketing email | First-party ID or Permutive user ID | [HTML image pixel](#firing-the-pixel) | | An ad server, display/web | Third-party cookie sync (legacy) or first-party ID | [Tracking URL](#firing-the-pixel) in the impression/view field | | An ad server, video / CTV / in-app | Device ID (MAID) via an ad-server macro | [Tracking URL](#firing-the-pixel) with ad-server macros — contact [Support](mailto:support@permutive.com) | On a page where the Permutive SDK is already loaded, prefer `permutive.track()` — use `px/track` where the SDK isn't available on that specific page (for example, a post-checkout page reached by redirect). ## Parameter reference All methods build a request to: ``` https://api.permutive.app/v2.0/px/track ``` | Parameter | Required | Description | | --------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `k` | Yes | Your workspace **public API key**. | | `e` | Yes | **Event name**. Must match a custom event schema in your workspace. | | `p` | No | **Event properties**, as a JSON object, URL-encoded (the snippets below handle this — see the [FAQ](#faq) for details). | | `i` | Conditional | **Identifier value** — the user identifier you're tracking against. Must be paired with `it`. At least one of `i` (+ `it`) or `u` must be set. | | `it` | Conditional | **Identifier tag** — the type/namespace of the value in `i` (e.g. `appnexus`, `idfa`, `aaid`, or your own first-party identifier tag). | | `u` | Conditional | **Permutive user ID**, used when you already know it. Use this *or* the `i` + `it` pair, not both. | | `rand` | Recommended | **Cache-buster.** Not read by Permutive — added so intermediaries (CDNs, ad servers, email clients) don't serve a cached response. Use a random number or a platform macro. | The endpoint also accepts `s` (session ID) and `vid` (view ID), which Permutive's own SDKs set to stitch events into a browsing session. Off-site tracking has no such session, so leave them out. Consent parameters such as `gdpr`, `gdpr_consent` and `consent` are **not** read by `px/track`. They belong to the upstream ID-sync provider (e.g. AppNexus, Google), which uses them to decide whether to serve the request at all. See [Consent and privacy](#consent-and-privacy). ## Identifying the user You attach an event to a user in one of the following ways. If you already hold the Permutive-assigned user ID (for example, you read it on your own site before redirecting), pass it as `u`. No `it` is needed. Use your own identifier (such as a hashed email) through Permutive's identity framework. Pass the value as `i` and its tag as `it`. This is the most reliable method across browsers and environments because it doesn't depend on third-party cookies. Contact [Support](mailto:support@permutive.com) to confirm the identifier tag to use. Use a partner's cookie (AppNexus, Google, The Trade Desk) by routing the request through that partner's sync endpoint, which substitutes its own ID into `i` before redirecting to `px/track`. This only works in browsers that allow third-party cookies, so its reach is shrinking; prefer a first-party identifier where you can. Patterns are in [Third-party cookie sync providers](#third-party-cookie-sync-providers). In mobile-app and connected-TV environments there is no cookie, but the ad server can supply the device's advertising ID through a macro. Pass the ID as `i` with the `it` tag `idfa` (iOS IDFA) or `aaid` (Android AAID). This is the right path for video/CTV — contact [Support](mailto:support@permutive.com) to set it up: share the event you want to track and the ad server you'll fire from, and Support will confirm the identifier setup for your workspace and the macro tokens to use in your tracking URL. ## Firing the pixel Use this when you paste a URL into a third-party platform (such as an ad server's impression/view-tracking field) and the platform fires the request for you. The snippets below build a URL you can copy. Run them in your browser console. ```javascript theme={"dark"} const publicKey = 'your-public-api-key'; const eventName = 'PartnerConversion'; const identifierValue = 'your-user-id'; const identifierTag = 'your-identifier-tag'; const eventProperties = { partner: 'Example Partner', value: 50.0 }; const baseUrl = 'https://api.permutive.app/v2.0/px/track'; const params = new URLSearchParams({ k: publicKey, i: identifierValue, it: identifierTag, e: eventName, p: JSON.stringify(eventProperties), rand: Math.round(Math.random() * 1000000) }); console.log(`${baseUrl}?${params}`); ``` **Example output:** ``` https://api.permutive.app/v2.0/px/track?k=your-public-api-key&i=your-user-id&it=your-identifier-tag&e=PartnerConversion&p=%7B%22partner%22%3A%22Example+Partner%22%2C%22value%22%3A50%7D&rand=123456 ``` ```javascript theme={"dark"} const publicKey = 'your-public-api-key'; const eventName = 'PartnerConversion'; const userId = 'permutive-user-id'; const eventProperties = { partner: 'Example Partner', value: 50.0 }; const baseUrl = 'https://api.permutive.app/v2.0/px/track'; const params = new URLSearchParams({ k: publicKey, u: userId, e: eventName, p: JSON.stringify(eventProperties), rand: Math.round(Math.random() * 1000000) }); console.log(`${baseUrl}?${params}`); ``` **Example output:** ``` https://api.permutive.app/v2.0/px/track?k=your-public-api-key&u=permutive-user-id&e=PartnerConversion&p=%7B%22partner%22%3A%22Example+Partner%22%2C%22value%22%3A50%7D&rand=123456 ``` Route the request through the partner's sync endpoint so it can inject its cookie ID. See [Third-party cookie sync providers](#third-party-cookie-sync-providers) for AppNexus, Google and The Trade Desk patterns — and note the [encoding](#faq) implications of the extra redirect hop. Many platforms expose a macro for the `rand` cache-buster (and for IDs). Prefer the platform's macro over a static value — check your platform's macro documentation, and see the [FAQ](#faq) on using macros. Use this when you can run JavaScript on the page where the event happens — a partner conversion page, an iframe, or a web video player. It lets you populate properties dynamically from the page. ```html theme={"dark"} ``` The same tag can serve several partner sites — infer the partner from `window.location.href` rather than hard-coding it. Use this where JavaScript isn't allowed, most commonly marketing emails. Build the URL with the snippets in the **Tracking URL** tab, then drop it into a 1×1 image: ```html theme={"dark"} ``` **Example (Permutive user ID):** ```html theme={"dark"} ``` Many email providers offer a cache-buster macro for `rand` — use it instead of a static number so opens aren't under-counted. ## Third-party cookie sync providers These route the `px/track` request through a partner's cookie-sync endpoint, which substitutes its own user ID before redirecting. They are **browser-only** and depend on third-party cookies. Prefer a first-party identifier where the environment allows it. Each of these adds a redirect hop, which adds an encoding layer to the nested `px/track` URL. The snippets handle this for you, but read the [encoding FAQ](#faq) before adapting them — the `p` value ends up double-encoded in the AppNexus and Trade Desk patterns. AppNexus substitutes its cookie ID where the `$UID` macro appears, then redirects to the nested URL. The entire redirect target is percent-encoded so its query string isn't confused with `getuid`'s own parameters; `getuid` decodes it exactly once before redirecting, so `p` arrives at `px/track` single-encoded. The `$UID` macro is substituted after that decode, so it's fine for it to be encoded (`%24UID`) along with the rest of the redirect target. ```javascript theme={"dark"} const publicKey = 'your-public-api-key'; const eventName = 'PartnerConversion'; const eventProperties = { partner: 'Example Partner', value: 50.0 }; const baseUrl = 'https://api.permutive.app/v2.0/px/track'; const params = new URLSearchParams({ k: publicKey, it: 'appnexus', e: eventName, p: JSON.stringify(eventProperties), rand: Math.round(Math.random() * 1000000) }); // encodeURIComponent (not encodeURI) so the whole inner URL is encoded as one value const redirect = encodeURIComponent(`${baseUrl}?${params}&i=$UID`); console.log(`https://ib.adnxs.com/getuid?${redirect}`); ``` **Example output:** ``` https://ib.adnxs.com/getuid?https%3A%2F%2Fapi.permutive.app%2Fv2.0%2Fpx%2Ftrack%3Fk%3Dyour-public-api-key%26it%3Dappnexus%26e%3DPartnerConversion%26p%3D%257B%2522partner%2522%253A%2522Example%2BPartner%2522%252C%2522value%2522%253A50%257D%26rand%3D123456%26i%3D%24UID ``` **Consent:** for EU/UK users you must append the provider's consent parameters (`gdpr` + `gdpr_consent`, or the `consent=1`/`consent=0` fallback) to the **AppNexus** URL — outside the encoded redirect target, e.g. `...%24UID&gdpr=1&gdpr_consent=YOUR_CONSENT_STRING`. Without them, `getuid` rejects the request outright with an HTTP 400 ("Request failed due to privacy signals"), so no event reaches Permutive. See [Consent and privacy](#consent-and-privacy). Google's cookie-match endpoint forwards your custom parameters to Permutive and appends the Google ID. ```javascript theme={"dark"} const publicKey = 'your-public-api-key'; const permutiveUserId = 'permutive-user-id'; // optional, if known const eventName = 'AdImpression'; const eventProperties = { campaign_id: 123 }; const params = new URLSearchParams({ google_nid: 'permutive_dmp', google_cm: '', type: 'ddp', k: publicKey, e: eventName, p: JSON.stringify(eventProperties), gdpr: 0, // set to 1 if GDPR applies, and add gdpr_consent rand: Math.round(Math.random() * 1000000) }); if (permutiveUserId) params.set('u', permutiveUserId); console.log(`https://cm.g.doubleclick.net/pixel?${params}`); ``` **Example output:** ``` https://cm.g.doubleclick.net/pixel?google_nid=permutive_dmp&google_cm=&type=ddp&k=your-public-api-key&e=AdImpression&p=%7B%22campaign_id%22%3A123%7D&gdpr=0&rand=123456&u=permutive-user-id ``` The Trade Desk carries custom parameters inside `ttd_passthrough`, which is itself URL-encoded — so the JSON in `p` ends up **double-encoded** (one decode by The Trade Desk, one by Permutive — see the [encoding FAQ](#faq)). ```javascript theme={"dark"} const publicKey = 'your-public-api-key'; const permutiveUserId = 'permutive-user-id'; const eventName = 'AdImpression'; const eventProperties = { campaign_id: 123 }; // These are forwarded to Permutive; URLSearchParams encodes them once... const passthrough = new URLSearchParams({ e: eventName, p: JSON.stringify(eventProperties) }); // ...and they're encoded a second time when nested as a parameter value. const params = new URLSearchParams({ ttd_pid: 'dbegppc', ttd_tpi: '1', ttd_puid: `${publicKey},${permutiveUserId}`, // , ttd_passthrough: passthrough.toString(), gdpr: 1, gdpr_consent: 'YOUR_CONSENT_STRING', rand: Math.round(Math.random() * 1000000) }); console.log(`https://match.adsrvr.org/track/cmf/generic?${params}`); ``` **Example output:** ``` https://match.adsrvr.org/track/cmf/generic?ttd_pid=dbegppc&ttd_tpi=1&ttd_puid=your-public-api-key%2Cpermutive-user-id&ttd_passthrough=e%3DAdImpression%26p%3D%257B%2522campaign_id%2522%253A123%257D&gdpr=1&gdpr_consent=YOUR_CONSENT_STRING&rand=123456 ``` ## Consent and privacy `px/track` records whatever it receives — it performs **no consent enforcement of its own**. It is your responsibility to ensure the pixel only sends events and identifiers where you are permitted to do so. Two mechanical points to be aware of: * The `gdpr`, `gdpr_consent` and `consent` parameters that appear in the [third-party cookie sync patterns](#third-party-cookie-sync-providers) are read by the **sync provider** (AppNexus, Google, The Trade Desk), not by Permutive. The provider uses them to decide whether to serve the request and return its ID — for example, AppNexus rejects requests without them outright for EU/UK traffic. * That provider-side gate only exists when a sync provider sits in front of `px/track`. When you fire the pixel directly (a first-party identifier, a Permutive user ID, or an ad-server tracking field pointing straight at `px/track`), there is no gate in the request path — only fire the pixel where your own consent controls permit it. This guide describes mechanisms, not legal advice. Confirm the requirements for your audiences with your privacy/legal team. ## Verifying your implementation Trigger the pixel (load the page, open the test email, or preview the creative in your ad server). In browser dev tools (Network tab) or your ad server's creative preview, confirm the final URL: macros have expanded, `p` is encoded the expected number of times, and the identifier is populated. Most issues are visible here. In the **Events** section of your dashboard, look for your event name (e.g. `PartnerConversion`). Allow a little time for events to surface. ### Common failure modes | Symptom | Likely cause | | ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | No events at all | Event schema not created for `e`; wrong `k`; pixel not firing (check Network tab). | | Events arrive but properties are garbled | Wrong number of encoding layers on `p` — see the [FAQ](#faq). | | No events for EU users via cookie sync (AppNexus / Google / TTD) | Consent gate blocking the request. If you have consent: check `gdpr` + `gdpr_consent` (or `consent`) are on the sync-provider URL, outside the encoded redirect target. If the user hasn't consented: this is expected — no ID sync means no pixel fires. | | No events for EU users on a direct pixel (first-party ID or `u`) | Not the pixel itself — `px/track` has no consent gate. Check your CMP / tag-manager conditions and whether your consent controls are suppressing the pixel before it fires. | | Cookie-sync pixel returns a 200 image but no event | The user has no cookie with that provider — e.g. `getuid` serves a GIF instead of redirecting, so nothing reaches `px/track`. | | Macro appears literally in the fired URL | Macro token was URL-encoded, or the platform doesn't recognize it — see the [FAQ](#faq). | | Identifier empty in app/CTV | Limit Ad Tracking / child-directed flag, or non-HTTPS creative. | ## FAQ The snippets in this guide handle encoding for you — `URLSearchParams` applies the one layer that a direct `px/track` request needs. If you're hand-assembling or adapting a URL, use this rule: **Count the number of times the value will be URL-*decoded* between where you write it and where Permutive reads it, and encode it that many times.** * `px/track` decodes the `p` parameter **once** to recover your JSON — so on a direct request, `p` must be **single-encoded**. * A property value sitting *inside* `p` is part of that JSON — it inherits `p`'s encoding; don't encode it separately. * If the **entire** `px/track` URL is carried as a parameter or redirect target inside another URL — an AppNexus `getuid` redirect, or The Trade Desk's `ttd_passthrough` — that outer hop adds **one more** decode, so everything inside (including `p`) needs **one extra** layer. The provider strips one layer when it redirects; `px/track` strips the last. **Spaces:** `URLSearchParams` encodes a space as `+`, not `%20`. Both decode to a space in a query string, but be aware of the difference if you hand-assemble a URL. Yes — most ad servers and email providers offer macros for cache-busters and IDs, and using them is recommended (see the `rand` tips above). One rule: **never URL-encode the macro token itself.** The platform matches it literally to substitute the value; an encoded token won't be recognized and will appear verbatim in the fired URL. ## Next steps Set up the custom event schema your pixel events will validate against. Troubleshoot an implementation or confirm identifier tags. # Adding Audiences in LiveRamp Source: https://docs.permutive.com/guides/connectivity/imports/adding-audiences-in-liveramp How to find, request, and distribute audience segments from the LiveRamp Data Marketplace to Permutive ## Overview This guide explains how to browse the LiveRamp Data Marketplace, request audience segments, and configure distribution so that data flows automatically into your Permutive workspace. **Prerequisites:** * An active LiveRamp account with access to the Data Marketplace * LiveRamp configured to send data to Permutive. [Learn how to configure your LiveRamp integration](/guides/connectivity/imports/ingesting-data-via-liveramp) * Your Permutive Organization ID (found in **Settings** in the Permutive Dashboard) ## Steps ### Log in to the LiveRamp Dashboard Log in to the LiveRamp Dashboard with your credentials. Navigate to the menu bar and click the **Data Marketplace** tab. Select the **Buy Data** tab. All data that LiveRamp holds is available for selection from here. ### Request Data Browse the Data Marketplace and enter keywords into the search bar to filter to specific data. If multiple results are returned, share a screenshot or a copy of the audience list with your client to help narrow down to the exact data sets they want. Use the **Providers** filter to browse segments from specific data providers. Data sets are free to collect in LiveRamp. Charges are only incurred once segments are used in the Cohort Builder. Once data is uploaded to Permutive, it will be visible under third-party data in the Cohort Builder. Select the segment you are interested in and click **Request Segment**. ### Set Up Distribution Open the destination accounts section. Here you can see all the different data sets that have previously been sent. If data has already been sent to Permutive, you can add the existing connection instead of creating a new one. If you are unsure whether an existing destination account is set up for Permutive, create a new one. Select **Permutive** as the destination, then choose **Customer-specific Marketplace** as the integration type. Fill in the destination account details: | Field | Description | | :-------------- | :--------------------------------------------------------------------------------------- | | Name | A descriptive name for the destination account, so it can be reused easily in the future | | Organization ID | Your Permutive Organization ID from **Settings** in the Dashboard | The data will automatically be linked to the organization associated with this ID and delivered to the workspace specified. Review the configuration and click **Create Destination Account**. ### Re-request Data After Distribution Is Configured Go back to the Data Marketplace and find the audience segments you requested earlier. Select the segments again and click **Add to Distribution**. Distribution can take up to 3 days to ingest, provided there are no errors in how the data has been sent. ## Next Steps Set up LiveRamp to send cohorts to Permutive Return to the Imports product overview # Configuring Taxonomy Source: https://docs.permutive.com/guides/connectivity/imports/configuring-taxonomy Set up a taxonomy to map segment codes to human-readable names for your imported audience data ## Overview A taxonomy maps raw audience segment codes from your imported data to human-readable names, so they can be used in Permutive cohort definitions and insights. Each taxonomy is one-to-one with an audience set (also called a Data Provider). An audience set is a way to separate and group different segments together. For example, you may have: * One audience set for your own first-party data analyzed and brought back in * Another audience set for self-sourced data through a partnership with another platform sharing demographic data Each audience set has its own taxonomy to keep the data separated in the UI and in the system. ## Taxonomy Fields | Field | Required | Description | | :-------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Code** | Yes | A unique identifier for the segment. Alphanumeric string, never displayed in the UI. Best practice is to use a sequence (e.g., `s001`, `s002`, `s003`) rather than human-readable words. This value can never be changed. | | **Name** | Yes | The display name shown in the Permutive Dashboard. Use hyphens to delimit category levels (e.g., `Demographic - Inferred Gender - Female`). Can be updated. | | **Description** | No | Description of the segment, shown in the Dashboard. | | **CPM (USD)** | No | The CPM for the segment, typically for third-party partner segments. Leave blank or set to `0` for self-sourced data. If using partner segments billed through Permutive, add the CPM per segment as per the partner taxonomy. | ## Example Taxonomy Below is an example taxonomy with four segments: | Code | Name | Description | CPM | | :--- | :------------------------------------- | :----------------------------------------------- | :--- | | 4412 | Demographic - Inferred Gender - Female | Users whose gender has been inferred as female. | | | 4981 | Demographic - Inferred Gender - Male | Users whose gender has been inferred as male. | | | 4011 | Demographic - Declared Gender - Female | Users who have specified their gender as female. | | | 4099 | Demographic - Declared Gender - Male | Users who have specified their gender as male. | 1.50 | ## Taxonomy CSV Format Upload your taxonomy as a CSV file with the following columns: ```csv theme={"dark"} ID,Name,Description,CPM (USD),Lifetime (days) 0001,Country - France,Users living in France,0,45 0002,Country - Spain,Users living in Spain,0,45 0006,"Income < $20,000","Having income of less than $20,000",0,60 0009,Gender - Female,People that identify as Female,0,30 0012,Subscriber - Premium,Paying subscribers,0,30 0013,Interest - Cars,Those who are interested in cars.,0,30 ``` The taxonomy for a given Data Provider/Audience Set must be exhaustive and cover every possible segment code present in the data. To manage taxonomy programmatically, use the [Batch Update](/api/taxonomy/batch-update) endpoint to create, update, and delete multiple segments in a single request. ## Important Guidelines * **Avoid major changes to existing segments.** We advise only cosmetic changes such as descriptions. Changing the name of a segment or category could impact teams creating cohorts in the Dashboard. * **Create new segments instead of recycling old ones.** If you need to change the definition of a segment, create a new segment rather than repurposing an existing code. * **Taxonomy is separate from data.** The taxonomy is relatively static and defines what segments mean. The audience data files are more transient and updated as needed to reflect users' membership in segments. If you find yourself needing to make major changes to segment definitions, contact [support](mailto:support@permutive.com) or your Customer Service Representative so they can help you plan the migration. ## Next Steps Learn about using second-party data in Permutive Manage taxonomy programmatically via the API # Ingesting Second-Party Data via LiveRamp Source: https://docs.permutive.com/guides/connectivity/imports/ingesting-data-via-liveramp Set up LiveRamp to send advertiser first-party cohorts to Permutive as second-party data ## Overview Advertisers or clients may ask you to ingest their first-party cohorts to use as second-party data for analysis of a campaign. This guide covers how to set up the LiveRamp connection and have the data ingested into Permutive. **Prerequisites:** * The advertiser/client must have a LiveRamp Connect account already set up * Your Permutive Organization ID (found in **Settings** in the Permutive Dashboard) ## Setup Steps ### Set Up in LiveRamp Ask the advertiser or client to follow these steps in their LiveRamp account: Log in to LiveRamp Connect and select **Distribute Data**. Select **+ New Destination Account**. Search for **permutive** in the search bar. Hover the cursor towards the bottom of the tile and select **Activate**. Three options will appear — activate either: * **Permutive 1P Onboarding**, or * **Permutive - Data Marketplace - Customer** Complete each field: | Field | Example | | :------------------------ | :----------------------------------------------- | | End Date | July 14, 2025 (can be removed altogether) | | Advertiser Name | The advertiser's name (not the publisher's name) | | Permutive Organization ID | Your Organization ID from Settings | | Destination Account Name | Publisher/Permutive - 01/14/2025 | Review the input and press **Create Destination Account**. Select the cohort(s) to send over and select **Add to Distribution**. The cohorts will be delivered to the Permutive platform within 3 days. ### Ingestion by Permutive When your client has confirmed they have sent their cohorts: 1. Contact [support@permutive.com](mailto:support@permutive.com) to let the team know so they can keep you updated on the progress of the ingestion. 2. Once the cohorts have been received by Permutive, they will take up to **3 days** to ingest (providing there are no errors in how the data has been sent by the client). ### Creating Cohorts with the Ingested Data Once the Permutive support team has confirmed the cohorts are available: Go to **Audience > Custom Cohorts** in the Permutive Dashboard. Select **+ Add Cohort**. Set up any first-party rules you'd like to combine. Choose **+OR** or **+AND**, then select **Audience Imports**. Search for and select the relevant second-party segment. Save the cohort. ## Support If you have any questions, contact customer support by emailing [support@permutive.com](mailto:support@permutive.com). ## Next Steps Learn about second-party data concepts in Permutive Set up segment taxonomy for your imported data # Second-Party Data with Permutive Source: https://docs.permutive.com/guides/connectivity/imports/second-party-data-overview Understand how to use second-party data in Permutive for audience enrichment and targeting ## Overview Second-party data is fundamentally someone else's first-party data. The client collects data straight from their audience, which comes from one source. This is typically user cohort membership data that comes from an advertising partner or an advertising partner's DMP. It could also be the publisher's first-party data on their subscribers or other identifiable users. ## Using Second-Party Data in Permutive With Permutive you can receive second-party data in two ways: * **LiveRamp**: Receive audience data through LiveRamp's data distribution network. See [Ingesting Second-party Data via LiveRamp](/guides/connectivity/imports/ingesting-data-via-liveramp) for setup instructions. * **Custom Setup (GCS Upload)**: Upload user ID and segment files directly to a Permutive-managed GCS bucket. See the [Imports product documentation](/products/connectivity/imports) for data file format and upload details. Once received, you can build Permutive cohorts using this data and target users. Before uploading data, you'll need to configure a [Taxonomy](/guides/connectivity/imports/configuring-taxonomy) to define your segment codes. ## Creating Cohorts with Second-Party Data Once your second-party data has been ingested and confirmed as available: Go to **Audience > Custom Cohorts** in the Permutive Dashboard. Select **+ Add Cohort**. Set up any first-party rules you want to combine with your second-party data. Choose **+OR** or **+AND**, then select **Audience Imports**. Search for and select the relevant second-party segment from your imported data. Save the cohort to begin targeting users who match both your first-party and second-party criteria. After saving, the **Predicted Audience Size** shown in the Cohort Builder is a historical estimate. The **Live Audience Size** will start at zero and grow over time as imported users are evaluated. This is expected — see [Understanding Audience Size for Import Cohorts](/guides/signals/cohorts/custom/using-audience-imports#understanding-audience-size-for-import-cohorts) for details. ## Next Steps Define segment codes and names for your imported data Set up second-party data ingestion via LiveRamp # Setting Up Google BigQuery Routing Source: https://docs.permutive.com/guides/connectivity/routing/setting-up-bigquery-routing Configure routing to Google BigQuery for near real-time event data streaming ## Overview Set up BigQuery routing to stream your first-party event data to your own Google Cloud Platform project in near real-time. This guide provides context for choosing BigQuery as your destination and what to expect after setup. **Prerequisites:** * Active Google Cloud Platform project * Permissions to grant IAM roles at the project level * BigQuery API enabled (default for new projects) * Understanding of your data residency requirements (EU vs US) ## When to Choose BigQuery **Best for:** * Publishers already using Google Cloud Platform * Teams requiring near real-time data access (approximately 5-minute latency) * Organizations with SQL-based analytics workflows * Publishers needing automatic schema management **Consider alternatives if:** * Your primary cloud provider is AWS (consider S3 Streaming) * You already have a Snowflake data warehouse ## Setup Steps BigQuery Routing enables self-service setup through the Permutive Dashboard. You do not need to manually create a dataset for the Routing destination. This is handled automatically by our system during the setup process. The only manual step required is granting permissions to our service account. Ensure you have a Google Cloud Platform project with BigQuery enabled. You'll need: * Your GCP **Project ID** * A unique name for the dataset Permutive will create * Your preferred data location (**US** or **EU**) Log in to your Permutive Dashboard and navigate to the **Routing** section from the main navigation menu. Click **Add Integration** and select **BigQuery** from the list of available destinations. Enter the following configuration details: | Setting | Description | | :----------- | :--------------------------------------------------------- | | **Project** | Your GCP project ID | | **Dataset** | A unique name for the dataset (must not already exist) | | **Location** | **US** or **EU** based on your data residency requirements | The Dashboard will display a Permutive service account email address with the format: ``` routing-customer-*@permutive-routing-production.iam.gserviceaccount.com ``` Grant this service account the **BigQuery User** role at the **project level** in your GCP IAM settings. If you manually created the dataset beforehand, grant the **BigQuery Data Owner** role instead. Return to the Permutive Dashboard and click **Confirm account access granted**. Only click this button after granting permissions in GCP. If access is incorrect, the setup will fail and you'll need to restart. The status will transition to **Running** when setup is complete. If the status shows **Failed**, contact [Technical Services](mailto:technical-services@permutive.com) for assistance. ## Common Pitfalls **Permission Level Mistake:** The most common setup error is granting BigQuery User role at the dataset level instead of the PROJECT level. Always grant at the project level unless you manually created the dataset. **Organization Policies:** If you encounter "domain restriction" errors, your GCP organization may block external service accounts. Work with your GCP administrator to add Permutive's domain (`@permutive-routing-production.iam.gserviceaccount.com`) to your organization's allowlist. **Historical Data:** Routing is point-in-time only. When you enable routing, only new events going forward will be exported. Historical data is not backfilled automatically. Contact [Support](mailto:support@permutive.com) if you need historical data. ## What Happens After Setup Once routing is active: 1. **Tables are created automatically** for each event type (e.g., `Pageview_events`, `VideoView_events`) 2. **Events stream in near real-time** with approximately 5-minute latency 3. **Schemas update automatically** when you add new event types or properties 4. **Daily partitions** organize data by event date for efficient querying ## Understanding Your Data Structure After setup, you'll have: * **Event tables:** One table per event type with daily partitions * **Identities table:** `identities` containing user identity mappings with daily partitions * **Segment metadata:** `segment_metadata` view showing segment definitions See the [BigQuery integration documentation](/integrations/data-collaboration/data-warehouses/bigquery#data-types) for detailed schema information. ## Next Steps View full integration documentation Return to Routing overview # Setting Up AWS S3 Batch Routing Source: https://docs.permutive.com/guides/connectivity/routing/setting-up-s3-batch-routing Configure scheduled daily exports to Amazon S3 in JSON or Parquet format ## Overview Set up S3 Batch routing to export your first-party event data to your AWS S3 bucket on a scheduled 24-hour cycle. Choose between JSON (GZIP compressed) or Parquet (Snappy compressed) format based on your data processing needs. **Prerequisites:** * AWS account with S3 bucket creation permissions * IAM permissions to create users and policies * S3 bucket in the appropriate AWS region * Secure method to share AWS credentials with Permutive * Understanding of your data processing requirements (JSON vs Parquet) ## When to Choose S3 Batch **Best for:** * Organizations needing scheduled daily exports instead of real-time streaming * Teams ingesting data into data warehouses on a batch schedule * Publishers requiring Parquet format for efficient columnar storage * Organizations preferring predictable export schedules over continuous streaming **Consider alternatives if:** * You need near real-time data access (use S3 Streaming, BigQuery, or Snowflake) * You require sub-hourly data updates * You prefer automatic schema management in a database ## Setup Steps S3 Batch routing requires coordination with Permutive support. Decide between JSON and Parquet formats: | Format | Best For | | :------------------- | :---------------------------------------------------------------- | | **JSON** (GZIP) | Human-readable, easier debugging, broader tool compatibility | | **Parquet** (Snappy) | Analytics workloads, better compression, data warehouse ingestion | Most organizations choose Parquet for production data pipelines due to better compression and query performance. Ensure you have an S3 bucket ready with public access blocked. Email [technical-services@permutive.com](mailto:technical-services@permutive.com) with: * **Bucket Name** * **Bucket Prefix** (optional, e.g., `permutive/`) * **Format:** JSON or Parquet Permutive will provide you with a bucket policy to attach to your S3 bucket. Attach the Permutive-provided bucket policy to your S3 bucket's permissions, then notify Permutive support to confirm. Permutive will complete the integration setup and notify you when data begins flowing on the next batch export cycle. ## Understanding S3 Batch Data Structure S3 Batch exports use Hive-style partitioning organized by event type and date: ### Folder Structure ``` s3://bucket/prefix/data/ ├── pageview_events/ │ ├── year=2026/month=1/day=15/ │ │ └── data-000000000000.json.gz │ └── year=2026/month=1/day=16/ ├── videoview_events/ │ └── year=2026/month=1/day=15/ ├── aliases/ │ └── year=2026/month=1/day=15/ ├── domains/ │ └── data-000000000000.json.gz └── segment_metadata/ └── data-000000000000.json.gz ``` ### Data Types and Sync Modes | Data Type | Description | Sync Mode | | -------------------------------- | -------------------------------- | ----------------------- | | Events (e.g., `pageview_events`) | User behavioral events | Incremental (append) | | `aliases` | Identity data and alias mappings | Incremental (append) | | `domains` | Domain-level metadata | Snapshot (full replace) | | `segment_metadata` | Segment definitions and metadata | Snapshot (full replace) | **Incremental tables** append new data each export cycle. **Snapshot tables** are fully replaced with each export to ensure the latest reference data. Snapshot tables (segment\_metadata, domains) are exported without date partitioning since they represent current state rather than time-series data. See the [S3 integration documentation](/integrations/data-collaboration/data-warehouses/aws-s3#batch-schema) for detailed schema information. ## Export Schedule and Timing **Batch Export Characteristics:** * **Frequency:** 24-hour cycles * **Scope:** Organization-level (includes all workspaces) * **Timing:** Contact your Customer Success Manager for specific schedule * **Partitioning:** Daily partitions by event type ## Common Considerations **Export Timing:** Batch exports run on 24-hour cycles. The exact timing is configured during setup. Contact your Customer Success Manager for your specific schedule. **Incremental vs Snapshot:** Understand the difference between incremental tables (events, aliases) that append data and snapshot tables (segment\_metadata, domains) that replace data. Design your data pipelines accordingly. **Organization-Level Scope:** S3 Batch routing operates at the organization level, exporting data for all workspaces within your organization, unlike streaming routing which is workspace-specific. ## What Happens After Setup Once batch routing is active: 1. **Daily exports run automatically** on the configured 24-hour schedule 2. **Event data is partitioned** by event type and date 3. **Snapshot tables are replaced** each export cycle 4. **Files are written** in your chosen format (JSON or Parquet) ## Next Steps View full integration documentation Return to Routing overview # Setting Up AWS S3 Streaming Routing Source: https://docs.permutive.com/guides/connectivity/routing/setting-up-s3-streaming-routing Configure near real-time event streaming to Amazon S3 with Hive-style partitioning ## Overview Set up S3 Streaming routing to export your first-party event data to your AWS S3 bucket in near real-time as GZIP-compressed JSONL files. This guide provides context for choosing S3 Streaming and what to expect after setup. **Prerequisites:** * AWS account with S3 bucket creation permissions * IAM permissions to create users and policies * S3 bucket in the appropriate AWS region * Secure method to share AWS credentials with Permutive ## When to Choose S3 Streaming **Best for:** * Organizations using AWS as their primary cloud provider * Teams needing raw event files for custom processing pipelines * Publishers requiring data in S3 for ingestion into other AWS services (Athena, Redshift, EMR) * Organizations preferring file-based data over database connections **Consider alternatives if:** * You prefer automatic schema management in a database (consider BigQuery or Snowflake) * You need immediate SQL query access without additional setup ## Setup Steps S3 Streaming routing requires coordination with Permutive support. Before contacting Permutive, ensure you have: * An S3 bucket with public access blocked * A dedicated IAM user with programmatic access * The IAM user granted `s3:List*`, `s3:Get*`, `s3:Delete*`, and `s3:Put*` permissions on the bucket * Access Key credentials for the IAM user Use a region-specific location (e.g., `us-east-1`, `eu-west-1`) rather than generic regions. Email [technical-services@permutive.com](mailto:technical-services@permutive.com) with: * **Bucket Name** * **Bucket Region** (e.g., `us-east-1`) * **Bucket Prefix** (optional, e.g., `permutive/`) * **Access Key ID** * **Secret Access Key** (use encrypted channel) * **Routing Mode:** Streaming Use 1Password shared vaults or GPG-encrypted emails to securely transmit credentials. Permutive will configure your routing instance and notify you when the integration is live. ## Understanding S3 Streaming Data Structure S3 Streaming uses Hive-style partitioning to organize data efficiently: ### Folder Structure ``` s3://bucket/prefix/ ├── type=events/ │ ├── year=2026/ │ │ ├── month=01/ │ │ │ ├── day=15/ │ │ │ │ ├── hour=14/ │ │ │ │ │ └── 2026-01-15T14:00:00.000000Z-abc123-worker1.jsonl.gz │ │ │ │ └── hour=15/ ├── type=sync_aliases/ │ └── year=2026/month=01/day=15/hour=14/... └── type=segment/ └── timestamp-hash-worker.jsonl.gz ``` ### File Format * **Format:** Newline-delimited JSON (JSONL) * **Compression:** GZIP (`.gz`) * **Extension:** `.jsonl.gz` * **Encoding:** UTF-8 ### Data Types Exported | Data Type | Description | Partitioned | | -------------- | ----------------------------- | ------------ | | `events` | User behavioral events | Yes (hourly) | | `sync_aliases` | Identity synchronization data | Yes (hourly) | | `segment` | Segment metadata snapshots | No | See the [S3 integration documentation](/integrations/data-collaboration/data-warehouses/aws-s3#streaming-schema) for detailed schema information. ## Common Considerations **Latency:** S3 Streaming has approximately 5-minute latency from event collection to file availability in S3. **Bucket Prefix:** Use a bucket prefix (e.g., `permutive/`) to organize Permutive data separately from other data in your bucket. The prefix should NOT include a leading `/` or the bucket name. **KMS Encryption:** If using customer-managed KMS keys for S3 encryption, ensure the Permutive IAM user has appropriate KMS permissions (`kms:Encrypt`, `kms:Decrypt`, `kms:GenerateDataKey`). Contact [Technical Services](mailto:technical-services@permutive.com) for KMS requirements. ## What Happens After Setup Once routing is active: 1. **Files stream to S3** in near real-time with approximately 5-minute latency 2. **Hive-style partitions** are created automatically by hour 3. **Event data** is written as GZIP-compressed JSONL files 4. **File naming** follows the pattern `{timestamp}-{hash}-{worker_id}.jsonl.gz` ## Next Steps View full integration documentation Return to Routing overview # Setting Up Snowflake Routing Source: https://docs.permutive.com/guides/connectivity/routing/setting-up-snowflake-routing Configure routing to Snowflake using Snowpipe for automated data loading ## Overview Permutive Routing makes it easy to load all of your first-party event data from web and mobile into Snowflake. The Permutive Snowflake Router gives you access to your raw event data, allowing you to run your own analysis or integrate with your existing data pipelines. With routing enabled, Permutive writes event data to your Snowflake instance continually throughout the day, in roughly 5-minute or 500MB batches. **Prerequisites:** * Active Snowflake account with administrative permissions * Ability to create databases, schemas, users, roles, and storage integrations * Access to a Snowflake warehouse ## Running Snowflake SQL This guide contains Snowflake SQL commands to run on your account. You can execute these by creating a new empty worksheet in Snowflake and running each step from there. You can run specific parts of the SQL by highlighting the line and clicking **Run** in the top right. This will execute only the highlighted line. ## Setup Steps Permutive requires full access to a dedicated database and schema, as we may need to re-create some resources during setup. This database and schema will store all Events, Aliases, and Segments from your Permutive account. You can name these anything you like, though we recommend a database called `PERMUTIVE` and a schema called `DATA`. **Create the database:** ```sql theme={"dark"} CREATE DATABASE IF NOT EXISTS "PERMUTIVE" COMMENT = 'Database used for routing data from Permutive'; ``` **Create the schema:** ```sql theme={"dark"} CREATE SCHEMA IF NOT EXISTS "PERMUTIVE"."DATA" COMMENT = 'Schema used for routing data from Permutive'; ``` Create a new user within your Snowflake instance to allow Permutive to write event data. You can name this user anything, though we recommend `permutive_routing`. Snowflake Routing requires key-pair authentication. Password authentication is not supported. Permutive will generate a public/private key pair and provide you with the public key to attach to this user. Contact Permutive to obtain the public key for your integration. Once you have the key, attach it to the user you created. Follow [Snowflake's key-pair authentication documentation](https://docs.snowflake.com/en/user-guide/key-pair-auth.html#step-4-assign-the-public-key-to-a-snowflake-user) (steps 4-5) to associate the public key with the user. When setting the public key, remove the header and footer lines (`-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----`) and concatenate into a single string without line breaks. Once the public key is attached, Permutive can log into your Snowflake instance to continue the setup process. Permutive requires permissions to create tables, stages, pipes, and a storage integration. **Create the role:** ```sql theme={"dark"} CREATE ROLE permutive_routing_role COMMENT = 'Role used by Permutive to load data into Snowflake'; ``` **Grant permissions to the role:** ```sql theme={"dark"} GRANT USAGE ON DATABASE permutive TO ROLE permutive_routing_role; GRANT USAGE, CREATE TABLE, CREATE STAGE, CREATE PIPE ON SCHEMA permutive.data TO ROLE permutive_routing_role; GRANT SELECT ON FUTURE TABLES IN SCHEMA permutive.data TO ROLE PUBLIC; GRANT OPERATE ON FUTURE PIPES IN SCHEMA permutive.data TO ROLE permutive_routing_role; GRANT CREATE INTEGRATION ON ACCOUNT TO ROLE permutive_routing_role; ``` ### Permissions Reference | Object Type | Object | Permission Required | Granted to Role | | :------------ | :--------------- | :--------------------------------------------- | :----------------------- | | Database | `permutive` | USAGE | `permutive_routing_role` | | Schema | `permutive.data` | USAGE, CREATE TABLE, CREATE STAGE, CREATE PIPE | `permutive_routing_role` | | Future Tables | `permutive.data` | SELECT | PUBLIC | | Future Pipes | `permutive.data` | OPERATE | `permutive_routing_role` | Associate the role with the user you created. If you used a different username than `permutive_routing`, replace it in the SQL below. ```sql theme={"dark"} GRANT ROLE permutive_routing_role TO USER permutive_routing; ALTER USER permutive_routing SET default_role = permutive_routing_role; ``` Permutive requires access to a warehouse to select copy history data from `PERMUTIVE.DATA.INFORMATION_SCHEMA`, which allows us to monitor the data going into your system. We only require the smallest warehouse possible, but will use any warehouse size you provide. Replace `` with the warehouse you want to grant access to. Your warehouse list can be found in **Admin > Warehouses** in Snowflake. ```sql theme={"dark"} GRANT USAGE ON WAREHOUSE TO ROLE permutive_routing_role; ``` Email [technical-services@permutive.com](mailto:technical-services@permutive.com) with the following details: * **User/Username** - The user created for Permutive * **Role** - The role name (e.g., `permutive_routing_role`) * **Warehouse** - The warehouse name you granted access to * **Database** - The database name (e.g., `PERMUTIVE`) * **Schema** - The schema name (e.g., `DATA`) * **Account URL** - Found in **Admin > Account** in Snowflake. Click the paperclip icon next to your account name to copy it. Format: `https://-.snowflakecomputing.com` Permutive will complete the storage integration and Snowpipe configuration. ## What Happens After Setup Once routing is active: 1. **Tables are created automatically** for events, aliases, and segment metadata 2. **Events stream via Snowpipe** in approximately 5-minute or 500MB batches 3. **All event types appear** in a single `EVENTS` table with an `EVENTNAME` column to distinguish types 4. **Schemas update automatically** when you add new event types or properties ## Data Included in the Export ### Events All event data is uploaded to a table called `EVENTS`. All event types are written to this table; use the `EVENTNAME` column to identify the event type. | Column Name | Column Type | | :------------- | :---------- | | TIME | Timestamp | | ORGANIZATIONID | String | | PROJECTID | String | | VIEWID | String | | SESSIONID | String | | USERID | String | | EVENTID | String | | EVENTNAME | String | | SEGMENTS | Array | | COHORTS | Array | | PROPERTIES | Object | **Schema Updates:** When a new event type is added, it automatically appears in this table with the appropriate `EVENTNAME`. When properties change within the `PROPERTIES` field, the object automatically contains all changes. ### Aliases Alias data is written to a table called `SYNC_ALIASES`. | Column Name | Column Type | | :------------ | :---------- | | TIME | Timestamp | | EVENT\_TYPE | String | | PERMUTIVE\_ID | String | | WORKSPACE\_ID | String | | ID | String | | TAG | String | ### Segment Metadata Segment metadata is written as a snapshot of the latest data, overwritten with each export. | Column Name | Column Type | | :------------ | :---------- | | INSERTED\_AT | Timestamp | | WORKSPACE\_ID | String | | NAME | String | | TAGS | Array | | METADATA | Object | | NUMBER | String | ## Notable Behavior ### Reporting Lag Snowflake provides metadata regarding ingested data from external sources. Permutive leverages Snowpipe to ingest data from our platform into your Snowflake instance, which means this metadata is available in the `COPY_HISTORY` table. This table provides metadata such as the location data was ingested from, the amount of data ingested, any errors during ingestion, and other statistics. Snowflake's documentation states that the `COPY_HISTORY` table has a lag of up to two hours. This means Permutive's internal alerting may be delayed by up to two hours. This is a result of Snowflake's implementation and has not been observed to cause issues in practice. ## Next Steps View full integration documentation Return to Routing overview # Actioning Updates to Your Source Schema Source: https://docs.permutive.com/guides/connectivity/sources/actioning-updates-to-your-source-schema How to apply schema changes from your data warehouse to an existing Permutive import ## Overview When your source data changes, Permutive can detect and apply certain types of schema updates to an existing import. This guide explains how to action those updates — including adding new columns — and how to resolve common schema-related errors. **Prerequisites:** * An active Connectivity source connection * An existing import configured against that source * Access to **Sources > Imports** in the Permutive Dashboard ## Supported Schema Changes What you can change depends on your source type. Select your source below for details. Support depends on the file format. We recommend **Parquet** where possible — it carries its schema per file, supports native typed columns, and avoids CSV parsing edge cases. ### Parquet (recommended) **Supported** * Adding new nullable columns (at any position) * Adding fields to nested struct columns — for example, if `address` contains `(city, street)` and a new `zip` field is added, you can accept the change to import all sub-fields. Nested struct changes are all-or-nothing: you must accept all sub-fields or none. **Not supported** * Widening or narrowing a column's data type (e.g. `INT64` → `NUMERIC`, `DATE` → `TIMESTAMP`) * Adding a missing [logical type](/guides/connectivity/sources/connecting-to-amazon-s3#parquet-logical-types) to an existing column — a plain `INT64` gaining a `TIMESTAMP` logical type is a type change, so a new import is required * Removing, renaming, or reordering columns **Why Parquet** * **Type safety** — native typed columns (int, float, string, timestamp) avoid issues like integers silently becoming floats * **Better compression** — typically 5–10× smaller than CSV * **No parsing edge cases** — no issues with escaping, quoting, delimiters, or encoding If you expect schema changes over time, consider migrating the table to Parquet. Parquet embeds the schema per file, so new and old files with different column layouts can co-exist under the same prefix — provided the files are named so that the alphabetically last file reflects the latest schema (Permutive infers the schema from the alphabetically last file in the prefix). ### CSV **Supported** * Adding new columns, **appended to the end of the row only** **Not supported** * Inserting a new column in the middle of the row * Removing, renaming, or reordering columns * Any change to an existing column's data type — CSV has no reliable way to enforce types * Nested struct columns (not supported in CSV) When you add a new column to a CSV source, existing CSV files written with the old schema must be cleared from the table prefix before triggering a resync. Mixing CSV files with different schemas under the same table prefix will cause the import to fail. Permutive's underlying infrastructure supports a maximum of 10,000 columns per table. This limit applies to all source types. If you make an unsupported change for your source type (such as removing or renaming columns, or an unsupported data type change), the source schema must be reverted to its previous state. Unsupported changes can cause cohorts that reference the affected import's data to stop functioning correctly until the schema is restored. See [Troubleshooting](#troubleshooting). **Supported** * Adding new columns * Widening a column's data type — supported widenings: * `Int` → `Float`, `String` * `Bool` → `Int`, `String` * `Float` → `String` * `Date` → `String` * Adding fields to nested struct columns — for example, if `address` contains `(city, street)` and a new `zip` field is added, you can accept the change to import all sub-fields. Nested struct changes are all-or-nothing: you must accept all sub-fields or none. **Not supported** * Removing or renaming columns * Narrowing a column's data type (e.g. `String` → `Int`, `Float` → `Int`, `String` → `Bool`) Permutive's underlying infrastructure supports a maximum of 10,000 columns per table. This limit applies to all source types. If you make an unsupported change for your source type (such as removing or renaming columns, or an unsupported data type change), the source schema must be reverted to its previous state. Unsupported changes can cause cohorts that reference the affected import's data to stop functioning correctly until the schema is restored. See [Troubleshooting](#troubleshooting). **Supported** * Adding new columns * Widening a column's data type — supported widenings: * `Int` → `Float`, `String` * `Bool` → `Int`, `String` * `Float` → `String` * `Date` → `String` **Not supported** * Removing or renaming columns * Narrowing a column's data type (e.g. `String` → `Int`, `Float` → `Int`, `String` → `Bool`) * Nested struct changes — Snowflake maps nested structs to `VARIANT` or `OBJECT` (both treated as `string`), so structural changes within nested fields are not detected. If your source contains semi-structured data, flatten the columns before importing. See [Handling Semi-Structured Data](/guides/connectivity/sources/connecting-to-snowflake#handling-semi-structured-data). Permutive's underlying infrastructure supports a maximum of 10,000 columns per table. This limit applies to all source types. If you make an unsupported change for your source type (such as removing or renaming columns, or an unsupported data type change), the source schema must be reverted to its previous state. Unsupported changes can cause cohorts that reference the affected import's data to stop functioning correctly until the schema is restored. See [Troubleshooting](#troubleshooting). ## How Schema Resync Works 1. You change the source schema in your data warehouse or data lake (for example, add a column to a CSV or Parquet table in S3). 2. You trigger a resync from the Permutive dashboard — detection does not happen automatically on a schedule. 3. Permutive re-reads the schema of every table under the connection's prefix. The resync can take up to 30 minutes. 4. If changes are detected, affected imports are flagged with a banner. You review the changes per import and choose which new columns to bring in. ## Steps 1. Go to **Connectivity > Connections** and open the affected connection (or open an affected import and use the header action), then click **Resync Connection**. 2. Confirm in the dialog: *"You are about to resync your connection to the data source. This can take up to 30 minutes to complete."* 3. The button shows **Resyncing...** while Permutive refreshes the schema from source. You can close the page — the resync runs in the background. Resync runs at the **connection** level and checks all tables under the connection's schema prefix. You don't need to trigger it separately for each import. Once the resync completes, the **Imports** page shows a banner — *"Schema changes detected in N imports"* — and you can use the **Pending changes** filter to find affected imports. Each affected row is marked with an info-circle tooltip. Open an affected import. You'll see one of two banners depending on what was detected: * **New changes detected in your source schema** (info) — *Supported* changes such as new columns. Click **Review Changes** to open the column-selection dialog. * **Unsupported changes detected in your source schema** (warning) — the detected changes require manual intervention (for example, a column was removed or renamed). Click **Learn more** for details; you'll need to revert the change at source or create a new import. In the **Review Changes** dialog, tick the checkbox next to each new column you want to include in the import, then save. You don't need to accept every new column — pick only the ones relevant to your use case, or dismiss the banner entirely if none are needed. The Supported-changes banner is dismissable per schema version. If the source schema changes again later, a new banner appears so you can review the new columns without re-surfacing the previous ones. After accepting, Permutive updates the import's column mapping and resumes syncing. Allow time for data to be ingested against the new columns, then check the import details page to confirm the update completed successfully. If data isn't appearing after a reasonable period, see [Troubleshooting](#troubleshooting) below. ## Troubleshooting 1. **Check the resync status** — on the import details page, confirm whether the resync shows as Completed, In Progress, or Error. If still in progress, wait before taking further action. 2. **Wait for ingestion** — data can take some time to appear after a schema update is applied. 3. **Check your source** — confirm the new columns are present and correctly defined in your data warehouse or lake. 4. **Check for unsupported changes** — if you've also renamed or removed a column, this may be blocking the update. 1. **Check the resync status** — look for an error on the import details page. The error message should indicate the type of issue. 2. **Check your source schema** — verify the columns are correctly defined in your data warehouse tool. 3. **Revert and re-apply** — revert the change at source, re-apply it, then trigger a refresh or resync in Permutive. 4. **Check for unsupported changes** — confirm you haven't also made a change listed under [Supported Schema Changes](#supported-schema-changes). **Solution:** If none of the above resolves the issue, contact [Permutive Support](mailto:technical-services@permutive.com) with the error message and the name of the affected import. Any change to an existing column's data type — whether widening (for example `INT64` → `NUMERIC`) or narrowing — is not supported. The change will be reported as **Unsupported** and must be reverted at source to restore normal import behaviour. Cohorts referencing the affected data may stop functioning correctly until the schema is reverted. **Solution:** 1. Revert the type change at source so the column matches the original type 2. Trigger **Resync Connection** to clear the unsupported-change state 3. If reverting is not possible, apply the updated schema to a new table, delete the existing import (this will impact associated cohorts), and create a new import pointing to the new table For Parquet sources, this includes adding a [logical type](/guides/connectivity/sources/connecting-to-amazon-s3#parquet-logical-types) to a column that didn't have one — for example, a plain `INT64` gaining a `TIMESTAMP` logical type. If the type change is a deliberate fix, resyncing the errored import won't recover it: resync the connection, confirm the column shows the corrected type in the connection's table fields, then create a new import. Removing columns is not currently supported on an existing import. **Solution:** 1. Delete the existing import in Permutive (if it's no longer needed, as this will impact associated cohorts) 2. Create a new import against the updated schema Permutive has detected a mismatch between the schema it has on record and what it can read from your source. For CSV sources, this most commonly happens when old and new files with different column layouts co-exist under the same table prefix. **Solution:** 1. For CSV: remove files that use the old schema from the table prefix so only the new schema remains 2. Revert any other recent schema changes at source if needed 3. Trigger **Resync Connection** from the Connections or Imports page If the resync still errors, contact [Permutive Support](mailto:technical-services@permutive.com). The source table or schema this import points to can no longer be found. **Solution:** 1. Review the change in your warehouse and restore or recreate the table or schema as needed 2. If needed, delete the import in Permutive and create a new one pointing to the correct location **Solution:** 1. Revert any recent changes at source 2. Re-apply the changes 3. Trigger a resync in Permutive from the import details page If the issue persists, delete the import and create a new one. Contact [Permutive Support](mailto:technical-services@permutive.com) if the error continues. ## Next Steps Learn more about managing source connections and schemas Set up a new import from a source connection # Connecting to Amazon S3 Source: https://docs.permutive.com/guides/connectivity/sources/connecting-to-amazon-s3 How to set up a connection to Amazon S3 to import data into Permutive ## Overview This guide walks you through connecting your Amazon S3 bucket to Permutive so you can import data for audience building and activation. You'll learn how to structure your S3 bucket, configure the required permissions, and create a connection in the Permutive dashboard. **Prerequisites:** * An AWS account with access to the S3 bucket you want to connect * Permission to modify the S3 bucket policy * Your data organized in the required directory structure (see below) ## Key Concepts Before setting up your connection, familiarize yourself with these terms: | Term | Description | | :------------- | :-------------------------------------------------------------------------------------------------- | | Bucket Root | The root name of the bucket without the `s3://` prefix and without any trailing slashes or prefixes | | S3 Prefix | A path within your S3 bucket where tables are stored | | Schema | A group of tables, represented by an S3 prefix location | | Table | A single table within Permutive, represented by a prefix under the schema prefix | | Data file | The files containing your data (CSV or Parquet format) | | Hive Partition | An S3 prefix in Hive partition format (e.g., `date=2025-01-01` or `region=EU`) | ## Step 1: Set Up Your Bucket Structure Permutive uses the concept of a **Schema** (containing multiple **Tables**) to organize your data. Since S3 doesn't have native schema or table concepts, you'll need to structure your bucket in a specific way. ### Schema Directory Structure Organize your bucket so that each table is a directory under your schema prefix: ``` s3:///// s3:///// s3:///// ``` When you provide the prefix to Permutive, every directory under that prefix is treated as a table. You can have multiple prefixes representing different schemas, each with multiple tables. Each schema prefix requires a separate Connection in Permutive. ### Table Directory Structure Within each table directory, you can organize your data files in one of two ways: We recommend partitioning your data, especially for event or user activity tables. Partitioning reduces costs by allowing Permutive to filter data upfront when querying. **Single partition:** ``` s3://///=/.csv ``` **Multiple partitions:** ``` s3://///=/=/.csv ``` Partition names become columns in your dataset, with the partition value populating the rows for all files under that partition. For non-partitioned tables, place your data files directly under the table prefix: ``` s3://///.csv ``` Permutive will scan for all files under the table prefix, regardless of subdirectory depth. For example, all these files would be included: ``` s3://///.csv s3://////.csv s3:///////.csv ``` In non-partitioned mode, Hive partition prefixes are ignored. The partition information won't be extracted as columns. ### Data Format Recommendations We highly recommend using Parquet due to its columnar storage benefits, which significantly improve query performance and reduce storage size. For Parquet files, we specifically recommend using the **ZSTD compression codec** to maximize storage efficiency and speed up data processing. We support: * `.csv` (uncompressed CSV) * `.gz` (gzipped CSV) For CSV files, especially large datasets, **gzipping is highly recommended** to reduce storage costs and enhance processing speed. All tables under a schema should use the same data format (either all CSV or all Parquet). ### Parquet Logical Types Every column in your Parquet files should define a [logical type](https://parquet.apache.org/docs/file-format/types/logicaltypes/) alongside its physical type. The physical type describes how values are stored (e.g. `INT64`, `BYTE_ARRAY`); the logical type tells Permutive how to interpret them (e.g. `TIMESTAMP`, `STRING`). A column without a logical type is read as its raw physical type. This matters most for timestamp columns. A Unix timestamp written as a plain `INT64` with no logical type is read as an integer, so it cannot be used as the **Time Field** when creating a User Activity import. A timestamp column should look like this: ```text theme={"dark"} process_ts physical = INT64 logical = TIMESTAMP(isAdjustedToUTC=true, unit=MILLIS) ``` Most Parquet writers set the logical type automatically when the column has a proper timestamp type. If you store Unix epoch values in an integer column, cast the column to a timestamp type before writing (for example with `to_timestamp()` in Spark or Databricks) rather than writing the raw integer. To check the types in a file, inspect its metadata with [PyArrow](https://arrow.apache.org/docs/python/parquet.html) or [pqrs](https://github.com/manojkarthick/pqrs): ```python theme={"dark"} import pyarrow.parquet as pq for field in pq.read_schema("file.parquet"): print(field.name, field.type) ``` Adding a missing logical type to a column that an existing import already uses changes the column's data type (e.g. `INT64` → `TIMESTAMP`), which is not a supported schema change. After correcting your files, resync the connection, confirm the column shows the correct type in the connection's table fields, then create a new import. See [Actioning Updates to Your Source Schema](/guides/connectivity/sources/actioning-updates-to-your-source-schema). ## Step 2: Configure Bucket Permissions Permutive needs permission to read data from your S3 bucket. You'll add an S3 Bucket Policy that grants Permutive read-only access. In the Permutive dashboard, go to **Connectivity > Catalog** and select **Amazon S3**. Begin entering your connection details (covered in Step 3). Once you enter your bucket name, Permutive will generate a bucket policy for you. Copy the S3 Bucket Policy displayed in the Permutive dashboard. It will look similar to this: ```json theme={"dark"} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam:::root" }, "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::", "Condition": { "StringEquals": { "aws:PrincipalArn": "arn:aws:iam:::role/" } } }, { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam:::root" }, "Action": "s3:GetObject", "Resource": [ "arn:aws:s3:::", "arn:aws:s3:::/*" ], "Condition": { "StringEquals": { "aws:PrincipalArn": "arn:aws:iam:::role/" } } } ] } ``` This policy grants Permutive the following permissions: * `s3:ListBucket` — List the contents of your bucket * `s3:GetObject` — Read objects from your bucket 1. Open the AWS Console and navigate to your S3 bucket 2. Go to the **Permissions** tab 3. Click **Edit** on the Bucket Policy section 4. Paste the policy generated from the Permutive dashboard 5. Save your changes If you've already added the policy to your bucket and want to use a new location within the same bucket, you don't need to re-add the policy. ## Step 3: Create the Connection In the Permutive dashboard, go to **Connectivity > Catalog** and select **Amazon S3**. Fill in the following fields: | Field | Description | | :--------------------------- | :------------------------------------------------------------------------------------------------- | | **Name** | A descriptive name for your connection in Permutive | | **AWS Bucket Region** | The region where your bucket is located (only supported regions are shown) | | **AWS Bucket Name** | The bucket name without any prefixes or suffixes (e.g., for `s3://my-bucket/*`, enter `my-bucket`) | | **AWS Bucket Schema Prefix** | The prefix path to your schema location, without a leading slash (e.g., `data/audiences/`) | | **Data Format** | Choose **Parquet** (recommended) or **CSV** | | **Data Partitioning** | Select whether all tables are partitioned or no tables are partitioned | **Data Partitioning Behavior:** * If set to "All tables are partitioned" — non-partitioned tables will be ignored * If set to "No tables are partitioned" — partition prefixes will be ignored and treated as regular directories Before completing the connection, ensure you've added the generated bucket policy to your S3 bucket (see Step 2). Click **Save** to create the connection. It will appear on your **Connections** page with a "Processing" status while Permutive validates access. Once validated, the status changes to "Active". ## Step 4: Create an Import Once your connection is active, you can create imports to bring data into Permutive. Go to **Connectivity > Imports** and click **Create Import**. 1. Select **Amazon S3** as the source type 2. Select your S3 connection 3. The schema prefix will be pre-selected (there's only one per connection) 4. Choose from the list of discovered tables 5. Continue with the standard import configuration For more details on configuring imports, see [Imports](/products/connectivity/imports). ## Troubleshooting If your connection remains in "Processing" status or fails: * Verify the bucket policy has been correctly applied * Check that the bucket name and region are correct * Ensure the schema prefix exists and contains table directories **Solution:** Double-check your AWS bucket policy in the S3 console and verify the bucket name matches exactly what you entered in Permutive. If you don't see expected tables after creating the connection: * Verify your directory structure matches the required format * Check that data files exist under each table directory * Ensure the data format setting matches your actual file format **Solution:** Review your S3 bucket structure and ensure each table is a direct subdirectory of the schema prefix. After making changes, run a schema resync in Permutive to refresh the available tables. If partition columns aren't appearing in your data: * Verify "All tables are partitioned" is selected in Data Partitioning * Check that partition directories use the correct Hive format (`column=value`) **Solution:** Update your connection settings or restructure your partition directories. ## Next Steps Learn how to import data from your S3 connection Return to Sources overview # Connecting to BigQuery Source: https://docs.permutive.com/guides/connectivity/sources/connecting-to-bigquery How to set up a connection to Google BigQuery to import data into Permutive ## Overview This guide walks you through connecting your Google BigQuery data warehouse to Permutive so you can import data for audience building and activation. BigQuery is one of the simplest sources to connect — you just need to grant Permutive access to your dataset and provide your project and dataset details. **Prerequisites:** * A Google Cloud Platform (GCP) account with BigQuery enabled * Access to manage IAM permissions on your BigQuery dataset * Knowledge of your GCP Project ID and Dataset name ## Step 1: Grant Permutive Access to Your Dataset Before creating the connection in Permutive, you need to grant read access to your BigQuery dataset. In the Google Cloud Console, navigate to **BigQuery** and select the dataset you want to connect to Permutive. Click on the dataset name, then click **Sharing** > **Permissions**. Click **Add Principal** and enter the following service account email: ``` connection@permutive.com ``` Assign the following role to the service account: * **BigQuery Data Viewer** (`roles/bigquery.dataViewer`) This grants Permutive read-only access to the tables within your dataset. Click **Save** to apply the permissions. For more details on configuring BigQuery IAM roles, see [Google's documentation on BigQuery access control](https://cloud.google.com/bigquery/docs/access-control). ## Step 2: Find Your Project ID and Dataset Name You'll need two pieces of information from your GCP account: ### GCP Project ID 1. In the Google Cloud Console, click on the project dropdown at the top of the page 2. Your **Project ID** is displayed next to each project name 3. Alternatively, go to **IAM & Admin** > **Settings** to see the Project ID ### Dataset Name 1. In BigQuery, expand your project in the Explorer panel 2. The **Dataset Name** is shown directly under your project The Project ID and Dataset Name are case-sensitive. Ensure you enter them exactly as they appear in your GCP account. ## Step 3: Create the Connection in Permutive In the Permutive dashboard, go to **Connectivity > Catalog** and select **Google BigQuery**. Click **Connect**. Fill in the following fields: | Field | Description | | :------------------- | :---------------------------------------------------------------------------------------------------------- | | **Name** | A descriptive name for your connection in Permutive. This is for your reference only. | | **GCP Project ID** | The Project ID from your GCP account (must match exactly) | | **GCP Dataset Name** | The Dataset name from your BigQuery account (must match exactly) | | **Confirmation** | Confirm that you've granted `connection@permutive.com` the `roles/bigquery.dataViewer` role on your dataset | Click **Save** to create the connection. It will appear on your **Connections** page with a "Processing" status while Permutive validates access to your dataset. Once validated, the status changes to "Active". ## Step 4: Create an Import Once your connection is active, you can create imports to bring data into Permutive. Go to **Connectivity > Imports** and click **Create Import**. 1. Select **Google BigQuery** as the source type 2. Select your BigQuery connection 3. Choose the table you want to import 4. Continue with the standard import configuration For more details on configuring imports, see [Imports](/products/connectivity/imports). ## Troubleshooting If your connection remains in "Processing" status or fails: * Verify that `connection@permutive.com` has been granted the `roles/bigquery.dataViewer` role * Check that the Project ID and Dataset Name are entered exactly as they appear in GCP (case-sensitive) * Ensure the dataset exists and contains tables **Solution:** Double-check IAM permissions in the Google Cloud Console and verify your Project ID and Dataset Name. If you receive permission errors after creating the connection: * The service account may not have been granted access correctly * The role may have been applied at the wrong level (project vs dataset) **Solution:** Ensure the `roles/bigquery.dataViewer` role is granted specifically on the dataset you're connecting to, not just at the project level. If you don't see expected tables after creating the connection: * Verify the dataset contains tables (not just views, if views aren't supported) * Check that permissions are applied to the correct dataset **Solution:** Review your dataset in BigQuery to confirm tables exist and permissions are correctly configured. After updating permissions, run a schema resync in Permutive to refresh the available tables. To access the resync option, go to **Create Import**, enter an import name, select the source, and select the connection—the **Resync with source** button will then appear. If you entered incorrect values: * You'll need to create a new connection with the correct values * The existing connection cannot be edited **Solution:** Create a new connection with the correct Project ID and Dataset Name. ## FAQ Permutive only requires read access to your data. The `roles/bigquery.dataViewer` role grants: * Read access to the dataset's metadata * Read access to all tables within the dataset Permutive cannot modify, delete, or write data to your BigQuery dataset. Yes, you can create separate connections for each dataset you want to import from. Each connection requires its own IAM configuration. Yes, you can create connections to datasets across multiple GCP projects. Ensure `connection@permutive.com` has the required permissions on each dataset. Permutive supports standard BigQuery tables. Partitioned tables are also supported. ## Next Steps Learn how to import data from your BigQuery connection Return to Sources overview # Connecting to Google Cloud Storage Source: https://docs.permutive.com/guides/connectivity/sources/connecting-to-gcs How to set up a connection to Google Cloud Storage (GCS) to import data into Permutive ## Overview This guide walks you through connecting your Google Cloud Storage (GCS) bucket to Permutive so you can import data for audience building and activation. Permutive offers two connection options: connecting to your own GCS bucket, or having Permutive provision a bucket for you. **Prerequisites:** * A Google Cloud Platform (GCP) account * For customer-owned buckets: access to manage IAM permissions on your GCS bucket * Your data organized in the required directory structure (see below) ## Key Concepts Before setting up your connection, familiarize yourself with these terms: | Term | Permutive Context | GCS Context | | :------------- | :-------------------------------- | :------------------------------------------------------------------------- | | Schema | A logical grouping of tables | A static prefix (folder) that contains multiple table subdirectories | | Table | A single dataset within Permutive | A sub-prefix (subdirectory) under the schema prefix | | Data File | Files that store the actual data | Files within the table prefix, often organized under Hive-style partitions | | Bucket Root | N/A | The root name of your GCS bucket (e.g., `my-bucket-name`) | | GCS Prefix | N/A | A path within your bucket where tables are located (e.g., `data/events/`) | | Hive Partition | N/A | A directory structure used to segment data (e.g., `date=2025-01-01`) | ## Step 1: Set Up Your Bucket Structure Since GCS doesn't have built-in schema or table concepts, your bucket must follow a specific prefix (folder) structure so Permutive can correctly infer and manage your data imports. The Permutive platform maps one Connection to one Schema Prefix. To manage multiple logical schemas, you must create distinct prefixes and therefore distinct connections. ### Schema Directory Structure Structure your bucket with a single GCS prefix under which all tables reside: ``` gs:///// gs:///// gs:///// ``` ### Table Directory Structure The structure within a table prefix depends on whether you enable Data Partitioning during connection setup. Supports Hive-style partitioning. Files are read only from the deepest partition level. **Single partition:** ``` gs://///date=2025-01-01/.csv ``` **Multiple partitions:** ``` gs://///date=2025-01-01/region=EU/.csv ``` We recommend partitioning all data where possible, especially for event or user activity tables, as it improves query performance and cost-efficiency. Permutive scans all files under the table prefix, regardless of their subdirectory depth. ``` gs://///.csv gs://///inner1/inner2/.csv ``` In non-partitioned mode, any Hive-style partitions (e.g., `date=...`) will be treated as part of the file path, and their partitioning meaning will be ignored. ### Data Format Recommendations We highly recommend using Parquet due to its columnar storage benefits, which significantly improve query performance and reduce storage size. For Parquet files, we recommend using the **ZSTD compression codec** to maximize storage efficiency and speed up data processing. We support: * `.csv` (uncompressed CSV) * `.gz` (gzipped CSV) For CSV files, especially large datasets, **gzipping is highly recommended** to reduce storage costs and enhance processing speed. All tables under a schema should use the same data format (either all CSV or all Parquet). ### Parquet Logical Types Every column in your Parquet files should define a [logical type](https://parquet.apache.org/docs/file-format/types/logicaltypes/) alongside its physical type. The physical type describes how values are stored (e.g. `INT64`, `BYTE_ARRAY`); the logical type tells Permutive how to interpret them (e.g. `TIMESTAMP`, `STRING`). A column without a logical type is read as its raw physical type. This matters most for timestamp columns. A Unix timestamp written as a plain `INT64` with no logical type is read as an integer, so it cannot be used as the **Time Field** when creating a User Activity import. A timestamp column should look like this: ```text theme={"dark"} process_ts physical = INT64 logical = TIMESTAMP(isAdjustedToUTC=true, unit=MILLIS) ``` Most Parquet writers set the logical type automatically when the column has a proper timestamp type. If you store Unix epoch values in an integer column, cast the column to a timestamp type before writing (for example with `to_timestamp()` in Spark or Databricks) rather than writing the raw integer. To check the types in a file, inspect its metadata with [PyArrow](https://arrow.apache.org/docs/python/parquet.html) or [pqrs](https://github.com/manojkarthick/pqrs): ```python theme={"dark"} import pyarrow.parquet as pq for field in pq.read_schema("file.parquet"): print(field.name, field.type) ``` Adding a missing logical type to a column that an existing import already uses changes the column's data type (e.g. `INT64` → `TIMESTAMP`), which is not a supported schema change. After correcting your files, resync the connection, confirm the column shows the correct type in the connection's table fields, then create a new import. See [Actioning Updates to Your Source Schema](/guides/connectivity/sources/actioning-updates-to-your-source-schema). ## Step 2: Create the Connection Permutive offers two connection options for GCS: Use this option to connect to an existing GCS bucket that you manage. ### Configure Bucket Permissions Before creating the connection, grant Permutive read access to your GCS bucket by assigning IAM roles to Permutive's service account: `connection@permutive.com` **Required roles:** * `roles/storage.objectViewer` * `roles/storage.bucketViewer` In the Google Cloud Console, navigate to your GCS bucket and open the **Permissions** tab. Click **Grant Access** and add `connection@permutive.com` as a principal. Assign the `Storage Object Viewer` and `Storage Legacy Bucket Reader` roles (or equivalent). Save the IAM policy changes. If you use fine-grained permissions or folder-level access, apply these permissions to the specific prefixes you intend to connect. If access is already granted for a parent prefix or the entire bucket, no further IAM changes are needed. ### Create the Connection in Permutive In the Permutive dashboard, go to **Connectivity > Catalog** and select **Google Cloud Storage**. Choose **Customer Owned Bucket**. Fill in the following fields: | Field | Description | | :-------------------- | :----------------------------------------------------------------------- | | **Name** | A descriptive name for your connection in Permutive | | **GCP Project ID** | The GCP project that the bucket belongs to | | **GCS Bucket Region** | Select from the available regions for your workspace | | **GCS Bucket Name** | The full bucket name (without `gs://` prefix) | | **Schema Prefix** | The prefix within the bucket that acts as your schema (no leading slash) | | **Data Format** | Choose **Parquet** (recommended) or **CSV** | | **Data Partitioning** | Select whether all tables are partitioned or no tables are partitioned | Click **Save** to create the connection. It will appear on your **Connections** page with a "Processing" status while Permutive validates access. Use this option to have Permutive create and manage a GCS bucket for you. In the Permutive dashboard, go to **Connectivity > Catalog** and select **Google Cloud Storage**. Choose **Permutive Provisioned Bucket**. Fill in the following fields: | Field | Description | | :------------------------- | :-------------------------------------------------------------------------------------------- | | **Name** | A descriptive name for your connection in Permutive | | **Upload-Access Accounts** | GCP principals (email addresses) granted permission to upload files. At least one is required | | **Read-Access Accounts** | Optional GCP principals granted read-only permission to files | | **Data Format** | Choose **Parquet** (recommended) or **CSV** | | **Data Partitioning** | Select whether all tables are partitioned or no tables are partitioned | The default Schema Prefix is `/`. The bucket region will automatically match the region where your data resides within BigQuery. Click **Save** to create the connection. Permutive will provision the bucket and display the bucket name on the Connection Details page once complete. ### Access Accounts When specifying GCP principals, you can choose from three types: * **Group** (recommended) * **User** * **Service Account** **We strongly recommend using Google Workspace group email addresses** over individual user accounts. Using groups allows you to manage user access without contacting Permutive. When a user is removed from your organization, their access is automatically revoked if they were part of a group. **IAM roles applied:** * Upload-Access accounts receive: `roles/storage.objectCreator` and `roles/storage.objectViewer` * Read-Access accounts receive: `roles/storage.objectViewer` ## Step 3: Create an Import Once your connection is active, you can create imports to bring data into Permutive. Go to **Connectivity > Imports** and click **Create Import**. 1. Select **Google Cloud Storage** as the source type 2. Select your GCS connection 3. Choose the discovered schema (matches the prefix defined in your connection) 4. Choose from the list of detected tables 5. Continue with the standard import configuration For more details on configuring imports, see [Imports](/products/connectivity/imports). ## Limitations **Important limitations to be aware of:** * **Partitioning Standard**: Only Hive-style partitioning is supported * **Mixed Partitioning**: Not supported in a single schema connection. All tables must either be partitioned or non-partitioned * **Schema Evolution**: Column changes (additions/removals) are not supported for GCS imports. If your column structure changes, you'll need to create a new connection ## Troubleshooting If your connection remains in "Processing" status or fails: * Verify the IAM permissions have been correctly applied to `connection@permutive.com` * Check that the bucket name and project ID are correct * Ensure the schema prefix exists and contains table directories **Solution:** Double-check your IAM settings in the Google Cloud Console and verify the bucket name matches exactly what you entered in Permutive. If you don't see expected tables after creating the connection: * Verify your directory structure matches the required format * Check that data files exist under each table directory * Ensure the data format setting matches your actual file format **Solution:** Review your GCS bucket structure and ensure each table is a direct subdirectory of the schema prefix. After making changes, run a schema resync in Permutive to refresh the available tables. If partition columns aren't appearing in your data: * Verify "All tables are partitioned" is selected in Data Partitioning * Check that partition directories use the correct Hive format (`column=value`) **Solution:** Update your connection settings or restructure your partition directories. The bucket name is generated upon connection creation. You can find the full GCS Bucket Name on the **Connection Details** page immediately after setup is complete. ## Next Steps Learn how to import data from your GCS connection Return to Sources overview # Connecting to Snowflake Source: https://docs.permutive.com/guides/connectivity/sources/connecting-to-snowflake How to set up a connection to Snowflake to import data into Permutive ## Overview This guide walks you through connecting your Snowflake data warehouse to Permutive so you can import data for audience building and activation. You'll configure your Snowflake instance with the necessary permissions using a setup script, then create the connection in the Permutive dashboard. **Prerequisites:** * A Snowflake account with `SECURITYADMIN` and `ACCOUNTADMIN` roles * Access to run SQL scripts in your Snowflake instance * Knowledge of the database, schema, and warehouse you want to connect ## Step 1: Set Up Your Snowflake Instance To connect Permutive to your Snowflake instance, you'll run a script that creates a dedicated user and role with read-only permissions. Choose between password authentication or key pair authentication. Password authentication is the most straightforward method. ### Run the Setup Script Before running the script, replace the following placeholders: * ``: The password for PERMUTIVE\_USER * ``: The warehouse Permutive will use to query data * ``: The database you want to import * ``: The schema containing the tables you want to import ```sql theme={"dark"} begin; -- create variables for user / password / role / warehouse / database (needs to be uppercase for objects) set role_name = 'PERMUTIVE_ROLE'; set user_name = 'PERMUTIVE_USER'; set user_password = ''; set warehouse_name = ''; set database_name = ''; set schema_name = ''; -- change role to securityadmin for user / role steps use role securityadmin; -- create role for permutive create role if not exists identifier($role_name); -- create a user for permutive create user if not exists identifier($user_name) password = $user_password default_role = $role_name default_warehouse = $warehouse_name timezone = 'UTC'; -- grant the role to the permutive user grant role identifier($role_name) to user identifier($user_name); -- change role to accountadmin to grant permissions use role ACCOUNTADMIN; -- grant permutive role access to warehouse grant usage on warehouse identifier($warehouse_name) to role identifier($role_name); -- grant permutive access to database grant usage on database identifier($database_name) to role identifier($role_name); use database identifier($database_name); -- add a statement like this one for each schema you want to have synced by permutive grant usage on schema identifier($schema_name) to role identifier($role_name); -- add statements granting select permissions grant select on all TABLES in schema identifier($schema_name) to role identifier($role_name); -- allow Permutive to see future tables within this Snowflake schema grant select on future TABLES in schema identifier($schema_name) to role identifier($role_name); commit; ``` This script will: * Create a new role: `PERMUTIVE_ROLE` * Create a new user: `PERMUTIVE_USER`, with its session timezone set to `UTC` * Grant read access to the specified database and schema * Grant read access to all current and future tables within the schema For enhanced security, you can use key pair authentication. ### Generate the Key Pair First, generate an unencrypted private key file: ```bash theme={"dark"} openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out rsa_key.p8 -nocrypt ``` Alternatively, for an encrypted private key: ```bash theme={"dark"} openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -v2 aes-256-cbc -out rsa_key.p8 ``` Generate the matching public key: ```bash theme={"dark"} openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub ``` View your public key: ```bash theme={"dark"} cat rsa_key.pub ``` When using the public key in the script, copy the contents **without** the `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` headers. ### Run the Setup Script Replace the following placeholders: * ``: Your public key (without headers) * ``: The warehouse Permutive will use to query data * ``: The database you want to import * ``: The schema containing the tables you want to import ```sql theme={"dark"} begin; -- create variables for user / password / role / warehouse / database (needs to be uppercase for objects) set role_name = 'PERMUTIVE_READ_ROLE'; set user_name = 'PERMUTIVE_USER'; set warehouse_name = ''; set database_name = ''; set schema_name = ''; -- change role to securityadmin for user / role steps use role securityadmin; -- create role for permutive create role if not exists identifier($role_name); -- create a user for permutive create user if not exists identifier($user_name) rsa_public_key = '' default_role = $role_name default_warehouse = $warehouse_name timezone = 'UTC'; -- grant the role to the permutive user grant role identifier($role_name) to user identifier($user_name); -- change role to accountadmin to grant permissions use role ACCOUNTADMIN; -- grant permutive role access to warehouse grant usage on warehouse identifier($warehouse_name) to role identifier($role_name); -- grant permutive access to database grant usage on database identifier($database_name) to role identifier($role_name); use database identifier($database_name); -- add a statement like this one for each schema you want to have synced by permutive grant usage on schema identifier($schema_name) to role identifier($role_name); -- add statements granting select permissions grant select on all TABLES in schema identifier($schema_name) to role identifier($role_name); -- allow Permutive to see future tables within this Snowflake schema grant select on future TABLES in schema identifier($schema_name) to role identifier($role_name); commit; ``` ### Verify the Key Setup Generate the fingerprint for your local public key: ```bash theme={"dark"} openssl rsa -pubin -in rsa_key.pub -outform DER | openssl dgst -sha256 -binary | openssl enc -base64 ``` Compare it with the fingerprint in Snowflake: ```sql theme={"dark"} DESC USER PERMUTIVE_USER; SELECT SUBSTR( (SELECT "value" FROM TABLE(RESULT_SCAN(LAST_QUERY_ID())) WHERE "property" = 'RSA_PUBLIC_KEY_FP'), LEN('SHA256:') + 1) AS key; ``` If the fingerprints match, the public key has been correctly set. **Create the user with its timezone set to `UTC`.** Permutive compares incremental cursor values as UTC instants. If the user's session timezone is not `UTC`, cursor columns without a timezone (`TIMESTAMP_NTZ`, `DATE`, `TIME`) are interpreted in the session timezone instead, which can cause syncs to silently miss rows — or import no rows at all. `TIMESTAMP_TZ` and `TIMESTAMP_LTZ` columns are unaffected. The setup scripts above set `TIMEZONE = 'UTC'` when creating the user. Because they use `CREATE USER IF NOT EXISTS`, they will **not** change a user that already exists — for an existing user, set it explicitly: ```sql theme={"dark"} ALTER USER PERMUTIVE_USER SET TIMEZONE = 'UTC'; ``` ## Step 2: Configure Network Policy (Optional) Skip this section if you're not using Network Policies to control traffic to your Snowflake instance. If you use Network Policies, add Permutive's IP addresses to your allowlist: ``` 34.76.252.197 34.77.88.160 34.77.189.31 34.77.208.157 34.77.250.131 34.79.146.181 35.187.87.199 35.187.167.110 35.195.246.181 35.205.59.235 104.199.7.80 104.199.24.198 ``` ### Create a User-Based Network Policy We recommend creating a Network Policy attached to the Permutive user: ```sql theme={"dark"} CREATE OR REPLACE NETWORK POLICY ALLOW_PERMUTIVE_POLICY ALLOWED_IP_LIST = ( '34.76.252.197', '34.77.88.160', '34.77.189.31', '34.77.208.157', '34.77.250.131', '34.79.146.181', '35.187.87.199', '35.187.167.110', '35.195.246.181', '35.205.59.235', '104.199.7.80', '104.199.24.198' ) BLOCKED_IP_LIST = (); ``` Attach the policy to the Permutive user: ```sql theme={"dark"} ALTER USER PERMUTIVE_USER SET NETWORK_POLICY = ALLOW_PERMUTIVE_POLICY; ``` ## Step 3: Create the Connection in Permutive In the Permutive dashboard, go to **Connectivity > Catalog** and select **Snowflake**. Fill in the following fields: | Field | Description | | :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------- | | **Database** | The database name from your setup script (use UPPERCASE) | | **Host** | Your Snowflake account URL. Find this under **Admin > Accounts > View Account Details** and copy the "Account/Server URL" (without `https://` prefix) | | **Port** | Leave as default `443` | | **User** | The user created by the script (`PERMUTIVE_USER`) | | **Role** | The role created by the script (`PERMUTIVE_ROLE` or `PERMUTIVE_READ_ROLE`) | | **Password** | The password from your script (for password authentication only) | | **Warehouse** | The Snowflake compute warehouse to use | Snowflake uses UPPERCASE for all database, schema, user, and role names. Ensure you use uppercase values when entering connection details. Click **Save** to create the connection. It will appear on your **Connections** page with a "Processing" status while Permutive validates the credentials. ## Step 4: Create an Import Once your connection is active, you can create imports. Permutive uses incremental updates based on a cursor column to sync data efficiently. ### Cursor Column Requirements When creating an import, you'll need to select a cursor column. This column should be: * Monotonically increasing over time * Not updated after creation * Preferably unique or high-cardinality * Not nullable **Good cursor examples:** * `CREATED_AT` or `UPDATED_AT` timestamp columns **Supported cursor data types:** * `TIMESTAMP_TZ`, `TIMESTAMP_NTZ`, `TIME`, `DATE` `TIMESTAMP_NTZ`, `DATE`, and `TIME` cursors have no timezone of their own, so they only compare correctly when the user's session timezone is `UTC` (see the timezone warning in Step 1). Go to **Connectivity > Imports** and click **Create Import**. 1. Select **Snowflake** as the source type 2. Select your Snowflake connection 3. Choose the schema and table to import 4. Select your cursor column 5. Continue with the standard import configuration For more details on configuring imports, see [Imports](/products/connectivity/imports). ## Supported Data Types Permutive supports the following Snowflake data types: | Category | Supported Types | | :-------- | :--------------------------------------------------------------- | | Numeric | `NUMBER`, `INTEGER`, `FLOAT`, `DOUBLE` | | String | `VARCHAR`, `TEXT`, `STRING` | | Date/Time | `DATE`, `TIME`, `TIMESTAMP_NTZ`, `TIMESTAMP_TZ`, `TIMESTAMP_LTZ` | | Logical | `BOOLEAN` | **Semi-structured data types (`OBJECT` and `ARRAY`) are not supported.** If your tables contain these types, you must transform the data into a flattened format before Permutive can sync it. ### Handling Semi-Structured Data If your source tables contain `OBJECT` or `ARRAY` columns, create a View that flattens the data: ```sql theme={"dark"} CREATE OR REPLACE VIEW FLATTENED_EVENTS AS SELECT t.USER_ID, t.EVENT_TIMESTAMP, f.VALUE:item_id::VARCHAR AS ITEM_ID, f.VALUE:quantity::NUMBER AS QUANTITY FROM RAW_EVENTS t, LATERAL FLATTEN(INPUT => t.ITEM_ARRAY) f; ``` Configure Permutive to sync from the view instead of the raw table. ## Security Best Practices * Use a dedicated read-only role and user per environment * Limit grants to only the schemas you need * Consider multi-factor policies for administrative users ### Granting Access to Multiple Schemas To grant access to additional schemas, repeat the following for each schema: ```sql theme={"dark"} grant usage on schema DATABASE_A.SCHEMA_X to role PERMUTIVE_READ_ROLE; grant select on all tables in schema DATABASE_A.SCHEMA_X to role PERMUTIVE_READ_ROLE; grant select on future tables in schema DATABASE_A.SCHEMA_X to role PERMUTIVE_READ_ROLE; ``` ## Troubleshooting If you receive authentication errors: * Verify the account locator/region in your host URL * Double-check the username and password * Ensure you're using UPPERCASE for database, schema, user, and role names **Solution:** Re-check your connection details and ensure they match exactly what was created by the setup script. If you receive permission errors: * Confirm `USAGE` grants on the warehouse, database, and schema * Verify `SELECT` grants on tables/views (including future tables) **Solution:** Re-run the permission grants from the setup script or add missing grants manually. After updating permissions, run a schema resync in Permutive to refresh the available tables. If you receive cursor-related errors: * Ensure the cursor column exists in the table * Verify it's one of the supported data types * Avoid using nullable columns as cursors **Solution:** Choose a different cursor column that meets the requirements. If rows are missing or being re-synced: * This can occur if multiple rows share the same cursor value **Solution:** If needed, reset the connection state in Permutive and re-run the import. Consider using a higher-cardinality cursor column. If an import runs successfully but misses rows that are present when you query the table directly, check the user's session timezone. When it is not `UTC`, cursor columns without a timezone (`TIMESTAMP_NTZ`, `DATE`, `TIME`) are interpreted in the session timezone, offsetting the cursor comparison. **Solution:** Set the user's session timezone to `UTC`, then trigger a new sync: ```sql theme={"dark"} ALTER USER PERMUTIVE_USER SET TIMEZONE = 'UTC'; ``` Confirm the effective value with: ```sql theme={"dark"} SHOW PARAMETERS LIKE 'TIMEZONE' IN USER PERMUTIVE_USER; ``` If connections are being blocked: * Ensure Permutive's IP addresses are allowlisted in your Network Policy **Solution:** Add Permutive's IP addresses to your Network Policy (see Step 2). If the public key fingerprints don't match: **Solution:** Reassign the public key to the user: ```sql theme={"dark"} ALTER USER PERMUTIVE_USER SET RSA_PUBLIC_KEY='MIIBIjANBgkqh...'; ``` ## FAQ All standard Snowflake editions are supported. Yes, with `USAGE` on the schema and `SELECT` on the view. If a table lacks a monotonically increasing column like a timestamp, it may not be suitable for incremental sync. You would need to add one to the table schema before importing. ## Next Steps Learn how to import data from your Snowflake connection Return to Sources overview # Creating an Import Source: https://docs.permutive.com/guides/connectivity/sources/creating-an-import How to create a data import from an active connection in Permutive ## Overview This guide walks you through creating an import from an active connection. Imports pull data from your connected data warehouses or data lakes into Permutive, where it can be used for audience building, targeting, and activation. Each import is configured with a specific data type and column mapping that determines how Permutive processes the incoming data. **Prerequisites:** * An active connection (see guides for [BigQuery](/guides/connectivity/sources/connecting-to-bigquery), [Snowflake](/guides/connectivity/sources/connecting-to-snowflake), [Amazon S3](/guides/connectivity/sources/connecting-to-amazon-s3), or [Google Cloud Storage](/guides/connectivity/sources/connecting-to-gcs)) * A table in your source that contains the data you want to import * Knowledge of which data type best matches your use case (see [Import Data Types](#import-data-types) below) ## Import Data Types Before creating an import, determine which data type matches the data you want to bring into Permutive. Each data type serves a different purpose and requires different column mappings. | Data Type | Purpose | Use Case | | :---------------- | :----------------------------- | :----------------------------------------------------------------------- | | **User Profile** | Static user attributes | Demographics, subscription tiers, preference data | | **User Activity** | Time-stamped events | Purchase history, content interactions, conversion events | | **Identity** | User identity mappings | Linking user IDs across systems, cross-device identity resolution | | **Group** | Household or group memberships | Household graphs, account-level groupings, shared identity relationships | ## Step 1: Start the Import Wizard In the Permutive Dashboard, go to **Connectivity > Imports** and click **Create Import**. Provide a descriptive name for your import. Choose a name that identifies the data source and purpose (e.g., "BigQuery - Purchase History", "Snowflake - CRM Profiles"). Choose the source platform your data is stored in (e.g., Google BigQuery, Snowflake, Amazon S3, Google Cloud Storage). Choose the active connection you want to import from. Only connections with an "Active" status are available. If your connection isn't listed, check its status on the **Connections** page. It may still be processing or may have become inactive. ## Step 2: Select Schema and Table Select the schema (dataset or database schema) that contains the table you want to import. The available schemas are discovered from your active connection. Select the table you want to import data from. Permutive displays the tables discovered within the selected schema. If you don't see an expected table, click **Resync with source** to refresh the list of available schemas and tables from your source platform. This is useful if tables have been added since the connection was created. ## Step 3: Select the Data Type Choose the data type that matches the structure and purpose of your data. The data type you select determines which columns you'll need to map in the next step. * **User Profile** — For importing static user attributes such as demographics and subscription tiers. Use this when your data describes properties of individual users that don't change frequently. * **User Activity** — For importing time-stamped event data such as purchase history, content interactions, or conversion events. Use this when your data contains records of actions users have taken, each associated with a timestamp. * **Identity** — For importing identity mappings that link different user identifiers together. Use this to enrich Permutive's Identity Graph with cross-system or cross-device identity relationships. * **Group** — For importing household or group membership data. Use this to associate users with groups such as households, accounts, or other shared identity structures. ## Step 4: Map Your Columns After selecting your data type, map the columns from your source table to the fields Permutive expects. Each data type has a set of required fields and optional attribute fields. ### User Profile | Field | Required | Description | | :------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **User ID** | Yes | The column containing unique user identifiers. The identifier type (e.g., Permutive User ID, email hash) that the column represents must also be declared. Identifier types must be [configured in the Identity Graph](/guides/signals/identity/configuring-identifiers) before they can be used. | | **Attributes** | No | Additional columns to import as user properties (e.g., age, subscription tier, region) | User Profile imports are designed for data that describes who your users are. Each row should represent a single user, and the User ID column must contain a unique identifier for that user. ### User Activity | Field | Required | Description | | :------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **User ID** | Yes | The column containing unique user identifiers. The identifier type (e.g., Permutive User ID, email hash) that the column represents must also be declared. Identifier types must be [configured in the Identity Graph](/guides/signals/identity/configuring-identifiers) before they can be used. | | **Time Field** | Yes | A timestamp column used for incremental sync. A time range that determines the sync window must also be selected. | | **Attributes** | No | Additional columns to import as event properties (e.g., product category, purchase amount) | The **Time Field** column is critical for User Activity imports. It must be a monotonically increasing timestamp that Permutive uses to track which records have already been imported. On each sync, Permutive reads only records with a time field value greater than the last imported value. The time field column should not be updated after a record is created. For S3 and GCS Parquet sources, the time field column must have a `TIMESTAMP` [logical type](/guides/connectivity/sources/connecting-to-amazon-s3#parquet-logical-types). An integer column holding Unix epoch values cannot be used as a time field. ### Identity and Group For detailed column mapping and setup instructions: * [Importing User Identity](/guides/signals/identity/importing-user-identity) for linking user-level identifiers * [Importing User Group Memberships](/guides/signals/identity/importing-user-group-memberships) for household and group relationships ### Data Retention (Identity and Group Identity imports) For Identity and Group Identity imports, you can configure a **data retention period** that determines how long imported data is kept in Permutive before it expires automatically. This is set per data type via the **Retain data for** dropdown on the import mapping card. Available retention periods range from 2 days to 180 days (the default). **For Group Identity imports**, data retention is an alternative to using the `is_deleted` column to manage stale data. Explicit deletions via `is_deleted` are the most efficient approach — only changed and deleted rows are processed, which minimizes data egress from your warehouse and keeps processing volumes low. However, if communicating deletions is cumbersome or not supported by your source system, a retention period is a convenient alternative. **For Identity imports**, data retention lets you align how long identity mappings are kept with how long the underlying identifiers remain useful. This is particularly relevant for identifiers that naturally go stale, such as cookie-based IDs or mobile advertising IDs. We recommend setting the retention period at least **2–3 days longer than your import frequency** to provide a safety margin in case of delayed processing. For example, if you refresh your data daily, a retention period of 3–7 days is a good starting point. Very short retention periods risk losing active data if an import run is delayed. ## Step 5: Save the Import Once your column mappings are configured, click **Save** to create the import. The import will begin processing and will sync data from your source on its next scheduled run. Imports sync on a daily schedule, pulling new and updated data from your source table into Permutive. ## Understanding Audience Size for Import Cohorts When you create a cohort that includes imported data, you may notice that the **Predicted Audience Size (PAS)** is significantly larger than the **Live Audience Size (LAS)** immediately after deployment. This is expected behavior. **Why this happens:** * **PAS is a historical estimate.** It is calculated server-side using the past 30 days of data, including imported identifiers resolved against the identity graph. This means PAS can reflect users who have not visited your properties recently. * **LAS counts only users evaluated since deployment.** LAS is updated periodically (approximately every 4 hours) as users interact with your properties. Imported users must return to your site or app before they can be counted in LAS. * **No backfill.** LAS does not retroactively count users who qualified before the cohort was deployed. It only counts users going forward from the moment of deployment. * **Identity resolution is progressive.** For imported users identified by external identifiers (such as hashed emails), the SDK must match the imported identity to a Permutive user ID when the user visits. This matching happens over time as users return. **What to expect:** * LAS will start at zero and grow over the first days and weeks as imported users visit your properties. * The rate of growth depends on your traffic volume and how frequently imported users visit. * After approximately 30 days, LAS should stabilize and more closely reflect PAS (assuming consistent user behavior). * LAS may never fully reach PAS, because not all historically qualifying users may return within the cohort's recency window. If LAS remains at zero for more than 48 hours after deployment, verify that the import is active and syncing data, and that the Permutive SDK is deployed on the properties where imported users are expected to visit. Also check that imported identifiers are correctly mapped and match the identity graph. For a full explanation of Predicted and Live Audience Sizes, see [Custom Cohorts: Audience Size Definitions](/products/signals/cohorts/custom#whats-the-difference-between-predicted-audience-size-and-live-audience-size). ## Managing Imports ### Viewing Import Status After creating an import, you can monitor it from the **Imports** page. Each import displays: * **Name** — The name you provided during creation * **Source** — The platform and connection used * **Data Type** — The type of data being imported * **Status** — Whether the import is active, processing, or has encountered errors * **Last Sync** — When data was last successfully imported ### Resyncing Schema If your source table has new columns added since the import was created, you can resync the schema to discover them: 1. Go to **Connectivity > Imports** and click **Create Import** 2. Enter a name, select the source, and select the connection 3. Click **Resync with source** to refresh the available schemas, tables, and columns Schema resync discovers new columns but does not automatically update existing imports. If you need to include new columns, you'll need to create a new import with the updated column mapping. ### Deleting an Import You can delete an import when it's no longer needed. Deleting an import has the following effects: * **Data syncing stops immediately** — No further data will be pulled from the source table * **Data retention** — For non-composable deployments, a 30-day time-to-live (TTL) is applied to the imported data. After 30 days, the data is removed from Permutive. For composable deployments, the data remains in your cloud environment * **Cohort Builder impact** — The import is removed from the Cohort Builder as an available data source. Any existing cohorts that reference the deleted import will display a warning indicating the import is no longer active * **Audience evaluation** — Cohort expressions that depend on the deleted import will stop evaluating against the imported data Before deleting an import, review any cohorts that depend on it. Deleting an import can affect active campaigns and audience targeting. Cohorts that reference the import will continue to exist but will no longer include users based on the deleted import's data. To delete an import: 1. Navigate to **Connectivity > Imports** 2. Select the import you want to delete 3. Click **Delete Import** and confirm the action ## Troubleshooting If your import fails during initial processing: * Verify the source connection is still active * Check that the selected table exists and contains data * Ensure your column mappings match the actual column names and data types in the source table **Solution:** Review the import configuration and source table. If the connection has become inactive, create a new connection and reconfigure the import. If expected columns don't appear during the column mapping step: * The schema may not have been refreshed since the columns were added * The column data type may not be supported **Solution:** Use the **Resync with source** option to refresh the available columns from your source table. If your User Activity import isn't picking up new records: * The time field column may not be monotonically increasing * Records may have been updated (changing the time field value) rather than inserted as new rows * The time field column may contain null values **Solution:** Ensure your time field column is a timestamp that is set once when a record is created and never updated. Remove or handle null values in the time field column. If imported data isn't available in the Cohort Builder: * The import may still be processing its first sync * The column mapping may be incorrect * The data type selected may not match the structure of your data **Solution:** Check the import status on the Imports page. If the sync completed successfully but data isn't appearing, review your column mappings and data type selection. You may need to delete the import and recreate it with the correct configuration. ## Next Steps Learn more about managing your source connections Set up user identity imports to link identifiers Set up household and group identity imports # Configuring Google Curate Source: https://docs.permutive.com/guides/curation/configuring-google-curate How to configure Google Ad Manager for Curation to enable cohort signals in Google Curate ## Overview Configuring Google Curate enables your cohort signals to flow to Google's curation marketplace. This allows Permutive to package your audience signals into multi-publisher curated deals available to advertisers through Google Curate. **Prerequisites:** * Active Google Ad Manager account * Permutive Web SDK deployed and operational * Standard or Custom Cohorts enabled for Curation * Signed Curation Order Form ## Steps Navigate to Google Ad Manager and enable curation settings: 1. Log in to [Google Ad Manager](https://admanager.google.com/) 2. Go to **Admin > Curation settings** 3. Click **Enable sharing** 4. **Important**: Select the **"Publisher Deploy"** option Selecting "Publisher Deploy" is critical because you already have the Permutive SDK in place, so no additional deployment steps are required. Reference: [Google's official guide to configuring curation settings](https://support.google.com/admanager/answer/15308224) Turn on Permutive as a secure signal provider in GAM: 1. In Google Ad Manager, navigate to **Admin > Secure signals** 2. Search for **"Permutive"** in the signal providers list 3. Click to enable the Permutive signal 4. Verify that **"Web integration deployment"** is set to **"Publisher Deploy"** Reference: [Google's Secure Signals documentation](https://support.google.com/admanager/answer/10488752) After completing the setup, verify that your domains are appearing in Permutive's Google Curate seat: 1. Contact your Permutive Customer Success Manager to verify your domains are visible in reporting 2. Allow 24-48 hours for data to begin flowing after configuration If your domains are not appearing after setup, verify both steps above were completed: * Curation settings are enabled with "Publisher Deploy" selected * Permutive secure signal is turned on in Secure Signals settings **Common Setup Issues:** * If domains don't appear in Google Curate reporting, double-check that both the Curation settings AND Secure Signals are enabled * Ensure "Publisher Deploy" is selected in both the Curation settings tab and the Secure Signals configuration * Contact Permutive Technical Services if issues persist after verifying all settings ## Next Steps Control which cohorts participate in curation Set up Prebid-based curation Return to Curation overview # Configuring Prebid for Curation Source: https://docs.permutive.com/guides/curation/configuring-prebid-for-curation How to configure Prebid.js to enable curation signals for SSP curation marketplaces ## Overview Configuring Prebid.js for Curation enables your cohort signals to flow to SSP curation marketplaces including Monetize, Index Exchange Marketplaces, and Pubmatic. This allows Permutive to package your audience signals into multi-publisher curated deals. **Prerequisites:** * Prebid.js v9.5.0 or later installed on your website * Permutive Web SDK deployed and operational * Standard or Custom Cohorts enabled for Curation * Signed Curation Order Form ## Steps Ensure your Prebid.js build includes the Permutive RTD (Real-Time Data) module. This module reads cohort data from local storage and passes it to configured SSPs. The module must be included in your Prebid.js build. Refer to the [Prebid integration documentation](/integrations/advertising/bidstream/prebid) for detailed setup instructions. Verify that the required SSP bid adapters are included in your Prebid.js build and properly configured: * **Monetize**: `appnexus` or `msft` bid adapter * **Index Exchange**: `ix` bid adapter * **Pubmatic**: `pubmatic` bid adapter These adapters must be present in your Prebid.js build to send bid requests containing curation signals to the respective SSPs. Add the Permutive RTD configuration to your Prebid.js setup: ```javascript theme={"dark"} pbjs.setConfig({ realTimeData: { auctionDelay: 200, dataProviders: [{ name: 'permutive', waitForIt: true }] } }); ``` **Critical settings:** * `waitForIt: true` ensures the auction waits for cohort data to be ready * `auctionDelay: 200` sets a 200ms maximum wait time (adjust as needed) If operating in GDPR regions, add Permutive as a vendor exception in your consent management configuration: ```javascript theme={"dark"} pbjs.setConfig({ consentManagement: { gdpr: { cmpApi: 'iab', timeout: 8000, allowAuctionWithoutConsent: true, defaultGdprScope: true, rules: [{ purpose: 'storage', enforcePurpose: true, enforceVendor: true, vendorExceptions: ["permutive"] }] } } }); ``` This ensures the RTD module can access cohort data stored locally by the Permutive SDK. **Troubleshooting Tips:** * Prebid.js version 9.5.0 or later is required for correct ORTB2 targeting * If cohorts aren't appearing in bid requests, verify local storage keys starting with `_p` contain data * Contact Permutive Technical Services if setup appears correct but signals are not flowing ## Next Steps Control which cohorts participate in curation Complete Prebid integration reference Return to Curation overview # Managing Cohort Exclusions Source: https://docs.permutive.com/guides/curation/managing-cohort-exclusions How to control which cohorts participate in Curation using UI toggles and exclusion tags ## Overview Managing cohort exclusions allows you to control which of your audience signals participate in Permutive's curation offering. This is critical for preventing channel conflict with direct-sold campaigns and protecting proprietary audience segments. **Prerequisites:** * Standard or Custom Cohorts enabled for Curation * Curation Order Form signed * Admin or appropriate permissions to manage cohort settings ## Understanding Cohort Exclusions Permutive provides two mechanisms for excluding cohorts from curation: * **Standard Cohorts**: Use UI toggles to quickly include or exclude standard cohorts from curation * **Custom Cohorts**: Apply a special `curation_exclusion` tag to prevent custom cohorts from being included in curated deals ## Steps Standard Cohorts can be managed directly through the Permutive UI: 1. Navigate to **Cohorts > Standard Cohorts** in the Permutive dashboard 2. Review the list of Standard Cohorts 3. Use the toggle switches in the Curation column to enable/disable individual cohorts Custom Cohorts require applying a special tag to exclude them from curation: 1. Navigate to **Cohorts > Custom Cohorts** 2. Identify the cohorts you want to exclude from curation 3. Click on each cohort to edit it 4. Add the tag `curation_exclusion` (exact spelling, case-sensitive) 5. Save the cohort **Important notes:** * The tag name must be exactly `curation_exclusion` (no spaces, underscores required) * Disabled cohorts are automatically excluded (no tag needed) For organizations with many custom cohorts, Permutive Technical Services can help with bulk exclusions: 1. Export your custom cohorts list from **Cohorts > Custom Cohorts > Export** 2. Review the list and identify cohorts to exclude 3. Mark cohorts for exclusion in a spreadsheet (add column "Excluded from Curation?") 4. Submit a request to [Technical Services](mailto:technical-services@permutive.com) with the spreadsheet 5. Technical Services will apply the `curation_exclusion` tag in bulk This is especially useful during initial curation setup when you need to review many cohorts at once. After configuring exclusions, verify they are working correctly: 1. Check that excluded cohorts have the `curation_exclusion` tag applied 2. Monitor for 24-48 hours to ensure excluded cohorts are not appearing in curation reporting 3. Contact your Customer Success Manager if excluded cohorts continue appearing in curated deals ## Next Steps Set up technical curation enablement Enable Google Curate integration Return to Curation overview # Pageview Comparisons FAQ Source: https://docs.permutive.com/guides/insights/pageview-comparisons Understanding how Permutive pageviews compare to Google Analytics ## Overview When comparing pageview numbers between Permutive and Google Analytics (GA), you may notice discrepancies. This is expected because these platforms are fundamentally different systems that track and report data differently. **Important:** Direct comparisons between Permutive and GA are only valid for **pageviews**. Never compare user counts between platforms, as different identity models make this comparison invalid. ## Fundamental Differences Permutive and GA use different tracking mechanisms and apply different filtering rules. Here's a comparison: | Aspect | Google Analytics | Permutive | | ----------------- | -------------------------------- | ---------------------------------------- | | **Tracking** | Uses analytics.js | Uses Permutive SDK | | **Consent** | Depends on your GA setup | May rely on consent-by-token | | **Bot Traffic** | May include bots unless filtered | Automatically excludes known bot traffic | | **User Identity** | Cookie/session-based | First-party identity models | | **When It Fires** | At page load (if script loads) | When SDK loads AND consent is given | These differences mean that even on the same page, GA and Permutive may record different numbers of pageviews. Several factors can contribute to differences in pageview counts: * **SDK Loading Timing:** The Permutive SDK and analytics.js may not load at the same time or on the same pages * **Consent Models:** Especially on EEA/UK domains, consent requirements can significantly reduce trackable pageviews in Permutive while GA may still collect data * **Bot Filtering:** GA may include bot or spam traffic unless explicitly filtered, while Permutive applies stricter automatic filters * **AMP/FIA Traffic:** AMP and Facebook Instant Articles traffic may not be captured by Permutive unless explicitly implemented * **Deployment Coverage:** Permutive may not be deployed on all pages, domains, or subdomains where GA is present ## Comparing Pageviews Accurately To make an accurate comparison, you need to ensure a **like-for-like** scenario. This means: **Always compare pageviews, not users.** User counts cannot be compared between platforms because they use fundamentally different identity models. **Best practices for comparison:** 1. Use the same fixed time period for both platforms 2. Compare the same domains and subdomains 3. Account for consent differences (Permutive may only track consented users) 4. Consider traffic type differences (Web, AMP, FIA) 5. Check if bot filtering is applied consistently When preparing GA data for comparison, please provide: 1. **Fixed Timeframe:** Set specific start and end dates * Break down by month if possible * Day-by-day breakdown helps speed up analysis 2. **Bot Traffic Status:** Does your GA traffic include bot traffic? 3. **Domain Breakdown:** Split traffic by domain * Include subdomain breakdown if relevant 4. **Browser Split:** Break down by browser type (Safari, Firefox, Chrome, Other) 5. **Traffic Type:** Split by type (Web, AMP, FIA) 6. **Pageviews Only:** Share pageview counts, not unique user counts 7. **Data Format:** Provide data as Google Sheets, CSV, or Excel When preparing Permutive data for comparison, identify where you retrieved the numbers from: **If using Audience Insights:** * Which cohort(s) are you analyzing? * What dimension filters are applied (e.g., domain, device type, browser)? * What date range is selected? **If using Planning:** * Which plan are you referencing? * What audiences are included in the plan? * What date range does the plan cover? **Additional information needed:** * **Consent Status:** Is consent-by-token active on the domains being compared? ## Troubleshooting Discrepancies A common cause of discrepancies is incomplete deployment. Check: **Domains and Subdomains:** * Is Permutive active on all domains where GA is deployed? * Are subdomains like `forum.example.com` or `blogs.example.com` included? **Page Types:** * Is Permutive firing on all page types (articles, index pages, section pages)? * Some implementations may only track certain page types **Platforms:** * Do you have AMP implementation? * Is Facebook Instant Articles (FIA) configured? * Are mobile apps instrumented with Permutive? Consent requirements can significantly impact pageview counts: * **EEA/UK Regions:** Consent models often reduce trackable pageviews in Permutive more than GA * **Per-Domain Consent:** Consent settings may vary by domain, so compare on a per-domain basis * **Consent-by-Token:** If enabled, only users who have provided consent will be tracked GA may continue collecting data in scenarios where Permutive is blocked by consent requirements. This is a common source of discrepancies. Temporary issues can cause pageview gaps: * **Permutive Outages:** Check if there were any service disruptions during the comparison period * **Deployment Changes:** Was Permutive temporarily removed or modified? * **Tag Manager Issues:** If deployed via a tag manager, were there configuration changes? Historical deployment data can often be investigated using monitoring tools. # Guides Source: https://docs.permutive.com/guides/introduction Step-by-step instructions to help you get the most out of Permutive Guides provide practical, task-oriented instructions for using Permutive's platform. Whether you're building audiences, configuring identity resolution, or setting up clean room collaborations, these guides walk you through each step. Build and manage custom, contextual, and modeled audiences. Configure identifiers and analyze audience overlap. Set up sources, imports, events, and routing. Enable privacy-safe data collaboration with advertisers. Configure Prebid and Google curated deals. Plan campaigns and analyze audience insights. # Building Audiences Source: https://docs.permutive.com/guides/planning/building-audiences Combine cohorts with boolean logic to build targeted audiences within plans ## Overview Audiences are combinations of cohorts connected using boolean logic to define target users for campaign targeting. Within a plan, you can build audiences that combine up to 15 cohorts across 3 groups, with real-time metrics showing unique users and pageviews as you build. This guide covers how to find relevant cohorts, combine them using boolean logic, and optimize audience reach. ## Creating a New Audience Navigate to **Planning** and select the plan where you want to create an audience. Click **Create a new audience** to open the audience builder. Use one of the cohort discovery methods (see below) to find relevant cohorts for your campaign. Click the **+** button next to a cohort or use the **Add cohort** button on recommendation cards to add cohorts to your audience. Adjust the boolean logic (ANY, ALL, NONE) within groups and between groups to define your targeting criteria. Check the real-time Unique Users and Pageviews estimates to ensure your audience meets campaign requirements. Give your audience a descriptive name (usually reflecting the targeting requirements from the brief) and save it. ## Finding Cohorts There are four ways to discover cohorts when building an audience: ### Recommendations Tab (AI Required) The Recommendations tab shows AI-powered suggestions for cohorts likely to be relevant based on: * The advertiser and plan information you provided * Likely targeting criteria for the advertiser * Trending cohorts with recent user growth Recommendations require AI products to be enabled for your workspace. ### AI Search (AI Required) Use natural language to describe the audience you're trying to build. AI Search finds relevant and adjacent cohorts even if you don't know your exact taxonomy or cohort naming conventions. Example searches: * "Users interested in luxury automobiles" * "Health and fitness enthusiasts" * "Parents with young children" AI Search requires AI products to be enabled for your workspace. ### Cohort Library The Cohort Library provides a complete list of all your cohorts with: * **Search**: Find cohorts by name * **Filter by tags**: Filter cohorts by taxonomy tags * **Filter by type**: Filter by cohort type (e.g., behavioral, contextual) Use the Cohort Library when you know the specific cohorts you want or need to browse your full taxonomy. ### Affinities Tab The Affinities tab helps you expand reach by finding cohorts that overlap with or index highly to a seed cohort. Choose a cohort from the dropdown that most closely matches your targeting requirement. See all cohorts ranked by overlap or index with your seed cohort. Add cohorts with high overlap or index to expand your audience while maintaining relevance. ## Boolean Logic ### Within Groups Each cohort group can use one of three operators: | Operator | Logic | Description | | :------- | :---- | :-------------------------------------------------------- | | ANY | OR | Users must be in at least one of the cohorts in the group | | ALL | AND | Users must be in all of the cohorts in the group | | NONE | NAND | Users must not be in any of the cohorts in the group | NONE logic is only available for groups after the first group. The first group cannot use NONE logic. ### Between Groups Groups are combined using: * **OR**: Users meeting criteria of either group * **AND**: Users meeting criteria of both groups Logic is evaluated from the top group to the bottom group. ### Example Combinations **Targeting car enthusiasts who are also in-market:** * Group 1 (ANY): Auto Enthusiasts, Car Reviews Readers * AND * Group 2 (ALL): In-Market Auto Buyers **Targeting parents, excluding certain demographics:** * Group 1 (ANY): Parents with Kids 0-5, Parents with Kids 6-12 * AND * Group 2 (NONE): Students, College Age ## Managing Cohorts in the Audience Builder ### Limits * **Maximum 3 groups** per audience * **Maximum 5 cohorts** per group * **Maximum 15 cohorts** total per audience ### Reorganizing Cohorts Cohorts added from the Search or Affinities tabs are automatically added to the latest group. To move cohorts between groups: 1. Click and hold on the cohort you want to move 2. Drag it to the desired group 3. Release to drop the cohort in its new position ## Real-time Metrics As you build your audience, the following metrics update in real-time: | Metric | Description | | :---------------- | :-------------------------------------------------------------------- | | Est. Unique Users | Estimated distinct users meeting the audience criteria (last 30 days) | | Est. Pageviews | Estimated pageviews from audience users (last 30 days) | Metrics are shown for: * Each individual group * The total audience Use these real-time metrics to assess whether your audience has sufficient scale for the campaign requirements before saving. ## Using the Analyze Tab The Analyze tab provides visualizations to understand audience composition: ### Browser Breakdown Shows the percentage of audience unique users across different browsers compared to your total user base. Identify browsers where the audience is over or under-represented. ### Domain Breakdown Shows the percentage of audience unique users across domains compared to your total user base. Useful for understanding where your audience is most active. ### Cohort Uniqueness Shows how each cohort contributes to the overall audience: * **Pink bar**: Users unique to that cohort (not in other cohorts) * **Grey bar**: Users also included in other cohorts in the audience Use this to identify which cohorts provide the most incremental reach. When using ALL (AND) logic, the Cohort Uniqueness chart won't show incremental users since users must be in all cohorts to qualify. ## Viewing Targeting Logic After building your audience, access the targeting logic panel to see: * **Cohorts List**: Full cohort names with their cohort IDs * **Boolean Expression**: Standard representation of your audience logic * **GAM Expression**: Copy-ready expression for Google Ad Manager line items (GAM 360 only) Use these expressions when activating the audience in your ad server. ## Best Practices **Start broad, then refine** — Begin with ANY (OR) logic to maximize reach, then add groups with AND logic to narrow the audience if needed. **Use the Analyze tab** — Check the Cohort Uniqueness visualization to understand which cohorts add incremental reach vs. overlap with existing cohorts. **Name audiences descriptively** — Use the targeting requirements from the brief as the audience name for easy reference. **Consider both reach and relevance** — Balance audience size with targeting precision. An audience that's too narrow may not deliver sufficient scale. ## What's Next After building an audience, explore Plan Insights to analyze your audience in detail: Analyze audience reach, overlap, and composition with Plan Insights # Creating a Plan Source: https://docs.permutive.com/guides/planning/creating-a-plan Learn how to create and edit plans for campaign opportunities ## Overview Plans are containers for campaign opportunity information in Permutive's Planning product. Each plan stores advertiser details, RFP deadlines, and one or more audiences that you build to respond to campaign briefs. This guide walks you through creating a new plan, configuring plan fields, editing existing plans, and organizing your plans list. ## Creating a New Plan In the Permutive Dashboard, go to **Planning** to access your plans list. Click the **Create Plan** button to open the plan creation form. Fill in the required plan information: * **Plan Name**: A descriptive name for the campaign opportunity (e.g., "Q1 Auto Campaign - BMW") * **Advertiser**: The advertiser associated with the campaign * **RFP Deadline**: The deadline for responding to the brief Optionally, add additional details to help organize and filter the plan: * **Agency**: The agency managing the campaign * **Budget**: The campaign budget * **Countries**: Country filters to apply to all metrics and visualizations * **Domains**: Domain filters to apply to all metrics and visualizations Click **Save** to create the plan. You'll be taken to the plan details view where you can start building audiences. Several of the fields for creating a Plan are also used as context for AI recommendations and search. You will see improved recommendations with more descriptive campaign names, advertiser information etc. ## Plan Fields Reference | Field | Required | Description | | :----------- | :------- | :----------------------------------------------------------- | | Plan Name | Yes | A descriptive name for the campaign opportunity | | Advertiser | Yes | The advertiser associated with the campaign | | RFP Deadline | Yes | The deadline for responding to the brief | | Agency | No | The agency managing the campaign | | Budget | No | The campaign budget | | Countries | No | Country filters that apply to all metrics and visualizations | | Domains | No | Domain filters that apply to all metrics and visualizations | Country and Domain filters set at the plan level apply across all metrics, visualizations, and Plan Insights for audiences within that plan. Use these filters when responding to briefs with specific geographic or site requirements. ## Editing an Existing Plan From the plans list, click on the plan you want to edit. Click the **Edit** button or pencil icon to modify the plan information. Make your changes to any of the plan fields (name, advertiser, deadline, etc.). Click **Save** to apply your updates. ## Organizing Your Plans ### Sorting Plans You can sort your plans list by the following columns: * **Name**: Alphabetical order by plan name * **Advertiser**: Alphabetical order by advertiser name * **RFP Deadline**: Chronological order by deadline date * **Created Date**: Chronological order by when the plan was created Click on a column header to sort by that field. Click again to reverse the sort order. ### Finding Plans Use the search functionality to quickly find plans by: * Plan name * Advertiser name ## Best Practices **Use descriptive plan names** — Include the advertiser, campaign type, and time period in your plan name for easy identification (e.g., "Nike - Summer Running Campaign - Q2 2025"). **Set accurate RFP deadlines** — This helps prioritize which plans need attention and enables sorting by deadline to focus on urgent briefs. **Apply filters early** — If the brief specifies geographic or domain requirements, set the Country and Domain filters when creating the plan so all subsequent audience metrics reflect these constraints. **Keep advertiser names consistent** — Use consistent naming for advertisers across plans to make searching and filtering easier. ## What's Next After creating a plan, you're ready to build audiences: Learn how to combine cohorts with boolean logic to build targeted audiences # Sharing Plans Source: https://docs.permutive.com/guides/planning/sharing-plans Share plans with colleagues via unique URLs ## Overview Plans can be shared with other users in your organization who have appropriate Permutive permissions. Sharing enables collaboration on campaign briefs, allowing team members to view plan details, audiences, and insights. This guide covers how to share plans and what shared users can access. ## Sharing a Plan Navigate to **Planning** and select the plan you want to share. Click the **Share** icon next to the plan name. A text box will appear with a unique URL for the plan. Click to copy this URL. Share the copied URL with colleagues via email, Slack, or other communication channels. ## What Shared Users Can Access When colleagues open a shared plan URL, they can: * View all plan details (name, advertiser, deadline, etc.) * View all audiences within the plan * Access Plan Insights for each audience * View audience configurations and targeting logic Shared users must have appropriate Permutive permissions to access the Planning product. Users without Planning access will not be able to view the shared plan. ## Permissions and Access ### Who Can Share Plans Any user with access to the Planning product can share plans they can view. ### Who Can View Shared Plans Shared plans are accessible to users who: * Have Permutive dashboard access * Have permissions to access the Planning product * Are part of the same organization/workspace ### Enterprise Workspace Considerations In Enterprise Workspace (EnWo) configurations: | Workspace Level | Can View | | :--------------------- | :-------------------------------------------------------------------------------------------- | | Parent workspace users | Plans created at parent level with parent-level cohorts and metrics across all children | | Child workspace users | Plans created at child level with parent + child cohorts and metrics for that child's domains | ## Best Practices **Include context when sharing** — When sending the plan URL, include a brief message explaining what feedback or input you need from colleagues. **Share early for collaboration** — Share plans with relevant team members early in the brief response process to gather input on audience strategies. **Verify recipient access** — Confirm that recipients have Planning access before sharing URLs to avoid confusion. ## Frequently Asked Questions Currently, shared plans are accessible to anyone in your organization with Planning permissions. You cannot restrict viewing to specific users. Access permissions for editing shared plans depend on your organization's Permutive permission configuration. Contact your administrator to understand editing permissions. Share URLs do not expire. The URL remains valid as long as the plan exists. No, shared plans are only accessible to users within your organization who have Permutive dashboard access and Planning permissions. ## Related Guides Learn how to create plans for campaign opportunities Combine cohorts with boolean logic to build targeted audiences # Using Plan Insights Source: https://docs.permutive.com/guides/planning/using-plan-insights Analyze audience reach, overlap, and composition with Plan Insights ## Overview Plan Insights provide detailed analytics for audiences within your plans. After saving an audience, you can access six insight tabs that help you understand reach, engagement, overlap with other cohorts, device distribution, and domain breakdown. This guide covers how to access Plan Insights and interpret the data in each tab. ## Accessing Plan Insights Navigate to **Planning** and select the plan containing the audience you want to analyze. Click on the audience to view its details. Click on the **Insights** tab or scroll to the Plan Insights section. If your plan has Country or Domain filters configured, these will automatically apply to all insights. You can also adjust filters as needed. ## Overview Tab The Overview tab provides reach and engagement metrics for your audience. ### Reach Metrics | Metric | Description | | :------------------------ | :----------------------------------------------------------------------------------- | | Unique Users | Estimated unique users in the last 30 days | | Avg. Weekly UU | Average unique users in a rolling 7-day window across the last 30 days | | Avg. Daily UU | Average daily unique users in the last 30 days | | Avg. Active Days Per User | Average number of days users in this audience visited your properties (last 30 days) | ### Engagement Metrics | Metric | Description | | :-------- | :------------------------------------------------------- | | Pageviews | Estimated pageviews for the audience in the last 30 days | | PV/UU | Average pageviews per unique user in the last 30 days | ### Comparison Metrics Each metric box shows a comparison figure: * For Unique Users metrics: Shows the percentage of the audience compared with total unique users for the selected filters (e.g., "50% of total") * For PV/UU: Shows how the audience compares to the average user, represented as a multiple (e.g., "1.5x" means 50% higher engagement than average) ### Audience Size Chart The chart at the bottom shows daily unique users or pageviews over the last 30 days: * Toggle between **UU** (Unique Users) and **PV** (Pageviews) using the switch in the top right * Export the chart data using the **Export data** button ## Overlap Tab The Overlap tab shows how your audience overlaps with cohorts organized by tags. ### Creating an Overlap Report Click the **Comparison tags** dropdown and select one or more tags. Each tag represents a group of cohorts. Scroll down to see a table for each selected tag, showing all cohorts within that tag. Click column headers to sort by any metric. Hover over the **i** icons for metric definitions. ### Overlap Metrics | Column | Description | | :---------------- | :----------------------------------------------------------------- | | Comparison Cohort | The cohort being compared | | Total Uniques | Total unique users in the comparison cohort | | Overlap Uniques | Users in both the audience and the comparison cohort | | Overlap | Percentage of audience users who are also in the comparison cohort | | Index | Audience overlap vs. total user overlap with the cohort | ### Understanding Index The Index shows how your audience compares to the total user base: * **Index > 1**: Your audience over-indexes for this cohort (higher affinity than average users) * **Index = 1**: Your audience has average affinity for this cohort * **Index \< 1**: Your audience under-indexes for this cohort (lower affinity than average users) High-index cohorts represent affinities that make your audience unique compared to the general user base. Use these insights when positioning audiences to advertisers. ### Exporting Overlap Data Click **Export Data** to the right of the Comparison tags dropdown to download the overlap data as a CSV file. ## Devices Tab The Devices tab shows audience distribution across devices, platforms, and browsers. ### Charts For each category, two charts are available: 1. **Distribution Chart**: Percentage breakdown by unique users or pageviews * Pink bars: Your audience * Grey bars: Total user base 2. **Engagement Chart**: Pageviews per unique user for each device/platform/browser ### Exporting Device Data Click **Export data** on any chart to download a CSV with: * Pageviews * Unique users * Pageviews per unique user For both the audience and total user base. ## Domains Tab The Domains tab shows how your audience is distributed across your properties. ### Available Metrics Toggle between three views: | Metric | Description | | :---------------- | :---------------------------------------------------------- | | Unique Users (UU) | Percentage of unique users for each domain | | Pageviews (PV) | Percentage of pageviews for each domain | | PV/UU | Engagement rate (pageviews per unique user) for each domain | ### Using Domain Insights Domain insights are useful for: * Understanding where your audience is most active * Demonstrating value of targeting across additional sites/apps when responding to briefs * Identifying high-engagement domains for the audience Hover over each bar in the chart to see total values, not just percentages. ### Exporting Domain Data Click **Export data** to download a CSV with pageviews, unique users, and PV/UU metrics for all domains. ## Categories Tab Requires Watson to be enabled for your workspace. The Categories tab shows content category insights based on Watson categorization. This helps understand what content topics your audience engages with. ## Activity Tab Requires Watson to be enabled for your workspace. The Activity tab shows activity-level insights based on Watson activity detection. This helps understand user behaviors and activities within your audience. ## Applying Filters ### Plan-Level Filters If your plan has Country or Domain filters configured, these automatically apply to all Plan Insights data. To change filters: 1. Navigate to the plan settings 2. Update the Country or Domain filters 3. Plan Insights will recalculate with the new filters ### Why Use Filters? Filters are useful when: * The brief specifies geographic requirements * You want to show reach for specific domains only * You need to exclude certain markets from the analysis ## Best Practices **Use tags strategically in Overlap** — Select comparison tags that align with the advertiser's targeting interests to demonstrate audience relevance. **Export data for proposals** — Use the export functionality to include Plan Insights data in RFP responses and media plans. **Check engagement, not just reach** — A smaller audience with higher PV/UU engagement may be more valuable than a larger audience with lower engagement. **Compare to total users** — The comparison metrics help contextualize your audience against the full user base. ## What's Next Share your plan with colleagues to collaborate on the RFP response: Learn how to share plans with colleagues via unique URLs # GAM API Usage Source: https://docs.permutive.com/guides/platform-guides/gam-api-usage How Permutive uses the Google Ad Manager reporting API for campaign optimization and insights ## Overview Google Ad Manager (GAM) is the most common ad server amongst top publishers. Permutive uses the GAM reporting API to provide campaign metadata, metrics, and recommendations across our Planning and Optimization products. This guide explains how we use GAM data and the permissions required to support this integration. ## Why Permutive Uses the GAM Reporting API When analyzing campaigns to identify optimization opportunities or share insights reports with advertisers, having context around campaign delivery and performance is critical. While Permutive's value lies in surfacing additional insights about users who engaged with a campaign based on first-party data, the ability to see actual impressions, clicks, and other metrics is essential for optimization use cases. ### Key Benefits **Improved user experience** — Ingesting order names, flight dates, and other attributes allows Permutive to display campaign information directly in the UI without manual data entry. **Campaign context** — View actual delivery and performance metrics within the Permutive UI alongside audience insights. **Cohort targeting analysis** — Understand which cohorts are actually targeted for a campaign and how they are performing. This is a valuable data point for users and an essential component for informing optimization recommendations. **Non-targeted cohort potential** — Analyze performance for all cohorts present when an impression was delivered, even if they weren't directly targeted. This powers the AI recommendations in Optimization. ## Data Collection and Usage Permutive has developed a comprehensive automated process for extracting reporting from the GAM API across customers. The ability to automate data ingestion and visualization is a valuable upgrade for customers, as this level of reporting would otherwise be highly time-consuming across all campaigns. ### What We Collect Due to different restrictions and limitations around how certain report dimensions and metrics can be combined, we pull multiple reports via the API to support various product use cases: | Report Type | Description | Use Case | | :------------------- | :--------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------- | | Order Information | Key metadata for orders and line items with associated aggregated metrics | Displaying order information in the UI and providing accurate, de-duplicated KPIs | | Targeted Cohorts | Delivery (impressions) and performance metrics (clicks, viewability) broken down by targeted cohorts | Understanding the target audience and displaying cohort performance data | | Non-targeted Cohorts | Impressions and performance metrics for cohorts not explicitly targeted | Analyzing potential performance of cohorts for optimization recommendations | ### Data Ingestion Process * Data is ingested at **daily intervals** for the previous completed day * **Maximum 24-hour delay** for data to appear in Permutive * No personal information is stored * All data is stored in EU BigQuery ### Reconciliation and Accuracy * Automated alerts when a report fails or there are gaps in the data * Roll-up metrics are reconciled against day-level reports to ensure accuracy ## Permissions Required The permissions outlined below are required to support all current integration use cases, including cohort activation and campaign optimization. ### Permission Configuration #### Access the Interface | Permission | Required | | :---------- | :------- | | Admin | No | | Ad Exchange | Yes | | Billing | No | | Delivery | Yes | | Overview | Yes | #### Define and Deliver Ads | Permission | Required | Notes | | :----------------------------------- | :------- | :------------------------------------------------------------ | | Ad units, placements, and key-values | Yes | All Permutive-related key-values need to be set as reportable | | Audience segments | No | | | Audience segments (third-party) | No | | | Orders and line items | Yes | If Teams are used, Permutive needs to be assigned accordingly | | Yield groups | Yes | | #### Policy and Protections | Permission | Required | | :------------------------------- | :------- | | Privacy and messaging | No | | EU user consent and CCPA setting | No | #### Reports | Permission | Required | | :-------------------- | :------- | | Ad exchange reports | Yes | | Ad manager reports | Yes | | Opportunities reports | No | | Teams reports | No | #### Sales | Permission | Required | | :-------------------- | :------- | | Sales proposals | No | | Buyer sales proposals | No | #### User Network Settings | Permission | Required | | :------------- | :------- | | Change history | Yes | #### Video Solutions | Permission | Required | | :--------- | :------- | | Ad rules | No | ### Teams Configuration In addition to the permissions above, for customers using the Teams functionality for managing user access, you must ensure that Permutive has access to all ad units and other inventory types. If Teams are used in GAM, the Permutive service account ([dfp@permutive.com](mailto:dfp@permutive.com)) must be assigned to appropriate teams to access order and line item data. ## How to Check Permissions ### Permission Check Endpoint Permutive can run an automated permission check that tests whether the service account has sufficient access to run line-item level reports. This check validates if there are sufficient permissions to extract data from the GAM report service. The permission check: 1. Runs test reports with different dimensions 2. Determines if line-item level reporting is possible 3. Identifies specific permission gaps if issues are found If line-item level reports return no impressions but other granularity levels work, it indicates insufficient permissions. ### Requesting a Permission Check Contact your Customer Success Manager (CSM) to request a permission check for your GAM network. They can verify: * Whether the Permutive service account has the required permissions * Specific permissions that may be missing * Teams configuration issues that could affect access ## Troubleshooting Permissions If campaigns are not appearing in Optimization despite having the integration set up: 1. Verify all required permissions are granted (see table above) 2. Check that Permutive-related key-values are set as reportable 3. If using Teams, ensure Permutive is assigned to the appropriate teams 4. Request a permission check from your Customer Success Manager (CSM) If order-level data appears but line item data is missing: 1. Verify the "Orders and line items" permission is granted 2. Check Teams configuration if applicable 3. Ensure the Permutive service account has access to all relevant ad units If you have multiple GAM network codes: 1. Each network requires separate permission configuration 2. Permissions must be granted in each GAM network individually 3. Some campaigns may appear for one network but not another if permissions differ If data appears stale or is not updating: 1. Data is ingested daily, so expect up to 24-hour delay 2. Check if there are any reported outages or ingestion failures 3. Contact support if data hasn't updated for more than 48 hours ## Related Documentation * [Optimization Product Documentation](/products/workflows-insights/optimization) — Learn how to use campaign optimization features * [Google Ad Manager Integration](/integrations/advertising/ad-servers/google-ad-manager) — Set up the GAM integration for cohort activation * [Google Support Documentation](https://support.google.com/admanager/answer/9154293) — Official GAM permissions documentation # Creating Contextual Cohorts Source: https://docs.permutive.com/guides/signals/cohorts/contextual/creating-contextual-cohorts How to build audience segments based on page content in Permutive ## Overview This guide walks you through creating a Contextual Cohort in the Permutive Dashboard. Contextual Cohorts enable you to target ads based on page content without processing user data. **Prerequisites:** * Access to the Permutive Dashboard with cohort creation permissions * Contextual Cohorts enabled in your account * For Affinity Cohorts: Custom Cohorts created with sufficient consented user engagement data * Contextual API implemented on your website OR Permutive SDK deployed (the SDK can segment contextual cohorts automatically without direct API implementation) ## Steps In the Permutive Dashboard, go to *Contextual* and click *+ Add Cohort*. Give your cohort a descriptive name and optional description. Choose from: * **Content Classifications**: Use NLP-generated classifications * **Page Properties**: Use editorial tags or custom metadata * **Affinity Cohort**: Target content with high engagement from a specific Custom Cohort * **Keyword Explorer**: Get AI-powered keyword recommendations for interest-based targeting Based on the predicate type (see sections below for detailed instructions). Use *+AND* or *+OR* to add additional conditions and build complex logic. Preview the estimated pageview reach (if available). Click *Save* to create the cohort. **Tip**: While detailed names and descriptions are optional, providing clear, descriptive names and detailed descriptions will enhance your experience when using Permutive's AI products. AI-powered features can better understand and recommend cohorts when they have meaningful context about their purpose and targeting logic. ## Predicate Types Content Classifications allow you to target based on automated NLP analysis of your content. To use Content Classifications in a cohort: 1. Select *Content Classifications* as your predicate type 2. Select a **Dimension Type**: * **Categories**: Topic classifications (requires taxonomy selection) * **Concepts**: Key themes * **Entities**: Named entities * **Keywords**: Important terms * **Sentiment**: Emotional tone * **Emotion**: Specific emotions 3. For **Categories**, select the **Taxonomy** (e.g., IAB 2.0, IAB 3.0, BVDW) 4. Choose an **Operator** (e.g., "is", "contains") 5. Enter or select your target **Value** Using content classifications **Example**: Target positive soccer content: * Dimension: Sentiment * Operator: is * Value: positive * AND Category (IAB 2.0): Sports > Soccer Page Properties allow you to target based on editorial metadata and custom content attributes. To use Page Properties in a cohort: 1. Select *Page Properties* as your predicate type 2. Choose a property from the dropdown (e.g., `client.section`, `client.author`, custom properties) 3. Select an operator (e.g., "is", "contains", "is not") 4. Enter the target value Using page properties **Example**: Target articles in the "Finance" section with a specific tag: * Property: `client.section` * Operator: is * Value: Finance * AND Property: `client.tags` * Operator: contains * Value: investment **Available Page Properties** The Page Properties dropdown includes custom Pageview properties you've defined and standard client properties. If you are implementing the Contextual API directly, you must pass these properties to the API for them to be available for targeting. If the SDK is making the call, it will automatically attach page properties. Note that enriched properties like ISP and Geo data are not available in Page Properties for Contextual Cohorts. Affinity Cohorts enable you to target content that shows high engagement from a specific Custom Cohort. To use Affinity Cohorts: 1. Select *Affinity Cohort* as your predicate type 2. Choose a **Custom Cohort** from the dropdown 3. Select an **Operator** (e.g., "is greater than") 4. Set an **Affinity Score threshold** Using affinity cohorts **Example**: Target content with 2x or greater engagement from "Sports Enthusiasts": * Custom Cohort: Sports Enthusiasts * Operator: is greater than * Value: 200 **Affinity Cohort Restrictions** You can select any Custom Cohort built from first-party behavioral data. However, you **cannot** select: * Modeled cohorts * Standard cohorts * Cohorts using third-party data **Recommended Starting Threshold**: We generally recommend starting with an affinity score threshold greater than 200 for a good balance of relevance and scale. ## Combining Multiple Conditions Contextual Cohorts support complex logic by combining multiple predicates: * Use *+AND* to require all conditions to be met * Use *+OR* to create alternative paths for targeting **Example**: Target finance content OR content with high affinity to investors: * Content Classification: Categories (IAB 2.0) is "Finance" * OR Affinity Cohort: Investor Cohort is greater than 150 **Example**: Target positive sports articles about soccer: * Content Classification: Categories (IAB 2.0) is "Sports > Soccer" * AND Content Classification: Sentiment is "positive" * AND Page Properties: client.section is "Sports" ## Next Steps Get AI-powered keyword recommendations Return to product overview # Custom Classifications via Webhook Source: https://docs.permutive.com/guides/signals/cohorts/contextual/custom-classifications-via-webhook How to bring your own content classification data into Permutive using the Webhook (Custom) provider ## Overview The Webhook (Custom) provider lets you connect Permutive to your own content classification system. Permutive calls an HTTPS endpoint you control to retrieve classifications and taxonomy definitions, giving you full flexibility to use an in-house or third-party classification system that is not available as a native provider. **Prerequisites:** * Access to the Permutive Dashboard with admin permissions * A publicly reachable HTTPS endpoint that implements the two request types described below * The Webhook (Custom) provider enabled for your workspace (contact your Customer Success Manager) ## How It Works Permutive calls your endpoint with two types of POST request: * **Classifications request** — sent each time Permutive needs to classify a URL. Your endpoint returns the classification results for that URL. * **Taxonomies request** — sent to retrieve the structure of any custom taxonomies your classifications reference. Only required if you use custom (non-standard) taxonomies. Both requests are sent to the same endpoint URL you configure in the dashboard. ## Enabling and Configuring the Provider In the Permutive Dashboard, go to *Contextual > Catalog*. Find the Webhook (Custom) provider in the catalog. If it is not visible, contact your Customer Success Manager to have it enabled for your workspace. Toggle the provider on to enable it. Set the following provider-specific fields: | Setting | Description | | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Endpoint** | The URL Permutive will call to request classifications and taxonomies. Must be a publicly reachable HTTPS URL. | | **Standard Taxonomies** | The standard taxonomies your endpoint may return in classification responses. Select any combination of IAB 2.0, IAB 2.2, and IAB 3.0. Leave empty if you use only custom taxonomies. | Save the provider settings. Permutive will begin calling your endpoint when pages are classified. **Optimizing quota usage**: Use the Selective Classifications Threshold in the provider settings to restrict classification calls to high-traffic URLs. This reduces unnecessary calls to your endpoint and focuses your classifications where they have the most impact. ## Endpoint Contract — Classification Requests When Permutive needs to classify a URL, it sends the following POST request to your endpoint: ```json theme={"dark"} { "type": "classify", "url": "http://example.com" } ``` Your endpoint must respond with a JSON object in this format: ```json theme={"dark"} { "classifications": [ { "value": "201", "type": "categories", "confidence": 0.72, "taxonomy": "iab_2.0" } ] } ``` ### Response Fields | Field | Type | Required | Description | | :------------------------------ | :--------------------- | :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `classifications` | Array of objects | Yes | List containing all classifications for the current URL. | | `classifications[#].value` | String | Yes | The classification value. If the type is `categories` and you use a standard taxonomy, this must exactly match the IAB category ID. | | `classifications[#].type` | String | Yes | Must be one of: `categories`, `keywords`, `entities`, `sentiment`, `emotion`, `concepts`. | | `classifications[#].confidence` | Number between 0 and 1 | No | Include this if you have a confidence rating for your classification. | | `classifications[#].taxonomy` | String | Only for `categories` | Only include if the type is `categories`. If you selected Standard Taxonomies in the dashboard, this must match `iab_2.0`, `iab_2.2`, or `iab_3.0`. Otherwise it should match the ID of your custom taxonomy. | **Supported dimension types**: The Webhook provider supports all dimension types — `categories`, `keywords`, `entities`, `concepts`, `sentiment`, and `emotion` — but the types actually available in Permutive depend on what your endpoint returns. ## Endpoint Contract — Taxonomy Requests When Permutive encounters a `taxonomy` value in a classification response that does not match a standard taxonomy, it calls your endpoint to retrieve the taxonomy definition: ```json theme={"dark"} { "type": "taxonomies" } ``` Your endpoint must respond with an array of taxonomy objects. If you do not use any custom taxonomies, return an empty array (`[]`). ```json theme={"dark"} [ { "id": "my_custom_taxonomy", "name": "My Custom Taxonomy", "url": "http://example.com", "values": [ { "id": "10", "name": "Books", "parent": null }, { "id": "123", "name": "Comic Books", "parent": "10" } ] } ] ``` ### Response Fields | Field | Type | Required | Description | | :-------------- | :--------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------- | | `id` | String | Yes | A unique identifier for your taxonomy. This must match the `taxonomy` value you return in classification responses for categories. | | `name` | String | Yes | Display name of your taxonomy. | | `url` | String | No | Optional; a URL with more information on your taxonomy. | | `values` | Array of objects | Yes | The list of entries in your taxonomy. | | `values.id` | String | Yes | The ID of an entry in your taxonomy. This must match the `value` you return for categories. | | `values.name` | String | Yes | The display name of the category — this is what will be shown in the dashboard. | | `values.parent` | String | No | Optional; if this is a sub-category, the `id` of the parent category. | Your endpoint can return classifications that reference both standard and custom taxonomies in the same response. For example: ```json theme={"dark"} { "classifications": [ { "value": "201", "type": "categories", "confidence": 0.85, "taxonomy": "iab_2.0" }, { "value": "123", "type": "categories", "confidence": 0.70, "taxonomy": "my_custom_taxonomy" }, { "value": "sport", "type": "keywords", "confidence": 0.90 } ] } ``` In this case, ensure that `iab_2.0` is listed under Standard Taxonomies in the dashboard configuration, and that `my_custom_taxonomy` is returned by your taxonomies endpoint with a matching `id`. ## Custom Classifications You can use the Webhook provider to import custom content classifications into Permutive. If you need to discuss your classification requirements or explore alternative approaches, contact your Customer Success Manager. ## Next Steps Configure general provider settings such as domains and quota Test how your webhook endpoint classifies content Build audience segments using your custom classifications Return to product overview # Activating Contextual Cohorts in Google Ad Manager Source: https://docs.permutive.com/guides/signals/cohorts/contextual/deploying-to-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. **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 ## Configure GAM Key-Values Before implementing the code, configure a key-value in Google Ad Manager. In Google Ad Manager, navigate to **Inventory > Key-Values** and click **New key-value**. Configure the key with these settings: * **Name**: `prmtvctx` * **Display name**: Permutive Contextual * **Value type**: Predefined * **Report on values**: Include values in reporting ## 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. Add this script to the `` 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. ```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. 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: ```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 } ``` 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`. 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") ``` An annotation processor such as kapt is required for Moshi codegen. 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` 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. ```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?, val url: String ) @JsonClass(generateAdapter = true) data class ContextualResponse( val cohorts: List?, val gam: List?, val xandr: List? ) interface PermutiveContextualApi { @POST("https://api.permutive.com/ctx/v1/segment") fun getContextualCohorts( @Query("k") apiKey: String, @Body body: ContextualBody ): Call } private const val PERMUTIVE_API_KEY = $API_KEY private const val CONTEXTUAL_TARGETING_KEY = "prmtvctx" class ContextualTargeting( private val customProperties: Map?, // 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) -> 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 { override fun onResponse( call: Call, response: Response ) { val cohorts = response.body()?.gam ?: emptyList() createContextualAdRequest(cohorts, onAdRequestCreated) } override fun onFailure( call: Call, t: Throwable ) { // Result.Failure passed to callback on failure onAdRequestCreated(Result.failure(t)) } } ) } private fun createContextualAdRequest( cohorts: List, onAdRequestCreated: (Result) -> 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?, url: String? = null, referrer: String? = null, title: String? = null, domain: String? = null ): Map { val client = buildMap { 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 } ) } } } ``` **Implementation timing**: Add the script as early as possible in the page `` to ensure the Contextual API call completes before the first ad request is made. ## Testing After implementing the code, verify that contextual targeting is working correctly: 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. Inspect the ad request in your browser's developer tools to confirm that the `prmtvctx` key-value is present with the expected cohort codes. Share a testing link with [Technical Services](mailto:technical-services@permutive.com) so we can help validate the deployment before releasing to production. ## Troubleshooting 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 Check that: * The API call completes before the ad request is made * The key-value `prmtvctx` is correctly configured in your ad server 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. ## Next Steps Full API documentation for the Contextual API Build audience segments based on page content Return to product overview # Enabling Classification Providers Source: https://docs.permutive.com/guides/signals/cohorts/contextual/enabling-classification-providers How to enable and configure content classification providers for Contextual Cohorts ## Overview This guide walks you through enabling content classification providers in Permutive. Classification providers power Content Classifications in Contextual Cohorts by analyzing your page content using NLP. **Prerequisites:** * Access to the Permutive Dashboard with admin permissions * A Permutive contract that includes content classification OR your own API key for a supported provider ## Enabling a Provider Navigate to *Contextual > Catalog* in the Permutive Dashboard. Browse available providers in the catalog. **If the provider is part of your Permutive contract**: Contact your Customer Success Manager to activate it. **If you have a direct relationship with the provider**: Toggle "Use your own API key" and enter your credentials. Content classification provider catalog ## Configuring a Provider After enabling a provider, configure settings to optimize performance and quota usage: | Setting | Description | | :-------------------------------------- | :------------------------------------------------------------------------------- | | **Requested Types** | Specify which dimension types to request (categories, keywords, sentiment, etc.) | | **Domains** | Restrict classifications to specific domains to optimize quota usage | | **Quota** | Maximum number of classifications in a specified timeframe | | **Selective Classifications Threshold** | Set a minimum traffic threshold to classify only high-traffic URLs | Provider configuration settings **URL Parameter**: The URL Parameter setting is configured at the catalog level, not per provider. You can find this setting in the top-right corner of the *Contextual > Catalog* page. Use it to append a parameter for paywall access during classification across all providers. **Optimizing quota usage**: Use selective classification thresholds to focus your quota on high-traffic URLs that will have the most impact on targeting. ## Available Providers For a complete list of available classification providers, their supported dimensions, and current status, see [Contextual Cohorts — Available Providers](/products/signals/cohorts/contextual#available-providers). ## Next Steps Test how providers classify your content Return to product overview # Previewing Classifications Source: https://docs.permutive.com/guides/signals/cohorts/contextual/previewing-classifications How to preview content classifications before building Contextual Cohorts ## Overview This guide shows you how to preview how a classification provider analyzes your content. Previewing classifications helps you understand what data will be available for cohort targeting before you start building. **Prerequisites:** * Access to the Permutive Dashboard * Provider availability varies: some providers (such as IBM Watson and TextRazor) allow previewing without credentials, while others (such as OS Data Solutions) require the provider to be enabled first ## Steps Navigate to *Contextual > Catalog*. Hover over a provider tile and click *Preview*. Enter a URL from your site and click *Classify*. Review the classification results, filtering by dimension type (categories, keywords, entities, concepts, sentiment, emotion). **Test multiple content types**: Preview classifications across different sections and content types on your site to understand the full range of targeting options available. ## Understanding Classification Results When you preview a URL, you'll see results organized by dimension type: | Dimension | Description | | :--------- | :--------------------------------------------------- | | Categories | Topic classifications (e.g., Sports > Soccer) | | Keywords | Important terms extracted from the content | | Entities | Named entities (people, places, organizations) | | Concepts | Abstract themes and ideas | | Sentiment | Overall emotional tone (positive, negative, neutral) | | Emotion | Specific emotions (joy, anger, sadness, etc.) | Each result includes a confidence score where applicable, helping you decide whether to set minimum confidence thresholds when building cohorts. ## Next Steps Build cohorts using classifications Return to product overview # Using Keyword Explorer Source: https://docs.permutive.com/guides/signals/cohorts/contextual/using-keyword-explorer How to use AI-powered keyword recommendations for building interest-based cohorts ## Overview Keyword Explorer uses machine learning to offer AI-powered keyword recommendations for building interest-based cohorts. By analyzing the similarities and connections between keywords, it suggests relevant terms that maximize both reach and relevance. **Prerequisites:** * Access to the Permutive Dashboard with cohort creation permissions * Keyword Explorer enabled for your workspace * Contextual API implemented on your website OR Permutive SDK deployed (the SDK can segment contextual cohorts without direct API implementation) ## Steps Select *Keyword Explorer* as your predicate type. A modal will open with the Keyword Explorer interface. Enter a **Topic** in the search bar and press Enter. Topics can be single words (e.g., "Fashion") or multiple words (e.g., "Tour de France"). Add multiple topics to narrow down to a specific concept (e.g., "cocktail" + "drink" vs "cocktail" + "dress"). Alternatively, you can input a prompt in the form of a targeting requirement (e.g., "Users interested in luxury travel"). Keyword Explorer will first transform the prompt into relevant topics and then show the results. Review the **Recommendations** table, which includes: * **Name**: Similar or adjacent keywords to your topic * **Property**: The Pageview properties where each keyword appears * **Relevance**: How closely related the keyword is to your topic (based on semantic similarity) * **Reach**: Incremental reach (accounts for overlap between already-selected keywords) Use **Filters** to refine recommendations: * **Properties**: Limit to specific Pageview properties * **Reach**: Set a minimum reach threshold * **Countries**: Filter by geographic location (based on `geo_info`) * **Domains**: Filter by specific domains (based on `client.domain` or `client.url`) Select the keywords you want to include by checking the boxes next to them. Review the **Total Reach** (deduplicated user count for your selection). Click *Add* to apply your keyword selections to the cohort. Using keyword explorer ## Example Build a Fashion cohort using Keyword Explorer: * Topic: "Fashion" * Select relevant recommendations: "style", "designer", "clothing", "trends", "runway" * Filter by Property: `client.title`, `client.tags` * Filter by Reach: Minimum 1,000 users * Total Reach: 45,000 users (deduplicated) **Keyword Explorer Settings** Keyword Explorer settings are configured at the organization level under *Settings > Integrations > Keyword Explorer*. You must be at the root workspace to access these settings. * **Minimum Reach**: Set a global minimum reach threshold for all recommendations * **Property Selection**: Choose which Pageview properties to include in the model training and recommendations * **Tokenize**: Enable for properties that require keyword extraction (like article titles), disable for properties that already contain individual keywords (like tags) ## Next Steps Learn the full cohort creation process Return to product overview # Bulk Activating Cohorts Source: https://docs.permutive.com/guides/signals/cohorts/custom/bulk-activating-cohorts How to request bulk activation of 30 or more cohorts ## Overview If you need to activate 30 or more cohorts at once, Permutive's [Technical Services](mailto:technical-services@permutive.com) team can process the activations in bulk using a CSV file you provide. This guide explains how to prepare the CSV and submit the request. **Prerequisites:** * 30 or more Custom or Lookalike cohorts that require activation. * The cohort ID (cohort code) for each cohort you want activated. * Destination-specific configuration values (e.g., network codes, advertiser IDs) ready for each activation. * Your workspace name and ID. **Limitations:** * Bulk activation is only supported for **Custom** and **Lookalike** cohorts. Contextual and Classification cohorts cannot be bulk activated. * A minimum of 30 cohorts is required per bulk activation request. * Each workspace requires a separate CSV file. ## Supported Destinations The following destinations are supported for bulk activation. Use the corresponding `activation_type` and `storage_key` column value in your CSV. | Destination | `activation_type` | `storage_key` | | :--------------------------------- | :---------------- | :------------------------------------- | | Google Ad Manager | `gam` | Leave blank | | DV360 | `dv360` | Leave blank | | Xandr / AppNexus | `appnexus` | Leave blank | | Nativo | `nativo` | Leave blank | | Facebook | `facebook` | Leave blank | | Magnite / Rubicon | `rubicon` | Leave blank | | PubMatic | `pubmatic` | Leave blank | | Trade Desk 1P | `trade_desk` | Leave blank | | Local Storage | `local_storage` | LocalStorage key you are activating to | | Index Exchange (via Local Storage) | `local_storage` | `_pindexs` | | Pinterest | `pinterest` | Leave blank | The [Index Exchange integration](https://docs.permutive.com/integrations/advertising/ssps/index-exchange) uses the Local Storage integration. In your CSV, set `activation_type` to `local_storage` and `storage_key` to `_pindexs`. Ensure the Local Storage integration is enabled in your Dashboard before submitting the request. ## Steps Collect the cohort ID (cohort code) for every cohort you want activated. You can find cohort codes in the *Custom Cohorts* list in the Permutive Dashboard — the code is displayed alongside each cohort's name. You will need to provide these alongside your CSV when contacting [Technical Services](mailto:technical-services@permutive.com). Both are available in your Permutive Dashboard workspace settings. Create a CSV file with the following header row, then add one row per cohort activation: ```csv theme={"dark"} "cohort_code","activation_type","ttd_secret","ttd_advertiserid","dfp_name","dfp_network_code","storage_key","dv360_seat","pinterest_tag_id","pinterest_tag_name" ``` See the [CSV Columns reference](#csv-columns) below for details on what to include in each field. Send the CSV file to [Technical Services](mailto:technical-services@permutive.com) along with your workspace name and workspace ID. Bulk activations are typically completed within two weeks of submission. ## CSV Columns | Column | Required | Description | | :------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------- | | `cohort_code` | Yes | The cohort ID of the cohort to activate. | | `activation_type` | Yes | The destination identifier. See [Supported Destinations](#supported-destinations) for the correct value. | | `ttd_secret` | No | Required for Trade Desk activations. Leave blank otherwise. | | `ttd_advertiserid` | No | Required for Trade Desk activations. Leave blank otherwise. | | `dfp_name` | No | Required for Google Ad Manager activations. Leave blank otherwise. | | `dfp_network_code` | No | Required for Google Ad Manager activations. Leave blank otherwise. | | `storage_key` | No | Required for Local Storage and Index Exchange activations. For Index Exchange, use `_pindexs`. Leave blank otherwise. | | `dv360_seat` | No | Required for DV360 activations. Leave blank otherwise. | | `pinterest_tag_id` | No | Required for Pinterest activations. The Pinterest pixel Tag ID associated with your Pinterest destination. Leave blank otherwise. | | `pinterest_tag_name` | No | Required for Pinterest activations. The human-readable name of your Pinterest destination. Leave blank otherwise. | Leave optional columns present in the header but with an empty value for rows that do not use them. Do not remove columns from the CSV header. ## Example The following example shows a single cohort (ID `123456`) activated to Pinterest, Facebook, and Index Exchange (via Local Storage): ```csv theme={"dark"} "cohort_code","activation_type","ttd_secret","ttd_advertiserid","dfp_name","dfp_network_code","storage_key","dv360_seat","pinterest_tag_id","pinterest_tag_name" 123456,"pinterest",,,,,,,"1234567890","Example Publisher" 123456,"facebook",,,,,,,, 123456,"local_storage",,,,,"_pindexs",,, ``` Each row represents one activation. A single cohort can have multiple rows if it needs to be activated to more than one destination. ## Next Steps Build a new audience segment using the Cohort Builder Create lookalike cohorts from a trained model Return to product overview # Creating Custom Cohorts Source: https://docs.permutive.com/guides/signals/cohorts/custom/creating-custom-cohorts How to build audience segments using the Cohort Builder ## Overview This guide walks you through creating a Custom Cohort in the Permutive Dashboard. Custom Cohorts let you define precise audience segments based on user behaviors, properties, and data sources. **Prerequisites:** * Access to the Permutive Dashboard with cohort creation permissions * Permutive SDK deployed and tracking events ## Steps In the Permutive Dashboard, go to *Custom Cohorts* and click the *+ Add Cohort* button. Give your cohort a unique, descriptive name and add relevant tags for organization or a description of the cohort. The description and tags are optional to include but can be helpful for discovery and cohort management later on. In the Cohort Builder, select the event you want to base your cohort on (e.g., Pageview, VideoView, custom event, etc.). Configure your targeting using: * **Properties**: Filter based on event properties like URL, title, or custom properties * **Recency**: Specify the time window (e.g., "in the past 30 days") * **Frequency**: Set how many times the action must be performed (e.g., "at least 3 times") Chain multiple conditions together using **AND** and **OR** logic to create complex targeting rules. Click *Calculate* next to Audience Size to see how many users would have qualified for this cohort over the past 30 days. This is the **Predicted Audience Size (PAS)** — a historical estimate useful for forecasting scale. Once the cohort is saved and deployed, a separate **Live Audience Size (LAS)** metric tracks users as they are evaluated (updated approximately every 4 hours). For more details, see [Predicted and Live Audience Size](/products/signals/cohorts/custom#whats-the-difference-between-predicted-audience-size-and-live-audience-size). If the audience size meets your needs, click *Save* to deploy the cohort. Creating a custom cohort ## Tips **Building effective cohorts:** * When using `client.url contains`, avoid the trailing `/` on the URL. The Permutive SDK sanitizes URLs, so `example.com/123/` is counted as `example.com/123`. * Commas are useful when uploading multiple property values at once. When building cohorts with values containing commas (e.g., $20,000), escape commas with a backslash: `\$20,000\`. ## Next Steps Send your cohort to advertising platforms Target users based on Internet Service Provider Build cohorts from external data sources Return to product overview # Creating User Group Cohorts Source: https://docs.permutive.com/guides/signals/cohorts/custom/creating-user-group-cohorts How to create cohorts that target groups of users (households) rather than individuals ## Overview User Group Cohorts (also called Household Cohorts) allow you to target groups of users rather than individuals. Behavior is aggregated across the entire group, enabling household-level targeting. **Prerequisites:** * User Group Cohorts enabled for your organization (contact your Customer Success Manager) * User group membership data imported via Connectivity * Access to the Permutive Dashboard with cohort creation permissions ## Steps Ensure User Group Cohorts have been enabled for your organization and that user group membership data has been imported via Connectivity. Navigate to *Custom Cohorts* and click *+ Add Cohort*. Toggle the *Cohort Precision Type* field to *User Group Cohort*. Define your cohort criteria. Note the aggregation behavior: * **First-party/engagement/session clauses**: Behavior is aggregated across the entire group * **Audience Imports/third-party clauses**: Satisfied if *any user within the group* meets the criteria Calculate the audience size and save the cohort. ## Identifying User Group Cohorts User Group Cohorts appear in your cohort list with a *User Group* indicator in the *Precision Type* column. ## Example | Cohort Type | Criteria | Who Qualifies | | :---------------- | :------------------------------------------------- | :---------------------------------------------------------- | | Regular Cohort | "Users who viewed 3+ sports articles" | Individual users who viewed 3+ articles | | User Group Cohort | "Households where users viewed 3+ sports articles" | All users in households where the combined view count is 3+ | ## Platform Support User Group Cohorts have limited platform support for activation. | Platform | Support | | :--------- | :--------------------------------------- | | Web | Full support | | iOS | Event collection only (no activation) | | Android | Event collection only (no activation) | | CTV | Event collection only (no activation) | | API Direct | Full support (client manages activation) | User Group cohorts are activated against User IDs, not Group IDs. ## Next Steps Set up user group data imports Learn cohort creation fundamentals Return to product overview # Deleting Custom Cohorts Source: https://docs.permutive.com/guides/signals/cohorts/custom/deleting-custom-cohorts How to remove Custom Cohorts from your project ## Overview This guide explains how to permanently delete a Custom Cohort from your project. **This action cannot be undone.** Deleting a cohort will remove it from all activations immediately. Users will no longer be targeted based on that cohort's criteria. **Prerequisites:** * Access to the Permutive Dashboard with cohort deletion permissions ## Steps Go to *Custom Cohorts* in the Permutive Dashboard. Find the cohort you want to delete using search or filters. Click the menu icon (⋮) next to the cohort. Select *Delete* and confirm the deletion when prompted. ## Next Steps Build a new audience segment Return to product overview # Editing Custom Cohorts Source: https://docs.permutive.com/guides/signals/cohorts/custom/editing-custom-cohorts How to modify existing Custom Cohort rules and settings ## Overview This guide explains how to edit an existing Custom Cohort to update its targeting rules, name, or other settings. **Prerequisites:** * Access to the Permutive Dashboard with cohort editing permissions * An existing Custom Cohort to modify ## Steps Navigate to *Custom Cohorts* and locate the cohort you want to edit. Use the search or filter options to find it quickly. Click on the cohort name to open it in the Cohort Builder. Make your desired changes to the cohort rules, name, description, or tags. Click *Calculate* to verify the new audience size reflects your intended changes. Click *Save* to deploy the updated cohort. Changes to cohort rules take effect immediately. Users will be added or removed from the cohort in real-time as they meet or no longer meet the updated criteria. ## Next Steps Remove cohorts you no longer need Return to product overview # Exporting Custom Cohorts Source: https://docs.permutive.com/guides/signals/cohorts/custom/exporting-custom-cohorts How to export a report of all Custom Cohorts in your project ## Overview This guide explains how to export a comprehensive report of all cohorts in your project for analysis, documentation, or sharing with your team. **Prerequisites:** * Access to the Permutive Dashboard ## Steps Go to *Custom Cohorts* in the Permutive Dashboard. Click the *Export* button in the toolbar. Select the date range for the export. The default date range is set to the last 30 days. Exporting custom cohorts - date range selection Click *Export* to generate the report. The export may take up to a minute to process. Once complete, the file will download automatically to your browser's download location. Custom cohorts export download ## What's Included The exported report includes all cohort metadata and logic: * Cohort name and description * Cohort code * Tags * Audience size * Precision type * Date created * Status (Enabled/Disabled) * Full cohort logic The date range only affects the Audience Size column in the export. Cohorts created after the selected date range will still appear in the exported list but will show an Audience Size of 0. The report includes all cohorts in your project regardless of which workspace you are currently viewing. ## Next Steps Build a new audience segment Return to product overview # Targeting Users by ISP Source: https://docs.permutive.com/guides/signals/cohorts/custom/targeting-users-by-isp How to create cohorts that target users based on Internet Service Provider ## Overview This guide explains how to create Custom Cohorts that target users based on their Internet Service Provider (ISP). ISP information is automatically collected from users' IP addresses on every Pageview using Maxmind's geo-location database. **Prerequisites:** * Permutive Web SDK deployed (ISP data is collected automatically) * Access to the Permutive Dashboard with cohort creation permissions ## Steps In the Permutive Dashboard, go to *Custom Cohorts* and click *+ Add Cohort*. In the Cohort Builder, select **Pageview** as your event. Add a property filter for `isp_info.isp`. Set the value to the ISP name you want to target (e.g., "BT", "Comcast", "AT\&T"). Optionally add recency, frequency, or other conditions. Calculate the audience size and save the cohort. Targeting users by ISP ## Platform Support **Mobile SDK limitation:** iOS and Android SDKs do not collect ISP properties automatically. These properties must be manually configured in your SDK implementation. See the SDK documentation for [iOS](/sdks/mobile/ios) and [Android](/sdks/mobile/android/overview) for instructions on how to enable ISP data collection. | Platform | ISP Data Available | | :------- | :---------------------------- | | Web | Automatic | | iOS | Manual configuration required | | Android | Manual configuration required | | CTV | Manual configuration required | ## Next Steps Learn cohort creation fundamentals Return to product overview # Using Audience Imports in Cohorts Source: https://docs.permutive.com/guides/signals/cohorts/custom/using-audience-imports How to create Custom Cohorts using external user lists and segments ## Overview This guide explains how to create Custom Cohorts that incorporate Audience Imports—external user lists or segments from partners or third-party data providers. **Prerequisites:** * Audience Imports configured and actively importing data * Access to the Permutive Dashboard with cohort creation permissions ## Steps Ensure your Audience Import has been configured and is successfully importing data. Check the import status in the Connectivity section of the Dashboard. Navigate to *Custom Cohorts* and click *+ Add Cohort*. Set up any additional first-party or behavioral rules as needed. You can combine Audience Imports with your own data. Click *+OR* or *+AND* and then select *Audience Imports* from the condition type dropdown. Select the relevant import from the dropdown list. Calculate the audience size and save the cohort. Creating cohorts with audience imports ## Understanding Audience Size for Import Cohorts When you create a cohort that includes audience import conditions, you may notice that the **Predicted Audience Size (PAS)** is significantly larger than the **Live Audience Size (LAS)** immediately after deployment. This is expected behavior. **Why this happens:** * **PAS is a historical estimate.** It is calculated server-side using the past 30 days of data, including imported identifiers resolved against the identity graph. This means PAS can reflect users who have not visited your properties recently. * **LAS counts only users evaluated since deployment.** LAS is updated periodically (approximately every 4 hours) as users interact with your properties. Imported users must return to your site or app before they can be counted in LAS. * **No backfill.** LAS does not retroactively count users who qualified before the cohort was deployed. It only counts users going forward from the moment of deployment. * **Identity resolution is progressive.** For imported users identified by external identifiers (such as hashed emails), the SDK must match the imported identity to a Permutive user ID when the user visits. This matching happens over time as users return. **What to expect:** * LAS will start at zero and grow over the first days and weeks as imported users visit your properties. * The rate of growth depends on your traffic volume and how frequently imported users visit. * After approximately 30 days, LAS should stabilize and more closely reflect PAS (assuming consistent user behavior). * LAS may never fully reach PAS, because not all historically qualifying users may return within the cohort's recency window. If LAS remains at zero for more than 48 hours after deployment, verify that the audience import is active and ingesting data, and that the Permutive SDK is deployed on the properties where imported users are expected to visit. Also check that imported identifiers are correctly mapped and match the identity graph. For a full explanation of Predicted and Live Audience Sizes, see [Custom Cohorts: Audience Size Definitions](/products/signals/cohorts/custom#whats-the-difference-between-predicted-audience-size-and-live-audience-size). ## Use Cases | Use Case | Description | | :--------------------- | :------------------------------------------------------------------- | | Second-party data | Combine partner audience lists with your first-party behavioral data | | Third-party enrichment | Layer third-party segments onto your audiences | | CRM targeting | Target users from your CRM exports | | Lookalike seeding | Use imported segments as seed audiences for lookalike modeling | ## Next Steps Set up Audience Imports Learn cohort creation fundamentals Return to product overview # Creating Lookalike Cohorts Source: https://docs.permutive.com/guides/signals/cohorts/modeled-lookalike/creating-lookalike-cohorts Learn how to use your trained model to create and activate lookalike cohorts. After your lookalike model has finished training, you can use the resulting curve to create specific cohorts for targeting. ## Understanding the Curve The **Precision vs. Reach** curve represents the trade-off between how closely users match your seed (Precision) and the total number of users you can target (Reach). * **High Precision (e.g., 90%)**: Users match the seed behavior very closely, but the audience size will be smaller. * **Low Precision (e.g., 10%)**: A much larger audience, but with a weaker behavioral match to the seed. ### Open Your Model Navigate to **Modeled Cohorts > Models** and click on your trained lookalike model. ### Select a Point on the Curve Click any point (represented by a circle) on the curve that matches your desired balance of reach and precision. ### Configure the Cohort A pop-up will appear. Provide the following details: * **Name**: Give your lookalike cohort a descriptive name (e.g., "Tennis Lovers - 80% Lookalike"). * **Activation Type**: Choose how you want to use this cohort: * **Publisher Ad Server**: For direct campaigns. * **Programmatic**: For activation via SSPs and DSPs. ### Save and Activate Click **Create Cohort**. Your new lookalike cohort will now appear in your list of cohorts and is ready for activation. You can create multiple cohorts from the same model at different similarity levels to test performance across different audience sizes. ## Important Notes * **100% Similarity**: Choosing 100% similarity will result in an audience identical to your seed segment with no lookalike expansion. * **Real-time Scoring**: Once created, users are scored against your lookalike cohorts in real-time as they browse your site. # Creating a Lookalike Model Source: https://docs.permutive.com/guides/signals/cohorts/modeled-lookalike/creating-lookalike-model Learn how to set up a new lookalike model to expand your audience reach. Lookalike models use machine learning to find users who behave similarly to a specific publisher "seed" audience. This guide walks you through the process of creating a new model. ## Prerequisites Before you begin, ensure you have: * A high-quality **publisher seed segment** (e.g., a custom cohort of email subscribers or converters). * At least **1,000 unique users per day** in your seed segment for optimal accuracy. * At least **10-20 active custom cohorts** in your project to provide enough behavioral signals. ### Access the Models Tab Log into the Permutive dashboard and navigate to **Modeled Cohorts** in the left-hand navigation bar. Then, click on the **Models** tab. ### Start a New Model Click the **+ Add Model** button. You will be prompted to choose the type of model you want to create. Select **Lookalike**. ### Select Your Publisher Seed Segment Choose the publisher cohort you want to use as your seed. Only matched cohorts or custom cohorts can be used as seed segments. ### Configure Advanced Settings (Optional) You can further refine your model by using the following options: * **Include Segments**: Restrict the model to only consider users within specific cohorts. * **Exclude Segments**: Ignore users in specific cohorts during the modeling process. * **Fixed Set**: Manually select the specific cohorts the model should use as behavioral signals (advanced users only). ### Build the Model Click **Build Lookalike Model**. ## What Happens Next? Once you click build, Permutive starts the training process. * **Training Time**: It typically takes up to **2 hours** for a model to finish training. * **Status**: The model will show as "Updating" or "In Progress" until it is ready. * **Result**: Once complete, you will see a **Precision vs. Reach** curve, which you can use to create lookalike cohorts. If your seed segment was created on the same day as the model, the build may fail or show 0 reach until a full day of data has been processed. # Deleting a Model Source: https://docs.permutive.com/guides/signals/cohorts/modeled-lookalike/deleting-lookalike-model Learn how to safely remove a lookalike model and understand the impact on associated cohorts. If a lookalike model is no longer needed, you can delete it from the Permutive dashboard. ## How to Delete ### Navigate to Models Go to **Modeled Cohorts > Models**. ### Select the Model Find the model you wish to remove and click the **Delete** (trash can) icon. ### Confirm Deletion A confirmation dialog will appear. Review the impact and confirm the deletion. ## Impact of Deletion When you delete a lookalike model: * **Retraining Stops**: The model will no longer be updated daily. * **Associated Cohorts**: Any lookalike cohorts created from this model will also be removed from your project. * **Active Campaigns**: If any of these cohorts are currently being targeted in your ad server or DSP, those campaigns will stop receiving new users for those segments. Deletion is permanent. Ensure that no active campaigns are relying on the lookalike cohorts associated with the model before proceeding. # Editing a Seed Segment Source: https://docs.permutive.com/guides/signals/cohorts/modeled-lookalike/editing-lookalike-seed Learn how to update the underlying seed for your lookalike model. If you need to change the definition of the publisher users your model is learning from, you can edit the properties of the seed segment. ## How to Edit ### Locate the Publisher Seed Segment Find the original custom or matched cohort you used as the seed for your model. ### Update Cohort Rules Edit the publisher cohort's rules or filters to reflect your new requirements. ### Automatic Retraining Once the seed segment is updated, Permutive will automatically detect the change. The associated lookalike model will be scheduled for retraining during the next daily cycle. Retraining takes up to **24 hours**. During this time, your existing lookalike cohorts will continue to function based on the previous version of the model. ## Impact on Existing Cohorts When the model finishes retraining with the new seed data: * All lookalike cohorts created from that model will automatically update to reflect the new behavioral patterns. * You do **not** need to recreate the lookalike cohorts manually. * The **Precision vs. Reach** curve may shift based on the new data. If you delete the seed segment entirely, the model will stop receiving new data and will eventually become less accurate, though existing lookalike cohorts will continue to work for a period of time. # Using Include/Exclude Segments Source: https://docs.permutive.com/guides/signals/cohorts/modeled-lookalike/lookalike-advanced-filtering Learn how to use advanced filtering to refine your lookalike model training. Include and Exclude segments allow you to control which users and behavioral signals are used during the lookalike modeling process. ## Include Segments By default, Permutive considers your entire audience when looking for lookalikes. Using **Include Segments** restricts the model to only find similar users within specific cohorts. ### Use Cases * **Geographic Restriction**: Only find lookalikes among users in a specific country. * **Device Restriction**: Only find lookalikes among mobile app users. **Important**: You must include a **reasonable number of cohorts** (typically 10 or more) to give the model enough behavioral signals to learn from. Adding only a single or a handful of cohorts is a common cause of model failure. Without a diverse set of features, the machine learning algorithm cannot identify the unique patterns that define your seed segment. ## Exclude Segments **Exclude Segments** tells the model to ignore specific users or signals during training. ### Use Cases * **Exclude Seed Users**: If you want to find *only* new users who look like your seed, but aren't already in it, you can exclude the seed segment itself. * **Remove Noise**: Exclude cohorts that are too similar to the seed or contain irrelevant behavioral data. ### Access Model Settings During the **Create Model** workflow, look for the **Include** or **Exclude** segment fields. ### Add Cohorts Search for and select the cohorts you want to use as filters. ### Build Model The model will now only learn from and score users based on these restrictions. Be careful not to over-restrict the model. If you exclude too many segments, the model may not have enough data to identify meaningful patterns, leading to poor performance or failed builds. # Validating Model Weights Source: https://docs.permutive.com/guides/signals/cohorts/modeled-lookalike/validating-model-weights Learn how to interpret model weights to ensure your lookalike model is accurate. Model weights provide transparency into the machine learning process by showing which behavioral signals (cohorts) the model found most significant when identifying lookalikes. ## How to View Weights Once a model has finished training, you can view the weights in the Permutive dashboard: 1. Navigate to **Modeled Cohorts > Models**. 2. Click on your trained model. 3. Scroll down to the **Model Weights** section. ## Interpreting Weights The model assigns a numerical weight to each cohort in your project's feature space. * **Positive Weights**: Users in these cohorts are *more likely* to be similar to your seed segment. * **Negative Weights**: Users in these cohorts are *less likely* to be similar to your seed segment. * **Magnitude**: The larger the absolute value of the weight, the stronger the signal. ### Example If you are building a lookalike model for a "Sports Lover" seed segment: * A cohort like "Rugby Fans" should ideally have a **high positive weight**. * A cohort like "Vegetarian Cooking" might have a **negative or neutral weight**. ## Using Weights for Validation Reviewing weights is a powerful way to "sanity check" your model: * **Unexpected Signals**: If a completely unrelated cohort has a very high weight, it may indicate noise in your data or a coincidental correlation. * **Missing Signals**: If a cohort you expect to be highly related has a low or negative weight, the model may not be capturing the behavioral patterns you intended. If the weights don't align with your expectations, consider expanding your feature space with more diverse cohorts or adjusting your seed segment definition. # Creating Classification Cohorts Source: https://docs.permutive.com/guides/signals/cohorts/modeled/creating-classification-cohorts How to create Classification Cohorts from a trained model ## Overview This guide walks you through creating Classification Cohorts from a trained Classification Model. Once your model has finished training, you can create cohorts for each label at different confidence thresholds. **Prerequisites:** * A trained Classification Model (training takes approximately 12 hours) * Access to the Permutive Dashboard with cohort creation permissions ## Understanding the Confidence Curve Once the model has been trained, you will see a curve that shows the expected reach for each point along the confidence scale. Classification cohort confidence curve The confidence measures how sure the model is about a given prediction. While the reach of a classification model is the entire audience, the model will be less certain about some users than others. Use the confidence threshold to restrict your model to only classify users it is more certain about. ## Steps Move the slider to select a minimum confidence threshold. Click on one of the *Dataset classes* buttons to create a cohort for each of the labels in the model. A modal will appear asking for confirmation to create the Classification Cohort. Confirm creation of a classification cohort **Multiple cohorts**: You can create multiple Classification Cohorts from the same model using different combinations of seed labels and confidence thresholds. ## Enabling Activations Once you create the Classification Cohort, you will find it on the *Modeled Cohorts* page. Here you can also enable activations for the cohort to push to ad servers such as Google Ad Manager and Xandr. Activations for a created classification cohort ## Next Steps Manage and filter your cohorts Return to product overview # Creating Classification Models Source: https://docs.permutive.com/guides/signals/cohorts/modeled/creating-classification-models How to create a Classification Model from partner or first-party data ## Overview This guide walks you through creating a Classification Model in Permutive. Classification Models learn patterns in your custom cohorts to predict which label (category) a user belongs to based on a seed dataset. **Prerequisites:** * Access to the Permutive Dashboard with model creation permissions * Classification Cohorts enabled for your organization (contact your Customer Success Manager) * For partner data: A partner dataset enabled by Permutive * For first-party data: At least 2 Custom Cohorts with 1,000+ users each ## Steps Navigate to *Modeled Cohorts* in the Permutive Dashboard, and click on the *Add Model* button. Select *Classification* as the model type. Next, select the type of seed you want to use: * You can leverage your own first-party data by selecting *Custom Cohorts*. * You can work with a partner dataset by selecting *Dataset*. Creating a classification model ## Using Partner Datasets Permutive allows you to leverage partner data for Classification Models, in case you don't have suitable first-party data for the model. Before selecting a partner dataset, Permutive needs to enable that partner for your project. Please contact your Customer Success Manager to understand the availability of data partners for your region and the process of enabling them. Creating a classification model from a partner dataset ## Using First-Party Seed Datasets You can select any of your custom cohorts as seed labels for your Classification Model. **Requirements:** * Minimum size of 1,000 users per seed cohort * Custom cohorts should not be overlapping (a single user should not be in more than one of the cohorts) * You must select at least 2 cohorts and no more than 5 Creating a classification model from a first-party seed **Training time**: After clicking *Create*, it will take around 12 hours for the model to train. You'll receive a notification when the model is ready. ## Next Steps Create cohorts from your trained model Return to product overview # Viewing Classification Cohorts Source: https://docs.permutive.com/guides/signals/cohorts/modeled/viewing-classification-cohorts How to view and manage your Classification Models and Cohorts ## Overview This guide shows you how to view and manage your Classification Models and Cohorts in the Permutive Dashboard. **Prerequisites:** * Access to the Permutive Dashboard * At least one Classification Model created ## Viewing Your Models and Cohorts Classification Models are shown together with Lookalike Models on the *Models* page, under the *Modeled Cohorts* section in the dashboard. The *Type* column specifies whether a model is a Classification or Lookalike model. Viewing your classification cohorts ## Filtering by Model Type You can filter by model type by clicking on the "Filter" button in the top right. To return to viewing all models, click on *Clear*. Viewing your classification cohorts by model type ## Next Steps Create a new model Return to product overview # Activating Standard Contextual Cohorts Source: https://docs.permutive.com/guides/signals/cohorts/standard-contextual/activating-standard-contextual-cohorts How to activate Standard Contextual Cohorts to ad servers and SSPs ## Overview Activate Standard Contextual Cohorts to your ad server to enable contextual targeting in your advertising campaigns. Standard Contextual Cohorts can be activated to Google Ad Manager (GAM) and Xandr. **Prerequisites:** * Standard Contextual Cohorts are automatically created when your account has both Contextual Cohorts and at least one content classification provider providing IAB category classifications enabled * Google Ad Manager or Xandr integration configured in the Permutive Dashboard * Appropriate permissions to manage cohort activations ## Steps In the Permutive Dashboard, navigate to *Contextual > Standard Cohorts* to view your Standard Contextual Cohorts. Click into the Standard Contextual Cohort you want to activate. Enable the relevant activation destination (Google Ad Manager or Xandr). For Google Ad Manager, Standard Contextual Cohorts use the `prmtvctx` key for key-value targeting. Check your ad server to confirm that the Standard Contextual Cohort is receiving real-time updates: * **Google Ad Manager**: Check key-value targeting for `prmtvctx` values * **Xandr**: Verify segment IDs appear in your platform's audience list ## Understanding Activation Behavior Standard Contextual Cohorts behave differently from behavioral cohorts: | Aspect | Standard Contextual Cohorts | Custom Cohorts | | :-------------------- | :---------------------------- | :------------------------------------------- | | **Activation timing** | Real-time on each pageview | Updated periodically based on user behavior | | **Scope** | Page-level (no user tracking) | User-level | | **Consent** | No consent required | May require consent depending on regulations | | **Targeting** | Targets content, not users | Targets users based on past behavior | ## Supported Activation Destinations Standard Contextual Cohorts can currently be activated to: * **Google Ad Manager** * **Xandr** ## Next Steps Generate PPS mapping files for Google Ad Manager View all contextual integration options Return to product overview # Enabling Standard Contextual Cohorts Source: https://docs.permutive.com/guides/signals/cohorts/standard-contextual/enabling-standard-contextual-cohorts How to enable content classification providers to generate Standard Contextual Cohorts ## Overview Enable Standard Contextual Cohorts by activating a content classification provider in your workspace. Once enabled, Permutive automatically generates ready-made contextual audience segments based on standardized taxonomies like IAB Content Taxonomy 2.0. **Prerequisites:** * Access to the Permutive Dashboard with admin permissions * Contextual Cohorts add-on included in your contract * Content classification provider enabled in your contract OR your own API key for a supported provider (e.g., IBM Watson) ## Steps In the Permutive Dashboard, navigate to *Contextual > Catalog*. Browse the available content classification providers in the catalog. IBM Watson is the most common provider for Standard Contextual Cohorts as it uses IAB Content Taxonomy 2.0. **If the provider is included in your Permutive contract**: Contact your Customer Success Manager to activate the provider for your workspace. **If you have a direct relationship with the provider**: Toggle "Use your own API key" and enter your API credentials. After enabling the provider, Permutive will begin classifying your content and automatically generating Standard Contextual Cohorts. This process typically takes a few hours as pages are classified. Navigate to *Contextual* in the Permutive Dashboard to view your Standard Contextual Cohorts. You should see cohorts for each category in the taxonomy (approximately 1,500 cohorts for IAB Content 2.0). ## What Happens After Enabling Once your classification provider is active: * **Automatic cohort generation**: Permutive creates Standard Contextual Cohorts for every category in your provider's taxonomy * **Continuous classification**: New pages are automatically classified as they are published * **Real-time assignment**: Users browsing classified pages are added to the appropriate Standard Contextual Cohorts in real-time * **No manual maintenance**: Unlike custom Contextual Cohorts, Standard Contextual Cohorts are maintained automatically **Allow time for initial classification**: It may take several hours after enabling a provider for your content to be classified and Standard Contextual Cohorts to appear in your dashboard. Contact your Customer Success Manager if cohorts don't appear within 24 hours. ## Next Steps Activate cohorts to ad servers and SSPs Generate PPS mapping files and upload to Google Ad Manager Return to product overview # Setting Up Google PPS Source: https://docs.permutive.com/guides/signals/cohorts/standard-contextual/setting-up-google-pps How to generate PPS mapping files and upload to Google Ad Manager ## Overview Set up Google Publisher Provided Signals (PPS) to pass Standard Contextual Cohorts into the Ad Exchange auction. This enables programmatic buyers to target your contextual inventory using standardized IAB categories. **Prerequisites:** * Standard Contextual Cohorts enabled and activated to Google Ad Manager * Google Ad Manager account with AdX enabled * GAM integration configured in Permutive with appropriate API permissions * The `prmtvctx` key configured in GAM key-value targeting ## Understanding PPS Publisher Provided Signals (PPS) is a Google Ad Manager feature that allows publishers to pass first-party contextual signals into the open auction. By mapping your Standard Contextual Cohorts to PPS, you enable buyers to bid more intelligently on contextually-enriched inventory. **PPS uses standardized taxonomies**: Google PPS requires standardized taxonomies to ensure consistent targeting across publishers. Standard Contextual Cohorts based on IAB Content Taxonomy 2.0 are specifically designed for this use case. ## Steps Before setting up PPS, verify that your Standard Contextual Cohorts are activated to Google Ad Manager in the Permutive Dashboard. Navigate to *Contextual* and confirm activation status. PPS mapping file generation is typically handled by [Technical Services](mailto:technical-services@permutive.com). Reach out to them to request PPS setup for your workspace. [Technical Services](mailto:technical-services@permutive.com) will generate a CSV mapping file that connects your Permutive Standard Contextual Cohort codes to the Google PPS taxonomy. This file maps the `prmtvctx` key-value pairs to PPS categories. **Sensitive categories are filtered**: Google automatically excludes certain sensitive categories from PPS (e.g., war/conflicts, medical health, religion, politics). The mapping tool filters these categories automatically. Once you receive the PPS mapping CSV file: 1. Log into Google Ad Manager 2. Navigate to *Admin > PPS Taxonomy* 3. Upload the CSV mapping file 4. Verify that the mapping is active In Google Ad Manager, enable PPS on the relevant ad units or inventory where you want to pass contextual signals to buyers. Test that PPS values are being passed correctly: * Use the Google Publisher Console to inspect ad requests * Verify that `prmtvctx` key-value pairs appear in the auction * Check that PPS taxonomy IDs are included in the bid request ## PPS Mapping File Structure The PPS mapping CSV file contains: | Column | Description | | :------------------ | :------------------------------------------------------- | | **Key** | The GAM key-value targeting key (typically `prmtvctx`) | | **Value** | Your Permutive cohort code (e.g., `IAB1-1`, `IAB12-3`) | | **PPS Taxonomy ID** | The corresponding Google PPS taxonomy ID | | **Category Name** | Human-readable category name (e.g., "Sports > Football") | **Incremental updates**: If new Standard Contextual Cohorts are added to your workspace (e.g., when your classification provider adds new categories), you may need to regenerate and re-upload the PPS mapping file to ensure complete coverage. ## Measuring PPS Impact After PPS is live, measure its impact on your revenue: 1. **Run PPS lift reports in GAM**: Compare PPS-tagged inventory against open AdX baseline 2. **Calculate eCPM delta**: Measure the incremental value of PPS-enriched impressions 3. **Analyze by category**: Identify which contextual categories drive the highest premiums **Allow time for optimization**: Buyers need time to adjust their bidding strategies to incorporate PPS signals. Allow at least 2-4 weeks before evaluating PPS impact. ## Troubleshooting For troubleshooting PPS mapping issues such as mapping file generation failures or categories rejected by Google, see [Standard Contextual Cohorts — Troubleshooting](/products/signals/cohorts/standard-contextual#troubleshooting). **Cause**: PPS may not be enabled on your ad units, or there's a delay in the mapping being applied. **Solution**: * Verify PPS is enabled on your ad units in Google Ad Manager * Check that the mapping file was successfully uploaded * Allow up to 24 hours for mapping changes to propagate * Use Google Publisher Console to debug bid requests ## Next Steps View full GAM integration documentation Learn more about cohort activation Return to product overview # Configuring Standard Cohorts Source: https://docs.permutive.com/guides/signals/cohorts/standard/configuring-standard-cohorts How to manage which Standard Cohorts are active and included in ad requests ## Overview This guide walks you through configuring Standard Cohorts in the Permutive Dashboard. By enabling or disabling specific cohorts, you control which pre-built audience segments are included in ad requests when users qualify for them. **Prerequisites:** * Standard Cohorts must be initially enabled by [Support](mailto:support@permutive.com) * Access to the Permutive Dashboard with Standard Cohorts permissions ## Steps In the Permutive Dashboard, go to **Audience** > **Standard Cohorts** to access the configuration page. The Standard Cohorts list displays all available cohorts with the following information: * **Name**: The cohort name based on IAB Audience Taxonomy (e.g., "Academic Interests", "Automotive", "Books and Literature") * **Type**: The type of this audience * **ID**: The unique cohort identifier * **Users**: The size of the audience * **Active**: Toggle switch to enable or disable the cohort Use the **Active** toggle switch next to any cohort to turn it on or off. When a cohort is active, it will be included in ad requests for users who qualify for that cohort. Click the **Filter** button to search for specific cohorts by name or category. This is useful when working with the \~200 available Standard Cohorts. To enable or disable all Standard Cohorts at once, click the **Enable/Disable All** button at the top of the page. ## Next Steps Return to product overview # Accessing Identity Insights Source: https://docs.permutive.com/guides/signals/identity/accessing-identity-insights How to access Identity Insights in the Permutive dashboard and navigate the three main tabs ## Overview Access Identity Insights dashboards to visualize your identity graph, understand identifier relationships, and monitor data collection health. Identity Insights provides three main views: Slicer (identity overlap), Ingestion (data collection patterns), and Domain Overlap (cross-domain stitching). **Prerequisites:** * Access to a Permutive workspace * Identity Graph configured with at least one identifier collecting data * Identity Insights is available to all publisher accounts ## Steps In the Permutive dashboard, click **Identity** in the navigation menu. Click on **Identity Insights** in the Identity section. This opens the Identity Insights dashboard with three main tabs. The **Slicer** tab (default view) displays identity overlap in a pivot table format. This shows how different identifiers relate to each other and which identifiers are commonly found together. Click the **Ingestion** tab to view daily data collection patterns. This tab shows data ingestion per day over a 7-day or 30-day period, helping you monitor the health of identity data collection. Click the **Domain Overlap** tab to see user overlap across different domains in your organization. This view helps assess cross-domain identity resolution effectiveness. **Tips for navigating Identity Insights:** * The Slicer tab is the default view and shows the most comprehensive identifier overlap data * Use the Ingestion tab to monitor data collection health and identify drops in ingestion * Domain Overlap updates monthly (on the 1st of each month) due to query cost considerations * Identity overlap and ingestion data are updated daily * All three tabs are available to all publisher accounts with no additional setup required Identity Insights reports on data from the Past 7 Days and Past 30 Days. If you've recently removed an identifier from your allow-list, it may still appear in reports for a period based on these time ranges. ## Next Steps Learn how to interpret the Slicer tab pivot table Use the Ingestion tab to track data collection Return to Identity Insights overview # Adding Identities via Identify Endpoint Source: https://docs.permutive.com/guides/signals/identity/adding-identities-via-identify How to implement the /identify endpoint across Web, iOS, Android, and API platforms, including priority configuration ## Overview The `/identify` endpoint is one of the methods for adding identities to your Identity Graph. It accepts a Permutive user ID (or generates a new one) and a prioritized list of identities, then links all provided identities to the resolved Permutive ID through identity resolution. **Prerequisites:** * Permutive SDK installed and configured for your platform * Identifiers configured in Identity > Identifiers dashboard * User has authenticated or identifier is available ## Steps Before sending identities, ensure the identifier types are configured in **Identity > Identifiers**. Identities with unconfigured identity types will not be added to the graph. Collect the identity values you want to send. Each identity consists of: * **Type**: The identifier type (e.g., `email_sha256`, `appnexus`, `user_id`) * **Value**: The actual identifier string * **Priority**: Integer where 0 = highest priority (optional, defaults to order in array) Implement the identify call with your platform's SDK or API. The system checks identities in priority order until it finds a match, then links all provided identities to that Permutive ID. Order identities by priority, placing the most stable and persistent identifiers first (priority 0). This improves identity resolution rates by checking the most reliable identifiers first. The identify endpoint returns the resolved Permutive ID. Store this ID for future identify calls to maintain consistency across sessions. ### Platform-Specific Implementation #### Web SDK ```javascript theme={"dark"} permutive.identify([ { id: "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", // SHA-256 hash of email tag: "email_sha256", priority: 0 }, { id: "abc123", tag: "appnexus", priority: 1 } ]); ``` #### iOS SDK ```swift theme={"dark"} let identities = [ // Use the SHA-256 hash of the email, not the plaintext email Identity(id: "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", tag: "email_sha256", priority: 0), Identity(id: "abc123", tag: "appnexus", priority: 1) ] Permutive.shared.identify(identities: identities) ``` #### Android SDK ```kotlin theme={"dark"} val identities = listOf( // Use the SHA-256 hash of the email, not the plaintext email Identity(id = "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", tag = "email_sha256", priority = 0), Identity(id = "abc123", tag = "appnexus", priority = 1) ) Permutive.identify(identities) ``` #### API Direct ```bash theme={"dark"} curl -X POST https://api.permutive.com/v2.0/identify \ -H "Content-Type: application/json" \ -H "X-API-Key: YOUR_API_KEY" \ -d '{ "user_id": "permutive-user-id-or-new", "identities": [ {"id": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae", "tag": "email_sha256", "priority": 0}, {"id": "abc123", "tag": "appnexus", "priority": 1} ] }' ``` **Tips for identity resolution:** * Place the most stable, persistent identifiers first (priority 0) - typically hashed emails or authenticated user IDs * Call identify whenever a user authenticates or a new identifier becomes available * Ensure consistent identifier formats (e.g., always hash emails the same way) * Use the same Permutive ID across sessions when available to maintain user continuity * Call identify on every page load or app launch to maintain identity resolution **Important:** * Identities with identifier types not configured in the dashboard will be ignored * A user cannot have multiple identities of the same identifier type * Identity resolution happens in real-time; allow time for propagation across systems * Priority order matters - identities are checked sequentially until a match is found ## Next Steps Set up identifier types before sending identities Analyze identity overlap and resolution effectiveness Return to Identity Graph overview # Analyzing Domain Overlap Source: https://docs.permutive.com/guides/signals/identity/analyzing-domain-overlap How to interpret domain overlap metrics and assess cross-domain user stitching effectiveness ## Overview The Domain Overlap view in Identity Insights shows user overlap across different domains within your organization, helping you assess how successful your identity framework is at stitching users across domains. This is important for cross-domain audience targeting and measurement. **Prerequisites:** * Access to Identity Insights dashboard * Permutive deployed across multiple domains in your organization * Domain overlap is calculated once per month (on the 1st of each month) ## Steps Go to **Identity > Identity Insights** and click the **Domain Overlap** tab. The Domain Overlap view shows the percentage of users who appear on multiple domains within your organization. Higher overlap percentages indicate more successful cross-domain identity resolution. Examine the overlap percentages between different domain pairs. This shows how well your identity solution is stitching users across specific domain combinations. High domain overlap (e.g., 60%+) indicates strong cross-domain identity resolution, meaning users are successfully recognized across multiple domains. Low overlap may indicate gaps in your identity strategy. Use domain overlap data to identify: * Domains with strong cross-domain recognition * Domain pairs with low overlap that may need identity strategy improvements * Opportunities to improve cross-domain targeting and measurement Review domain overlap data regularly to track changes in cross-domain identity resolution over time and measure the impact of any identity strategy improvements. **Tips for analyzing domain overlap:** * Higher overlap percentages indicate better cross-domain identity resolution, which improves targeting and measurement accuracy * Compare overlap across different domain pairs to identify which combinations have the strongest identity stitching * Use domain overlap data to validate the effectiveness of your cross-domain identity strategy * Low overlap may indicate the need for additional identifiers or identity resolution improvements * Domain overlap is particularly important for organizations with multiple properties or brands **Important:** * Domain overlap is calculated once per month on the 1st of each month - you cannot customize the date range * The view automatically refreshes with new data points at the beginning of each month * If you don't see updated data, wait until the 1st of the next month * Domain overlap requires Permutive to be deployed across multiple domains - single-domain organizations won't see meaningful overlap data * The calculation is based on query cost considerations, which is why custom date ranges aren't supported **Note:** Domain overlap metrics help you understand cross-domain user stitching effectiveness, which is critical for accurate cross-domain audience targeting and measurement. Use this data to validate and optimize your identity resolution strategy. ## Next Steps Learn about identifier overlap analysis Track daily data collection patterns Return to Identity Insights overview # Configuring Identifiers Source: https://docs.permutive.com/guides/signals/identity/configuring-identifiers How to add, view, and manage identifiers in the Identity Graph dashboard, including custom identifier setup ## Overview Configure identifiers in the Identity Graph dashboard to define which identity types Permutive recognizes and can collect. Identifiers must be configured before you can send identities of that type via the identify endpoint or import identity data via Connectivity. **Prerequisites:** * Access to a Permutive workspace * Workspace permissions to configure Identity settings ## Steps In the Permutive dashboard, click **Identity** in the navigation menu, then open the **Identifiers** tab. View all configured identifiers in the table. Each identifier shows: * Identifier name (namespace) * Type (User-level or User Group-level) * Volume metrics (number of identities) * Coverage percentage (compared to total Permutive IDs) Click **New Custom ID** button. Enter a distinct name for your identifier (this becomes the namespace). Click **Add** to create the identifier. When creating a new identifier, choose whether it's a **User-level** identifier (for individual users) or **User Group-level** identifier (for households or groups). This determines how the identifier can be used in identity resolution and imports. Auto-collection is available (web only) for the following identifiers: * Google AMP Client ID (`amp`) * Xandr’s 3rd party cookie ID (`appnexus`) * SAP Customer Data Cloud / Gigya user ID (`gigya`) * IP Address (`ip_address`) * Permutive’s 3rd party cookie ID (`pxid`) * Sailthru user ID (`sailthru`) * Trade Desk’s 3rd party cookie ID (`tradedesk`) When adding a new identifier supported by auto-collection: * Enter the exact name as it appears above (`ip_address`, `pxid` etc.). This will automatically surface the auto-collection checkbox. * Tick the **auto-collection** checkbox to have Permutive’s Web SDK automatically capture that identifier’s value. After adding, confirm the new identifier appears in the Identifiers table with its type designation. **Tips for configuring identifiers:** * Use clear, descriptive names for custom identifiers (e.g., `user_id`, `household_id`, `customer_id`) * Standard identifiers like `appnexus`, `pxid`, and `email_sha256` may already be configured * User Group-level identifiers are required for importing household/user group membership data * Standard Identity Graph supports up to 5 identifier types; contact [Support](mailto:support@permutive.com) to upgrade to Advanced ID Graph for more * Identifiers must be configured before sending identities of that type via the identify endpoint **Important:** * Once an identifier is created, you cannot change its type (User-level vs User Group-level) without contacting [Support](mailto:support@permutive.com) * Deleting an identifier prevents new identities of that type from being added, but existing identities remain in the graph * Identifier names are case-sensitive and must be unique within your workspace ## Next Steps Learn how to analyze identifier metrics and usage Implement the identify endpoint to send identities Return to Identity Graph overview # Importing User Group Memberships Source: https://docs.permutive.com/guides/signals/identity/importing-user-group-memberships How to set up Connectivity imports for user group memberships, including data format requirements and configuration ## Overview Import user group memberships (e.g., household relationships) from your data warehouse using Connectivity. This enables household-level identity resolution and allows you to build User Group Cohorts that target groups of users rather than individuals. **Prerequisites:** * Connectivity set up and configured for your data warehouse * At least one User-level identifier configured in Identity > Identifiers * At least one User Group-level identifier configured in Identity > Identifiers * User group membership data available in your data warehouse ## Steps In **Identity > Identifiers**, ensure you have: * At least one **User-level** identifier (e.g., `email_sha256`, `appnexus`) for identifying individual users * At least one **User Group-level** identifier (e.g., `household_id`, `group_id`) for identifying user groups Create or identify a table in your data warehouse with the following columns: * **user\_id** (required): Contains user identifiers matching your configured User-level identifier * **group\_id** (required): Contains group identifiers matching your configured User Group-level identifier * **cursor** (required): Timestamp column indicating when the user was added to the group (used for incremental processing) * **is\_deleted** (optional): Boolean column where `TRUE` indicates the user should be removed from the group In the Permutive dashboard, navigate to **Connectivity** and create a new connection to your data warehouse if one doesn't exist. Create a new Connectivity import and select **Group Identity Data** as the data type. This import type is specifically designed for user group membership data. Map your data warehouse columns to the required fields: * Map the **Group ID** column to your User Group-level identifier * Map the **User ID** column to your User-level identifier * Map the **Timestamp** column to your cursor field * Optionally map the **Delete** column if you have an is\_deleted field After the first import runs, verify in the Connectivity dashboard that data was successfully imported. Check for any error messages in the import logs. ### Data Format Requirements Your data warehouse table should follow this structure: | user\_id | group\_id | cursor | is\_deleted | | -------- | ------------ | ------------------- | ----------- | | user123 | household456 | 2025-01-01 10:00:00 | FALSE | | user124 | household456 | 2025-01-01 10:00:00 | FALSE | | user125 | household457 | 2025-01-02 14:30:00 | FALSE | | user123 | household456 | 2025-01-15 09:00:00 | TRUE | **Important notes:** * Each Connectivity import supports only one type of identifier for User and Group data * You cannot mix multiple identifier types (e.g., both HEM and AppNexus IDs) in the same import * The user\_id can be a Permutive user ID or any configured User-level identifier * The group\_id must match a configured User Group-level identifier ### Managing Stale Data There are two approaches to managing data that is no longer current: **Explicit deletions (recommended for most use cases)** Use the `is_deleted` column to signal which rows should be removed. This is the most efficient approach — only changed and deleted rows are processed, which minimizes data egress from your warehouse and keeps processing volumes low. If your source system can produce deletion rows, this is the cheaper, more deterministic option. **Data retention (TTL)** As an alternative, you can configure a **data retention period** when creating the import. This causes imported group memberships to expire automatically after the configured number of days, removing the need to explicitly signal deletions in your source data. This is useful when communicating deletions is cumbersome or not supported by your source system — for example, household graphs that are refreshed in full on a regular schedule. Set a retention period that comfortably exceeds your refresh frequency, and old memberships will roll off on their own. We recommend setting the retention period at least **2–3 days longer than your import frequency** to allow for delayed processing. For example, if you refresh daily, a retention period of 3–7 days is a good starting point. Very short retention periods risk losing active data if an import run is delayed. **Tips for user group imports:** * Use a timestamp column that updates whenever a user-group relationship changes * The cursor column enables incremental processing, so only new or changed rows are processed * Ensure identifier values in your data match exactly the namespaces configured in Permutive * Test with a small dataset first to verify the import configuration **Important:** * Both User-level and User Group-level identifiers must be configured before creating the import * The identifier types in your data must exactly match the namespaces configured in Permutive * Each import can only use one identifier type for user\_id and one for group\_id * If you need to use multiple identifier types, create separate imports for each type * User group membership data is processed incrementally based on the cursor column ## Next Steps Set up User-level and User Group-level identifiers Learn about building cohorts that target user groups Return to Identity Graph overview # Importing User Identity Source: https://docs.permutive.com/guides/signals/identity/importing-user-identity How to set up Connectivity imports for user identity data, including data format requirements and configuration ## Overview Import user identity data from your data warehouse using Connectivity. This enables you to link additional user-level identifiers to existing users in the Identity Graph, allowing for cross-device and cross-system identity resolution. **Prerequisites:** * Connectivity set up and configured for your data warehouse * A first User-level identifier configured in Identity > Identifiers (acts as the match key) * A second User-level identifier configured in Identity > Identifiers (the identifier to import) * User identity data available in your data warehouse ## Steps In **Identity > Identifiers**, ensure you have: * A **first User-level** identifier (e.g., `permutive_user_id`, `email_sha256`) that will act as the match key. Note: both columns are treated symmetrically, but it helps to think of the first as the match key. * A **second User-level** identifier (e.g., `rampid`, `uid2`) that you want to import and link to users Create or identify a table in your data warehouse with three columns (names can be anything): * **Match key column** (required): Contains user identifiers matching your first User-level identifier — used to find existing users * **Identifier to import column** (required): Contains the new identifiers you want to link to matched users * **Timestamp column** (optional): Indicates when the identity mapping was created (used for incremental processing) In the Permutive dashboard, navigate to **Connectivity** and create a new connection to your data warehouse if one doesn't exist. Create a new Connectivity import and select **User Identity Data** as the data type. This import type is specifically designed for linking user-level identifiers. Map your data warehouse columns to the required fields: * Map the **First ID** field to your primary User-level identifier (the match key) * Map the **Second ID** field to your secondary User-level identifier (the identifier to import) * Map the **Timestamp** field to your cursor column After the first import runs, verify in the Connectivity dashboard that data was successfully imported. Check for any error messages in the import logs. ### Data Format Requirements Your data warehouse table needs three columns. The column names can be anything — what matters is the data each column contains: | Match Key (First ID) | Identifier to Import (Second ID) | Timestamp (Cursor) | | -------------------- | -------------------------------- | ------------------- | | user123 | ramp\_abc123 | 2025-01-01 10:00:00 | | user124 | ramp\_def456 | 2025-01-01 10:00:00 | | user125 | ramp\_ghi789 | 2025-01-02 14:30:00 | **Important notes:** * **Match Key (First ID)**: Contains the identifier used to find existing users in your Identity Graph. If no matching user is found, a new user is created with both identifiers. * **Identifier to Import (Second ID)**: Contains the new identifiers you want to add to matched users. * **Timestamp (Cursor)**: Used for incremental processing — only rows with timestamps newer than the last import are processed. * Each import supports only one identifier type per column. You cannot mix multiple identifier types (e.g., both HEM and AppNexus IDs) in the same import. * If you need to import multiple identifier types, create separate imports for each type. ### How Conflicts with Existing Data Are Handled When import data references users that already exist in the Identity Graph, the outcome depends on the current state: | Scenario | Outcome | | --------------------------------------------------------- | ------------------------------------- | | Both users exist, target has no identity of that type | Second identity added to first user | | Both users exist, target already has that identifier type | Row skipped | | Only first user exists | Second identity added to first user | | Only second user exists | First identity added to second user | | Neither user exists | New user created with both identities | ### Data Retention You can configure a **data retention period (TTL)** when creating the import. This determines how long imported identity mappings are kept before they expire automatically. The default is 180 days. Shorter retention periods can be useful for identifiers that naturally go stale, such as cookie-based IDs (which may rotate due to browser restrictions like ITP) or mobile advertising IDs (which users can reset at any time). **Tips for user identity imports:** * Use a timestamp column that records when the identity mapping was created * The timestamp column enables incremental processing, so only new rows are processed on each sync * Ensure identifier values in your data match exactly the namespaces configured in Permutive * Test with a small dataset first to verify the import configuration * If a match key identifier exists in the Identity Graph, the second identifier is linked to the existing user. Otherwise, a new user is created **Important:** * Both User-level identifiers (First ID and Second ID) must be configured in Identity > Identifiers before creating the import * The First ID and Second ID must be of **different** identifier types — you cannot use the same type (e.g., `email_sha256`) for both columns * If using `permutive_id` as an identifier type, ensure all values are valid UUIDs * The identifier types in your data must exactly match the namespaces configured in Permutive * Each import can only use one identifier type for the First ID and one for the Second ID * If you need to import multiple identifier types, create separate imports for each combination * User identity data is processed incrementally based on the timestamp column * If neither identifier exists in the Identity Graph, a new user will be created with both identifiers associated with it ## Next Steps Set up User-level identifiers for your Identity Graph Learn about importing household and group relationships Return to Identity Graph overview # Monitoring Data Ingestion Source: https://docs.permutive.com/guides/signals/identity/monitoring-data-ingestion How to use the Ingestion tab to monitor daily data collection, identify drops in ingestion, and troubleshoot data collection issues ## Overview The Ingestion tab in Identity Insights displays daily data collection patterns over time, helping you monitor the health of your identity data collection. Use this view to identify drops in ingestion, troubleshoot integration issues, and ensure consistent data quality. **Prerequisites:** * Access to Identity Insights dashboard * At least one identifier configured and collecting data * Ingestion data is updated daily ## Steps Go to **Identity > Identity Insights** and click the **Ingestion** tab. Choose between **Past 7 Days** or **Past 30 Days** using the date range selector. The chart displays data ingestion per day for the selected period. Examine the chart to understand your normal data ingestion patterns. Look for consistent daily volumes and identify any baseline trends. Look for sudden decreases in daily ingestion volumes. Drops may indicate: * Integration issues with identity vendors * Changes to your site or app affecting identity collection * Vendor-specific problems * SDK deployment issues If you notice a drop, review any recent changes to your site, app, or SDK configuration that might affect identity collection. Check deployment logs and vendor status pages. Confirm that identity vendors are still properly integrated and that API keys or configurations haven't changed. Check vendor documentation for any updates or deprecations. If the drop persists or you need assistance identifying the cause, contact [Support](mailto:support@permutive.com) with details about when the drop occurred and any recent changes. **Tips for monitoring ingestion:** * Establish a baseline by reviewing ingestion patterns over several weeks * Compare 7-day and 30-day views to identify both short-term and longer-term trends * Look for patterns - consistent drops on specific days may indicate scheduled maintenance or vendor issues * Monitor multiple identifiers to identify if drops are identifier-specific or platform-wide * Use the Ingestion tab proactively to catch issues before they impact downstream use cases * Daily updates mean you can quickly identify and respond to ingestion problems **Important:** * Ingestion data reflects the Past 7 Days and Past 30 Days only - historical data beyond this range is not available * Drops in ingestion may take a day to appear in the dashboard due to data processing time * Some identifiers may have naturally lower volumes - focus on significant drops relative to baseline * Temporary drops may occur during SDK deployments or vendor maintenance windows ## Next Steps Analyze identifier relationships and overlap Assess cross-domain identity resolution Return to Identity Insights overview # Understanding ID Resolution Privacy Considerations Source: https://docs.permutive.com/guides/signals/identity/understanding-id-resolution-privacy Evaluate the trade-offs between improved audience reach and privacy when enabling identifiers for identity resolution ## Overview When you enable an identifier for identity resolution, Permutive uses it to link user activity across devices, sessions, and domains into a single Permutive ID. This creates a more complete picture of each user, improving audience accuracy and reach — but it also means that any party who knows the identifier value could potentially use it to look up information associated with that user via Permutive's public APIs. This guide explains the trade-offs involved, what data is accessible, and how to make informed decisions about which identifiers to enable for resolution. ## The trade-off ### Benefits of enabling resolution Enabling an identifier for identity resolution allows Permutive to recognize the same user across multiple touchpoints. This improves: * **Audience accuracy**: Cohort membership is based on a user's full behavioral history rather than a single session or device. * **Cross-device reach**: Users can be reached on any of their devices based on activity observed on another. * **Measurement and insights**: Analytics reflect deduplicated users rather than fragmented device-level profiles. * **Enriched user profiles**: If you import identity data via [Connectivity](/guides/signals/identity/importing-user-identity), enabling resolution for the imported identifier allows the SDK to resolve users to profiles that have been enriched with additional linked identifiers and associated data. The more identifiers you enable for resolution, the more connections Permutive can make, and the more complete your user profiles become. ### Privacy considerations When an identifier is enabled for resolution, it can be used via the Permutive **[identify endpoint](/api/identity/identify)** to resolve a user's Permutive ID. Once a Permutive ID is known, certain information about that user is available from Permutive's API endpoints — the same endpoints that the Permutive SDK uses to power on-device segmentation. Identifiers that are widely known or publicly obtainable (such as email addresses) carry a higher risk than identifiers that are only available in controlled contexts (such as first-party cookie IDs that are only accessible within your own domain). ### API authentication All Permutive API endpoints require an API key. However, the endpoints used by the SDK — including the **[identify endpoint](/api/identity/identify)** and the endpoints that return user data for on-device segmentation — use a **public** API key. This is the same key embedded in the Permutive SDK deployed on your site, which is visible to anyone who inspects the page source. This means that the API key does not act as a meaningful access barrier. The primary factor controlling exposure is whether a third party knows the value of an identifier that is enabled for resolution. The **[Retrieve Identities](/api/identity/retrieve)** endpoint, which returns the full list of identifiers associated with a user, requires a **private** API key and is not accessible via the SDK or public key. A third party cannot use identity resolution to discover a user's other identifiers. ## What data is accessible via Permutive's APIs If someone obtains a user's Permutive ID, the following types of information are available from Permutive's API endpoints. Importantly, Permutive does **not** expose raw event-level data (such as a list of pages visited) through these endpoints. ### Cohort segmentation state This is the data Permutive uses to evaluate cohort membership on-device. It reflects aggregated behavioral signals derived from the user's activity on your properties — for example, the number of times a user has viewed content in a particular category over a given time window. **What it contains**: Aggregated counts and behavioral signals structured according to your cohort definitions (e.g., "3 sport-related pageviews in the last 30 days"). **What it does not contain**: Raw events, page URLs, article titles, or any directly readable record of the user's browsing activity. **How interpretable is it?** The data is stored in a compact, encoded format that has no meaning on its own. For example, a cohort defined as "users with 3 or more sport-related pageviews in the last 30 days" would be represented in the state as something like: ```json theme={"dark"} {"82401":{"a4f2e71d03":["p","w",67798,{"67898":3}]}} ``` Interpreting this requires knowledge of your specific cohort definitions, which are encoded within the SDK deployed on your site. While it is theoretically possible for a technically sophisticated actor to cross-reference the state with the SDK code, the effort required is significant and the information gained would be limited to the behavioral categories you have defined in your cohorts. ### Third-party and audience import data If you use third-party data providers (such as LiveRamp or Eyeota) or import your own audience segments, the segment IDs associated with a user are accessible via the API. **What it contains**: A list of segment IDs from your configured data providers. **How interpretable is it?** This depends on the data provider. Some providers use opaque numeric segment IDs that convey no information without access to the provider's taxonomy. However, other providers may use human-readable segment identifiers (e.g., descriptive names like `male` or `high-income`). If your data providers use readable segment IDs, this data could reveal information about a user's inferred demographics or interests. ### Cohort memberships The IDs of cohorts that a user belongs to (including custom cohorts, advertiser cohorts, and curation cohorts) are accessible via the API. **How interpretable is it?** Cohort IDs are opaque identifiers with no inherent meaning. Without access to your Permutive dashboard, there is no way to determine what any cohort ID represents. This data reveals only that a user belongs to some number of cohorts, not what those cohorts are. ## Assessing risk by identifier type Not all identifiers carry the same level of risk when enabled for resolution. Consider how accessible the identifier value is to a potential third party: | Accessibility | Examples | Risk level | Guidance | | :-------------------------------------- | :---------------------------------------------------------------------------------- | :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Publicly known or easily obtained** | Email addresses, hashed emails (`email_sha256`) | Higher | Consider carefully. These identifiers may be known to parties outside your organization. Evaluate whether the resolution benefits justify the exposure for your use case. | | **Available within a browsing context** | Third-party cookie IDs, mobile advertising IDs (`aaid`, `idfa`) | Moderate | These are generally accessible only to parties who can interact with the user's device or browser in an advertising context. | | **Controlled, first-party only** | First-party cookie IDs (`pxid`), internal user IDs, publisher-provided IDs (`ppid`) | Lower | These identifiers are typically only accessible within your own domain or systems, limiting the risk of external access. | Certain identifiers should never be used for identity resolution because they represent groups of users rather than individuals. Examples include IP addresses and household IDs. Permutive blocks these identifier types from being used for resolution. ## Recommendations * **Consult your privacy team** before enabling any identifier for resolution, particularly identifiers that could be considered publicly available. * **Start with lower-risk identifiers**: First-party cookie IDs and internal user IDs offer strong resolution benefits with limited external exposure. * **Review your third-party data providers**: If you use audience imports, check whether your providers use human-readable segment IDs. If so, consider requesting opaque IDs or discuss with your Permutive account team. * **Consider your threat model**: The primary concern is whether the resolution benefits (improved audience accuracy and reach) outweigh the theoretical risk of someone using a known identifier to look up limited, aggregated data about a user. The data accessible via Permutive's APIs is heavily abstracted and not directly interpretable without significant technical effort and insider knowledge. ## Related resources Guide for adding and managing identifiers in the Identity Graph dashboard Guide for implementing the identify endpoint across platforms Core concepts of identity, identifiers, and identity resolution in Permutive How data controllers can signal user consent to Permutive # Understanding Identity Overlap Source: https://docs.permutive.com/guides/signals/identity/understanding-identity-overlap How to interpret the Slicer tab pivot table, including how to read overlap percentages and identify relationships between identifiers ## Overview The Slicer tab in Identity Insights displays identity overlap in a pivot table format, showing how different identifiers relate to each other. Understanding how to read this table helps you assess identifier relationships, identify gaps in your identity graph, and evaluate vendor performance. **Prerequisites:** * Access to Identity Insights dashboard * At least two identifiers configured and collecting data * Identity overlap data is updated daily ## Steps Navigate to **Identity > Identity Insights** and ensure you're on the **Slicer** tab (this is the default view). The pivot table reads **top-to-bottom** rather than left-to-right. Start with the identifier in the top row, then read down the column to see overlap percentages. For each identifier in the top row, read down its column to see what percentage of users with each identifier also have the top-row identifier. For example, if AppNexus is in the top row, reading down shows what percentage of users with each identifier also have AppNexus. Use the view toggle to switch between percentage overlap and absolute values (sum view). Absolute values show the actual number of users who have both identifiers. High overlap percentages indicate strong relationships between identifiers - these identifiers are commonly found together. Low overlap may indicate gaps in identity collection or resolution. Use the table to understand the relative scale of each identifier and how they compare to each other. This helps identify which identifiers provide the most coverage in your identity graph. ### Reading the Pivot Table **Example interpretation:** * If `email_sha256` is in the top row and `appnexus` shows 45% in its column, this means 45% of users with AppNexus also have a hashed email * If `appnexus` is in the top row and `email_sha256` shows 30% in its column, this means 30% of users with hashed emails also have AppNexus * The percentages are not symmetric - they represent different perspectives of the same relationship **Tips for interpreting overlap:** * High overlap (70%+) between two identifiers indicates they're commonly found together and may have similar user coverage * Low overlap (under 20%) may indicate the identifiers target different user segments or there are gaps in collection * Use absolute values to understand the actual scale of overlapping users * Compare overlap percentages across different identifier pairs to identify the strongest relationships * Overlap data helps assess which identity vendors are performing well relative to each other **Important:** * The pivot table orientation (top-to-bottom) can be counter-intuitive - always start with the top-row identifier * Overlap percentages are directional - the percentage depends on which identifier is in the top row * Identity overlap data reflects the Past 7 Days and Past 30 Days - recent changes may take time to appear * Identifiers that have been removed from your allow-list may still appear if data was collected in the reporting time range ## Next Steps Learn how to navigate the Identity Insights dashboard Track daily data collection patterns Return to Identity Insights overview # Viewing Identifier Details Source: https://docs.permutive.com/guides/signals/identity/viewing-identifier-details How to access and interpret the Identifier Details page, including metrics analysis and usage visualization ## Overview The Identifier Details page provides comprehensive visibility into each identifier's scale, coverage, and usage across the Permutive platform. Use this view to understand identifier performance, identify optimization opportunities, and troubleshoot identity resolution issues. **Prerequisites:** * At least one identifier configured in Identity > Identifiers * Identifier must have associated identity data collected ## Steps Go to **Identity** in the navigation menu, then open the **Identifiers** tab. In the Identifiers table, view volume and coverage metrics for each identifier: * **Volume**: Number of unique identities of this type * **Coverage**: Percentage of your total Permutive IDs that have this identifier Find the identifier you want to review. Click the **•••** menu (three dots) next to the identifier name, then select **View Details**. On the Identifier Details page, review how the identifier's scale compares to your total universe. This helps identify: * Gaps in identifier coverage * Opportunities to expand identifier collection * Relative performance compared to other identifiers View where each identifier is being used across the Permutive platform: * **Connectivity**: Used in Connectivity imports * **Audience Imports**: Referenced in audience import configurations * **Activations**: Used for audience activation to destinations * **Routing**: Included in routing data exports * **Data Collaboration**: Used in data collaboration use cases Use the usage visualization to spot quick-win optimizations: * Identifiers with high volume but low usage may indicate untapped value * Identifiers used across multiple products show strong integration * Low coverage may indicate collection or resolution issues **Tips for using Identifier Details:** * Compare identifier scale against your total universe to identify gaps and opportunities * Review product usage to highlight operational impact and value * Use the details view to accelerate troubleshooting by showing where an identifier is used end-to-end * The details view informs strategy for consolidation, expansion, or deprecation of identifiers * Metrics are updated regularly; allow a few hours for new data to appear **Note:** The Identifier Details page shows current usage and metrics. Historical trends and detailed analytics are available in Identity Insights. ## Next Steps Learn how to add and manage identifiers View detailed identity analytics and overlap analysis Return to Identity Graph overview # Implementation Source: https://docs.permutive.com/implementation-overview A high-level guide to deploying Permutive across your properties This page provides an overview of the typical Permutive implementation journey. Your [Technical Services](mailto:technical-services@permutive.com) team will guide you through each phase, but this overview helps you understand what's involved and where to find detailed documentation. We strongly recommend deploying changes to a staging environment first, so your [Technical Services](mailto:technical-services@permutive.com) team can verify the implementation before production rollout. ## Prerequisites Before beginning implementation, ensure you have: * **Workspace credentials**: Your Organization ID, Workspace ID, and API keys (provided by your Customer Success Manager) * **Dashboard access**: Login credentials for the [Permutive Dashboard](https://dash.permutive.com) * **Technical resources**: Development access to deploy code to your web, mobile, or CTV properties ## Implementation phases A typical Permutive implementation follows these phases: ```mermaid theme={"dark"} flowchart LR A[Planning] --> B[SDK Deployment] B --> C[Cohort Creation] C --> D[Activation] ``` ### 1. Planning Before any code is deployed, your [Technical Services](mailto:technical-services@permutive.com) team will work with you to define what data you'll capture and which identifiers you'll collect. This planning phase shapes the deployment itself. #### Event schema design Your [Technical Services](mailto:technical-services@permutive.com) team will help you define the event schemas that capture your user behavior and contextual data. This includes: **Standard events (Collectors):** * **Pageview**: Page metadata such as article category, author, publish date, content type * **Engagement**: Time on page, scroll depth, and interaction signals * **Video**: Play, pause, complete, and quartile events for video content **Custom events:** Business-specific events such as subscription actions, search queries, or e-commerce activity. Your [Technical Services](mailto:technical-services@permutive.com) team will design schemas that capture the properties needed for your targeting use cases. Learn more about events and how they power segmentation #### Identity planning Determine which identifiers you'll collect and how they'll be passed to Permutive: * **First-party IDs**: Authenticated user IDs from your login system—requires calling `permutive.identify()` in your deployment * **Third-party IDs**: Partner identifiers like UID2, ID5, or RampID—may require additional SDK configuration or server-side integration * **Device IDs**: IDFA (iOS) or AAID (Android)—requires deploying the appropriate provider module in mobile SDKs * **Household IDs**: For CTV environments—typically imported via Connectivity from your data warehouse Identifiers are configured in the Dashboard under **Settings → Identity**, and your [Technical Services](mailto:technical-services@permutive.com) team will advise on the technical requirements for each. Learn more about identifiers and identity management ### 2. SDK deployment With your event schemas and identity requirements defined, deploy the Permutive SDK to your properties. The SDK runs on-device to capture events, process cohorts in real-time, and activate audiences. For desktop and mobile web properties For native iOS applications For native Android applications For connected TV applications **Deployment includes:** * Adding the SDK snippet or package to your codebase * Configuring your Workspace ID and API key * Implementing event tracking according to your agreed schemas * Adding `permutive.identify()` calls for any first-party or third-party identifiers * Configuring consent handling for your regions (see below) * Placing the SDK early in your page/app lifecycle to ensure cohorts are available for ad requests **Verification:** Use the [Permutive Chrome Extension](https://chrome.google.com/webstore/detail/permutive) (web) or SDK logging (mobile/CTV) to verify events fire correctly with expected properties. Your [Technical Services](mailto:technical-services@permutive.com) team will perform a full verification before signing off on production deployment. **API Direct deployment**: In some environments where SDK deployment isn't possible (e.g., certain CTV or server-side contexts), Permutive can be integrated directly via API. If this applies to your use case, your [Technical Services](mailto:technical-services@permutive.com) team will guide you through an API Direct deployment approach. ### 3. Cohort creation Once your SDK is deployed and data is flowing, build audiences using the cohort builder in the Permutive Dashboard. Cohorts can be based on: * **Behavioral rules**: Users who performed specific actions (e.g., "read 3+ sports articles in 7 days") * **Contextual signals**: Page content classifications, keywords, or affinity scores * **Imported data**: Audiences from your data warehouse or third-party providers Your Customer Success Manager can help you define cohorts that align with your commercial strategy. Understand cohort types and how they work Step-by-step guide to building cohorts ### 4. Activation Connect Permutive to your ad servers, SSPs, and other platforms to activate cohorts for targeting. **Common activation destinations:** | Type | Platforms | | --------------- | ------------------------------------------------ | | Ad Servers | Google Ad Manager, FreeWheel, Xandr, Equativ | | SSPs | Prebid, Magnite, PubMatic, Index Exchange, OpenX | | DSPs | The Trade Desk, DV360 | | Data Warehouses | BigQuery, Snowflake, AWS S3 | Activations are configured in the Dashboard under **Settings → Integrations**, and your [Technical Services](mailto:technical-services@permutive.com) team will help ensure cohorts flow correctly to each platform. Browse all available integrations ## Handling consent Permutive supports flexible consent handling to comply with GDPR, CCPA, and other privacy regulations. Consent configuration is part of your SDK deployment. **Typical configurations:** | Region | Configuration | | ------------------- | ------------------------------------------------------------------------------------------ | | GDPR (opt-in) | Set `consentRequired: true`; call `permutive.consent()` when CMP grants consent | | CCPA (opt-out) | Set `consentRequired: false`; call `permutive.consent({ opt_in: false })` if user opts out | | No consent required | Set `consentRequired: false` | Learn more about consent handling ## Getting help Throughout your implementation, you have access to: * **Customer Success Manager**: For strategy, cohort planning, and commercial alignment * **[Technical Services](mailto:technical-services@permutive.com)**: For technical setup and configuration * **[Support](mailto:support@permutive.com)**: For troubleshooting and help Technical API documentation # AdsWizz Source: https://docs.permutive.com/integrations/advertising/ad-servers/adswizz Integrate with AdsWizz for audio advertising and cohort activation.
AdsWizz

AdsWizz

AdsWizz is an audio advertising platform specializing in programmatic audio and podcast monetization.

## Overview AdsWizz is an audio advertising platform that specializes in programmatic audio and podcast monetization. The AdsWizz integration enables real-time cohort activation and ad impression event collection across audio environments including web players, mobile apps, and smart speakers. This integration is Bidirectional: * **Source:** Permutive collects ad impression events from AdsWizz Audioserve campaigns, enabling cohort creation based on audio ad delivery data. * **Destination:** Permutive cohorts are activated for campaign targeting in AdsWizz. This integration supports two distinct activation methods: **Key-Value Activation:** Real-time cohort activation via key-value targeting (Web, iOS, Android, API Direct). Cohort IDs are stored in local storage (Web), retrieved via SDK APIs (iOS/Android), or returned from the CCS API (API Direct), then appended to AdsWizz ad requests using the `aw_0_1st.permutive` custom parameter. **Identity Based Activation:** Server-to-server cohort activation where Permutive uploads audience data enriched with cohort memberships to AdsWizz, keyed by identity tags such as IDFA, AAID, and IP Address. See [Supported Identity Tags](#supported-identity-tags) for the full list. Cohorts appear as Audiences in AdsWizz. Use cases include: * Activate Permutive cohorts in AdsWizz for real-time audience targeting across audio inventory. * Collect impression data from AdsWizz campaigns to build cohorts based on audio ad exposure. * Target audio campaigns using first-party data segments from Permutive. * Enable cross-environment activation across web players, mobile apps, and smart speakers. * Activate audiences in environments without an SDK using identity-based, server-to-server delivery (Identity Based). * Leverage key-value targeting in AdsWizz with Permutive cohorts for flexible campaign configuration. ## Choosing an Activation Method AdsWizz supports two activation methods. Choose the method that best fits your use case: | Aspect | Key-Value Activation | Identity Based Activation | | ------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | | **Mechanism** | Client-side, cohort IDs appended to ad requests | Server-to-server transfer | | **Environments** | Web, iOS, Android, API Direct | Web, iOS, Android, API Direct (Smart Speakers) | | **Sync Timing** | Real-time | Daily | | **AdsWizz Feature** | Key-value targeting (`aw_0_1st.permutive`) | Audiences | | **Use Cases** | Real-time targeting on owned audio inventory across all environments | Audience activation in environments without an SDK, cross business unit data sharing | ## Environment Compatibility The table below shows which activation method is available for each environment: | Environment | Key-Value Activation | Identity Based Activation | | -------------- | -------------------- | ------------------------- | | **Web** | | | | **iOS** | | | | **Android** | | | | **API Direct** | | | **API Direct** covers smart speakers, Alexa skills, and other environments where SDK deployment isn't feasible. For Identity Based Activation in these environments, IP address is used as the identity tag. ## Supported Identity Tags These identity tags apply to **Identity Based Activation** only — Permutive uploads cohort memberships to AdsWizz keyed by the identifiers below. (Key-Value Activation does not use identity tags; it appends cohort IDs to ad requests client-side.) | Identity Tag (`tag`) | Description | Environment | | -------------------- | --------------------------------------- | -------------------------------------------------------------- | | `idfa` | Apple Identifier for Advertisers (IDFA) | iOS | | `aaid` | Android Advertising ID (AAID) | Android | | `ip_address` | IP Address | Web, smart speakers, and other environments without device IDs | Identity Based Activation requires that Permutive is collecting the identity tags you select. Work with your Permutive Customer Success Manager to ensure identity collection is configured properly for your desired tags. ## Prerequisites **For Key-Value Activation:** * **Permutive SDK or API Key**: The Permutive SDK must be deployed (Web/iOS/Android) or you must have a Permutive API key for API Direct implementations. * **AdsWizz Account**: Active AdsWizz account with access to Audioserve. * **AdsWizz Technical Account Manager Coordination**: Publishers must notify their AdsWizz Technical Account Manager to whitelist custom parameters for reporting, targeting, and forecasting. * **For Impression Tracking**: A custom event schema must be configured in Permutive (discuss with your Customer Success Manager), and the Permutive user ID must be passed in ad requests. **For Identity Based Activation:** * **AdsWizz Account**: Active AdsWizz account with access to Audioserve. * **Permutive Identity Collection**: Identity collection must be configured in Permutive for the identity tags you want to send (see [Supported Identity Tags](#supported-identity-tags)). * **AdsWizz Technical Account Manager Coordination**: Publishers must coordinate with their AdsWizz Technical Account Manager to initiate the technical setup. AdsWizz provisions a bucket/path that Permutive uses to upload audience data files on the customer's behalf. * **Integration Enablement**: Identity Based Activation is enabled by Permutive on request. Contact your Customer Success Manager to discuss enablement and timelines. ## Setup ### Key-Value Activation Navigate to the integrations page in the Permutive dashboard (Settings → Integrations) and enable the AdsWizz integration. 1. Click **Settings** in the navigation menu 2. Select **Integrations** 3. Find **AdsWizz** in the integrations catalog 4. Click to enable the integration Once enabled, you can activate individual cohorts for AdsWizz via cohort settings or the cohort activations page. Contact your AdsWizz Technical Account Manager to whitelist the custom parameters you'll be passing. The key-value format for Permutive cohorts is: * **Key**: `aw_0_1st.permutive` * **Value**: Comma-separated cohort IDs Additionally, notify them about the Permutive user ID parameter: * **Key**: `aw_0_1st.permutiveid` * **Value**: Permutive user ID The `aw_0_1st` prefix identifies first-party data collected by the publisher. This coordination is essential to enable reporting, targeting, and forecasting options within AdsWizz. In the Permutive dashboard, activate cohorts for AdsWizz: 1. Navigate to the cohort you want to activate 2. Go to cohort settings or the cohort activations page 3. Enable the AdsWizz activation for that cohort Activated cohort IDs will be made available in the appropriate storage location for each environment. Follow the environment-specific implementation steps in the tabs below (Web, iOS, Android) to retrieve cohort IDs and append them to AdsWizz ad requests. For Smart Speaker environments, see the API Direct implementation details in the Web tab. If you want to collect impression events from AdsWizz campaigns for cohort building and analytics, discuss this with your Customer Success Manager first. This requires: * A custom event schema to be set up within Permutive * Permutive user ID to be passed in AdsWizz ad requests (as `aw_0_1st.permutiveid`) * Tracking pixel placed in Ad/Creative in Audioserve campaigns Once the event schema is configured, you can proceed with implementing the impression tracking pixel (see Web tab for details). ## Cohort Activation on Web To pass Permutive cohorts in your AdsWizz ad requests, retrieve cohort IDs from local storage and append them as key-value pairs to the ad request URL: ```javascript theme={"dark"} // Retrieve Permutive cohorts from local storage var permutiveCohorts = JSON.parse(localStorage.getItem('_padswizz')) || []; // Retrieve Permutive user ID for impression tracking var permutiveUserId = window.permutive.context.user_id || ''; // Construct your AdsWizz ad request URL var adsWizzUrl = 'https://[YOUR_ADSWIZZ_DOMAIN]/adserver/...'; // Append Permutive cohorts as key-values if (permutiveCohorts.length > 0) { adsWizzUrl += '&aw_0_1st.permutive=' + permutiveCohorts.join(','); } // Append Permutive user ID for impression tracking if (permutiveUserId) { adsWizzUrl += '&aw_0_1st.permutiveid=' + permutiveUserId; } // Use the URL in your ad request // (Implementation varies based on your player setup) ``` **Important Notes:** * The Permutive SDK automatically populates the `_padswizz` local storage field with activated cohort IDs * Replace `[YOUR_ADSWIZZ_DOMAIN]` with your actual AdsWizz configuration * All custom parameters must use the `aw_0_1st` prefix to identify first-party publisher data * The same key-value format applies across all integration types (VAST, AIS, ORTB) * Ensure your AdsWizz TAM has whitelisted these parameters ## Advanced: Enable Impression Event Collection To collect impression events from AdsWizz for cohort building and analytics: 1. **Add Tracking Pixel to Ad/Creative**: In your AdsWizz Audioserve campaigns, add the following tracking pixel to each Ad/Creative you want to track: ``` https://api.permutive.app/v2.0/px/track?k=PERMUTIVE_API_KEY&u=${parameters["aw_0_1st.permutiveid"]!}&e=AdsWizzImpression&p={"campaign_id":"{campaignID}","zone_id":"{ZoneId}","publisher_name":"{publishername}","listener_id":"{listenerid}","campaign_name":"{campaignName}","order_name":"{orderName}","advertiser_id":"{advertiserid}","advertiser_name":"{advertisername}"} ``` **Important Notes:** * Replace `PERMUTIVE_API_KEY` with your actual Permutive API key * The macro `${parameters["aw_0_1st.permutiveid"]!}` extracts the Permutive user ID from the ad request * This requires the Permutive user ID to be passed in ad requests (see cohort activation code above) * AdsWizz macros (e.g., `{campaignID}`, `{ZoneId}`) capture impression metadata 2. **Available AdsWizz Macros**: The following macros can be used to capture impression metadata: * `{campaignID}` - Campaign ID * `{ZoneId}` - Zone ID * `{publishername}` - Publisher name * `{listenerid}` - Listener ID (AdsWizz user ID - cookie on web, MAID/device ID in other environments) * `{campaignName}` - Campaign name * `{orderName}` - Order name * `{advertiserid}` - Advertiser ID * `{advertisername}` - Advertiser name For the full list of supported macros, consult your AdsWizz Technical Account Manager. 3. **Custom Event Schema**: Before implementing impression tracking, ensure your custom event schema has been configured in Permutive. Discuss with your Customer Success Manager to set this up. ## Cohort Activation on iOS Retrieve Permutive cohort IDs for AdsWizz using the `activations` property: ```swift theme={"dark"} // Import Permutive SDK import Permutive // Access AdsWizz Cohort IDs directly let adsWizzCohorts: [String] = activations['adswizz_keyvalue'] // Retrieve Permutive user ID for impression tracking let permutiveUserId = Permutive.user.id ?? "" // Construct your AdsWizz ad request URL var adsWizzUrl = "https://[YOUR_ADSWIZZ_DOMAIN]/adserver/..." // Append Permutive cohorts as key-values if !adsWizzCohorts.isEmpty { let cohortsString = adsWizzCohorts.joined(separator: ",") adsWizzUrl += "&aw_0_1st.permutive=\(cohortsString)" } // Append Permutive user ID for impression tracking if !permutiveUserId.isEmpty { adsWizzUrl += "&aw_0_1st.permutiveid=\(permutiveUserId)" } // Use the URL in your ad request ``` **Important Notes:** * The `activations` property provides direct access to cohort IDs configured for AdsWizz activation * Use the key `adswizz_keyvalue` to retrieve AdsWizz-specific cohorts * Replace `[YOUR_ADSWIZZ_DOMAIN]` with your actual AdsWizz configuration * All custom parameters must use the `aw_0_1st` prefix ## Cohort Activation on Android Retrieve Permutive cohort IDs for AdsWizz using the `TriggersProvider` API. The callback is invoked whenever cohort membership changes: ```kotlin theme={"dark"} val triggerAction: TriggerAction = triggersProvider.cohortActivations( activationType = "adswizz_keyvalue", callback = object : Method> { override fun invoke(cohorts: List) { if (cohorts.isNotEmpty()) { // Cohorts available for AdsWizz activation // Pass these to your AdsWizz ad requests as key-values println("AdsWizz cohorts: $cohorts") } else { println("No active cohorts with an AdsWizz activation configured.") } } } ) ``` Once you have the cohort IDs, append them to your AdsWizz ad request URL as key-values using the format `aw_0_1st.permutive=[cohort_ids]`. **Important Notes:** * The `TriggersProvider` API allows you to listen for cohort membership changes * Use the activation type `adswizz_keyvalue` to retrieve AdsWizz-specific cohorts * All custom parameters must use the `aw_0_1st` prefix * See the [Android SDK documentation](https://sdk-docs.permutive.com/core/com.permutive.android/-triggers-provider/query-reactions.html) for more details ## Cohort Activation via API Direct In environments where it is not possible to deploy a Permutive SDK (e.g. some smart speaker environments), you can use Permutive's CCS (Custom Cohort Segmentation) API for event tracking and segmentation instead. Access to this API requires customer-specific authorization. You will need a dedicated API key from Permutive. ### How it Works The CCS API can be used to: 1. Track events for a specific user (optional) 2. Retrieve cohort IDs for activation The `events` array in the request is optional. If you only need to retrieve cohort memberships without tracking a new event, you can pass an empty array. **Example Request:** ```bash theme={"dark"} curl --request POST \ --url 'https://api.permutive.app/ccs/v1/segmentation?activations=true&synchronous-validation=false&k=PERMUTIVE_API_KEY' \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --data ' { "alias": { "tag": "example", "id": "12345" }, "events": [ { "name": "AudioView", "time": "2025-10-02T09:40:43.179Z", "view_id": "a55a9c8b-4a0b-4fe7-99a9-170624850501", "session_id": "1d145310-1338-4ff7-96dd-e6128f54cbe5", "properties": { "audio": { "title": "...", "genre": "..." } } } ] } ' ``` The API accepts a user identity passed in the `alias` object. Permutive then either links this identity to an existing Permutive user ID or creates a new Permutive user ID if the identity hasn't been seen before. **Identity Options for Smart Speakers:** * **Account-linked (authenticated) users:** Use an internal cross-platform user ID that identifies the user across your environments * **Non account-linked users:** Use a device ID specific to the smart speaker * **AdsWizz listener ID:** Can also be used as the identity alias For cross-environment segmentation and targeting, the identity provided should be consistent with identities used in your other environments (web, app). **Example Response:** ```json theme={"dark"} { "user_id": "2008c38f-dece-4570-976d-87593ed001c3", "cohorts": ["12345", "23456", "34567"], "activations": { "adswizz_keyvalue": ["12345"] } } ``` The API response includes cohort IDs for AdsWizz activation under the `adswizz_keyvalue` key within the `activations` object. These can then be passed to AdsWizz as key-values for activation using the same format: `aw_0_1st.permutive=[cohort_ids]`. **Important Notes:** * Replace `PERMUTIVE_API_KEY` with your actual Permutive API key * The `activations=true` query parameter is required to receive activation data in the response ### Identity Based Activation Contact your Permutive Customer Success Manager to enable Identity Based Activation for AdsWizz. This is a server-to-server integration that must be configured by Permutive. Contact your AdsWizz Technical Account Manager to initiate the technical setup on the AdsWizz side. AdsWizz provisions a bucket/path that Permutive uses to upload audience data files on your behalf, and shares the destination details with Permutive. Once enabled, navigate to Settings → Integrations in the Permutive Dashboard. Find "AdsWizz (ID-based)" in the list of available integrations and click to configure it. Choose which identity tags you want to send to AdsWizz. See [Supported Identity Tags](#supported-identity-tags) for all available types. Select the tags that match your use case and the environments where you need activation. Save your configuration. Permutive will begin syncing cohort data to AdsWizz on a daily basis for users with the selected identity types. Navigate to your cohorts in the Permutive Dashboard and enable the "AdsWizz (ID-based)" activation toggle for each cohort you want to sync to AdsWizz. After the first sync completes (within 24 hours), check your AdsWizz dashboard to verify that audiences appear with names matching your activated cohorts. Cohort data syncs to AdsWizz daily. Changes to cohort membership will be reflected in AdsWizz within 24 hours of the next scheduled sync. Identity Based Activation for AdsWizz is enabled by Permutive on request. The integration approach has been mapped out with AdsWizz and can be made available within a few weeks of a customer request. Contact your Customer Success Manager to get started. Identity Based Activation for Web enables server-to-server cohort syncing using IP Address. No SDK code changes are required to append cohorts to ad requests — activation happens server-to-server. **How it works:** * Permutive collects IP addresses from your web users * Cohort memberships are synced daily to AdsWizz server-to-server * Cohorts appear as Audiences in AdsWizz, ready for campaign targeting * Data syncing happens automatically once configured in the Permutive Dashboard **Prerequisites:** * Permutive must be collecting IP addresses from your web environment * Work with your Permutive Customer Success Manager to ensure proper identity collection Identity Based Activation for iOS enables server-to-server cohort syncing using IDFA (Identifier for Advertisers). No SDK code changes are required on iOS devices. **How it works:** * Permutive collects IDFA values from iOS users via the Permutive SDK * Cohort memberships are synced daily to AdsWizz server-to-server * Cohorts appear as Audiences in AdsWizz, ready for campaign targeting * Data syncing happens automatically once configured in the Permutive Dashboard **Prerequisites:** * Permutive SDK must be deployed on your iOS app * IDFA collection must be enabled and properly configured * Users must grant tracking permission for IDFA to be available Identity Based Activation for Android enables server-to-server cohort syncing using AAID (Android Advertising ID). No SDK code changes are required on Android devices. **How it works:** * Permutive collects AAID values from Android users via the Permutive SDK * Cohort memberships are synced daily to AdsWizz server-to-server * Cohorts appear as Audiences in AdsWizz, ready for campaign targeting * Data syncing happens automatically once configured in the Permutive Dashboard **Prerequisites:** * Permutive SDK must be deployed on your Android app * AAID collection must be enabled and properly configured Identity Based Activation for smart speakers and other server-side audio environments enables server-to-server cohort syncing using IP Address. No SDK deployment is required in the activation environment. **How it works:** * Permutive collects IP addresses from your smart speaker or server-side audio environments * Cohort memberships are synced daily to AdsWizz server-to-server * Cohorts appear as Audiences in AdsWizz, ready for campaign targeting * Data syncing happens automatically once configured in the Permutive Dashboard **Prerequisites:** * Permutive must be collecting IP addresses from your smart speaker environment * Work with your Permutive Customer Success Manager to ensure proper identity collection ## Data Types The AdsWizz integration involves the following data types. Impression events are collected into Permutive (Source), while cohort activation data is delivered to AdsWizz (Destination): Fired when an audio ad impression is served through AdsWizz Audioserve. This event captures campaign, publisher, and listener metadata for cohort building and analytics. The ID of the campaign associated with the impression. The ID of the zone where the ad was served. The name of the publisher associated with the impression. The AdsWizz listener ID (cookie on web, MAID/device ID in other environments). The name of the campaign associated with the impression. The name of the order associated with the impression. The ID of the advertiser associated with the impression. The name of the advertiser associated with the impression. When you activate a cohort using Identity Based Activation, Permutive uploads audience data enriched with cohort memberships to AdsWizz via a server-to-server transfer. **Data Transfer:** * Cohort data is synced daily from Permutive to AdsWizz * Data is keyed by the identity tags you selected during configuration (see [Supported Identity Tags](#supported-identity-tags)) * Each user's cohort memberships are associated with their identity values **AdsWizz Audiences:** * Cohorts appear as Audiences in AdsWizz * Named using the cohort name from Permutive * Available for campaign targeting in AdsWizz's audience targeting interface * Updated daily with the latest cohort membership data Impression events require additional setup. A custom event schema must be configured in Permutive (discuss with your Customer Success Manager), and the tracking pixel must be placed in Ad/Creative in Audioserve campaigns. See the "Advanced: Enable Impression Event Collection" section in Setup for implementation details. ## Troubleshooting **Symptoms:** Cohort IDs are not appearing in `_padswizz` local storage (Web) or not returned by SDK APIs (iOS/Android). **Solutions:** * Verify the AdsWizz integration is enabled in the Permutive dashboard (Settings → Integrations) * Check that cohorts have been activated for AdsWizz in the Permutive dashboard (via cohort settings or cohort activations page) * Ensure the Permutive SDK is properly deployed and initialized * For Web, check browser developer tools → Application → Local Storage to verify the `_padswizz` key exists * For iOS/Android, verify you're using the correct activation type key (`adswizz_keyvalue`) **Symptoms:** Permutive cohorts are not included in AdsWizz ad request URLs, or targeting doesn't work as expected. **Solutions:** * Verify cohort IDs are being retrieved correctly from local storage (Web) or SDK APIs (iOS/Android) * Ensure the key-value format is correct: `aw_0_1st.permutive=[cohort_ids]` * Confirm the custom parameters are being appended to the AdsWizz ad request URL * Check that the Permutive SDK loads before the AdsWizz ad request is made * Verify the cohorts are activated with an AdsWizz activation (not just other activations) **Symptoms:** Key-values are being passed in ad requests but AdsWizz is not recognizing them for targeting or reporting. **Solutions:** * Contact your AdsWizz Technical Account Manager to whitelist the custom parameters * Confirm that the `aw_0_1st.permutive` and `aw_0_1st.permutiveid` parameters have been whitelisted * Verify the key-value format matches AdsWizz requirements (prefix `aw_0_1st` for first-party data) * Allow time for whitelisting changes to take effect in AdsWizz systems * Check AdsWizz campaign targeting interface to confirm parameters are available **Symptoms:** AdsWizz impression events are not appearing in Permutive analytics or event stream. **Solutions:** * Verify the tracking pixel has been added to Ad/Creative in AdsWizz Audioserve campaigns * Ensure the custom event schema has been configured in Permutive (discuss with your Customer Success Manager) * Confirm the Permutive user ID is being passed in ad requests (as `aw_0_1st.permutiveid`) * Check that the tracking pixel URL is correctly formatted with the Permutive API key * Verify AdsWizz macros are correctly replacing with actual values (e.g., `{campaignID}`, `{ZoneId}`) * Test the tracking pixel URL manually to confirm it's reachable **Symptoms:** The CCS API response includes an empty `adswizz_keyvalue` array or doesn't include the key at all. **Solutions:** * Verify the AdsWizz integration is enabled in the Permutive dashboard * Check that cohorts have been activated for AdsWizz * Ensure the user identified in the API request (via `alias`) is a member of activated cohorts * Confirm the `activations=true` query parameter is included in the API request URL * Allow time for cohort membership to be computed after sending events * Verify the API key used in the request has the correct permissions **Symptoms:** Audiences are not appearing in AdsWizz, or cohort memberships are not syncing as expected. **Solutions:** * Verify that cohorts are activated with the "AdsWizz (ID-based)" toggle enabled in the Permutive Dashboard * Remember that cohort data syncs daily — allow up to 24 hours for changes to appear in AdsWizz * Confirm that Permutive is collecting the identity tags you selected (see [Supported Identity Tags](#supported-identity-tags)) by checking with your Permutive Customer Success Manager * Verify your selected identity tags are correct in Settings → Integrations * Confirm Identity Based Activation has been enabled for your workspace (this is enabled by Permutive on request) For Identity Based Activation issues, contact your Permutive Customer Success Manager for assistance. ## Changelog ### June 2026 * Documented Identity Based Activation for AdsWizz: server-to-server cohort upload keyed by IDFA, AAID, or IP Address, with cohorts appearing as Audiences in AdsWizz (available on request) ### October 2025 * AdsWizz key-value integration released for Web, iOS, and Android environments * Added support for API Direct implementation for smart speaker environments * Enabled AdsWizz impression event collection via tracking pixel For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Catalog Source: https://docs.permutive.com/integrations/advertising/ad-servers/catalog Browse our comprehensive catalog of ecosystem integrations. # Ad Server Integrations
Google Ad Manager

Google Ad Manager

Google Ad Manager is a comprehensive ad management platform that helps publishers sell, deliver, and optimize ads across web, app, and video content.

Microsoft Monetize

Microsoft Monetize (Ad Server)

Microsoft Monetize's ad server helps publishers manage campaigns and optimize yield across direct and programmatic channels.

Equativ

Equativ

Equativ (formerly Smart AdServer) provides publishers with an independent ad server and SSP for managing and monetizing inventory.

FreeWheel

FreeWheel

Freewheel offers a premium video ad server and SSP tailored to broadcasters and publishers for advanced monetization.

Nativo

Nativo

Nativo helps publishers deliver and manage native advertising formats that blend seamlessly with editorial content.

AdsWizz

AdsWizz

AdsWizz is an audio advertising platform specializing in programmatic audio and podcast monetization.

# Equativ Source: https://docs.permutive.com/integrations/advertising/ad-servers/equativ Integrate with Equativ for ad server and SSP functionality.
Equativ

Equativ

Equativ (formerly Smart AdServer) provides publishers with an independent ad server and SSP for managing and monetizing inventory.

## Overview Equativ (formerly Smart AdServer) is an independent advertising technology platform providing publishers with ad server and SSP functionality for managing and monetizing their digital inventory. Our integration with Equativ enables cohort activation and impression event collection. This integration is Bidirectional: * **Source:** Permutive collects impression events from Equativ ad server, enabling cohort creation based on campaign delivery data. Impression event collection is currently web-only. * **Destination:** Permutive cohorts are activated in Equativ as keyword targeting options, with cohort IDs passed as key-value pairs in ad requests to Equativ. Cohort activation supports two methods: * **Key-value activation:** Cohort IDs are read from the Permutive SDK and passed as key-value pairs in Equativ ad requests via Equativ's tag (Web), mobile SDK (iOS/Android), or CTV SDK. * **[Server-side identity-based activation](/integrations/server-side-identity-based-activation):** Can be an alternative path where client-side activation isn't viable. Permutive uploads cohort memberships to Equativ keyed by identity values (cookie ID, IDFA, AAID, IP address, or hashed email). Cohorts appear as Equativ targeting segments ready for use. Use cases include: * Activate Permutive cohorts in Equativ for audience targeting across display and video inventory. * Collect impression data from Equativ campaigns to build cohorts based on ad exposure and campaign performance. * Target campaigns using first-party data segments from Permutive. * Enable publishers to monetize inventory more effectively with data-driven targeting. * Leverage Equativ's keyword targeting capabilities with Permutive cohorts for flexible campaign configuration. * Activate audiences across Web, in-app (iOS/Android), and CTV inventory through key-value or server-side identity-based methods. ## Environment Compatibility | Environment | Activation | | -------------- | -------------------------------------------------------------------------------------------------------------------- | | **Web** | Live — cohort IDs read from `_psmart` local storage and passed in `sas.call()` target parameter | | **iOS** | Available — customer implementation via Equativ's iOS SDK | | **Android** | Available — customer implementation via Equativ's Android SDK | | **CTV** | Available — customer implementation via Equativ's CTV SDK, or via Permutive CCS API for SDK-less setups | | **API Direct** | Available — cohort IDs retrieved via Permutive CCS API and passed to Equativ ad calls | **[Server-side identity-based activation](/integrations/server-side-identity-based-activation)** can be an alternative path for this integration, particularly where client-side activation isn't viable. Permutive can support your team in setting this up — contact your Customer Success Manager to discuss. Impression event collection is currently web-only. The mobile / CTV / API Direct paths above cover cohort activation only. ## Prerequisites **Required for all activation methods:** * **Equativ Network ID** — your unique Equativ network identifier. See [How to find the Network ID](https://help.smartadserver.com/s/question/0D51v00005cG9vXCAS/how-can-i-get-my-network-id) in Equativ's documentation. * **Equativ Username and Password** — API credentials with permissions to create and manage keyword targeting in your Equativ account. * **Access to Equativ Campaign Setup** — ability to configure keyword targeting in your Equativ campaigns. **For Web:** * **Permutive Web SDK** — deployed on your web pages where Equativ ad requests are made. * **Equativ tag (smart.js)** — deployed on your pages. * **Access to Equativ Creative Section** — required only if you want to collect impression events (web-only). **For iOS / Android:** * **Permutive Mobile SDK** — deployed in your iOS / Android app. * **Equativ mobile SDK** — deployed in your app. * **Customer implementation** — to retrieve Permutive cohorts from the SDK and attach them as targeting key-values in Equativ ad calls. **For CTV and other environments:** * Contact your Permutive Customer Success Manager to discuss the integration approach for your stack. Where Equativ's CTV SDK is in use, cohorts can be passed as key-values from the Permutive Mobile SDK; where it isn't, cohorts can be retrieved server-side via the Permutive CCS API and attached to Equativ ad calls. **For server-side identity-based activation:** * This connector is not built today. If your use case calls for it, identity collection would need to be configured in Permutive for the identity types you want to use (cookie ID, IDFA, AAID, IP address, hashed email). * Contact your Permutive Customer Success Manager to discuss. ## Setup Navigate to the integrations page in the Permutive dashboard (Settings → Integrations) and add the Equativ integration. 1. Click the **Add Integration** button 2. Select **Equativ** from the list of available integrations 3. Enter your Equativ credentials: * **Network ID**: Your Equativ network identifier * **Username**: Your Equativ API username * **Password**: Your Equativ API password 4. Click **Save** Once configured, Permutive will automatically create a keyword group in your Equativ account and push activated cohort names as keywords for targeting. After configuring the integration, verify that: 1. The integration appears as active in your Permutive dashboard integrations page 2. A Permutive keyword group has been created in your Equativ account 3. You can see activated cohorts appearing as keywords in the Equativ targeting interface If you want to collect impression events from Equativ campaigns for cohort building and analytics, discuss this with your Customer Success Manager first. This requires: * A new event schema to be set up within Permutive * Additional Web code implementation (documented in the Web tab below) Once the event schema is configured, you can proceed with implementing the impression tracking code. ## Cohort Activation on Web To pass Permutive cohorts in your Equativ ad requests, include the cohort data from local storage in the request's `target` parameter: ```javascript theme={"dark"}
``` **Important Notes:** * Replace `[NETWORK_ID]`, `[SITE_ID]`, `[PAGE_ID]`, and `[FORMAT_ID]` with your actual Equativ configuration values * The Permutive SDK automatically populates the `_psmart` local storage field with activated cohort IDs * See the [Equativ Tagging Guide](https://help.smartadserver.com/s/article/Tagging-guide) for details on extracting your configuration values ## Advanced: Enable Impression Event Collection To collect impression events from Equativ for cohort building and analytics: 1. **Add Creative Script**: In your Equativ ad server, add the following script to the **Creative Section** of campaigns you want to track: ```javascript theme={"dark"} ``` This script uses Equativ macros to capture impression metadata and send it to Permutive via postMessage. 2. **Add Event Queue Script** (Optional but Recommended): To handle impressions that fire before the Permutive SDK has fully initialized, add this script alongside your Permutive deployment tag: ```javascript theme={"dark"} ;(function () { var messageDataQueue = [] var sdkReady = false var listener = function (event) { if (typeof event.data === 'object' && 'origin' in event.data && event.data.origin === 'permutive') { if (event.data.type === 'sdk-init') { sdkReady = true window.removeEventListener('message', listener) messageDataQueue.forEach(function (msgData) { window.postMessage(msgData, '*') }) return } } if (!sdkReady) { messageDataQueue.push(event.data) } } window.addEventListener('message', listener, false) })() ``` This ensures all impression events are captured and processed, even those that occur during page load.
Equativ supports in-app inventory via its iOS mobile SDK. Activation of Permutive cohorts on iOS in-app traffic requires customer implementation to retrieve cohort signals from the Permutive Mobile SDK and attach them as key-value targeting in Equativ ad calls. **High-level pattern:** 1. Initialise the Permutive Mobile SDK in your iOS app. 2. Retrieve the user's active cohort IDs from the SDK. 3. Attach the cohort IDs as a key-value (typically under the key `permutive`) in your Equativ iOS SDK ad request — mirroring the `target` parameter used in the Web `sas.call()`. 4. Verify that Equativ receives Permutive cohort data in the ad request. Contact your Permutive Customer Success Manager — our team will partner with you on the integration. Impression event collection from Equativ is currently web-only. This tab covers cohort activation only. Equativ supports in-app inventory via its Android mobile SDK. Activation of Permutive cohorts on Android in-app traffic requires customer implementation to retrieve cohort signals from the Permutive Mobile SDK and attach them as key-value targeting in Equativ ad calls. **High-level pattern:** 1. Initialise the Permutive Mobile SDK in your Android app. 2. Retrieve the user's active cohort IDs from the SDK (e.g. via `TriggersProvider`). 3. Attach the cohort IDs as a key-value (typically under the key `permutive`) in your Equativ Android SDK ad request — mirroring the `target` parameter used in the Web `sas.call()`. 4. Verify that Equativ receives Permutive cohort data in the ad request. Contact your Permutive Customer Success Manager — our team will partner with you on the integration. Impression event collection from Equativ is currently web-only. This tab covers cohort activation only. Activating Permutive cohorts on Equativ CTV inventory depends on your CTV ad-serving stack: * **If Equativ's CTV SDK is part of your stack**, cohorts retrieved from the Permutive Mobile SDK can be attached as key-value targeting in Equativ CTV ad calls, mirroring the iOS / Android pattern above. * **For environments where an SDK cannot be deployed**, Permutive's CCS API can be used server-side to retrieve cohort IDs for each user, which can then be attached as key-values in Equativ ad calls. Contact your Permutive Customer Success Manager to discuss your CTV environment and the right activation approach. Impression event collection from Equativ is currently web-only. This tab covers cohort activation only.
## Data Types With your Equativ integration setup, you'll see the following additional event types collected in Permutive: Fired when an ad impression is served through Equativ ad server. This event captures campaign, creative, and targeting metadata for cohort building and analytics. The ID of the advertiser associated with the impression. May come from direct campaigns (`sas_advertiserId`) or programmatic campaigns (`sas_rtb_advertiserId`). The ID of the campaign associated with the impression. May come from direct campaigns (`sas_campaignId`) or programmatic campaigns (`sas_rtb_campaignId`). The ID of the creative that was displayed. May come from direct campaigns (`sas_creativeId`) or programmatic campaigns (`sas_rtb_creativeId`). The ID of the insertion order (equivalent to line item ID in other ad servers). Used for campaign reporting and analysis. The deal ID for programmatic transactions (`sas_rtb_dealId`). Available only for programmatic impressions. The CPM value for the impression. May be the standard CPM (`sas_cpm`) or the cleared price in publisher currency (`sas_rtb_clearedPricePublisherCurrency`) for programmatic impressions. The Equativ tag ID associated with the ad placement. The height of the creative in pixels. The width of the creative in pixels. The keyword targeting parameters applied to the impression, including Permutive cohort IDs if cohort activation is enabled. Impression events require additional setup in the Equativ Creative Section. See the "Advanced: Enable Impression Event Collection" section in Setup for implementation details. ## Troubleshooting **Symptoms:** Activated cohorts don't appear as available keywords in Equativ campaign targeting interface. **Solutions:** * Verify the Equativ integration is properly configured in the Permutive dashboard with correct Network ID, Username, and Password * Check that cohorts have been activated for Equativ in the Permutive dashboard (either via cohort settings or cohort activations page) * Confirm that a Permutive keyword group has been created in your Equativ account (this should happen automatically upon integration setup) * Allow a few minutes for newly activated cohorts to sync to Equativ - keyword creation is not instantaneous * Verify your Equativ API credentials have permissions to create and manage keyword targeting **Symptoms:** Permutive cohorts are not included in the `target` parameter of Equativ ad requests, or targeting doesn't work as expected. **Solutions:** * Verify the `_psmart` local storage field is being populated by the Permutive SDK - check browser developer tools → Application → Local Storage * Ensure your Equativ ad request code is correctly reading from `localStorage.getItem('_psmart')` and parsing the JSON * Confirm the `target` parameter in your `sas.call()` includes the Permutive cohorts: `target: 'permutive=' + permutive_cohorts_for_smart.join(',')` * Check that the Permutive SDK loads before the Equativ ad request is made * Verify the cohorts are activated with an Equativ activation (not just other activations) **Symptoms:** Equativ impression events are not appearing in Permutive analytics or event stream. **Solutions:** * Verify the impression tracking script has been added to the Creative Section in Equativ ad server * Check that the `permutiveEventSmart` postMessage is being sent - use browser developer tools → Console to check for errors * Ensure the Permutive SDK is loaded on pages where Equativ ads are served * If using the optional event queue script, verify it's placed alongside the Permutive deployment tag (not inside the creative) * Check browser console for any cross-origin or security errors that might block postMessage communication * Confirm the creative script is using the correct Equativ macros (e.g., `[sas_advertiserId]`, `[sas_campaignId]`) **Symptoms:** When targeting multiple Permutive cohorts, Equativ treats them as OR conditions rather than AND conditions. **Solutions:** * This is expected behavior - Equativ's keyword targeting uses OR logic by default when multiple keywords are selected from the same keyword group * To create AND logic or more complex targeting combinations, you must manually enter cohort IDs in the Equativ targeting interface rather than selecting them from the dropdown * See [Equativ's keyword targeting documentation](https://help.smartadserver.com/s/article/Using-keyword-targeting) for details on advanced targeting syntax * Consider building more specific cohorts in Permutive that combine your desired conditions, then activate those single cohorts to Equativ **Symptoms:** Error when trying to add or configure the Equativ integration in the Permutive dashboard. **Solutions:** * Verify you have the correct Equativ Network ID - see [How to find the Network ID](https://help.smartadserver.com/s/question/0D51v00005cG9vXCAS/how-can-i-get-my-network-id) * Confirm your Equativ API credentials are correct and have not expired * Check that your Equativ user account has API access and permissions to create keyword groups * If the issue persists, contact Permutive support with the error message ## Changelog ### January 2022 * Added support for Equativ impression event collection via creative pixel * Enabled Equativ Campaign Insights dashboards for customers with integration configured For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # FreeWheel Source: https://docs.permutive.com/integrations/advertising/ad-servers/freewheel Integrate with FreeWheel for targeting and campaign optimization
FreeWheel

FreeWheel

FreeWheel offers a premium video ad server and SSP tailored to broadcasters and publishers for advanced monetization.

## Overview FreeWheel is a premium video ad server used by media companies, broadcasters, and publishers with significant video content and ad inventory. Our integration with FreeWheel enables activation of Permutive cohorts for campaign targeting within the FreeWheel ad server. This integration supports two distinct activation methods: **Key-Value Activation:** Real-time cohort activation via FreeWheel Federated Segments (Web, iOS, Android, CTV, API Direct). Cohort IDs are stored in local storage (Web), retrieved via SDK APIs (iOS/Android), or returned from the CCS API (API Direct). **Identity Based Activation:** Server-to-server cohort activation where Permutive uploads audience data enriched with cohort memberships to FreeWheel, keyed by identity tags such as IDFA, AAID, IP Address, and Connected TV device IDs. See [Supported Identity Tags](#supported-identity-tags) for the full list. Cohorts appear as Audiences in FreeWheel. Use cases include: * Activate Permutive cohorts in FreeWheel for real-time audience targeting on video inventory across all environments * Target premium video campaigns using first-party data segments from Permutive * Enable audience extension by activating cohorts on non-owned properties (Identity Based) * Share audience data across business units using identity-based activation * Enable broadcasters and publishers to monetize video content more effectively with data-driven targeting ## Choosing an Activation Method FreeWheel supports two activation methods. Choose the method that best fits your use case: | Aspect | Key-Value Activation | Identity Based Activation | | --------------------- | --------------------------------------------------------------- | ---------------------------------------------------- | | **Mechanism** | Client-side, cohort IDs appended to ad requests | Server-to-server transfer | | **Environments** | Web, iOS, Android, CTV, API Direct | Web, iOS, Android, CTV, API Direct | | **Sync Timing** | Real-time | Daily | | **FreeWheel Feature** | Federated Segments | Audiences | | **Use Cases** | Real-time targeting on owned properties across all environments | Audience extension, cross business unit data sharing | ## Environment Compatibility The table below shows which activation method is available for each environment: | Environment | Key-Value Activation | Identity Based Activation | | -------------- | -------------------- | ------------------------- | | **Web** | | | | **iOS** | | | | **Android** | | | | **CTV** | | | | **API Direct** | | | ## Supported Identity Tags These identity tags apply to **Identity Based Activation** only — Permutive uploads cohort memberships to FreeWheel keyed by the identifiers below, and each maps to a FreeWheel `id_type`. (Key-Value Activation does not use identity tags; it appends cohort IDs to ad requests client-side.) | Identity Tag (`tag`) | Description | Environment | | -------------------- | --------------------------------------------------------------------------------- | -------------------------- | | `idfa` | Apple Identifier for Advertisers (IDFA) | iOS | | `aaid` | Android Advertising ID (AAID) | Android | | `ip_address` | IP Address | CTV and other environments | | `ctv` | Connected TV advertising IDs — RIDA (Roku), TIFA (Samsung), AFAI (Amazon Fire TV) | CTV | Identity Based Activation requires that Permutive is collecting the identity tags you select. Work with your Permutive Customer Success Manager to ensure identity collection is configured properly for your desired tags. ## Prerequisites **For Key-Value Activation:** * **FreeWheel Account** - You must have an active FreeWheel ad server account * **FreeWheel Client ID** - Your unique FreeWheel Client ID * **Federated Segments Enabled** - Your FreeWheel account manager must allow Permutive to create Federated Segments in your FreeWheel account * **Permutive SDK** - The Permutive SDK must be deployed on your web properties where FreeWheel ad requests are made **For Identity Based Activation:** * **FreeWheel Account** - You must have an active FreeWheel ad server account * **FreeWheel Client ID** - Your unique FreeWheel Client ID * **Permutive Identity Collection** - Identity collection must be configured in Permutive for the identity tags you want to send (see [Supported Identity Tags](#supported-identity-tags)) ## Setup ### Key-Value Activation Contact your FreeWheel account manager and ask them to allow Permutive to create Federated Segments in your FreeWheel account. This is required for the integration to work. Contact your Permutive Customer Success Manager to request the FreeWheel integration. You will need to provide your FreeWheel Client ID. Your Permutive Customer Success Manager will enable the FreeWheel integration in your Permutive workspace using this Client ID. In the Permutive Dashboard, navigate to one of your cohorts. Check that the FreeWheel activation sync is visible. Toggle it to *On* and save the cohort. In your FreeWheel dashboard, verify that a new Federated Segment has been created with a name matching your cohort ID. This confirms your integration setup is successful. The FreeWheel integration uses a client-side key-value approach with FreeWheel Federated Segments. When cohorts are activated to FreeWheel in the Permutive Dashboard, Permutive creates a Federated Segment in FreeWheel, and the cohort IDs are stored in the browser's local storage under a FreeWheel-specific key. **You must implement code to retrieve these cohort IDs from local storage and append them to your FreeWheel ad requests.** Permutive provides the cohort data, but your implementation is responsible for passing it to FreeWheel. #### Accessing FreeWheel Cohorts for Targeting Cohort IDs are stored in the local storage variable `_pfws` (this is the default key for FreeWheel activations). This variable is updated as soon as Permutive has segmented the user on the page. Below is an example of how to retrieve Permutive cohort IDs in a variable called `cohorts`, which you can then append to your FreeWheel ad requests: ```javascript theme={"dark"} var cohorts; try { cohorts = JSON.parse(window.localStorage._pfws || '[]').slice(0, 250).toString(); } catch (e) { cohorts = ''; } // Use the cohorts variable to append to your FreeWheel ad request // Example: append as key-value pairs in your ad request configuration ``` #### Implementation Notes * Ensure the Permutive SDK loads before you attempt to retrieve cohort IDs from local storage. * The cohort IDs are updated in real-time as users enter or exit cohorts. * You can append up to 250 cohort IDs per ad request (the `.slice(0, 250)` in the example limits this). * Coordinate with your development team to integrate this code into your FreeWheel ad request flow. For technical assistance with implementation, contact Permutive Support. Retrieve Permutive cohort IDs for FreeWheel using the `activations` property: ```swift theme={"dark"} // Import Permutive SDK import Permutive // Access FreeWheel Cohort IDs directly let freeWheelCohorts: [String] = activations['freewheel'] // Construct your FreeWheel ad request // Append Permutive cohorts as key-values if !freeWheelCohorts.isEmpty { let cohortsString = freeWheelCohorts.joined(separator: ",") // Append cohortsString to your FreeWheel ad request } ``` **Important Notes:** * The `activations` property provides direct access to cohort IDs configured for FreeWheel activation * Use the key `freewheel` to retrieve FreeWheel-specific cohorts * Coordinate with your development team to integrate this code into your FreeWheel ad request flow Retrieve Permutive cohort IDs for FreeWheel using the `TriggersProvider` API. The callback is invoked whenever cohort membership changes: ```kotlin theme={"dark"} val triggerAction: TriggerAction = triggersProvider.cohortActivations( activationType = "freewheel", callback = object : Method> { override fun invoke(cohorts: List) { if (cohorts.isNotEmpty()) { // Cohorts available for FreeWheel activation // Pass these to your FreeWheel ad requests as key-values println("FreeWheel cohorts: $cohorts") } else { println("No active cohorts with a FreeWheel activation configured.") } } } ) ``` Once you have the cohort IDs, append them to your FreeWheel ad request. **Important Notes:** * The `TriggersProvider` API allows you to listen for cohort membership changes * Use the activation type `freewheel` to retrieve FreeWheel-specific cohorts * See the [Android SDK documentation](https://sdk-docs.permutive.com/core/com.permutive.android/-triggers-provider/query-reactions.html) for more details In environments where it is not possible to deploy a Permutive SDK (e.g. some CTV environments), you can use Permutive's CCS (Custom Cohort Segmentation) API for event tracking and segmentation instead. Access to this API requires customer-specific authorization. You will need a dedicated API key from Permutive. ### How it Works The CCS API can be used to: 1. Track events for a specific user (optional) 2. Retrieve cohort IDs for activation The `events` array in the request is optional. If you only need to retrieve cohort memberships without tracking a new event, you can pass an empty array. **Example Request:** ```bash theme={"dark"} curl --request POST \ --url 'https://api.permutive.app/ccs/v1/segmentation?activations=true&synchronous-validation=false&k=PERMUTIVE_API_KEY' \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --data ' { "alias": { "tag": "example", "id": "12345" }, "events": [] } ' ``` The API accepts a user identity passed in the `alias` object. Permutive then either links this identity to an existing Permutive user ID or creates a new Permutive user ID if the identity hasn't been seen before. **Example Response:** ```json theme={"dark"} { "user_id": "2008c38f-dece-4570-976d-87593ed001c3", "cohorts": ["12345", "23456", "34567"], "activations": { "freewheel": ["12345", "23456"] } } ``` The API response includes cohort IDs for FreeWheel activation under the `freewheel` key within the `activations` object. These can then be passed to FreeWheel as key-values for activation. **Important Notes:** * Replace `PERMUTIVE_API_KEY` with your actual Permutive API key * The `activations=true` query parameter is required to receive activation data in the response ### Identity Based Activation Contact your Permutive Customer Success Manager to enable Identity Based Activation for FreeWheel. This is a server-to-server integration that must be configured by Permutive. Once enabled, navigate to Settings → Integrations in the Permutive Dashboard. Find "FreeWheel (ID-based)" in the list of available integrations and click to configure it. Provide your FreeWheel Client ID in the configuration form. This is the same Client ID used for key-value activation if you have that enabled. Choose which identity tags you want to send to FreeWheel. See [Supported Identity Tags](#supported-identity-tags) for all available types. Select the tags that match your use case and the environments where you need activation. Save your configuration. Permutive will begin syncing cohort data to FreeWheel on a daily basis for users with the selected identity types. Navigate to your cohorts in the Permutive Dashboard and enable the "FreeWheel (ID-based)" activation toggle for each cohort you want to sync to FreeWheel. After the first sync completes (within 24 hours), check your FreeWheel dashboard to verify that audiences appear with names matching your activated cohorts. Cohort data syncs to FreeWheel daily. Changes to cohort membership will be reflected in FreeWheel within 24 hours of the next scheduled sync. Identity Based Activation requires that Permutive is collecting the identity types you select. Work with your Permutive Customer Success Manager to ensure identity collection is configured properly for your desired identity types. Identity Based Activation for iOS enables server-to-server cohort syncing using IDFA (Identifier for Advertisers). No SDK code changes are required on iOS devices. **How it works:** * Permutive collects IDFA values from iOS users via the Permutive SDK * Cohort memberships are synced daily to FreeWheel server-to-server * Cohorts appear as Audiences in FreeWheel, ready for campaign targeting * Data syncing happens automatically once configured in the Permutive Dashboard **Prerequisites:** * Permutive SDK must be deployed on your iOS app * IDFA collection must be enabled and properly configured * Users must grant tracking permission for IDFA to be available Identity Based Activation for Android enables server-to-server cohort syncing using AAID (Android Advertising ID). No SDK code changes are required on Android devices. **How it works:** * Permutive collects AAID values from Android users via the Permutive SDK * Cohort memberships are synced daily to FreeWheel server-to-server * Cohorts appear as Audiences in FreeWheel, ready for campaign targeting * Data syncing happens automatically once configured in the Permutive Dashboard **Prerequisites:** * Permutive SDK must be deployed on your Android app * AAID collection must be enabled and properly configured Identity Based Activation for CTV enables server-to-server cohort syncing using IP Address or other supported identifiers. No SDK code changes are required on CTV devices. **How it works:** * Permutive collects IP addresses (or other supported identifiers) from CTV users * Cohort memberships are synced daily to FreeWheel server-to-server * Cohorts appear as Audiences in FreeWheel, ready for campaign targeting * Data syncing happens automatically once configured in the Permutive Dashboard **Prerequisites:** * Permutive must be collecting IP addresses or other supported identifiers from your CTV environment * Work with your Permutive Customer Success Manager to ensure proper identity collection for CTV ## Data Types The FreeWheel integration is a Destination-only integration for cohort activation. It does not collect event data from FreeWheel. When you activate a cohort to FreeWheel using Key-Value Activation, Permutive creates a Federated Segment in FreeWheel and makes cohort IDs available for targeting via key-value pairs. **Cohort ID Format:** * Cohort IDs are stored as an array in local storage under the `_pfws` key * Each ID corresponds to a cohort the user is a member of * Maximum of 250 cohort IDs can be passed per ad request **FreeWheel Federated Segment:** * Created automatically when a cohort is activated * Named using the cohort ID from Permutive * Searchable in FreeWheel's audience targeting interface * Can be used for campaign targeting (include or exclude) * Represents a reusable audience segment across all sites and site sections within your FreeWheel network When you activate a cohort using Identity Based Activation, Permutive uploads audience data enriched with cohort memberships to FreeWheel via a server-to-server transfer. **Data Transfer:** * Cohort data is synced daily from Permutive to FreeWheel * Data is keyed by the identity tags you selected during configuration (see [Supported Identity Tags](#supported-identity-tags)) * Each user's cohort memberships are associated with their identity values **FreeWheel Audiences:** * Cohorts appear as Audiences in FreeWheel * Named using the cohort name from Permutive * Available for campaign targeting in FreeWheel's audience targeting interface * Updated daily with the latest cohort membership data ## Troubleshooting If you cannot see the FreeWheel integration option in your Permutive Dashboard: * Verify that your Permutive Customer Success Manager has enabled the integration for your workspace * Confirm that your FreeWheel account manager has allowed Permutive to create Federated Segments in your FreeWheel account * Check that your FreeWheel Client ID was provided to Permutive Contact your Permutive Customer Success Manager if the integration is still not visible. If cohorts are activated in Permutive but Federated Segments are not created in FreeWheel: * **Client ID**: Verify that the correct FreeWheel Client ID was provided to Permutive * **Federated Segments Permission**: Check with your FreeWheel account manager that Permutive has been allowed to create Federated Segments in your FreeWheel account * **Cohort Activation**: Ensure the cohort has the FreeWheel activation toggle set to *On* in the Permutive Dashboard If the issue persists after checking these items, contact Permutive Support. If you cannot find cohort IDs in local storage (`_pfws` key): * **Cohort Activation**: Verify the cohort has the FreeWheel activation toggle set to *On* in the Permutive Dashboard * **SDK Deployment**: Ensure the Permutive SDK is properly deployed and loading on your web pages * **User Membership**: Confirm the current user is actually a member of the activated cohort (check in browser console using `permutive.segments()`) * **Browser Console**: Open browser console and check `window.localStorage._pfws` to see if the key exists If the key exists but is empty, the user may not be in any activated cohorts. If cohort IDs are in local storage but not appearing in your FreeWheel ad requests: * **Implementation**: Verify you've implemented the code to retrieve cohort IDs from local storage (see Web Setup tab) * **Timing**: Ensure the Permutive SDK loads before you attempt to retrieve cohort IDs * **Ad Request Configuration**: Check that your ad request code is correctly appending the cohort variable to FreeWheel ad calls * **Network Inspection**: Use browser developer tools to inspect network requests to FreeWheel and verify cohort IDs are included For implementation assistance, contact Permutive Support. If cohort IDs are not being returned by the SDK APIs: * **Cohort Activation**: Verify the cohort has the FreeWheel activation toggle set to *On* in the Permutive Dashboard * **SDK Deployment**: Ensure the Permutive SDK is properly deployed and initialized in your app * **Activation Type**: Verify you're using the correct activation type key (`freewheel`) * **User Membership**: Confirm the current user is a member of the activated cohort For iOS, check the `activations` property. For Android, ensure the `TriggersProvider` callback is properly set up with activation type `freewheel`. If the CCS API response includes an empty `freewheel` array or doesn't include the key at all: * **Integration Enabled**: Verify the FreeWheel integration is enabled in the Permutive dashboard * **Cohort Activation**: Check that cohorts have been activated for FreeWheel * **User Membership**: Ensure the user identified in the API request (via `alias`) is a member of activated cohorts * **Query Parameter**: Confirm the `activations=true` query parameter is included in the API request URL * **API Key**: Verify the API key used in the request has the correct permissions For API Direct issues, contact your Permutive Customer Success Manager for assistance. If you need to update your FreeWheel Client ID: * Contact your Permutive Customer Success Manager with the new Client ID * Permutive will update the configuration for your integration Do not attempt to update the Client ID directly - always work with your Customer Success Manager. If you're experiencing issues with Identity Based Activation: * **Missing Audiences**: Verify that cohorts are activated with the "FreeWheel (ID-based)" toggle enabled in the Permutive Dashboard * **Sync Timing**: Remember that cohort data syncs daily - allow up to 24 hours for changes to appear in FreeWheel * **Identity Collection**: Confirm that Permutive is collecting the identity tags you selected (see [Supported Identity Tags](#supported-identity-tags)) by checking with your Permutive Customer Success Manager * **Configuration**: Verify your FreeWheel Client ID and selected identity tags are correct in Settings → Integrations For Identity Based Activation issues, contact your Permutive Customer Success Manager for assistance. ## Changelog ### May 2026 * Documented the full set of supported identity tags for Identity Based Activation, including Connected TV device IDs (RIDA, TIFA, AFAI) ### January 2026 * Added Identity Based Activation support for all environments * Identity Based Activation enables server-to-server cohort upload keyed by AAID, IDFA, or IP Address * Cohorts activated via Identity Based Activation appear as Audiences in FreeWheel ### November 2025 * Documentation updated to clarify that the integration uses FreeWheel Federated Segments ### January 2023 * Initial release of FreeWheel integration for cohort activation For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Google Ad Manager Source: https://docs.permutive.com/integrations/advertising/ad-servers/google-ad-manager Integrate with Google Ad Manager for targeting and campaign optimization
Google Ad Manager

Google Ad Manager

Google Ad Manager is a comprehensive ad management platform that helps publishers sell, deliver, and optimize ads across web, app, and video content.

## Overview Google Ad Manager (GAM) is Google's comprehensive ad serving platform that helps publishers manage their advertising inventory and maximize revenue. Our integration with Google Ad Manager (GAM) primarily enables you to target your advertising campaigns with Permutive cohorts. Additionally, this integration can be used to ingest ad impressions and clicks into Permutive. This integration is both a Source and Destination: * **Source:** Permutive is able to ingest ad impressions and clicks. * **Destination:** Permutive is able to activate your cohorts in GAM. Use cases include: * Send Permutive cohorts to your ad server so that they can be used for decisioning and targeting in real-time. * Track ad impressions and clicks in Permutive, for insights, retargeting or modeling. * **(Advanced) Ad Logs:** Capture every ad click in Permutive through Ad Logs in GAM 360. * **(Advanced) Campaign Optimization:** Optimize your GAM campaigns with Permutive's AI. ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------- | | **Web** | | -- | | **iOS** | | Cohort targeting and ad impression events are supported. Advanced event collection (SlotViewable, SlotClicked, etc.) requires Ad Logs | | **Android** | | Cohort targeting and ad impression events are supported. Advanced event collection (SlotViewable, SlotClicked, etc.) requires Ad Logs | | **CTV** | | Supported via Permutive CTV SDKs | | **API Direct** | | For environments where SDK deployment is not possible | ## Prerequisites * **Network Code** - You must provide your Network Code, which is displayed in your [GAM dashboard](https://support.google.com/admanager/answer/7674889?hl=en). * **Permissions** - You must grant Permutive permission to *create targeting keys and values* in your GAM account. This allows Permutive to programmatically create targeting key-values when you configure cohorts for activation into GAM. To do this, assign [*dfp@permutive.com*](mailto:dfp@permutive.com) a custom role with the following permissions: * View ad units, placements and key-values * Edit ad units, placements and key-values * Edit key-values values * Access to Inventory tab #### Ad Logs Prerequisites For more comprehensive ad click tracking, Permutive is able to ingest GAM Data Transfer Files for customers using Google Ad Manager 360. * You must be using Google Ad Manager 360. * You should have a Data Transfer storage bucket setup, as described [here](https://support.google.com/admanager/answer/1733127). * You must be an Administrator of your GAM network, and able to grant access to your Data Transfer storage bucket. #### Campaign Optimization Prerequisites To utilize Permutive's [Optimization product](/products/workflows-insights/optimization) with your GAM campaigns, there are additional prerequisites: * **Permissions** - Additional GAM permissions are required to enable Campaign Optimization. See the [GAM API Usage Guide](/guides/platform-guides/gam-api-usage) for the complete list of required permissions. Your CSM will work with you to ensure the custom role for [*dfp@permutive.com*](mailto:dfp@permutive.com) includes all necessary permissions. * **AI Opt-in** - You must opt in to Permutive's AI product development principles to access AI-based recommendations. * **Cohorts** - At least 100 cohorts activated to GAM is recommended for consistent AI recommendations. ## Setup You must enable the Google Ad Manager integration in the Permutive dashboard, if this has not already be done. Please ensure you’ve already granted permissions in GAM appropriately (see prerequisites). In the Permutive dashboard, navigate to your workspace's integrations page. Click *Add Integration* and select *Google Ad Manager*. You will be asked to enter your *Network Code* at this stage. In your GAM dashboard, head to "Inventory" => *Key-Values* and search for `permutive`. You should find that a targeting key has been created for *permutive*, with the Reportable attribute set to *On*. If the *Reportable* attribute is *Off*, click on *permutive* and select *include values in reporting*. Hit *Save*. If you cannot find the targeting key-value, please reach out to Permutive Support. In the Permutive Dashboard, navigate to one of your cohorts. Check that the Google Ad Manager activation sync is visible. You can try toggling this to *On*. After doing this, in your GAM dashboard you should see a new targeting value created under the *permutive* targeting key, with the value corresponding your cohort's ID. This verifies your integration setup is successful. ### Ad Logs Setup You must first enable the GAM Logs integration in your Permutive Dashboard. Grant read access to your Ad Manager storage bucket to the user shown in the integration configuration page in the dashboard. For more information please see the "Manage access to your Ad Manager storage buckets" section of the GAM [Data Transfer Files documentation](https://support.google.com/admanager/answer/1733127). In your GAM dashboard, under *Inventory* => *Key-Values*, create the following keys: * puid * prmtvvid * prmtvsid * prmtvwid The value type for all keys must be set as *dynamic* and reporting should be *disabled*. You should see a new event, called *GamLogSlotClicked* on the Events page in your Permutive Dashboard. Check back in 24 hours -- you should see on the Events page that Permutive is ingesting these events from your Ad Logs (check under *Recent Events*). In order for the integration to track impressions and clicks from Mobile Apps, you must first configure our GAM integration for Android and/or iOS. ### Campaign Optimization Setup Please speak to your CSM to enable this feature. ### Deploy GAM specific JavaScript You'll need to deploy a small piece of additional JavaScript on-site, alongside the standard Permutive tag. This script ensures that we are always able to target users with cohort membership data from their previous pages views or browsing sessions. We recommend deploying this script immediately above the Permutive tag. In many cases, this step will have been carried out as part of your initial Permutive deployment. ```javascript theme={"dark"} window.googletag = window.googletag || {}; window.googletag.cmd = window.googletag.cmd || []; window.googletag.cmd.push(function () { if (window.googletag.pubads().getTargeting('permutive').length === 0) { var kvs = window.localStorage.getItem('_pdfps'); window.googletag.pubads().setTargeting('permutive', kvs ? JSON.parse(kvs) : []); } }); ``` ### Integrate Permutive with Google Mobile Ads on iOS You'll need to update your iOS apps to include the Permutive iOS SDK to target your GAM ads in-app. Our SDK offers a `googleCustomTargeting` function to return a dictionary of current targeting data. This is able to be added to the `customTargeting` of `GAMRequest` instances to include current Permutive information for ad targeting. The returned dictionary contains Permutive targeting data for the user. If the ad request is for a "page" being tracked through a `PageTracker`, the `googleCustomTargeting` function can accept an optional active `PageTracker` instance parameter, if available, to improve targeting information. If you are already setting `customTargeting` property on your `GAMRequest`, you will need to merge the returned dictionary with your existing values. The returned dictionary from calling `googleCustomTargeting` will contain the following keys and their associated values: ```json theme={"dark"} [ "permutive": ["Cohort1", "Cohort2"], "puid": "permutiveID", "ptime": timestamp, "prmtvwid": randomUUID, "prmtvsid": sessionId, // if available "prmtvvid": viewID // if available (will be added if using a PageTracker) ] ``` To use this method, simply call `Permutive.shared.googleCustomTargeting`. You can optionally pass in a `PageTracker` instance if you wish to populate the prmtvvid field as shown in the example below. ```swift Swift theme={"dark"} let banner = GAMBannerView(adSize: kGADAdSizeBanner) let adRequest = GAMRequest() adRequest.customTargeting = Permutive.shared.googleCustomTargeting(adTargetable: pageTracker) /* if using pagetracker, otherwise can be nil*/ banner.load(request) ``` ```objective-c Objective-C theme={"dark"} GAMRequest *adRequest = [GAMRequest request]; NSDictionary *customTargeting = [Permutive.shared googleCustomTargetingWithAdTargetable:nil]; request.customTargeting = customTargeting; [bannerView loadRequest:adRequest]; ``` ### Integrate Permutive with Google Mobile Ads on Android You'll need to integrate your Android apps to include the Permutive Android SDK to target your ads in-app. Alongside our Android SDK, you must add our google-ads dependency to your Gradle build: ```kotlin Kotlin theme={"dark"} implementation("com.permutive.android:google-ads:2.2.0") ``` ```groovy Groovy theme={"dark"} implementation "com.permutive.android:google-ads:2.2.0" ``` Our Android SDK then provides an easy way to add Permutive targeting data to your Ad Manager ad requests: ```kotlin Kotlin theme={"dark"} // To add custom targeting: val adRequest1 = AdManagerAdRequest.Builder() .addPermutiveTargeting(permutive) .build() // Or simply without the build call: val adRequest2 = AdManagerAdRequest.Builder() .buildWithPermutiveTargeting(permutive) ``` ```java Java theme={"dark"} // To add custom targeting: AdManagerAdRequest adRequest = new AdManagerAdRequest.Builder() .addPermutiveTargeting(permutive) .build(); ``` ### Cohort Activation via API Direct In environments where it is not possible to deploy a Permutive SDK, you can use Permutive's CCS (Custom Cohort Segmentation) API for event tracking and segmentation instead. Access to this API requires customer-specific authorization. You will need a dedicated API key from Permutive. #### How it Works The CCS API can be used to: 1. Track events for a specific user (optional) 2. Retrieve cohort IDs for activation The `events` array in the request is optional. If you only need to retrieve cohort memberships without tracking a new event, you can pass an empty array. **Example Request:** ```bash theme={"dark"} curl --request POST \ --url 'https://api.permutive.app/ccs/v1/segmentation?activations=true&synchronous-validation=false&k=PERMUTIVE_API_KEY' \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --data ' { "alias": { "tag": "user_id", "id": "12345" }, "events": [ { "name": "Pageview", "time": "2025-10-02T09:40:43.179Z", "properties": {} } ] } ' ``` The API accepts a user identity passed in the `alias` object. Permutive then either links this identity to an existing Permutive user ID or creates a new Permutive user ID if the identity hasn't been seen before. For cross-environment segmentation and targeting, the identity provided should be consistent with identities used in your other environments (web, app). **Example Response:** ```json theme={"dark"} { "user_id": "2008c38f-dece-4570-976d-87593ed001c3", "cohorts": ["12345", "23456", "34567"], "activations": { "target_dfp": ["12345"] } } ``` The API response includes cohort IDs for GAM activation under the `target_dfp` key within the `activations` object. These can then be passed to GAM as key-values for targeting. **Important Notes:** * Replace `PERMUTIVE_API_KEY` with your actual Permutive API key * The `activations=true` query parameter is required to receive activation data in the response ## Usage * [Activate Contextual Cohorts in GAM](/guides/signals/cohorts/contextual/deploying-to-google-ad-manager) — target ads based on page content without the Permutive SDK * [Optimize your campaigns with Campaign Optimization](/products/workflows-insights/optimization) ## Data Types With your GAM integration setup, you'll see the following additional event types collected in Permutive: Tracks for every ad impression that becomes viewable. Based on Google Publisher Tag's [ImpressionViewableEvent](https://developers.google.com/publisher-tag/reference#googletag.events.ImpressionViewableEvent). The Advertiser ID for this impression, from GAM. The Line Item ID for this impression, from GAM. The Campaign ID for this impression, from GAM. The Creative ID for this impression, from GAM. The service name, from GAM. The ad unit path, from GAM. The slot element ID, from GAM. Tracks a subset of your ad clicks. The Advertiser ID for this impression, from GAM. The Line Item ID for this impression, from GAM. The Campaign ID for this impression, from GAM. The Creative ID for this impression, from GAM. The service name, from GAM. The ad unit path, from GAM. The slot element ID, from GAM. Indicates whether an empty creative was served. The width of the creative served. The height of the creative served. ### Ad Logs Data Types Tracks your ad clicks comprehensively, based on GAM's [Data Transfer Files](https://support.google.com/admanager/answer/1733124?hl=en#zippy=%2Cabout-the-data-contained-in-data-transfer-files%2Cdownload-a-sample-file%2Chow-files-are-delivered%2Cfile-names%2Cdata-transfer-files-in-the-ad-request-process%2Cstore-files-locally%2Clearn-about-the-bigquery-data-transfer-service%2Cmake-large-data-transfer-files-easier-to-process). The Advertiser ID for this impression, from GAM. The Line Item ID for this impression, from GAM. List of targeting keys present on the impression. Represents a GAM targeting key. Represents GAM targeting values. ## Troubleshooting If you see an error when enabling the Google Ad Manager integration, check that you have granted the correct permissions to *[dfp@permutive.com](mailto:dfp@permutive.com)* (see Prerequisites). Double check that you have granted the correct permissions to *[dfp@permutive.com](mailto:dfp@permutive.com)* (see Prerequisites). In rare cases, you may have reached GAMs limit of 200 active targeting keys. In this case, you must remove an old/inactive key to enable Permutive. If Permutive cohorts are not being passed to GAM, check the following: * **Timing Issue**: Open your browser console and append `?googfc` to your site URL to access the GAM console. Look for the "permutive" targeting key in the **Page Request** tab. If Permutive values appear **after** "Calling fillslot", the targeting is being set too late. To fix this, ensure the Permutive tag loads as early as possible in the page ``, ideally before `googletag.enableServices()` is called. * **Missing Cached Segments**: Deploy the GAM-specific JavaScript snippet (shown in the Web setup tab) to ensure cached cohorts from previous sessions are always available for targeting. * **First Pageview Targeting**: If first-pageview targeting is critical, consider implementing a timeout that waits for Permutive to load before enabling GAM services. For iOS and Android apps, verify the following: * **iOS**: Confirm you're calling `Permutive.shared.googleCustomTargeting()` and passing the result to your `GAMRequest.customTargeting` property. If using a PageTracker, pass it as a parameter to include the `prmtvvid` field. * **Android**: Ensure you've added the `com.permutive.android:google-ads` dependency and are using `.addPermutiveTargeting(permutive)` or `.buildWithPermutiveTargeting(permutive)` on your `AdManagerAdRequest.Builder`. * **AAID/IDFA**: Use a proxy tool (like mitmproxy) to verify the Permutive SDK is tracking the AAID (Android) or IDFA (iOS) identifier. On Android, this requires explicit implementation as outlined in the developer documentation. * **Event Validation**: Check that Permutive events are being accepted (HTTP 200 response) and not rejected (4xx response). Enable debug mode on Android with `setDeveloperMode(true)` to see detailed logs. If your targetable impression volume is lower than expected: * Ensure the Permutive tag loads early in the page lifecycle, preferably in the `` tag rather than through a tag manager. * Implement the GAM-specific JavaScript snippet to read cached cohorts from local storage, ensuring returning visitors are immediately targetable. * Review the timing of when `googletag.enableServices()` is called relative to Permutive initialization. Consider adding a configurable timeout (e.g., 2 seconds) to wait for Permutive before enabling GAM services. ## Changelog ### June 2025 * Enhanced support for activating Contextual Cohorts via GAM For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Nativo Source: https://docs.permutive.com/integrations/advertising/ad-servers/nativo Integrate with Nativo for targeting and campaign optimization
Nativo

Nativo

Nativo helps publishers deliver and manage native advertising formats that blend seamlessly with editorial content.

## Overview Nativo is a native advertising platform that helps publishers deliver and manage native ad formats. The Permutive integration with Nativo enables you to activate your Permutive cohorts for targeting in Nativo ad campaigns. This integration is a Destination: * **Destination:** Permutive activates cohorts into Nativo for audience targeting via key-value pairs. This integration supports two activation methods: * **Key-value activation:** Cohort IDs are read from the Permutive SDK and passed as key-value pairs in Nativo ad requests via Nativo's tag (Web) or mobile SDK (iOS/Android). * **[Server-side identity-based activation](/integrations/server-side-identity-based-activation):** Can be an alternative path where client-side activation isn't viable. Permutive uploads cohort memberships to Nativo keyed by identity values (cookie ID, IDFA, AAID, IP address, or hashed email). Cohorts appear as Nativo targeting segments ready for use. Use cases include: * Target Nativo ad campaigns with Permutive audience segments * Deliver personalized native advertising based on user behavior and interests * Optimize native ad performance with audience insights * Activate audiences across Web and in-app (iOS/Android) inventory through key-value or server-side identity-based methods ## Environment Compatibility | Environment | Activation | | -------------- | -------------------------------------------------------------------------------------------------------------- | | **Web** | Live — cohort IDs read from `_pnativo` local storage and attached to `window.ntvConfig.keyValues` | | **iOS** | Available — customer implementation via Nativo's iOS SDK | | **Android** | Available — customer implementation via Nativo's Android SDK | | **API Direct** | Available — cohort IDs retrieved via Permutive CCS API and passed to Nativo ad calls | **[Server-side identity-based activation](/integrations/server-side-identity-based-activation)** can be an alternative path for this integration, particularly where client-side activation isn't viable. Permutive can support your team in setting this up — contact your Customer Success Manager to discuss. ## Prerequisites **Required for all activation methods:** * **Nativo Account** — an active Nativo account with access to manage ad campaigns and targeting settings. * **Active Cohorts** — cohorts created in Permutive that you want to activate for targeting in Nativo campaigns. **For Web:** * **Permutive Web SDK** — deployed on your web properties where Nativo ads will be served. * **Nativo tag** — deployed on your pages with the cohort-passing script (see Web setup tab). **For iOS / Android:** * **Permutive Mobile SDK** — deployed in your iOS / Android app. * **Nativo mobile SDK** — deployed in your app. * **Customer implementation** — to retrieve Permutive cohorts from the SDK and attach them as targeting key-values in Nativo ad calls. **For server-side identity-based activation:** * This connector is not built today. If your use case calls for it, identity collection would need to be configured in Permutive for the identity types you want to use (cookie ID, IDFA, AAID, IP address, hashed email). * Contact your Permutive Customer Success Manager to discuss. ## Setup In the Permutive dashboard, navigate to your workspace's integrations page. Click *Add Integration* and select *Nativo* from the list of available ad server integrations. Once enabled, you'll be able to activate cohorts to Nativo from the cohort configuration page. Navigate to the cohort you want to activate for Nativo targeting. In the cohort's activation settings, toggle the Nativo activation to *On*. When a cohort is activated, Permutive will automatically sync the cohort membership data to the browser's local storage, making it available for Nativo ad requests. After enabling the integration and activating cohorts, you can verify the setup by: 1. Open your browser's Developer Tools 2. Navigate to the Application tab (Chrome) or Storage tab (Firefox) 3. Check Local Storage for your domain 4. Look for the `_pnativo` key - it should contain an array of cohort IDs for activated cohorts Once verified, proceed to deploy the Nativo-specific script on your web properties (see Web tab). ### Deploy Nativo-specific JavaScript You'll need to deploy a small piece of JavaScript on your web properties to enable Permutive to append targeting key-values to Nativo ad requests. This script reads cohort membership data from local storage and makes it available to Nativo's ad server. Deploy this script on pages where Nativo ads will be served. We recommend placing it before Nativo's ad request initialization: ```javascript theme={"dark"} ``` **What this script does:** * Initializes the `window.ntvConfig` object if it doesn't exist * Reads cohort membership data from the `_pnativo` local storage key * Appends cohort IDs as a comma-separated string to `window.ntvConfig.keyValues.permutive` * Makes this data available for Nativo to use in ad targeting This script should be deployed on all pages where Nativo ads are served, alongside your standard Permutive tag. Nativo supports in-app inventory via its iOS mobile SDK. Activation of Permutive cohorts on iOS in-app traffic requires customer implementation to retrieve cohort signals from the Permutive Mobile SDK and attach them as key-value targeting in Nativo ad calls. **High-level pattern:** 1. Initialise the Permutive Mobile SDK in your iOS app. 2. Retrieve the user's active cohort IDs from the SDK. 3. Attach the cohort IDs as a key-value (under the key `permutive`) in your Nativo iOS SDK ad request — mirroring the `window.ntvConfig.keyValues.permutive` pattern used on Web. 4. Verify that Nativo receives Permutive cohort data in the ad request. Contact your Permutive Customer Success Manager — our team will partner with you on the integration. Nativo supports in-app inventory via its Android mobile SDK. Activation of Permutive cohorts on Android in-app traffic requires customer implementation to retrieve cohort signals from the Permutive Mobile SDK and attach them as key-value targeting in Nativo ad calls. **High-level pattern:** 1. Initialise the Permutive Mobile SDK in your Android app. 2. Retrieve the user's active cohort IDs from the SDK (e.g. via `TriggersProvider`). 3. Attach the cohort IDs as a key-value (under the key `permutive`) in your Nativo Android SDK ad request — mirroring the `window.ntvConfig.keyValues.permutive` pattern used on Web. 4. Verify that Nativo receives Permutive cohort data in the ad request. Contact your Permutive Customer Success Manager — our team will partner with you on the integration. ## Data Types The Nativo integration is a cohort activation integration and does not collect event data into Permutive. Instead, it passes cohort membership data from Permutive to Nativo for ad targeting purposes. **Cohort Data Format:** Cohort IDs are stored in the browser's local storage under the `_pnativo` key as a JSON array. These are then passed to Nativo's `window.ntvConfig.keyValues.permutive` as a comma-separated string. Example format: ```javascript theme={"dark"} // Local storage: _pnativo ["cohort-id-1", "cohort-id-2", "cohort-id-3"] // Passed to Nativo as: window.ntvConfig.keyValues.permutive = "cohort-id-1,cohort-id-2,cohort-id-3" ``` ## Troubleshooting If you don't see the `_pnativo` key in your browser's local storage: 1. Verify that the Nativo integration is enabled in the Permutive dashboard 2. Check that you have activated at least one cohort for Nativo 3. Ensure the Permutive SDK is properly deployed and firing page view events 4. Verify that the user qualifies for at least one activated cohort 5. Check browser console for any JavaScript errors that might be blocking Permutive You can verify cohort membership by checking other Permutive local storage keys or using the Permutive Chrome Extension. If Nativo campaigns are not receiving cohort targeting data: 1. Verify that the Nativo-specific script has been deployed on the page 2. Check that the script is placed before Nativo's ad request initialization 3. Open browser Developer Tools and check the console for any JavaScript errors 4. Verify that `window.ntvConfig.keyValues.permutive` is populated by inspecting the console: ```javascript theme={"dark"} console.log(window.ntvConfig.keyValues.permutive); ``` 5. Ensure that the `_pnativo` local storage key contains valid cohort data If the key-value is populated correctly but Nativo still isn't receiving it, contact Nativo support to verify their side of the integration. If you see cohorts with 0 live size in Permutive insights despite having qualifying users: 1. Check that users are actively browsing your site and triggering page view events 2. Verify that cohort logic is correctly configured and users meet the qualification criteria 3. Allow up to 24 hours for cohort membership data to populate in insights 4. Check the cohort's definition to ensure there are no overly restrictive filters If the issue persists after 24 hours, contact Permutive Support for investigation. If cohort data in `_pnativo` is being cleared between browsing sessions: 1. Check browser settings to ensure local storage is not being cleared on exit 2. Verify that third-party cookie blocking or privacy settings are not affecting local storage 3. Check for browser extensions that might be clearing local storage 4. On Safari, ensure Intelligent Tracking Prevention (ITP) settings are not interfering Permutive automatically refreshes local storage data on each page view, so even if data is cleared, it should be repopulated on the next visit. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Microsoft Monetize Source: https://docs.permutive.com/integrations/advertising/ad-servers/xandr Integrate with Microsoft Monetize (Ad Server) for real-time cohort targeting and campaign insights
Microsoft Monetize

Microsoft Monetize (Ad Server)

Microsoft Monetize (formerly Xandr, AppNexus) is a comprehensive ad serving platform that enables publishers to manage and monetize their advertising inventory with advanced targeting and reporting capabilities.

## Overview Microsoft Monetize (formerly Xandr, AppNexus) is Microsoft's ad serving platform that provides publishers with tools to manage, deliver, and optimize advertising campaigns. Our integration with Microsoft Monetize enables you to activate Permutive cohorts for real-time targeting and collect comprehensive ad performance data for insights and optimization. This integration is both a Source and Destination: * **Source:** Permutive collects ad impression and click events from Microsoft Monetize. * **Destination:** Permutive activates your cohorts in Microsoft Monetize using targeting key-value pairs. Use cases include: * Target your advertising campaigns in real-time with rich Permutive cohorts using Microsoft Monetize's ad server. * Track ad impressions and clicks in Permutive for insights, retargeting, or modeling. * **(Advanced) Log-Level Data:** Ingest comprehensive ad event data from Microsoft Monetize Log-Level Data Feeds for enhanced reporting and mobile support. ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----------------------------------------------------- | | **Web** | | Full support with AppNexus Seller Tag | | **iOS** | | Supported via Permutive SDK add-on | | **Android** | | Supported via Permutive SDK add-on | | **CTV** | | Supported via Permutive CTV SDKs | | **API Direct** | | For environments where SDK deployment is not possible | ## Prerequisites * **Member ID** - You must provide your Microsoft Monetize Member ID, which can be found in your [Microsoft Monetize dashboard](https://monetize.xandr.com/). * **User Credentials** - You must create a Microsoft Monetize user account for Permutive with appropriate permissions. We recommend using an account with the following permissions: * View and edit key-values * Create and manage targeting keys * Access to inventory settings * **AppNexus Seller Tag** - For Web integration, you must already have the [AppNexus Seller Tag](https://learn.microsoft.com/en-us/xandr/seller-tag/seller-tag) installed on your site and configured to serve your advertising campaigns. #### Log-Level Data Prerequisites For comprehensive ad event tracking including mobile support and line item IDs, Permutive can ingest Microsoft Monetize Log-Level Data Feeds. * You must have access to Microsoft Monetize's Log-Level Data Feeds feature. * You must be able to configure log export to a Permutive-provided GCS bucket. * You must provide your Microsoft Monetize Member ID (this must match the member ID writing to the GCS bucket). ## Setup You must enable the Microsoft Monetize (Ad Server) integration in the Permutive dashboard. In the Permutive dashboard, navigate to your workspace's integrations page. Click *Add Integration* and select *Microsoft Monetize (Ad Server)*. You will be asked to provide: * Your Microsoft Monetize Member ID * Microsoft Monetize username and password (for API access) In your Microsoft Monetize dashboard, navigate to *Inventory* => *Key-Values* and search for `permutive`. You should find that a targeting key has been created for *permutive*. If you cannot find the targeting key, please reach out to Permutive Support. In the Permutive Dashboard, navigate to one of your cohorts. Check that the Microsoft Monetize (Ad Server) activation sync is visible. You can try toggling this to *On*. After doing this, in your Microsoft Monetize dashboard you should see a new targeting value created under the *permutive* targeting key, with the value corresponding to your cohort's ID. This verifies your integration setup is successful. ### Log-Level Data Setup Enable the Microsoft Monetize Log-Level Data integration in your Permutive Dashboard. You will need to specify: * Your Microsoft Monetize Member ID * Whether to ingest clicks * Whether to ingest impressions When enabled, Permutive will create a GCS bucket `permutive-xandr-logs-` for your log files. In your Microsoft Monetize account, configure your [Log-Level Data Feeds](https://learn.microsoft.com/en-us/xandr/log-level-data/log-level-data-feeds) to export to the Permutive-provided GCS bucket. Check back after 24 hours. You should see new events (`XandrLogAdImpression` and `XandrLogAdClick`) on the Events page in your Permutive Dashboard with line item IDs included. Log-Level Data integration requires the standard Microsoft Monetize (Ad Server) integration (for event collection) to be enabled first. ### Deploy Microsoft Monetize-specific JavaScript You'll need to deploy additional JavaScript on-site, alongside the standard Permutive tag. This script ensures that we are always able to target users with cohort membership data from their previous page views or browsing sessions, and that we collect all ad events even if they occur before Permutive has fully loaded. We recommend deploying this script immediately above the Permutive tag: ```javascript theme={"dark"} window.apntag = window.apntag || {}; window.apntag.anq = window.apntag.anq || []; window.__permutive = window.__permutive || {}; window.__permutive.appnexusEvents = window.__permutive.appnexusEvents || []; [ "adRequested", "adAvailable", "adBadRequest", "adLoaded", "adNoBid", "adError", "adCollapse", ].forEach(function (eventType) { window.apntag.anq.push(function () { window.apntag.onEvent(eventType, function (arg) { window.__permutive.appnexusEvents.push({ eventType: eventType, arg: arg, }); }); }); }); window.apntag.anq.push(function () { var original = window.apntag.defineTag; window.apntag.defineTag = function (arg) { original(arg); try { if (arg.targetId) { var kvs = window.localStorage.getItem("_papns"); window.apntag.setKeywords( arg.targetId, { permutive: kvs ? JSON.parse(kvs) : [] }, { overrideKeyValue: true } ); } } catch (e) { /* Ignore */ } }; }); ``` In many cases, this step will have been carried out as part of your initial Permutive deployment. ### Integrate Permutive with Microsoft Monetize on iOS You'll need to integrate the Permutive iOS SDK with your Microsoft Monetize (Xandr) ad requests to enable cohort targeting in-app. Permutive offers automatic Xandr ad targeting through a Permutive SDK Add-On library. This add-on provides seamless integration with the Xandr SDK to automatically apply Permutive targeting to your ad requests. For complete implementation details and the latest installation instructions, please refer to the [Permutive iOS SDK Xandr Add-On documentation](https://github.com/permutive-engineering/permutive-ios-sdk-xandr-plugin). You must have the Permutive iOS SDK installed and configured before adding the Xandr add-on. ### Integrate Permutive with Microsoft Monetize on Android You'll need to integrate the Permutive Android SDK with your Microsoft Monetize (Xandr) ad requests to enable cohort targeting in-app. Alongside our Android SDK, you must add our Xandr/AppNexus add-on dependency to your Gradle build: ```kotlin Kotlin theme={"dark"} implementation("com.permutive.android:appnexus:1.7.0") ``` ```groovy Groovy theme={"dark"} implementation "com.permutive.android:appnexus:1.7.0" ``` Our Android SDK then provides methods to automatically add Permutive targeting to your Xandr ads and track impressions: ```kotlin Kotlin theme={"dark"} // For AdViews - add Permutive targeting banner.addPermutiveTargeting(permutive) banner.loadAd() // For NativeAdRequests - add Permutive targeting nativeAd.addPermutiveTargeting(permutive) // For impression tracking - without existing listener banner.setPermutiveAdListener(permutive) // For impression tracking - with existing listener banner.setPermutiveAdListener( permutive, object : AdListener { // ... your listener methods } ) ``` ```java Java theme={"dark"} // For AdViews - add Permutive targeting banner.addPermutiveTargeting(permutive); banner.loadAd(); // For NativeAdRequests - add Permutive targeting nativeAd.addPermutiveTargeting(permutive); // For impression tracking banner.setPermutiveAdListener(permutive); ``` If you are already using an `AdListener`, pass it as the optional second parameter to `setPermutiveAdListener` to avoid conflicts with Permutive impression tracking. ### Cohort Activation via API Direct In environments where it is not possible to deploy a Permutive SDK, you can use Permutive's CCS (Custom Cohort Segmentation) API for event tracking and segmentation instead. Access to this API requires customer-specific authorization. You will need a dedicated API key from Permutive. #### How it Works The CCS API can be used to: 1. Track events for a specific user (optional) 2. Retrieve cohort IDs for activation The `events` array in the request is optional. If you only need to retrieve cohort memberships without tracking a new event, you can pass an empty array. **Example Request:** ```bash theme={"dark"} curl --request POST \ --url 'https://api.permutive.app/ccs/v1/segmentation?activations=true&synchronous-validation=false&k=PERMUTIVE_API_KEY' \ --header 'accept: application/json' \ --header 'content-type: application/json' \ --data ' { "alias": { "tag": "user_id", "id": "12345" }, "events": [ { "name": "Pageview", "time": "2025-10-02T09:40:43.179Z", "properties": {} } ] } ' ``` The API accepts a user identity passed in the `alias` object. Permutive then either links this identity to an existing Permutive user ID or creates a new Permutive user ID if the identity hasn't been seen before. For cross-environment segmentation and targeting, the identity provided should be consistent with identities used in your other environments (web, app). **Example Response:** ```json theme={"dark"} { "user_id": "2008c38f-dece-4570-976d-87593ed001c3", "cohorts": ["12345", "23456", "34567"], "activations": { "appnexus_adserver": ["12345"] } } ``` The API response includes cohort IDs for Microsoft Monetize activation under the `appnexus_adserver` key within the `activations` object. These can then be passed to Microsoft Monetize as key-values for targeting. **Important Notes:** * Replace `PERMUTIVE_API_KEY` with your actual Permutive API key * The `activations=true` query parameter is required to receive activation data in the response ## Data Types With your Microsoft Monetize (Ad Server) integration setup, you'll see the following additional event types collected in Permutive: Tracks ad impression events from the AppNexus Seller Tag on web. Based on Microsoft Monetize's ad event callbacks. Unique identifier for the ad auction. The Advertiser ID for this impression. The Line Item ID for this impression (only available with Log-Level Data integration). The Campaign ID for this impression. The Creative ID for this impression. The ad tag/placement identifier. Tracks ad click events from the AppNexus Seller Tag on web. Unique identifier for the ad auction. The Advertiser ID for this click. The Campaign ID for this click. The Creative ID for this click. The ad tag/placement identifier. ### Log-Level Data Event Types Tracks comprehensive ad impression data from Microsoft Monetize Log-Level Data Feeds. Includes line item IDs and supports mobile platforms. Unique identifier for the ad auction, linking to edge SDK events. The Advertiser ID for this impression. The Line Item ID for this impression, enabling detailed campaign reporting. The Campaign ID for this impression. The Creative ID for this impression. Your Microsoft Monetize Member ID. Tracks comprehensive ad click data from Microsoft Monetize Log-Level Data Feeds, including clicks from mobile apps. Unique identifier for the ad auction, linking to edge SDK events. The Advertiser ID for this click. The Line Item ID for this click. The Campaign ID for this click. The Creative ID for this click. Your Microsoft Monetize Member ID. Log-Level Data events are linked to edge SDK events via the `auction_id` field, allowing Permutive to enrich log data with user IDs and session context. ## Troubleshooting If you see an error when enabling the Microsoft Monetize (Ad Server) integration, check that you have provided valid Microsoft Monetize credentials with the correct permissions. The user account must have permissions to view and edit key-values and access inventory settings. Verify your Member ID is correct. Double check that the Microsoft Monetize user credentials provided to Permutive have the correct permissions to create and manage targeting keys. The user must have access to the inventory section and permissions to edit key-values. If you have reached Microsoft Monetize's limit of targeting keys, you may need to remove an old/inactive key first. First, verify the cohort is toggled *On* for Microsoft Monetize (Ad Server) activation in the Permutive Dashboard. Then check in your Microsoft Monetize dashboard under *Inventory* => *Key-Values* that the cohort ID appears as a value under the `permutive` targeting key. If the value is missing, try toggling the cohort activation off and on again, and contact Permutive Support if the issue persists. Verify that you have enabled both the standard Microsoft Monetize (Ad Server) integration (for event collection) and the Log-Level Data integration. Check that your Microsoft Monetize Log-Level Data Feeds are correctly configured to export to the Permutive-provided GCS bucket. Allow 24 hours for initial data to appear. If events still don't appear, verify your Member ID matches in both the dashboard configuration and the service account writing to the GCS bucket. Line item IDs are only available when using the Log-Level Data integration. Standard edge SDK events (`AppNexusAdImpression` and `AppNexusAdClick`) do not include line item IDs due to limitations of the AppNexus Seller Tag API. To get line item IDs, you must enable the Log-Level Data Feeds integration. ## Changelog ### June 2025 * Enhanced support for activating cohorts via Microsoft Monetize (Ad Server) with improved targeting key management ### November 2019 * Initial release of Microsoft Monetize (Ad Server) (formerly Xandr AdServer, AppNexus) integration with cohort activation and event collection For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Amazon Publisher Services (APS) Source: https://docs.permutive.com/integrations/advertising/bidstream/aps Activate identity signals in Amazon Publisher Services bidstream
Amazon Publisher Services

Amazon Publisher Services (APS)

Amazon Publisher Services (APS) is a header bidding platform that helps publishers maximize revenue through real-time bidding. Permutive activates identity signals in APS bid requests.

## Overview Amazon Publisher Services (APS) is a cloud-based header bidding solution that enables publishers to offer their ad inventory to multiple demand sources. APS provides publishers with access to Amazon's demand alongside other programmatic buyers through a transparent, fee-free marketplace. Key benefits include: * **Access to Amazon Demand:** Connect with Amazon as a bidder on your inventory. As one of the most visited sites globally, Amazon offers significant advertiser demand. * **Reduced Latency:** APS's server-side header bidding infrastructure minimizes page load impact compared to traditional client-side solutions. * **Transparency:** Full visibility into who bids, at what price, and how auctions are won—with no hidden fees. * **Signal Measurement:** APS's Signal IQ helps publishers measure how identity signals influence bid rates, CPMs, and revenue from demand partners. Permutive integrates with APS to activate identity signals from third-party identity providers in bid requests, improving addressability and match rates for demand-side platforms. This integration is a Destination only: * **Destination (Identity Signal Activation):** Permutive retrieves identity signals from third-party identity providers (RampID, ID5, UID2) and makes them available for APS bid requests via the ORTB2 `user.eids` field. The APS integration currently supports identity signal activation only. Cohort activation is not available for APS at this time. ### How It Works 1. **Integration Enablement:** The integration is enabled and configured by Permutive and APS upon customer request—no publisher-side module installation is required. 2. **Identity Provider Configuration:** Identity providers (RampID, ID5, UID2) must be configured in the Permutive Dashboard. The Permutive Web SDK retrieves identity envelopes from these providers. 3. **Client-Side Activation:** Identity signals are included in APS bid requests via the standard ORTB2 `user.eids` field, ensuring compatibility with DSPs that support this standard. Use cases include: * Pass identity signals (RampID, ID5, UID2) to APS bidders for enhanced addressability and targeting. * Improve match rates with demand-side platforms by providing persistent identity signals. * Leverage APS's Signal IQ to measure the revenue impact of your identity signal investments. ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | -------------------------- | | **Web** | | Requires Permutive Web SDK | | **iOS** | | Not supported | | **Android** | | Not supported | | **CTV** | | Not supported | | **API Direct** | | Not supported | ## Prerequisites Before enabling the APS integration, ensure you have the following: * **Permutive Web SDK:** The Permutive Web SDK must be deployed on your website to collect and retrieve identity signals. * **Identity Provider Setup:** You must have at least one identity provider (RampID, ID5, or UID2) configured in the Permutive Dashboard. See the [RampID](/integrations/identity/rampid), [ID5](/integrations/identity/id5), or [UID2](/integrations/identity/uid2) integration documentation for setup instructions. * **APS Account:** You must have an active Amazon Publisher Services account with header bidding enabled. * **Customer Success Manager Approval:** This integration requires coordination between Permutive and APS and must be enabled by your Customer Success Manager. ## Setup Ensure you have configured at least one identity provider (RampID, ID5, or UID2) in the Permutive Dashboard. Navigate to **Identity** → **Catalog** and follow the setup instructions for your chosen identity provider(s). See the [Identity Integration Catalog](/integrations/identity) for detailed setup guides for each provider. The APS integration is available on request and requires coordination between Permutive and Amazon Publisher Services. Contact your Customer Success Manager to: * Request enablement of the APS integration * Provide your APS account details * Confirm which identity providers you want to activate (RampID, ID5, UID2, or a combination) Your CSM will work with both Permutive's engineering team and APS to enable the integration on your behalf. After the integration is enabled, verify that identity signals are being included in APS bid requests: 1. Open your browser's Developer Tools and navigate to the Network tab 2. Filter for requests to APS endpoints (typically `aax.amazon-adsystem.com`) 3. Inspect the bid request payload and look for the `user.eids` field 4. Confirm that your configured identity providers (RampID, ID5, UID2) are present in the `eids` array If you do not see identity signals in the bid requests, contact Permutive Support for assistance. ## Data Types The APS integration does not collect data from APS. It only sends identity signals to APS for activation in bid requests. For details on the identity signals sent to APS, refer to the documentation for your configured identity provider: * [RampID Integration](/integrations/identity/rampid) * [ID5 Integration](/integrations/identity/id5) * [UID2 Integration](/integrations/identity/uid2) ## Troubleshooting If identity signals are not present in APS bid requests: 1. **Verify Identity Provider Configuration:** Ensure your identity provider (RampID, ID5, or UID2) is properly configured in the Permutive Dashboard under **Settings > Identity**. 2. **Check Web SDK Deployment:** Confirm the Permutive Web SDK is deployed and loading correctly on your website. Open your browser's Developer Tools Console and look for any Permutive-related errors. 3. **Verify Integration Enablement:** Confirm with your Customer Success Manager that the APS integration has been fully enabled for your account. 4. **Check User Consent:** If operating in regions with GDPR or similar privacy regulations, verify that the user has provided appropriate consent for identity signal collection and activation. 5. **Contact Support:** If identity signals are still not appearing, contact Permutive Support with the following information: * Your APS account ID * Which identity providers you have configured (RampID, ID5, UID2) * Example bid request payloads showing the missing `user.eids` field If the integration was previously working but identity signals are no longer appearing: 1. **Check for Identity Provider Expiration:** Some identity providers have time-limited envelopes. Verify that your identity provider tokens have not expired by checking the Permutive Web SDK's local storage for identity data. 2. **Verify APS Configuration:** Confirm with APS that there have been no recent changes to your APS account configuration that might affect identity signal passthrough. 3. **Review Permutive Dashboard Settings:** Check the Permutive Dashboard to ensure your identity provider configuration has not been modified or disabled. 4. **Contact Support:** If the issue persists, contact Permutive Support for assistance in diagnosing the problem. The APS integration currently supports identity signal activation only. Cohort activation is not available for APS at this time. If you need cohort activation in the programmatic bidstream, consider using the [Prebid integration](/integrations/advertising/bidstream/prebid), which supports both cohort activation and identity signal activation. Contact your Customer Success Manager if you have questions about roadmap or future support for cohort activation in APS. ## Changelog For a complete list of updates and changes, visit [changelog.permutive.com](https://changelog.permutive.com). # Catalog Source: https://docs.permutive.com/integrations/advertising/bidstream/catalog Browse our comprehensive catalog of bidstream integrations. # Bidstream Integrations
Prebid

Prebid

Prebid is an open-source header bidding solution that enables publishers to maximize revenue through real-time bidding.

Amazon Publisher Services

Amazon Publisher Services

Amazon Publisher Services (APS) is a header bidding platform that helps publishers maximize revenue. Permutive activates identity signals in APS bid requests.

# Prebid Source: https://docs.permutive.com/integrations/advertising/bidstream/prebid Integrate with Prebid for targeting and campaign optimization
Prebid

Prebid

Prebid is an open-source header bidding solution that helps publishers maximize revenue through real-time bidding.

## Overview Prebid is an open-source header bidding solution that enables publishers to offer their ad inventory to multiple demand sources simultaneously before making calls to their ad servers. Permutive provides two specialized Prebid modules that work independently or together: 1. **Real-Time Data (RTD) Module** - Activates cohort signals in bid requests for audience targeting 2. **Identity Manager Module** - Passes identity signals from third-party identity providers to bidders This integration is both a Source and Destination: * **Source:** Permutive collects bid data and auction information from Prebid.js auctions, including bid prices, response times, and winning bids. * **Destination (Cohort Activation):** Permutive activates your cohorts in Prebid.js auctions for real-time targeting via the RTD module. * **Destination (Identity Signal Activation):** Permutive passes identity signals (RampID, ID5, UID2) to bidders via the Identity Manager module. ### How It Works **RTD Module (Cohort Activation):** The Permutive RTD module acts as a bridge between the Permutive Web SDK and Prebid.js: 1. When users fall into activated cohorts, these are detected by Permutive's Prebid RTD module 2. Cohort data is attached to the `ortb2.user.data` object in bid requests for configured SSPs 3. SSPs receive cohort targeting data and can use it for real-time decisioning **Identity Manager Module (Identity Signal Activation):** The Permutive Identity Manager module retrieves identity signals from third-party identity providers and makes them available in Prebid auctions: 1. Identity providers (RampID, ID5, UID2) generate identity envelopes through Permutive's Identity Manager 2. The Permutive Identity Manager module retrieves these identities from the Permutive SDK 3. Identity signals are passed to bidders via the `user.eids` object in bid requests 4. Bidders can decrypt and use these identity signals for targeting and measurement ### Cohort Types * **Standard Cohorts** (IDs > 1,000,000): Automatically available to all configured AC bidders without specific activation. These are general audience segments. * **Custom Cohorts**: Must be explicitly activated for each SSP in the Permutive Dashboard. These are your custom-built audience segments. * **Advertiser Cohorts**: Must be configured by your Customer Success Manager for customers using Permutive's Data Clean Room. Use cases include: * Send Permutive cohorts to your Prebid.js auctions for real-time targeting and decisioning across multiple SSPs. * Pass identity signals (RampID, ID5, UID2) from third-party identity providers to bidders for enhanced addressability and targeting. * Track bid data and auction performance in Permutive for insights and optimization. * Build cohorts based on bidder behavior, auction outcomes, and advertiser information. * Optimize header bidding performance with data-driven audience insights and identity resolution. ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | -------------------------- | | **Web** | | Requires Prebid.js v9.5.0+ | | **iOS** | | Not supported | | **Android** | | Not supported | | **CTV** | | Not supported | | **API Direct** | | Not supported | ## Prerequisites **For both modules:** * **Prebid.js Version**: You must have Prebid.js v9.5.0 or later installed on your website. * **Web SDK**: The Permutive Web SDK must be deployed on your site. * **GDPR/Consent Management**: If operating in regions with GDPR requirements, you must configure the Prebid consent management module with proper storage exceptions for Permutive. **For Cohort Activation (RTD Module):** * **Permutive RTD Module**: The `permutiveRtdProvider` module must be included in your Prebid.js build. This module is essential for reading cohort data from local storage and passing it to SSPs. **For Identity Signal Activation (Identity Manager Module):** * **Permutive Identity Manager Module**: The `permutiveIdentityManagerIdSystem` module must be included in your Prebid.js build. * **Identity Provider Setup**: You must have at least one identity provider (RampID, ID5, or UID2) configured in the Permutive Dashboard. See the [Identity Integration Catalog](/integrations/identity) for details on setting up identity providers. ## Setup Choose which module(s) you want to configure based on your needs. You can use either module independently or both together. The Permutive Real-Time Data (RTD) module must be included in your Prebid.js build. This module reads cohort data from local storage and passes it to configured SSPs. Ensure your Prebid.js build includes the `permutiveRtdProvider` module. Configure your Prebid.js setup to include the Permutive RTD module. Add the following configuration to your Prebid.js setup: ```javascript theme={"dark"} pbjs.setConfig({ realTimeData: { auctionDelay: 200, // Milliseconds to delay auction for RTD data dataProviders: [{ name: 'permutive', waitForIt: true, // Delays auction until Permutive data is ready }] } }); ``` **Configuration Notes:** * **`waitForIt: true`**: Ensures the auction waits for the RTD module to complete before sending bid requests. **This is critical** for cohorts to be included in all bid requests. * **`auctionDelay`**: Maximum time (in milliseconds) to wait for RTD data. Recommended value is 200-300ms to balance cohort availability with auction speed. If you operate in regions with GDPR requirements, you must configure Prebid's consent management to allow Permutive access to local storage. Without this, the RTD module cannot read cohort data. As Permutive acts as a processor on behalf of publishers, consent is given to the Permutive SDK by the publisher, not by the GDPR Consent Management Module. **Important**: While Permutive is listed as a TCF vendor (ID: 361), Permutive does not obtain consent directly from the TCF. If you are using the TCF Control Module and need to prevent Permutive from being blocked, you must add Permutive as a vendor exception. Here's an example configuration: ```javascript theme={"dark"} pbjs.setConfig({ consentManagement: { gdpr: { cmpApi: 'iab', timeout: 8000, allowAuctionWithoutConsent: true, defaultGdprScope: true, rules: [ { purpose: 'storage', enforcePurpose: true, enforceVendor: true, vendorExceptions: ["permutive"] }, { purpose: 'basicAds', enforcePurpose: true, enforceVendor: true, vendorExceptions: [] } ] } } }); ``` Consult the [Prebid consent management documentation](https://docs.prebid.org/dev-docs/modules/consentManagement.html) and [Permutive RTD Provider documentation](https://docs.prebid.org/dev-docs/modules/permutiveRtdProvider.html#context) for detailed guidance. In the Permutive Dashboard, navigate to your events page. You should start seeing Prebid event data appear. For cohorts with Prebid activation configured, you should see these cohorts available for targeting in your Prebid auctions. **To verify cohorts are being passed to SSPs:** 1. Add `?pbjs_debug=true` to your URL 2. Open your browser's Developer Console 3. Filter logs for "permutiveRTD" to see RTD module activity 4. Look for "Bid requests" logs in Prebid to see cohort data attached to bid requests in the `ortb2.user.data` field Before enabling the Prebid Identity Manager module, you must first configure at least one identity provider in the Permutive Dashboard: 1. Navigate to **Identity** → **Catalog** in your Permutive Dashboard 2. Select an identity provider (RampID, ID5, or UID2) 3. Follow the setup instructions for your chosen provider For detailed setup instructions, see the individual identity integration documentation: * [RampID Integration](/integrations/identity/rampid) * [ID5 Integration](/integrations/identity/id5) * [UID2 Integration](/integrations/identity/uid2) Identity provider configuration (Partner IDs, consent setup, etc.) is handled in the Permutive Dashboard and documented in the individual identity integration guides. The Prebid module configuration below only controls how Prebid retrieves and passes these identities. The Permutive Identity Manager module must be included in your Prebid.js build. Ensure your Prebid.js build includes the `permutiveIdentityManagerIdSystem` module. If you need to rebuild Prebid with this module, contact your Prebid implementation team or use the [Prebid.js download tool](https://docs.prebid.org/download.html) and select "Permutive Identity Manager" from the User ID Modules list. The Permutive Identity Manager module is separate from the Permutive RTD module. Both modules can coexist in the same Prebid setup. Add the following configuration to your Prebid.js setup: ```javascript theme={"dark"} pbjs.setConfig({ userSync: { userIds: [{ name: 'permutiveIdentityManagerId', params: { ajaxTimeout: 90 // Wait up to 90ms for Permutive SDK to become available }, storage: { type: 'html5', name: 'permutiveIdentityManagerId', refreshInSeconds: 5 } }], auctionDelay: 100 // Wait for identity signals before starting auction } }); ``` **Configuration Notes:** * **`ajaxTimeout`**: Time (in milliseconds) the module will wait for the Permutive SDK to become available. The default is 50ms. Increase this value if your SDK loads slowly. * **`auctionDelay`**: Time (in milliseconds) to delay the auction to ensure identity signals are available. Recommended value is 100ms. * **`storage`**: Configures how Prebid stores and refreshes identity data. The module reads identity signals from the Permutive SDK's storage. For more details, see the [Prebid Identity Manager module documentation](https://docs.prebid.org/dev-docs/modules/userid-submodules/permutiveIdentityManagerIdSystem.html). To verify that identity signals are being passed to bidders: 1. Add `?pbjs_debug=true` to your URL 2. Open your browser's Developer Console 3. Look for "User ID" logs in Prebid showing the Permutive Identity Manager module 4. Check bid requests for identity data in the `user.eids` field You can also verify in the Permutive Dashboard: 1. Navigate to **Identity** → **Insights** 2. Check that identity signals are being generated for your configured providers ## Data Types With your Prebid integration setup, you'll see the following additional event types collected in Permutive: Tracks individual bids from Prebid auctions. This event contains detailed information about each bid received during an auction. Unique identifier for the Prebid auction. The ad unit code for the slot being auctioned. The bidder code (e.g., 'appnexus', 'pubmatic'). The exact bid price from the bidder in CPM. The width of the returned creative size. The height of the returned creative size. The amount of time for the bidder to respond with the bid in milliseconds. Boolean indicating whether the bid is available. Boolean indicating whether this bid won the auction. The advertiser ID from the bid response. The advertiser name from the bid response. If the bid is associated with a Deal, this field contains the deal ID. The ID of the selected creative for the ad. Prebid bid price bucket. The time at which the prebid request was sent. The time at which the prebid response was received. Tracks winning bids from Prebid auctions. This event is fired when a bid wins an auction and includes all the same properties as PrebidBid. Unique identifier for the Prebid auction. The ad unit code for the slot being auctioned. The bidder code (e.g., 'appnexus', 'pubmatic'). The exact bid price from the bidder in CPM. The width of the returned creative size. The height of the returned creative size. The amount of time for the bidder to respond with the bid in milliseconds. Boolean indicating whether the bid is available. Boolean indicating whether this bid won the auction (always true for this event). The advertiser ID from the bid response. The advertiser name from the bid response. If the bid is associated with a Deal, this field contains the deal ID. The ID of the selected creative for the ad. Prebid bid price bucket. The time at which the prebid request was sent. The time at which the prebid response was received. Tracks aggregated auction data containing multiple bids and no-bid responses for a single auction. This event provides comprehensive auction-level insights. Unique identifier for the Prebid auction. Array of bid objects containing detailed bid information. Array of no-bid responses from bidders who chose not to bid. Array of auction objects containing multiple ad units and their associated bids. ## Troubleshooting Check that your Prebid.js version is 9.5.0 or later. Verify that the Permutive RTD module (`permutiveRtdProvider`) is included in your Prebid.js build and properly configured. Ensure the Prebid integration is enabled in your Permutive Dashboard. Use the debugging query parameters `?__permutive.loggingEnabled=true&pbjs_debug=true` in your browser to view detailed logs in the developer console. **Check local storage first**: Open your browser's Developer Tools, navigate to Application > Local Storage, and verify that cohort data is being written to keys starting with `_P` (e.g., `_prubicons`, `_papns`, `_pindexs`). If these keys are empty, cohorts are not being activated in the Permutive Dashboard. Standard cohorts (IDs over 1,000,000) are automatically available to all AC bidders without specific activation, but custom cohorts must be explicitly activated for each SSP. If you're seeing warnings about "TCF2 framework" or "activity control" denying access in your browser console, your Prebid consent management configuration is preventing the RTD module from reading Permutive cohorts. **Background**: While Permutive is listed as a TCF vendor (ID: 361), Permutive does not obtain consent directly from the TCF framework. Consent is given to the Permutive SDK by the publisher, not by the GDPR Consent Management Module. **Solution**: If using the TCF Control Module, add Permutive as a vendor exception in your consent management configuration. Add the following to your `pbjs.setConfig().consentManagement.gdpr.rules`: ```javascript theme={"dark"} { purpose: 'storage', enforcePurpose: true, enforceVendor: true, vendorExceptions: ["permutive"] } ``` Refer to the [Prebid Permutive RTD Provider documentation](https://docs.prebid.org/dev-docs/modules/permutiveRtdProvider.html#context) for detailed guidance. If cohorts are missing from some bid requests, ensure your Real-Time Data configuration includes `waitForIt: true` in the Permutive RTD provider setup. This setting delays the auction until the Permutive RTD module has completed processing. Also verify that `auctionDelay` is set to an appropriate value (typically 200ms or higher) in your `pbjs.setConfig().realTimeData` configuration. If you recently upgraded to Prebid.js v9.5.0 or later and are experiencing issues, this version fixed a critical bug related to how the Permutive RTD module pushes values into ORTB2 objects. If you're on an older version and seeing intermittent targeting issues (especially when numeric targeting values are present), upgrade to v9.5.0 or later. After upgrading, verify that all required modules (RTD module, bid adapter modules, Permutive RTD provider) are still included in your build. Some publishers run multiple versions of Prebid.js on their site (e.g., one for display ads and another for video). This can cause conflicts with the Permutive RTD module. Ensure you're using a single, consistent Prebid.js setup across your site. If identity signals (RampID, ID5, UID2) are not appearing in the `user.eids` field of bid requests: 1. **Verify identity provider is enabled**: Navigate to **Identity** → **Catalog** in the Permutive Dashboard and confirm your identity provider is enabled and configured correctly 2. **Check Prebid module inclusion**: Verify the `permutiveIdentityManagerIdSystem` module is included in your Prebid build by checking your Prebid configuration 3. **Confirm consent**: Ensure your identity provider has proper consent in your CMP. Each identity provider requires specific TCF purposes to be enabled 4. **Check SDK timing**: The Identity Manager module needs the Permutive SDK to be available. Increase the `ajaxTimeout` parameter if your SDK loads slowly 5. **Review debug logs**: Add `?pbjs_debug=true` to your URL and check for "User ID" logs in the console For identity provider-specific troubleshooting, see the individual identity integration documentation: * [RampID Troubleshooting](/integrations/identity/rampid#troubleshooting) * [ID5 Troubleshooting](/integrations/identity/id5#troubleshooting) * [UID2 Troubleshooting](/integrations/identity/uid2#troubleshooting) If you see empty `user.eids` in some bid requests but not others, the Identity Manager module may not be loading in time for the auction. This can happen if: * The Permutive SDK loads slowly on your page * The `ajaxTimeout` parameter is too low * The `auctionDelay` in userSync configuration is too short **Solution**: Increase both the `ajaxTimeout` (in the module params) and `auctionDelay` (in userSync config). Start with `ajaxTimeout: 90` and `auctionDelay: 100`, then adjust based on your page load times. If you're using both modules and experiencing slow auctions, you may need to optimize your timeout configuration: * RTD module `auctionDelay`: Controls how long to wait for cohort data (recommended: 200ms) * Identity Manager `auctionDelay`: Controls how long to wait for identity signals (recommended: 100ms) * Identity Manager `ajaxTimeout`: Controls how long to wait for the SDK (recommended: 50-90ms) **Tip**: The RTD module and Identity Manager module delays are independent. If using both, consider the cumulative impact on auction timing and adjust accordingly to balance data availability with page performance. To troubleshoot Prebid integration issues, add these query parameters to your URL: `?__permutive.loggingEnabled=true&pbjs_debug=true`. This enables detailed logging from both the Permutive SDK and Prebid.js in your browser's developer console. Look for logs tagged with "permutiveRTD" to see exactly what the RTD module is doing, including which segments it's reading and which bidders it's attaching data to. For the Identity Manager module, look for "User ID" logs showing identity retrieval. ## Changelog ### July 2025 * Enhanced support for advertiser cohort activation via Prebid * Improved Prebid RTD module configuration options ### June 2025 * Added support for PrebidAuctions event collection * Enhanced bid data collection and auction insights For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Adform Source: https://docs.permutive.com/integrations/advertising/dsps/adform Integrate with Adform DSP for identity-based audience activation
Adform

Adform

Adform is an omnichannel demand-side platform enabling advertisers to buy digital advertising across display, video, mobile, connected TV, and audio, with strong presence in Europe.

## Overview Adform is an omnichannel demand-side platform (DSP) enabling advertisers to buy digital advertising across display, video, mobile, connected TV, and audio. Popular in Europe, Adform offers strong identity support with ID Fusion technology for cross-device targeting and measurement. This integration uses [server-side identity-based activation](/integrations/server-side-identity-based-activation), where Permutive uploads audience data enriched with cohort memberships to Adform, keyed by identity types. Cohort data syncs daily to Adform's DMP, making your first-party audiences available for targeting in programmatic campaigns. This integration is a Destination only: * **Destination:** Permutive sends cohort memberships server-to-server to Adform DMP via daily batch uploads, creating audiences based on identity matching (Adform Cookie, AAID, IDFA, or IP Address). Use cases include: * **Audience extension**: Make Permutive cohorts available to advertisers and agencies outside your owned inventory, enabling monetization of first-party data across the open web * **Programmatic deals**: Offer enriched audiences to selected advertisers via private marketplace deals in Adform, increasing eCPMs * **Cross-device activation**: Reach audiences across display, video, mobile, and CTV using identity-based activation * **Partner/agency collaboration**: Share first-party cohorts with agency or advertiser DSP seats for campaign targeting * **First-party data monetization**: Leverage first-party behavioral data in programmatic campaigns without relying on third-party cookies ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ------------------------------------------------------ | | **Web** | | Via Adform Cookie (ID sync from Web SDK) or IP Address | | **iOS** | | Via IDFA | | **Android** | | Via AAID | | **CTV** | | Via IP Address | | **API Direct** | | Via any supported identifier | ## Supported Identity Tags Permutive uploads cohort memberships to Adform keyed by the identity tags below. When configuring the integration, select the tags that match the environments you need to activate — Permutive must be collecting each tag you choose. | Identity Tag (`tag`) | Description | Environment | | -------------------- | ---------------------------------------------------- | -------------------------- | | `adform` | Adform Cookie ID (collected via the Web SDK ID sync) | Web | | `idfa` | Apple Identifier for Advertisers (IDFA) | iOS | | `aaid` | Android Advertising ID (AAID) | Android | | `ip_address` | IP Address | CTV and other environments | Identity Based Activation requires that Permutive is collecting the identity tags you select. Work with your Permutive Customer Success Manager to ensure identity collection is configured properly for your desired tags. ## Prerequisites * **Adform Account**: Active Adform DSP or DMP account with appropriate permissions for audience management. * **Adform DMP Account ID**: Your unique Adform DMP Account ID. This identifier links Permutive's cohort uploads to your Adform account. * **Permutive Identity Collection**: Identity collection must be configured in Permutive for the identity types you want to send (Adform Cookie, AAID, IDFA, or IP Address). Work with your Permutive Customer Success Manager to ensure identity collection is properly configured. ## Setup Contact your Permutive Customer Success Manager to request the Adform (ID-based) integration. Permutive will coordinate with Adform to provision the necessary access and credentials for cohort uploads. Once enabled by Permutive, navigate to Settings → Integrations in the Permutive Dashboard. Find "Adform (ID-based)" in the list of available integrations and click to configure it. Provide your Adform DMP Account ID in the configuration form. This identifier ensures cohort data is uploaded to the correct Adform account. If you don't have your Adform DMP Account ID, contact your Adform account team to obtain it. Choose which identifier types you want to send to Adform. Available options include: * **Adform Cookie** - For web users (identifier tag: `adform`) * **AAID** - Android Advertising ID (for Android app users) * **IDFA** - Identifier for Advertisers (for iOS app users) * **IP Address** - IP addresses (for CTV and other environments) Select the identity types that match your use case and the environments where you need activation. Identity Based Activation requires that Permutive is collecting the identity types you select. Work with your Permutive Customer Success Manager to ensure identity collection is configured properly for your desired identity types. Save your configuration. Permutive will begin syncing cohort data to Adform on a daily basis for users with the selected identity types. Cohort data syncs to Adform daily. Changes to cohort membership will be reflected in Adform within 24 hours of the next scheduled sync. Navigate to your cohorts in the Permutive Dashboard and enable the "Adform (ID-based)" activation toggle for each cohort you want to sync to Adform. After the first sync completes (within 24 hours), check your Adform dashboard to verify that audiences appear with names matching your activated cohorts. Web activation uses Adform Cookie via an ID sync mechanism. When the Adform integration is enabled, the Permutive Web SDK automatically initiates an ID sync with Adform to collect Adform cookies into your identity graph. **How it works:** * The Permutive Web SDK makes a request to Adform's ID sync endpoint * Adform responds with the user's Adform cookie (identifier tag: `adform`) * Permutive stores this identifier in the publisher's identity graph * Cohort memberships are synced daily to Adform server-to-server, keyed by Adform Cookie * These cohorts become available for targeting in Adform DSP campaigns **Prerequisites for Web:** * Permutive Web SDK must be deployed on your website * Adform integration must be enabled in the Permutive Dashboard * "Adform Cookie" must be selected as an identifier type in the integration configuration * Users must have appropriate consent for identity collection (if applicable in your region) **No code changes required** - the ID sync happens automatically once the integration is enabled in the Permutive Dashboard. Data syncing occurs server-to-server on a daily schedule. Identity Based Activation for iOS enables server-to-server cohort syncing using IDFA (Identifier for Advertisers). No SDK code changes are required on iOS devices. **How it works:** * Permutive collects IDFA values from iOS users via the Permutive SDK * Cohort memberships are synced daily to Adform server-to-server, keyed by IDFA * Cohorts appear as audiences in Adform, ready for campaign targeting * Data syncing happens automatically once configured in the Permutive Dashboard **Prerequisites for iOS:** * Permutive SDK must be deployed on your iOS app * IDFA collection must be enabled and properly configured * Users must grant tracking permission (ATT framework) for IDFA to be available * "IDFA" must be selected as an identifier type in the integration configuration Identity Based Activation for Android enables server-to-server cohort syncing using AAID (Android Advertising ID). No SDK code changes are required on Android devices. **How it works:** * Permutive collects AAID values from Android users via the Permutive SDK * Cohort memberships are synced daily to Adform server-to-server, keyed by AAID * Cohorts appear as audiences in Adform, ready for campaign targeting * Data syncing happens automatically once configured in the Permutive Dashboard **Prerequisites for Android:** * Permutive SDK must be deployed on your Android app * AAID collection must be enabled and properly configured * "AAID" must be selected as an identifier type in the integration configuration Identity Based Activation for CTV enables server-to-server cohort syncing using IP Address or other supported identifiers. No SDK code changes are required on CTV devices. **How it works:** * Permutive collects IP addresses (or other supported identifiers) from CTV users * Cohort memberships are synced daily to Adform server-to-server, keyed by IP Address * Cohorts appear as audiences in Adform, ready for campaign targeting * Data syncing happens automatically once configured in the Permutive Dashboard **Prerequisites for CTV:** * Permutive must be collecting IP addresses or other supported identifiers from your CTV environment * Work with your Permutive Customer Success Manager to ensure proper identity collection for CTV * "IP Address" must be selected as an identifier type in the integration configuration ## Data Types The Adform integration is a Destination-only integration for cohort activation. It does not collect event data from Adform. When you activate a cohort to Adform using Identity Based Activation, Permutive uploads cohort membership data to Adform DMP on a daily basis, keyed by the selected identity types. **Data Format:** * Cohort data is synced daily from Permutive to Adform via server-to-server batch uploads * Data is keyed by selected identity types (Adform Cookie, AAID, IDFA, or IP Address) * Each user's cohort memberships are associated with their identity values * Cohorts appear as audiences in Adform's audience interface, ready for targeting * Audience data is updated daily with the latest cohort membership information **Cohort Naming:** * Audiences in Adform will use the cohort names defined in Permutive * Ensure cohort names are descriptive and recognizable for campaign targeting in Adform **Data Refresh:** * Daily sync schedule ensures cohort membership is updated within 24 hours * Changes to cohort membership (users entering or exiting cohorts) will be reflected in the next scheduled sync ## Troubleshooting **Possible causes:** * The integration was recently enabled and data hasn't synced yet * Adform DMP Account ID is incorrect in the Permutive Dashboard * Cohorts are not activated with the "Adform (ID-based)" toggle enabled * Identity collection is not properly configured for the selected identity types **Solution:** 1. Verify your Adform DMP Account ID in the Permutive Dashboard matches your Adform account 2. Check that cohorts are toggled "On" for Adform (ID-based) activation in the cohort settings 3. Wait 24-48 hours after initial activation for the first sync to complete 4. Confirm that Permutive is collecting the selected identity types (Adform Cookie, AAID, IDFA, or IP Address) 5. Check that cohorts have sufficient active members in Permutive 6. Contact your Permutive Customer Success Manager if audiences still do not appear **Cause:** Audience sizes in Adform may be smaller than expected due to identity matching limitations. **Steps to diagnose and improve:** 1. Verify that Permutive is collecting the identity types you selected (e.g., Adform Cookie for web) 2. For web activation, confirm the Permutive Web SDK is properly deployed and the ID sync is functioning 3. Check that users are granting appropriate consent for identity collection (if applicable) 4. Review cohort rules to ensure they're not too restrictive 5. Allow 24-48 hours after initial activation for audiences to fully populate 6. Monitor identity collection rates in the Permutive Dashboard **Expected match rates:** Match rates vary based on the identity type, user consent rates, browser mix, and device type. Web activation via Adform Cookie typically achieves higher match rates in browsers that support third-party cookies. **Cause:** The Adform Cookie ID sync may not be functioning properly for web users. **Solution:** 1. Verify the Permutive Web SDK is deployed on your website 2. Check that the Adform integration is enabled in the Permutive Dashboard 3. Confirm `adform` is selected as an identifier type in the integration configuration 4. Open your browser's developer tools and check the Network tab for requests to Adform's ID sync endpoint 5. Verify user consent is granted for identity collection (if using a Consent Management Platform) 6. Check that third-party cookies are not blocked in the browser (some browsers block third-party cookies by default) If the ID sync is still not working after these checks, contact Permutive Support with your workspace ID and browser details. **Cause:** Cohort membership changes may not be reflected in Adform audiences immediately. **Solution:** 1. Remember that cohort data syncs to Adform daily - allow up to 24 hours for changes to appear 2. Verify the cohort is still activated with the "Adform (ID-based)" toggle enabled 3. Check that users are still entering the cohort in Permutive (view cohort size trends) 4. Confirm the integration is still enabled and properly configured in Settings → Integrations 5. Contact Permutive Support if data consistently fails to update after 48 hours **Cause:** The identity types you want to use are not available for selection in the integration configuration. **Solution:** 1. Contact your Permutive Customer Success Manager to enable identity collection for the desired identity types 2. Verify your Permutive workspace is configured to collect the specific identifiers (e.g., IDFA, AAID, IP Address) 3. For web activation via Adform Cookie, ensure the Web SDK is deployed and the ID sync is enabled 4. For mobile activation, ensure the Permutive SDK is collecting mobile advertising IDs with appropriate user permissions ## Changelog ### January 2026 * Initial release of Adform DSP integration * Identity Based Activation support for Web, iOS, Android, and CTV environments * Server-to-server cohort upload keyed by Adform Cookie, AAID, IDFA, or IP Address For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Catalog Source: https://docs.permutive.com/integrations/advertising/dsps/catalog Browse our comprehensive catalog of ecosystem integrations. # DSP Integrations
DV360

DV360

Google Display & Video 360 is a comprehensive programmatic advertising platform for display, video, and audio campaigns.

The Trade Desk

The Trade Desk

The Trade Desk is a technology company that empowers buyers of advertising through its self-service, cloud-based platform.

Adform

Adform

Adform is an omnichannel demand-side platform enabling advertisers to buy digital advertising across display, video, mobile, connected TV, and audio.

StackAdapt

StackAdapt

StackAdapt is a self-serve programmatic advertising platform enabling advertisers to plan, execute, and manage data-driven digital advertising campaigns across display, native, video, CTV, audio, and in-game channels.

# DV360 Source: https://docs.permutive.com/integrations/advertising/dsps/dv360 Integrate with DV360 for targeting and campaign optimization
DV360

DV360

Display & Video 360 is Google's enterprise demand-side platform for programmatic advertising across display, video, and audio.

## Overview Display & Video 360 (DV360) is Google's enterprise demand-side platform for buying and optimizing programmatic media across display, video, audio and more. Our integration with DV360 enables [server-side identity-based activation](/integrations/server-side-identity-based-activation), allowing you to target your Permutive cohorts in DV360 campaigns. This integration is a Destination only: * **Destination:** Permutive sends cohort memberships server-to-server to DV360 via the Audience Partner API, creating and updating user lists based on Google cookie matching. The standard integration uses cookie-based identity matching (Google cookie collected on Web), which limits live activation to Web. Extending the integration to support iOS (mobile advertising IDs), Android (mobile advertising IDs), and CTV (IP or hashed email) can be an alternative path — contact your Permutive Customer Success Manager to discuss your use case. Use cases include: * Send Permutive cohorts to DV360 for activation across display campaigns * Keep DV360 audience lists in sync with live cohort membership changes * Report on targeted impressions usage in DV360 for auditing and billing * Optimize display campaigns using high-quality first-party cohorts ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------------ | ------------------------------------------------------------------ | | **Web** | | Live — via Google cookie (Audience Partner API) | | **iOS** | | Via mobile advertising IDs or hashed email — discuss with your CSM | | **Android** | | Via mobile advertising IDs or hashed email — discuss with your CSM | | **CTV** | | Via IP or hashed email — discuss with your CSM | | **API Direct** | | Via any supported identifier | The DV360 integration currently uses cookie-based identity matching (Web only). Extending it to iOS, Android, and CTV via alternative identity types is something Permutive can support, though it requires additional build work — contact your Permutive Customer Success Manager to discuss your use case. ## Prerequisites * **Permutive Web SDK**: Must be deployed on your site to collect the Google cookie required for DV360 matching. This integration relies on cookie-based matching using identifiers collected on web. * **DV360 account details**: You will need the following information from your DV360 account: * **Entity ID**: Your DV360 Entity ID (also called Partner ID or Advertiser ID depending on your setup) * **Product Type**: Either `Invite_partner` or `Invite_advertiser` depending on your DV360 account structure * **Seat mapping (DV360)**: Your DV360 seat must be linked to Permutive's partner account before audiences can sync. You can activate this link directly in DV360 by navigating to **Partner Settings** → **Linked Account** → **Link new account** and searching for **Permutive**.
This mapping step is critical and must be completed before cohorts can be activated. Without this mapping, audience lists will not populate in your DV360 account. * **Permissions**: You must have appropriate permissions in your DV360 account to view and manage audience lists (First & Third Party Audiences). * **Consent**: Ensure your consent configuration allows collection of Google cookies and activation of data to DV360 in applicable regions (e.g., GDPR, CCPA). If using a Consent Management Platform (CMP) with IAB's Transparency and Consent Framework (TCF), ensure users have granted consent for Google Advertising Products (vendor ID 755). Without consent, the cookie sync will not execute and users cannot be matched. ## Setup Before configuring the integration in Permutive, link your DV360 seat to Permutive's partner account: 1. Log in to your DV360 account 2. Navigate to **Partner Settings** → **Linked Account** 3. Click **Link new account** 4. Search for **Permutive** and complete the linking process
This mapping must be completed first. Without it, audience lists will not appear or populate in your DV360 account even after setup is complete.
In the Permutive Dashboard, navigate to your workspace's integrations page. Click Add Integration and select DV360. Enter the following details from your DV360 account: * **Entity ID**: Your DV360 Entity ID (Partner ID or Advertiser ID) * **Product Type**: Select either Invite\_partner or Invite\_advertiser based on your DV360 account structure
If you're unsure which Product Type to use, check with your Google account team or your DV360 administrator.
Confirm that the Permutive Web SDK is deployed on your site(s) and collecting data. Verify that your consent configuration allows collection of Google cookies in regions where you plan to activate. Activate a small test cohort to DV360: 1. Navigate to your cohorts in the Permutive Dashboard 2. Select a test cohort and choose DV360 as the activation destination 3. Wait a few minutes for processing 4. Check your DV360 account under **Audiences** → **First & Third Party Audiences** to confirm the audience list appears 5. Monitor the list size over the next 24 hours to ensure it populates correctly
Audience lists may take several hours to fully populate in DV360. Initial match rates depend on your site traffic volume and consent rates.
The standard DV360 integration uses cookie-based matching and does not cover iOS in-app inventory by default. Extending the integration to support iOS via mobile advertising IDs or hashed email can be an alternative path. **What this involves:** * Permutive would extend the existing DV360 connector to upload cohort memberships keyed by mobile advertising IDs (or hashed email) in addition to the existing Google cookie flow. * Identity collection from your iOS app via the Permutive Mobile SDK would need to be configured. * Cohorts would then become available for targeting against DV360's iOS in-app inventory. This requires scoping on Permutive's side and coordination with your Google account team. Contact your Permutive Customer Success Manager to discuss your use case. The standard DV360 integration uses cookie-based matching and does not cover Android in-app inventory by default. Extending the integration to support Android via mobile advertising IDs or hashed email can be an alternative path. **What this involves:** * Permutive would extend the existing DV360 connector to upload cohort memberships keyed by mobile advertising IDs (or hashed email) in addition to the existing Google cookie flow. * Identity collection from your Android app via the Permutive Mobile SDK would need to be configured. * Cohorts would then become available for targeting against DV360's Android in-app inventory. This requires scoping on Permutive's side and coordination with your Google account team. Contact your Permutive Customer Success Manager to discuss your use case. The standard DV360 integration uses cookie-based matching and does not cover CTV inventory by default. Extending the integration to support CTV via IP address or hashed email can be an alternative path. **What this involves:** * Permutive would extend the existing DV360 connector to upload cohort memberships keyed by the chosen CTV-relevant identity (IP address, hashed email, or another supported identifier). * Identity collection from your CTV environments would need to be configured. * Cohorts would then become available for targeting against DV360's CTV inventory. This requires scoping on Permutive's side and coordination with your Google account team. Contact your Permutive Customer Success Manager to discuss your use case.
## Data Types This integration exports data from Permutive to DV360. Permutive sends cohort membership updates server-to-server to DV360 via the Audience Partner API to create and update user lists (First & Third Party Audiences) for targeting in your DV360 campaigns. The unique ID of the cohort in Permutive that is being activated. The human-readable name of the cohort as it appears in the Permutive Dashboard. This name will be used to identify the audience list in DV360. The type of identifier used for matching in DV360. For this integration, the identifier type is based on Google cookies collected via the Permutive Web SDK. The hashed identifier value (Google cookie) used to match users into DV360 audience lists. Indicates whether the user should be added to or removed from the DV360 audience list based on cohort membership changes. Values: add or remove The time when the cohort membership update was processed by Permutive. **Data Refresh Frequency**: Audience lists in DV360 are updated periodically as users enter or exit cohorts in Permutive. Updates are typically processed within minutes to hours, though DV360's own list processing may add additional latency. ## Troubleshooting **Cause**: Audiences created via the Audience Partner API (cookie-based) are classified as Third-Party data and cannot be used for YouTube & partners line items in DV360. **Why**: YouTube targeting requires audiences that can be matched to signed-in Google users. Cookie-based audiences from this integration cannot meet this requirement. **Alternative solutions for YouTube targeting**: 1. **Customer Match**: Upload hashed email addresses or phone numbers from your CRM directly to DV360 2. **GAM Segments**: Use Google Ad Manager (GAM) segments if you have GAM 360 integration 3. **Floodlight Audiences**: Create audiences based on website activity using Floodlight tags 4. **Optimized Targeting**: Use an eligible first-party list as a seed and let Google's machine learning expand the audience Display inventory targeting via DV360 is fully supported with this integration. Only YouTube & partners line items have this limitation. **Most Common Cause**: Seat mapping has not been completed. **Steps to resolve**: 1. Confirm that your DV360 account is linked to Permutive: * In DV360, go to **Partner Settings** → **Linked Account** * Verify that Permutive appears in your linked accounts * If not linked, click **Link new account** and search for **Permutive** 2. Verify the integration is enabled in the Permutive Dashboard under Integrations 3. Check that the correct **Entity ID** and **Product Type** are configured in the Permutive Dashboard 4. Ensure you have permissions to view **First & Third Party Audiences** in your DV360 account If the issue persists after confirming the above, contact Permutive Support with your workspace ID and cohort details. **Cause**: This integration relies on Google cookies collected via the Permutive Web SDK on web environments. **Steps to diagnose and improve**: 1. Verify the Permutive Web SDK is properly deployed on your site(s) 2. Check that your consent configuration allows collection of Google cookies in the regions where users are located 3. Confirm recent site traffic volume is sufficient for meaningful cohort sizes 4. Review cohort rules to ensure they're not too restrictive 5. Check cookie consent rates - low consent rates will reduce match rates 6. Allow 24-48 hours after initial activation for the audience to fully populate **Expected match rates**: Typically 60-80% of cookied web traffic, but this varies significantly based on consent rates, browser mix, and user behavior. **Steps to resolve**: 1. Allow 2-4 hours for DV360 to process the audience list after initial activation 2. Verify your DV360 account is still linked to Permutive: * In DV360, go to **Partner Settings** → **Linked Account** * Confirm Permutive appears as a linked account 3. Check that you're viewing the correct DV360 account (Partner vs. Advertiser level) 4. Confirm the cohort has sufficient active members in Permutive 5. Check that the Web SDK is collecting data and that users are entering the cohort If the list size remains at zero after 24 hours, verify the account linking and check Permutive logs for activation errors. **Common causes**: * Incorrect Entity ID or Product Type configuration * Expired or invalid API credentials (managed by Permutive) * DV360 API rate limiting during high-volume activations **Steps to resolve**: 1. Verify your Entity ID and Product Type are correct in the Permutive Dashboard 2. Check that your DV360 account is active and accessible 3. If activating many cohorts simultaneously, try spacing out activations 4. Contact Permutive Support if API errors persist, providing your workspace ID and error timestamp You can set up automated monthly reports in DV360 to track targeted impressions for billing purposes. **Steps**: 1. Navigate to the **Offline Reporting** tab in DV360 2. Set the desired date range (typically monthly) 3. Select the following **Dimensions**: * Advertiser * Audience List * Cost * ID * Type 4. Select the following **Metrics**: * Billable Impressions (impressions that reach a specific audience) 5. Schedule the report to be sent to `ti_usage@permutive.com` on the 1st of every month 6. Optionally, save the report to a Google Drive folder for your own records ## Changelog No changes listed yet. For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # StackAdapt Source: https://docs.permutive.com/integrations/advertising/dsps/stackadapt Activate your Permutive audiences in StackAdapt for programmatic campaign targeting
StackAdapt

StackAdapt

StackAdapt is a self-serve programmatic advertising platform enabling advertisers to plan, execute, and manage data-driven digital advertising campaigns across display, native, video, CTV, audio, and in-game channels.

## Overview Permutive syncs your audiences to StackAdapt on a daily basis via [server-side identity-based activation](/integrations/server-side-identity-based-activation), so you can target them in programmatic campaigns across display, native, video, CTV, audio, and in-game channels. Data flows one way — from Permutive to StackAdapt. Once synced, your cohorts appear as audiences in StackAdapt, ready for campaign targeting. Use cases include: * **Audience activation**: Make your Permutive cohorts available for targeting in StackAdapt campaigns * **Cross-device reach**: Reach audiences across web, mobile, and CTV environments * **Programmatic deals**: Offer enriched audiences to advertisers via private marketplace deals, increasing eCPMs * **First-party data monetization**: Use first-party behavioral data in programmatic campaigns without relying on third-party cookies ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ------------------------------------- | | **Web** | | Via StackAdapt Cookie or hashed email | | **iOS** | | Via IDFA or hashed email | | **Android** | | Via AAID or hashed email | | **CTV** | | Via IP Address | | **API Direct** | | Via any supported identifier | ## Supported Identity Tags Permutive activates your cohorts to StackAdapt by matching users on the identity tags below. A user is included in a sync whenever Permutive holds one of these identifiers for them, and each tag maps to a StackAdapt match type. Your Permutive CSM configures which tags to sync based on the environments you need to reach. | Identity Tag (`tag`) | Description | Environment | | -------------------- | -------------------------------------------------------- | ---------------- | | `stackadapt` | StackAdapt Cookie ID (collected via the Web SDK ID sync) | Web | | `email_sha256` | SHA-256 hashed email | All environments | | `idfa` | Apple Identifier for Advertisers (IDFA) | iOS | | `aaid` | Android Advertising ID (AAID) | Android | | `ip_address` | IP Address | CTV | Identity-based activation requires that Permutive is collecting the identity tags you want to use. Work with your Permutive CSM to confirm identity collection is configured for your selected tags. ## Prerequisites * **StackAdapt account** — An active StackAdapt DSP account. * **StackAdapt Account ID** — Your unique Account ID. If you don't have this, contact your StackAdapt account team. * **Integration enabled by Permutive** — Contact your Permutive Customer Success Manager to set up the integration. Your CSM will configure the integration using your Account ID and the identity types needed for your environments. ## Setup Your Permutive CSM handles the integration setup, including connecting your StackAdapt Account ID and configuring which identity types to sync. Once the integration is enabled, you can start activating cohorts. In the Permutive Dashboard, go to your cohorts and enable the "StackAdapt (ID-based)" activation toggle for each cohort you want to sync to StackAdapt. Cohort data syncs to StackAdapt daily. Changes to cohort membership are reflected within 24 hours of the next scheduled sync. After the first sync completes (within 24 hours), check your StackAdapt dashboard to confirm that your audiences appear with names matching your Permutive cohorts. ## Data Types Permutive sends cohort membership data to StackAdapt on a daily basis. Each synced cohort appears as a targetable audience in StackAdapt using the same name as in Permutive. * **Sync frequency:** Daily * **Updates:** Changes to cohort membership (users entering or exiting) are reflected in the next sync * **Full refresh:** A complete refresh of all audience data occurs every 30 days ## Troubleshooting **Possible causes:** * The integration was recently enabled and data hasn't synced yet * Cohorts are not activated for StackAdapt in the Permutive Dashboard **What to do:** 1. Check that your cohorts have the "StackAdapt (ID-based)" toggle enabled 2. Wait 24–48 hours after initial activation for the first sync to complete 3. Confirm that your cohorts have active members in Permutive 4. Contact your Permutive CSM if audiences still don't appear **Cause:** Audience sizes in StackAdapt may be smaller than expected due to identity matching rates. **What to do:** 1. Ask your Permutive CSM to check that identity collection is working for your selected identity types 2. Consider enabling SHA-256 hashed email as an additional identity type to improve match rates 3. Review your cohort rules to ensure they're not too restrictive 4. Allow 24–48 hours after initial activation for audiences to fully populate **Cause:** Cohort membership changes are not immediate — data syncs daily. **What to do:** 1. Allow up to 24 hours for changes to appear 2. Verify the cohort still has the "StackAdapt (ID-based)" toggle enabled 3. Contact your Permutive CSM if data consistently fails to update after 48 hours **Cause:** When you disable a cohort activation in Permutive, the corresponding audience in StackAdapt has a **30-day expiry** and will not be removed immediately. **What to expect:** 1. After disabling, no new data is sent for that segment 2. The segment remains visible in StackAdapt for up to 30 days before it expires automatically 3. If you need a segment removed sooner, contact StackAdapt directly ## Changelog ### March 2026 * Initial release of StackAdapt DSP integration * Audience activation support for Web, iOS, Android, and CTV environments For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # The Trade Desk Source: https://docs.permutive.com/integrations/advertising/dsps/the-trade-desk Integrate with The Trade Desk for targeting and campaign optimization
The Trade Desk

The Trade Desk

The Trade Desk is the world's largest independent demand-side platform, enabling advertisers to buy digital advertising across multiple channels with advanced targeting capabilities.

## Overview The Trade Desk is a leading independent demand-side platform (DSP) that empowers advertisers and agencies to programmatically buy digital advertising inventory across display, video, mobile, connected TV, audio, and native formats. Our integration with The Trade Desk enables [server-side identity-based activation](/integrations/server-side-identity-based-activation), allowing you to target your Permutive cohorts in programmatic campaigns. This integration is a Destination: * **Destination:** Permutive activates your cohorts to The Trade Desk via a server-to-server API, enabling you to target users with cohort membership data in programmatic advertising campaigns. The standard Web integration works via a two-step process: 1. **Cookie Sync**: The Permutive Web SDK initiates a cookie sync with The Trade Desk to obtain and store The Trade Desk's cookie ID as an alias for each user. 2. **Cohort Activation**: When users fall into activated cohorts, Permutive looks up The Trade Desk cookie ID from the aliases table and makes a server-side API call to The Trade Desk, informing them of the user's cohort membership. There are two variations of this integration: * **The Trade Desk 1P**: For first-party targeting and retargeting your own website visitors across The Trade Desk's inventory. * **The Trade Desk 3P**: For selling your Permutive audiences to advertisers using The Trade Desk's platform, enabling third-party data monetization. The standard integration uses cookie-based identity matching, which limits live activation to Web. Extending the integration to support iOS (IDFA), Android (AAID), and CTV (IP or hashed email) can be an alternative path — contact your Permutive Customer Success Manager to discuss your use case. Use cases include: * Retarget your website visitors across display, video, and CTV campaigns with cohorts built from first-party behavioral data. * Activate lookalike audiences and custom segments for programmatic advertising. * Sell third-party audience segments to advertisers through The Trade Desk's marketplace. * Measure campaign performance and optimize based on audience engagement. ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------------ | -------------------------------------------------------------- | | **Web** | | Live — via Trade Desk cookie ID (third-party cookie dependent) | | **iOS** | | Via IDFA — discuss with your CSM | | **Android** | | Via AAID — discuss with your CSM | | **CTV** | | Via IP or hashed email — discuss with your CSM | | **API Direct** | | Via any supported identifier | The Trade Desk integration currently uses cookie-based identity matching (Web only). Extending it to iOS, Android, and CTV via alternative identity types is something Permutive can support, though it requires additional build work — contact your Permutive Customer Success Manager to discuss your use case. ## Prerequisites **Required for all activation methods:** * **The Trade Desk Account** — an active advertiser or agency account with The Trade Desk. * **Advertiser ID** — your Trade Desk Advertiser ID, configured in the Permutive Dashboard. This identifier links your Permutive cohorts to your Trade Desk account. **For Web (live today):** * **Permutive Web SDK** — deployed on your website to enable the cookie sync and cohort activation mechanisms. * **Third-party cookies** — users must have third-party cookies enabled in their browsers for the cookie sync to function (primarily Chrome-based browsers — see Web setup tab). **For iOS / Android / CTV:** * This connector currently uses cookie-based matching only. If your use case calls for IDFA, AAID, IP, or hashed email, additional build work would be needed. * Identity collection for the chosen identity types would need to be configured in Permutive as part of any extension. * Contact your Permutive Customer Success Manager to discuss. ## Setup You must enable The Trade Desk integration in the Permutive Dashboard. 1. Navigate to the Permutive dashboard 2. Go to your workspace's integrations page 3. Click "Add Integration" and select either: * **The Trade Desk - 1P** for first-party retargeting, or * **The Trade Desk - 3P** for third-party audience sales 4. Enter your Trade Desk Advertiser ID when prompted 5. Save the integration configuration Once enabled, the Permutive SDK will automatically begin the cookie sync process for users visiting your site. After enabling the integration, you must configure which cohorts should be activated to The Trade Desk. 1. In the Permutive Dashboard, navigate to one of your cohorts 2. Look for The Trade Desk activation option in the cohort configuration 3. Toggle the activation to "On" for cohorts you want to sync to The Trade Desk 4. The activation will fire when users enter or exit the cohort When a user falls into an activated cohort, Permutive will: * Look up The Trade Desk cookie ID from the user's aliases * Make a server-side API request to The Trade Desk * Inform The Trade Desk that this user is a member of the cohort Verify that the integration is working correctly. 1. Check your browser's developer tools Network tab for requests to The Trade Desk domain during the cookie sync process 2. Look for redirect requests from The Trade Desk back to Permutive's identity API 3. In The Trade Desk platform, verify that your Permutive audiences are appearing and being populated 4. Check that audience sizes in The Trade Desk match your expected cohort sizes (accounting for cookie matching rates) The cookie sync happens client-side via the SDK, while cohort membership updates are sent server-side. There may be a slight delay between a user entering a cohort and appearing in The Trade Desk audience. This integration requires third-party cookies to be enabled. As of 2023, this means the Web integration primarily works in Chrome-based browsers. Safari and Firefox block third-party cookies by default, which prevents the cookie sync mechanism from functioning. ### Cookie Sync Process The Permutive Web SDK automatically handles the cookie sync with The Trade Desk. Here's how it works: 1. **Initial Request**: The SDK makes a request to The Trade Desk's domain 2. **302 Redirect**: The Trade Desk responds with a 302 redirect back to Permutive's identity API 3. **Cookie ID Capture**: The redirect URL includes the user's Trade Desk cookie ID, which was read from the request headers 4. **Alias Storage**: Permutive's identity API stores this cookie ID as an alias (type: `tradedesk` for 1P or `thirdparty_tradedesk` for 3P) This process happens automatically when users visit your site with the Permutive SDK deployed. ### Browser Compatibility The cookie sync requires third-party cookies to function: * **Chrome and Chromium-based browsers**: Fully supported * **Safari**: Not supported (blocks third-party cookies by default) * **Firefox**: Not supported (blocks third-party cookies by default with Enhanced Tracking Protection) * **Brave**: Not supported (blocks third-party cookies by default) ### Verifying Cookie Sync To verify the cookie sync is working: 1. Open your browser's developer tools 2. Navigate to the Network tab 3. Visit your website 4. Look for requests to The Trade Desk domain 5. Check for a 302 redirect response 6. Verify the redirect URL points to Permutive's identity API and includes cookie ID parameters The standard Trade Desk integration uses cookie-based matching and does not cover iOS in-app inventory by default. Extending the integration to support iOS via IDFA can be an alternative path. **What this involves:** * Permutive would extend the existing Trade Desk connector to upload cohort memberships keyed by IDFA in addition to the existing cookie ID flow. * IDFA collection from your iOS app via the Permutive Mobile SDK (with ATT consent) would need to be configured. * Cohorts would then become available for targeting against The Trade Desk's iOS in-app inventory. This requires scoping on Permutive's side and coordination with your Trade Desk account team. Contact your Permutive Customer Success Manager to discuss your use case. The standard Trade Desk integration uses cookie-based matching and does not cover Android in-app inventory by default. Extending the integration to support Android via AAID can be an alternative path. **What this involves:** * Permutive would extend the existing Trade Desk connector to upload cohort memberships keyed by AAID in addition to the existing cookie ID flow. * AAID collection from your Android app via the Permutive Mobile SDK would need to be configured. * Cohorts would then become available for targeting against The Trade Desk's Android in-app inventory. This requires scoping on Permutive's side and coordination with your Trade Desk account team. Contact your Permutive Customer Success Manager to discuss your use case. The standard Trade Desk integration uses cookie-based matching and does not cover CTV inventory by default. Extending the integration to support CTV via IP address or hashed email can be an alternative path. **What this involves:** * Permutive would extend the existing Trade Desk connector to upload cohort memberships keyed by the chosen CTV-relevant identity (IP address, hashed email, or another supported identifier). * Identity collection from your CTV environments would need to be configured. * Cohorts would then become available for targeting against The Trade Desk's CTV inventory. This requires scoping on Permutive's side and coordination with your Trade Desk account team. Contact your Permutive Customer Success Manager to discuss your use case. ## Data Types When a user enters an activated cohort, Permutive sends the following information to The Trade Desk via server-side API: The Trade Desk's cookie ID for the user, obtained during the cookie sync process. The Permutive cohort ID that the user has entered. Your Trade Desk Advertiser ID configured in the Permutive Dashboard. ## Troubleshooting The Trade Desk integration requires third-party cookies, which are blocked by default in Safari and Firefox. **Solution**: This is an expected limitation. The integration will only work in browsers that support third-party cookies (primarily Chrome-based browsers). There is no workaround for this limitation with the current integration architecture. Consider using UID2 or other cookieless identifier solutions for broader browser reach. Contact your Customer Success Manager for information about alternative identity solutions. You may notice that your audience sizes in The Trade Desk are smaller than your cohort sizes in Permutive. **Possible causes**: * Users visiting in browsers that block third-party cookies (Safari, Firefox) * Users with ad blockers that prevent cookie syncing * Users who have cleared their cookies since visiting your site * Geographic differences (The Trade Desk may not operate in all regions) **Solution**: Match rates of 40-60% are typical due to third-party cookie limitations. To improve match rates: * Focus measurement on Chrome users where possible * Implement UID2 for cookieless identification * Ensure the Permutive SDK loads before users leave the page Your activated cohorts may not appear in The Trade Desk interface. **Possible causes**: * The integration was recently enabled and data hasn't populated yet * Advertiser ID is incorrect in Permutive Dashboard * Cohort has no users with successful cookie syncs **Solution**: 1. Verify your Advertiser ID in the Permutive Dashboard matches your The Trade Desk account 2. Check that cohorts are toggled "On" for The Trade Desk activation 3. Wait 24-48 hours after enabling activation for initial population 4. Contact The Trade Desk support to verify they're receiving data from Permutive Users may still appear in The Trade Desk audiences even after they've exited a cohort in Permutive. **Solution**: This may be due to caching or TTL (time-to-live) settings on The Trade Desk's side. Audience removals can take 24-48 hours to fully propagate. If users consistently remain in audiences longer than expected, contact The Trade Desk support to review your account's TTL settings. Ad blocking browser extensions may prevent the cookie sync process from completing. **Solution**: This is an expected limitation. Users with ad blockers will not successfully complete the cookie sync and therefore won't be activatable in The Trade Desk. This is factored into expected match rates. There is no workaround for this limitation. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Catalog Source: https://docs.permutive.com/integrations/advertising/ssps/catalog Browse our comprehensive catalog of supply-side platform integrations. # SSP Integrations
Microsoft Monetize (SSP)

Microsoft Monetize (SSP)

Microsoft Monetize's programmatic advertising platform for advanced audience targeting and campaign management.

OpenX

OpenX

OpenX provides a programmatic advertising platform that helps publishers maximize revenue through advanced yield management.

PubMatic

PubMatic

PubMatic is a digital advertising technology company that maximizes the value of content for publishers and advertisers.

Magnite

Magnite

Magnite is the world's largest independent sell-side advertising platform, helping publishers maximize their digital advertising revenue.

Index Exchange

Index Exchange

Index Exchange is a global advertising marketplace that connects premium publishers with programmatic buyers.

# Index Exchange Source: https://docs.permutive.com/integrations/advertising/ssps/index-exchange Integrate with Index Exchange for SSP functionality
Index Exchange

Index Exchange

Index Exchange is a global ad exchange that helps publishers connect with premium demand through real-time bidding.

## Overview Index Exchange is a global ad exchange that helps publishers connect with premium demand through real-time bidding. Our integration with Index Exchange allows you to activate Permutive cohorts for targeting on Index Exchange's supply-side platform. This integration is a Destination: * **Destination:** Permutive cohorts are activated and passed to Index Exchange through bid requests, enabling advertisers to target your audiences programmatically. This integration supports two activation methods: * **Real-time bidstream activation:** Cohort IDs are attached to bid requests as first-party data via Prebid.js (Web), Prebid Mobile (iOS/Android), and other Prebid integrations. * **[Server-side identity-based activation](/integrations/server-side-identity-based-activation):** Can be an alternative path where client-side activation isn't viable. Permutive uploads cohort memberships to Index Exchange keyed by identity values (cookie ID, IDFA, AAID, IP address, or hashed email). Cohorts appear as Index Exchange segments ready for targeting. Use cases include: * **Cookie-less audience activation at scale:** Activate Permutive cohort signals programmatically via the bidstream without reliance on third-party cookies, enabling addressable reach across all users * **Private Marketplace (PMP) deals:** Use Custom Cohorts, DCR Cohorts, and Curated Cohorts to create premium, curated inventory packages for direct buyer relationships * **Open Marketplace (OMP) monetization:** Leverage Standard Cohorts to make your audiences available to demand partners in the open auction, increasing bid density and CPMs * **Maximize programmatic yield:** Make Permutive audiences available through Index Exchange's ad exchange to increase competition and drive higher CPMs * **Cross-environment activation:** Activate audiences across Web, in-app (iOS/Android), and CTV inventory through real-time bidstream or server-side identity-based methods ## Environment Compatibility | Environment | Activation | | -------------- | -------------------------------------------------------------------- | | **Web** | Live — Permutive RTD module in Prebid.js | | **iOS** | Available — via Prebid Mobile (customer implementation) | | **Android** | Available — via Prebid Mobile (customer implementation) | | **CTV** | Discuss with your CSM | | **API Direct** | Discuss with your CSM (for SDK-less environments) | **[Server-side identity-based activation](/integrations/server-side-identity-based-activation)** can be an alternative path for this integration, particularly where client-side activation isn't viable. Permutive can support your team in setting this up — contact your Customer Success Manager to discuss. ## Supported Cohort Types Index Exchange activation supports all current Permutive cohort types. Availability depends on your workspace participating in the relevant Permutive offering. | Cohort type | Availability for Index Exchange | Product required | | --------------------------- | ------------------------------- | --------------------------------------- | | **Custom Cohorts** | Live | Core Platform | | **Standard Cohorts** | Live | [Curation](/products/curation) | | **Curation Cohorts** | Live | [Curation](/products/curation) | | **Data Clean Room Cohorts** | Live | [Data Clean Room](/products/clean-room) | **Cohort type support beyond Web** The availability shown above reflects Web today. In mobile (iOS/Android) and CTV environments where this integration is available, cohort type support depends on the Permutive Mobile/CTV SDKs: * **Custom Cohorts** — supported across all environments where this integration is available. * **Data Clean Room Cohorts** — not currently exposed by the Permutive Mobile/CTV SDKs, but cohort signals can be retrieved with customer integration via an additional Permutive API call from your application code. * **Standard** and **Curation Cohorts** — not currently exposed by the Permutive Mobile/CTV SDKs. Support is coming — reach out to your Customer Success Manager if you're interested. Cohort type availability also depends on your workspace participating in the relevant Permutive offering. Contact your Customer Success Manager to enable additional cohort types or discuss your use case. ## Prerequisites **Required for all activation methods:** * **Index Exchange SSP Account** — an active Index Exchange SSP account **For Web (Prebid.js):** * **Permutive Web SDK** — deployed on your site * **Prebid.js with Permutive RTD module** — configured to pass cohort data to Index Exchange * **Index Exchange bidder adapter** — the [`ix`](https://docs.prebid.org/dev-docs/bidders/ix.html) bidder adapter configured in your Prebid.js setup (this is the bidder code for Index Exchange) **For iOS / Android (Prebid Mobile):** * **Permutive Mobile SDK** — deployed in your iOS / Android app * **Prebid Mobile SDK** — configured in your app * **Prebid Server** — that Prebid Mobile communicates with, with the `ix` bidder configured (Prebid Mobile passes bid requests to Prebid Server, which handles bidder routing) * **Customer implementation** — to retrieve Permutive cohorts from the SDK and attach them to Prebid Mobile bid requests **For CTV and other environments:** * Contact your Permutive Customer Success Manager to discuss the integration approach for your stack. **For server-side identity-based activation:** * This connector is not built today. If your use case calls for it, identity collection would need to be configured in Permutive for the identity types you want to use (cookie ID, IDFA, AAID, IP address, hashed email). * Contact your Permutive Customer Success Manager to discuss. ## Setup The Index Exchange SSP integration works automatically through Prebid.js once cohorts are activated. Navigate to the cohort you wish to activate in the Permutive dashboard. For **Custom Cohorts** (publisher-specific audiences), enable the Index Exchange SSP activation. When activated, cohort data will be stored in the `_pindexs` local storage key, which the Permutive RTD module reads and passes to Index Exchange. Custom Cohorts require explicit activation per cohort. Standard, Curated, and Data Clean Room cohorts are configured by your Customer Success Manager. Contact your CSM to enable these cohort types for your integration. Ensure your Prebid.js configuration includes the Permutive RTD module with Index Exchange support: Verify that the Permutive SDK is loading correctly and that cohort data is being written to local storage. After activating cohorts and configuring Prebid.js, verify that cohort data is being passed to Index Exchange in bid requests. Use the **Professor Prebid Chrome extension** ([download here](https://chromewebstore.google.com/detail/professor-prebid/kdnllijdimhbledmfdbljampcdphcbdc)) to inspect bid requests: 1. Install the Professor Prebid extension 2. Open the extension while on your page 3. Navigate to the "Bid Requests" tab 4. Find Index Exchange (`ix`) bidder requests 5. Verify that the ORTB2 object contains Permutive cohort data in `ortb2.user.data` and `ortb2.user.keywords` Alternatively, use browser developer tools to: 1. Check local storage for the `_pindexs` key (Custom Cohorts) or `_pssps` key (Standard/Curated/DCR cohorts) 2. Inspect network requests to Index Exchange endpoints 3. Verify that the ORTB2 object in bid requests contains Permutive cohort data It may take a few minutes for cohort membership to populate after initial page load. ### Integrate with Prebid.js The Index Exchange SSP integration works through the Permutive Real-Time Data (RTD) module in Prebid.js. The module reads cohort data from local storage (set by the Permutive SDK) and attaches it to bid requests as first-party data following OpenRTB 2.x conventions. #### Configuration In your Prebid.js configuration, include the Permutive RTD module: ```javascript theme={"dark"} pbjs.setConfig({ realTimeData: { dataProviders: [ { name: "permutive", waitForIt: true, params: { maxSegs: 500, }, }, ], }, }); ``` #### How it Works The Index Exchange SSP integration operates through a client-side mechanism: **1. Local Storage Cohort Exposure** The Permutive Web SDK exposes cohort signals via local storage: * **Custom Cohorts**: Stored in the `_pindexs` local storage key * **Standard, Curated, and DCR Cohorts**: Stored in the `_pssps` local storage key **2. Real-Time Bid Enrichment** The Permutive RTD module acts as a bridge between the Permutive Web SDK and Prebid.js: 1. The Permutive RTD module reads cohort IDs from local storage (`_pindexs` for Custom Cohorts, `_pssps` for Standard/Curated/DCR cohorts) 2. Cohort data is attached to the ORTB2 object in bid requests for Index Exchange 3. For Custom Cohorts, the module updates two locations: * `ortb2.user.data` – adds a data provider entry for `permutive.com` with the list of cohort IDs in the `segment` field * `ortb2.user.keywords` – adds a keyword group called `permutive` containing the list of cohort IDs 4. Index Exchange receives cohort targeting data in the bid request and makes it available to demand partners (DSPs) for real-time targeting #### Consent While Permutive is listed as a TCF vendor (ID: 361), Permutive does not typically obtain vendor consent from the TCF, but instead relies on publisher purpose consents. Publishers wishing to use TCF vendor consent instead can add 361 to their CMP and set `params.enforceVendorConsent` to `true`: ```javascript theme={"dark"} pbjs.setConfig({ realTimeData: { dataProviders: [{ name: 'permutive', params: { enforceVendorConsent: true // Require TCF vendor consent for Permutive (ID: 361) } }] } }) ``` For more details on Prebid.js configuration, see the [Prebid.js documentation](https://docs.prebid.org/dev-docs/modules/permutiveRtdProvider.html). Index Exchange supports in-app inventory via Prebid Mobile (Prebid SDK for iOS). Prebid Mobile passes bid requests to a Prebid Server instance, which routes them to Index Exchange via the `ix` bidder. Activation of Permutive cohorts on iOS in-app traffic requires customer implementation to retrieve cohort signals from the Permutive Mobile SDK and attach them to Prebid Mobile bid requests. **High-level pattern:** 1. Initialise the Permutive Mobile SDK in your iOS app. 2. Retrieve the user's active cohort IDs from the SDK. 3. Attach the cohort IDs to your Prebid Mobile bid request configuration as first-party user data, matching the structure used by the Permutive RTD module on web — see the Web tab for the full ORTB2 mapping (`ortb2.user.data` and `ortb2.user.keywords`). 4. Verify that bid requests reaching Index Exchange (via your Prebid Server) carry Permutive cohort data. Contact your Permutive Customer Success Manager — our team will partner with you on the integration. Index Exchange supports in-app inventory via Prebid Mobile (Prebid SDK for Android). Prebid Mobile passes bid requests to a Prebid Server instance, which routes them to Index Exchange via the `ix` bidder. Activation of Permutive cohorts on Android in-app traffic requires customer implementation to retrieve cohort signals from the Permutive Mobile SDK and attach them to Prebid Mobile bid requests. **High-level pattern:** 1. Initialise the Permutive Mobile SDK in your Android app. 2. Retrieve the user's active cohort IDs from the SDK (e.g. via `TriggersProvider`). 3. Attach the cohort IDs to your Prebid Mobile bid request configuration as first-party user data, matching the structure used by the Permutive RTD module on web — see the Web tab for the full ORTB2 mapping (`ortb2.user.data` and `ortb2.user.keywords`). 4. Verify that bid requests reaching Index Exchange (via your Prebid Server) carry Permutive cohort data. Contact your Permutive Customer Success Manager — our team will partner with you on the integration. CTV activation on Index Exchange typically depends on a server-side ad-serving setup (e.g. Prebid Server with SSAI). Permutive partners with your team on a tailored solution to enrich Index Exchange bid requests with cohort data. Contact your Permutive Customer Success Manager to discuss your CTV environment and the right activation approach. ## Data Types The Index Exchange SSP integration is a destination-only integration focused on cohort activation. Permutive does not collect event data from Index Exchange SSP. When cohorts are activated to Index Exchange, the following data is transmitted via the Prebid.js bidstream: Permutive passes cohort data to Index Exchange SSP via the ORTB2 object in Prebid.js bid requests. Different cohort types are written to specific ORTB2 locations: | ORTB2 Location | Cohort Types | | ----------------------------------------- | ---------------------------------------------- | | `ortb2.user.data` (name: `permutive.com`) | Standard Cohorts, DCR Cohorts, Curated Cohorts | | `ortb2.user.data` (name: `permutive`) | Custom Cohorts | | `ortb2.user.keywords` (`permutive`) | Custom Cohorts | | `ortb2.user.keywords` (`p_standard`) | Standard Cohorts, DCR Cohorts, Curated Cohorts | | `ortb2.user.keywords` (`p_standard_aud`) | Curated Cohorts | The Permutive Web SDK exposes cohort data via local storage, which the Permutive RTD module reads to populate bid requests. Local storage key containing Custom Cohort IDs for Index Exchange. Local storage key containing Standard Cohorts, Curated Cohorts, and Data Clean Room Cohorts. ## Troubleshooting If Permutive cohorts aren't being passed to Index Exchange in your bid requests: * Verify that the Permutive RTD module is properly configured in your Prebid.js setup * Confirm that the Permutive SDK is loading correctly on your pages * Use browser developer tools to inspect local storage and verify that the `_pindexs` key (for Custom Cohorts) or `_pssps` key (for Standard/Curated/DCR cohorts) contains cohort data * Check your Prebid.js console logs for any errors related to the Permutive RTD module * Use the **Professor Prebid Chrome extension** to inspect ORTB2 data being sent to Index Exchange bid requests * Inspect network requests to Index Exchange endpoints to verify ORTB2 data is present If you see cohort data in local storage but not in bid requests, verify that your Prebid.js configuration includes `waitForIt: true` for the Permutive RTD module. If local storage keys are empty or missing: * Verify that Custom Cohorts have been activated in the Permutive dashboard with Index Exchange SSP enabled * Check that users actually qualify for the activated cohorts based on cohort definitions * Ensure the Permutive SDK is loading before Prebid.js attempts to read cohort data * Verify that third-party cookies and local storage are not blocked by browser settings or extensions * Check the browser console for any Permutive SDK errors during page load If cohorts are being passed in bid requests but you don't see them available for targeting in the Index Exchange UI: * Verify with your Index Exchange account team that your SSP seat is configured to receive and process Permutive cohort data * Check that DSPs connected to your Index Exchange seat have access to user data signals from the bid request * Ensure that your Index Exchange line items or deals are configured to accept and use targeting data from the bid stream * Confirm that the ORTB2 data structure matches Index Exchange's expectations for user data and keywords In GDPR regions, consent management may affect cohort data transmission: * **Note:** Permutive is a TCF vendor (ID: 361), but by default relies on publisher purpose consents rather than TCF vendor consent * If you've enabled `params.enforceVendorConsent: true`, verify that Permutive (vendor ID 361) is added to your CMP and consent is being collected * Verify that your Consent Management Platform (CMP) is properly configured and Permutive has consent to process user data * Check that the Permutive SDK is receiving consent signals correctly * Ensure Prebid.js is configured to respect consent signals and only pass data when consent is granted If you're not seeing Index Exchange bid requests in the Professor Prebid Chrome extension: * Verify that the `ix` bidder adapter is properly configured in your Prebid.js setup * Check that Index Exchange is enabled and actively bidding on your inventory * Ensure you're viewing the correct page where Index Exchange bidder is configured * Try refreshing the page with the extension open to capture new bid requests * Check your Prebid.js console logs to confirm Index Exchange bidder is being called ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Magnite Source: https://docs.permutive.com/integrations/advertising/ssps/magnite Integrate with Magnite SSP for real-time cohort activation
Magnite

Magnite

Magnite is a leading independent sell-side advertising platform that helps publishers maximize revenue through programmatic advertising.

## Overview Magnite (formerly Rubicon Project) is a leading independent sell-side advertising platform that enables publishers to maximize their advertising revenue through programmatic channels. Our integration with Magnite SSP allows you to activate Permutive cohorts for targeting on Magnite's supply-side platform. This integration is a Destination: * **Destination:** Permutive cohorts can be activated and synced with Magnite SSP, enabling advertisers to target your audiences programmatically. This integration supports two activation methods: * **Real-time bidstream activation:** Cohort IDs are attached to bid requests as first-party data via Prebid.js (Web), Prebid Mobile (iOS/Android), and other Prebid integrations. * **[Server-side identity-based activation](/integrations/server-side-identity-based-activation):** Can be an alternative path where client-side activation isn't viable. Permutive uploads cohort memberships to Magnite's Segment Management API keyed by identity values (cookie ID, IDFA, AAID, IP address, or hashed email). Cohorts appear as Magnite segments ready for targeting. Use cases include: * **Cookie-less audience activation at scale:** Activate Permutive cohort signals programmatically via the bidstream without reliance on third-party cookies, enabling addressable reach across all users * **Private Marketplace (PMP) deals:** Use Permutive cohorts to create premium, curated inventory packages for direct buyer relationships * **Maximize programmatic yield:** Make Permutive cohorts available through Magnite's ad exchange to increase competition and drive higher CPMs * **Cross-environment activation:** Activate audiences across Web, in-app (iOS/Android), and CTV inventory through real-time bidstream or server-side identity-based methods ## Environment Compatibility | Environment | Activation | | -------------- | -------------------------------------------------------------------- | | **Web** | Live — Permutive RTD module in Prebid.js | | **iOS** | Available — via Prebid Mobile (customer implementation) | | **Android** | Available — via Prebid Mobile (customer implementation) | | **CTV** | Discuss with your CSM | | **API Direct** | Discuss with your CSM (for SDK-less environments) | **[Server-side identity-based activation](/integrations/server-side-identity-based-activation)** can be an alternative path for this integration, particularly where client-side activation isn't viable. Permutive can support your team in setting this up — contact your Customer Success Manager to discuss. ## Supported Cohort Types Magnite SSP activation supports multiple Permutive cohort types. Custom Cohorts are enabled by default; other cohort types can be enabled on request. Availability also depends on your workspace participating in the relevant Permutive offering. | Cohort type | Availability for Magnite | Product required | | --------------------------- | ----------------------------- | --------------------------------------- | | **Custom Cohorts** | Live | Core Platform | | **Standard Cohorts** | On request | [Curation](/products/curation) | | **Curation Cohorts** | On request | [Curation](/products/curation) | | **Data Clean Room Cohorts** | On request | [Data Clean Room](/products/clean-room) | **Cohort type support beyond Web** The availability shown above reflects Web today. In mobile (iOS/Android) and CTV environments where this integration is available, cohort type support depends on the Permutive Mobile/CTV SDKs: * **Custom Cohorts** — supported across all environments where this integration is available. * **Data Clean Room Cohorts** — not currently exposed by the Permutive Mobile/CTV SDKs, but cohort signals can be retrieved with customer integration via an additional Permutive API call from your application code. * **Standard** and **Curation Cohorts** — not currently exposed by the Permutive Mobile/CTV SDKs. Support is coming — reach out to your Customer Success Manager if you're interested. Cohort type availability also depends on your workspace participating in the relevant Permutive offering. Contact your Customer Success Manager to enable additional cohort types or discuss your use case. ## Prerequisites **Required for all activation methods:** * **Magnite SSP Account** — an active Magnite SSP account * **Magnite Publisher ID** — your Magnite SSP Account ID **For Web (Prebid.js):** * **Permutive Web SDK** — deployed on your site * **Prebid.js with Permutive RTD module** — configured to pass cohort data to Magnite * **Rubicon bidder adapter** — the [`rubicon`](https://docs.prebid.org/dev-docs/bidders/rubicon.html) bidder adapter configured in your Prebid.js setup (this is the bidder code for Magnite) **For iOS / Android (Prebid Mobile):** * **Permutive Mobile SDK** — deployed in your iOS / Android app * **Prebid Mobile SDK** — configured in your app * **Prebid Server** — that Prebid Mobile communicates with, with the `rubicon` bidder configured (Prebid Mobile passes bid requests to Prebid Server, which handles bidder routing) * **Customer implementation** — to retrieve Permutive cohorts from the SDK and attach them to Prebid Mobile bid requests **For CTV and other environments:** * Contact your Permutive Customer Success Manager to discuss the integration approach for your stack. **For server-side identity-based activation:** * This connector is not built today. If your use case calls for it, identity collection would need to be configured in Permutive for the identity types you want to use (cookie ID, IDFA, AAID, IP address, hashed email). * Contact your Permutive Customer Success Manager to discuss. ## Setup You must enable the Magnite SSP integration in the Permutive Dashboard, if this has not already been done. In the Permutive dashboard, navigate to your workspace's integrations page. Click *Add Integration* and select *Magnite SSP*. You will be prompted to enter your **Magnite Publisher ID** (your Magnite SSP Account ID). This Publisher ID is used to authenticate with the Magnite API and tells Permutive which Magnite account to create and manage segments in. When you activate cohorts, Permutive uses this ID to make server-side API calls to the Magnite Segment Management API on your behalf. The integration may also be listed as "Rubicon SSP" in some areas of the dashboard, as Magnite was formerly known as Rubicon Project. Once the integration is enabled, you can configure individual cohorts for activation to Magnite SSP. Navigate to the cohort you wish to activate, and toggle the Magnite SSP activation to *On*. When you enable this activation, Permutive will automatically create the corresponding segment in your Magnite SSP account using the Magnite Segment Management API. After activating a cohort, verify that the segment has been created successfully in your Magnite SSP dashboard and that cohort data is being passed in bid requests. **Verify segment creation:** In your Magnite SSP account, navigate to your segment management section. You should see segments created by Permutive, typically with names corresponding to your cohort IDs or names. **Verify bid request data:** Use the [Professor Prebid Chrome extension](https://chromewebstore.google.com/detail/professor-prebid/kdnllijdimhbledmfdbljampcdphcbdc) to inspect bid requests: 1. Check local storage for the `_prubicons` key (Custom Cohorts) 2. Find Magnite (`rubicon`) bidder requests in Professor Prebid 3. Verify that the ORTB2 object contains Permutive cohort data in `ortb2.user.data` and `ortb2.user.keywords` It may take a few minutes for segments to appear in the Magnite UI and for cohort membership to populate after initial page load. ### Integrate with Prebid.js The Magnite SSP integration works through the Permutive Real-Time Data (RTD) module in Prebid.js. The module reads Custom Cohort data from local storage (set by the Permutive SDK) and attaches it to bid requests as first-party data following OpenRTB 2.x conventions. #### Configuration In your Prebid.js configuration, include the Permutive RTD module: ```javascript theme={"dark"} pbjs.setConfig({ realTimeData: { dataProviders: [ { name: "permutive", waitForIt: true, params: { maxSegs: 500, }, }, ], }, }); ``` #### How it Works The Magnite SSP integration operates through a two-step process: **1. Segment Pre-Creation (Server-Side)** When you activate a Custom Cohort in Permutive, segments are created in your Magnite account via the Segment Management API. This pre-creation step is necessary because Magnite needs to register each segment ID and map it to a meaningful segment name before it can be used for targeting. This ensures that when Magnite receives bid requests containing segment IDs, it can recognize them and make them available to buyers (DSPs) for targeting. **2. Local Storage Cohort Exposure** The Permutive Web SDK exposes Custom Cohort signals via local storage: * **Custom Cohorts**: Stored in the `_prubicons` local storage key **3. Real-Time Bid Enrichment (Client-Side)** The Permutive RTD module acts as a bridge between the Permutive Web SDK and Prebid.js: 1. The Permutive RTD module reads Custom Cohort IDs from local storage (`_prubicons`) 2. Cohort data is attached to the ORTB2 object in bid requests for Magnite 3. The module updates two locations: * `ortb2.user.data` – adds a data provider entry for `permutive` with the list of cohort IDs in the `segment` field * `ortb2.user.keywords` – adds a keyword group called `permutive` containing the list of cohort IDs 4. Magnite receives cohort targeting data in the bid request and makes it available to demand partners (DSPs) for real-time targeting #### Consent While Permutive is listed as a TCF vendor (ID: 361), Permutive does not typically obtain vendor consent from the TCF, but instead relies on publisher purpose consents. Publishers wishing to use TCF vendor consent instead can add 361 to their CMP and set `params.enforceVendorConsent` to `true`: ```javascript theme={"dark"} pbjs.setConfig({ realTimeData: { dataProviders: [{ name: 'permutive', params: { enforceVendorConsent: true // Require TCF vendor consent for Permutive (ID: 361) } }] } }) ``` For more details on Prebid.js configuration, see the [Prebid.js documentation](https://docs.prebid.org/dev-docs/modules/permutiveRtdProvider.html). Magnite supports in-app inventory via Prebid Mobile (Prebid SDK for iOS). Prebid Mobile passes bid requests to a Prebid Server instance, which routes them to Magnite via the `rubicon` bidder. Activation of Permutive cohorts on iOS in-app traffic requires customer implementation to retrieve cohort signals from the Permutive Mobile SDK and attach them to Prebid Mobile bid requests. **High-level pattern:** 1. Initialise the Permutive Mobile SDK in your iOS app. 2. Retrieve the user's active cohort IDs from the SDK. 3. Attach the cohort IDs to your Prebid Mobile bid request configuration as first-party user data — typically via `ortb2.user.data` (with a `permutive` data provider entry) and `ortb2.user.keywords`. This mirrors the structure used by the Permutive RTD module on web. 4. Verify that bid requests reaching Magnite (via your Prebid Server) carry Permutive cohort data. Contact your Permutive Customer Success Manager — our team will partner with you on the integration. Magnite supports in-app inventory via Prebid Mobile (Prebid SDK for Android). Prebid Mobile passes bid requests to a Prebid Server instance, which routes them to Magnite via the `rubicon` bidder. Activation of Permutive cohorts on Android in-app traffic requires customer implementation to retrieve cohort signals from the Permutive Mobile SDK and attach them to Prebid Mobile bid requests. **High-level pattern:** 1. Initialise the Permutive Mobile SDK in your Android app. 2. Retrieve the user's active cohort IDs from the SDK (e.g. via `TriggersProvider`). 3. Attach the cohort IDs to your Prebid Mobile bid request configuration as first-party user data — typically via `ortb2.user.data` (with a `permutive` data provider entry) and `ortb2.user.keywords`. This mirrors the structure used by the Permutive RTD module on web. 4. Verify that bid requests reaching Magnite (via your Prebid Server) carry Permutive cohort data. Contact your Permutive Customer Success Manager — our team will partner with you on the integration. CTV activation on Magnite typically depends on a server-side ad-serving setup (e.g. Prebid Server with SSAI). Permutive partners with your team on a tailored solution to enrich Magnite bid requests with cohort data. Contact your Permutive Customer Success Manager to discuss your CTV environment and the right activation approach. ## Data Types The Magnite SSP integration is a destination-only integration focused on cohort activation. Permutive does not collect event data from Magnite SSP. When Custom Cohorts are activated to Magnite, the following data is transmitted via the Prebid.js bidstream: Permutive passes Custom Cohort data to Magnite SSP via the ORTB2 object in Prebid.js bid requests: | ORTB2 Location | Cohort Types | | ------------------------------------- | -------------- | | `ortb2.user.data` (name: `permutive`) | Custom Cohorts | | `ortb2.user.keywords` (`permutive`) | Custom Cohorts | By default, only Custom Cohorts appear in Magnite bid requests via the Web RTD module. Standard, Data Clean Room, and Curation cohort support is available on request — see [Supported Cohort Types](#supported-cohort-types). The Permutive Web SDK exposes cohort data via local storage, which the Permutive RTD module reads to populate bid requests. Local storage key containing Custom Cohort IDs for Magnite. Permutive creates and manages segments in your Magnite SSP account via the Magnite Segment Management API. Each activated Custom Cohort is represented as a segment in Magnite. The unique identifier for the segment in Magnite, typically corresponding to the Permutive cohort ID. The name of the segment as it appears in Magnite, typically matching the cohort name in Permutive. ## Troubleshooting If you've activated a Custom Cohort in Permutive but don't see the corresponding segment in your Magnite SSP account: * Verify that the Magnite SSP integration is properly enabled in your Permutive Dashboard with the correct Magnite Publisher ID * Check that you have the correct permissions in your Magnite account to view segments * Ensure there are no special characters in your cohort names, as these can cause issues with segment creation * Wait a few minutes, as there may be a delay between activation in Permutive and segment creation in Magnite If the issue persists, contact Permutive Support with your cohort ID and workspace details. If Permutive Custom Cohorts aren't being passed to Magnite in your bid requests: * Verify that the Permutive RTD module is properly configured in your Prebid.js setup * Confirm that the Permutive SDK is loading correctly on your pages * Use browser developer tools to inspect local storage and verify that the `_prubicons` key contains cohort data * Check your Prebid.js console logs for any errors related to the Permutive RTD module * Use the [Professor Prebid Chrome extension](https://chromewebstore.google.com/detail/professor-prebid/kdnllijdimhbledmfdbljampcdphcbdc) to inspect ORTB2 data being sent to Magnite bid requests If you see cohort data in local storage but not in bid requests, verify that your Prebid.js configuration includes `waitForIt: true` for the Permutive RTD module. If the `_prubicons` local storage key is empty or missing: * Verify that Custom Cohorts have been activated in the Permutive dashboard with Magnite SSP enabled * Check that users actually qualify for the activated cohorts based on cohort definitions * Ensure the Permutive SDK is loading before Prebid.js attempts to read cohort data * Verify that third-party cookies and local storage are not blocked by browser settings or extensions * Check the browser console for any Permutive SDK errors during page load If you see an error when trying to enable the Magnite SSP integration in the Permutive Dashboard: * Verify that you have the necessary permissions in your Permutive workspace * Ensure your workspace has the correct product entitlements to use SSP activations * Verify that your Magnite Publisher ID is correct * Contact Permutive Support if you believe you should have access but are unable to enable the integration In GDPR regions, consent management may affect cohort data transmission: * **Note:** Permutive is a TCF vendor (ID: 361), but by default relies on publisher purpose consents rather than TCF vendor consent * If you've enabled `params.enforceVendorConsent: true`, verify that Permutive (vendor ID 361) is added to your CMP and consent is being collected * Verify that your Consent Management Platform (CMP) is properly configured and Permutive has consent to process user data * Check that the Permutive SDK is receiving consent signals correctly * Ensure Prebid.js is configured to respect consent signals and only pass data when consent is granted ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # OpenX Source: https://docs.permutive.com/integrations/advertising/ssps/openx Integrate with OpenX for targeting and campaign optimization
OpenX

OpenX

OpenX is a programmatic advertising platform that helps publishers maximize their digital advertising revenue.

## Overview OpenX is a programmatic advertising platform and supply-side platform (SSP) that helps publishers maximize their digital advertising revenue. Our integration with OpenX SSP allows you to activate Permutive cohorts for targeting on OpenX's supply-side platform. This integration is a Destination: * **Destination:** Permutive cohorts are activated and passed to OpenX SSP through bid requests, enabling advertisers to target your audiences programmatically. This integration supports two activation methods: * **Real-time bidstream activation:** Cohort IDs are attached to bid requests as first-party data via Prebid.js (Web), Prebid Mobile (iOS/Android), and other Prebid integrations. * **[Server-side identity-based activation](/integrations/server-side-identity-based-activation):** Can be an alternative path where client-side activation isn't viable. Permutive uploads cohort memberships to OpenX keyed by identity values (cookie ID, IDFA, AAID, IP address, or hashed email). Cohorts appear as OpenX segments ready for targeting. Use cases include: * **Cookie-less audience activation at scale:** Activate Permutive cohort signals programmatically via the bidstream without reliance on third-party cookies, enabling addressable reach across all users * **Private Marketplace (PMP) deals:** Use DCR Cohorts and Curated Cohorts to create premium, curated inventory packages for direct buyer relationships * **Open Marketplace (OMP) monetization:** Leverage Standard Cohorts to make your audiences available to demand partners in the open auction, increasing bid density and CPMs * **Maximize programmatic yield:** Make Permutive audiences available through OpenX's ad exchange to increase competition and drive higher CPMs * **Cross-environment activation:** Activate audiences across Web, in-app (iOS/Android), and CTV inventory through real-time bidstream or server-side identity-based methods ## Environment Compatibility | Environment | Activation | | -------------- | -------------------------------------------------------------------- | | **Web** | Live — Permutive RTD module in Prebid.js | | **iOS** | Available — via Prebid Mobile (customer implementation) | | **Android** | Available — via Prebid Mobile (customer implementation) | | **CTV** | Discuss with your CSM | | **API Direct** | Discuss with your CSM (for SDK-less environments) | **[Server-side identity-based activation](/integrations/server-side-identity-based-activation)** can be an alternative path for this integration, particularly where client-side activation isn't viable. Permutive can support your team in setting this up — contact your Customer Success Manager to discuss. ## Supported Cohort Types OpenX SSP activation supports multiple Permutive cohort types. Standard, Data Clean Room, and Curation Cohorts are enabled by default; Custom Cohorts can be enabled on request. Availability also depends on your workspace participating in the relevant Permutive offering. | Cohort type | Availability for OpenX | Product required | | --------------------------- | ----------------------------- | --------------------------------------- | | **Custom Cohorts** | On request | Core Platform | | **Standard Cohorts** | Live | [Curation](/products/curation) | | **Curation Cohorts** | Live | [Curation](/products/curation) | | **Data Clean Room Cohorts** | Live | [Data Clean Room](/products/clean-room) | **Cohort type support beyond Web** The availability shown above reflects Web today. In mobile (iOS/Android) and CTV environments where this integration is available, cohort type support depends on the Permutive Mobile/CTV SDKs: * **Custom Cohorts** — supported across all environments where this integration is available. * **Data Clean Room Cohorts** — not currently exposed by the Permutive Mobile/CTV SDKs, but cohort signals can be retrieved with customer integration via an additional Permutive API call from your application code. * **Standard** and **Curation Cohorts** — not currently exposed by the Permutive Mobile/CTV SDKs. Support is coming — reach out to your Customer Success Manager if you're interested. Cohort type availability also depends on your workspace participating in the relevant Permutive offering. Contact your Customer Success Manager to enable additional cohort types or discuss your use case. ## Prerequisites **Required for all activation methods:** * **OpenX SSP Account** — an active OpenX SSP account **For Web (Prebid.js):** * **Permutive Web SDK** — deployed on your site * **Prebid.js with Permutive RTD module** — configured to pass cohort data to OpenX * **OpenX bidder adapter** — the [`openx`](https://docs.prebid.org/dev-docs/bidders/openx.html) bidder adapter configured in your Prebid.js setup **For iOS / Android (Prebid Mobile):** * **Permutive Mobile SDK** — deployed in your iOS / Android app * **Prebid Mobile SDK** — configured in your app * **Prebid Server** — that Prebid Mobile communicates with, with the `openx` bidder configured (Prebid Mobile passes bid requests to Prebid Server, which handles bidder routing) * **Customer implementation** — to retrieve Permutive cohorts from the SDK and attach them to Prebid Mobile bid requests **For CTV and other environments:** * Contact your Permutive Customer Success Manager to discuss the integration approach for your stack. **For server-side identity-based activation:** * This connector is not built today. If your use case calls for it, identity collection would need to be configured in Permutive for the identity types you want to use (cookie ID, IDFA, AAID, IP address, hashed email). * Contact your Permutive Customer Success Manager to discuss. ## Setup The OpenX SSP integration works automatically through Prebid.js once cohorts are configured. Standard, Curated, and Data Clean Room cohorts are enabled by default. Custom Cohort activation to OpenX is available on request. Contact your CSM if you need to enable Custom Cohort activation, or to confirm your default cohort configuration. See [Supported Cohort Types](#supported-cohort-types) for the full list of cohort types and their availability. Ensure your Prebid.js configuration includes the Permutive RTD module with OpenX support: Verify that the Permutive SDK is loading correctly and that cohort data is being written to local storage. After configuring Prebid.js, verify that cohort data is being passed to OpenX in bid requests. Use browser developer tools to: 1. Check local storage for the `_pssps` key (Standard/Curated/DCR cohorts) 2. Use the [Professor Prebid Chrome extension](https://chromewebstore.google.com/detail/professor-prebid/kdnllijdimhbledmfdbljampcdphcbdc) to inspect bid requests and verify that ORTB2 data contains Permutive cohort data in `ortb2.user.data` and `ortb2.user.keywords` It may take a few minutes for cohort membership to populate after initial page load. ### Integrate with Prebid.js The OpenX SSP integration works through the Permutive Real-Time Data (RTD) module in Prebid.js. The module reads cohort data from local storage (set by the Permutive SDK) and attaches it to bid requests as first-party data following OpenRTB 2.x conventions. #### Configuration In your Prebid.js configuration, include the Permutive RTD module: ```javascript theme={"dark"} pbjs.setConfig({ realTimeData: { dataProviders: [ { name: "permutive", waitForIt: true, params: { maxSegs: 500, }, }, ], }, }); ``` #### How it Works The OpenX SSP integration operates through a client-side mechanism: **1. Local Storage Cohort Exposure** The Permutive Web SDK exposes cohort signals via local storage: * **Standard, Curated, and DCR Cohorts**: Stored in the `_pssps` local storage key **2. Real-Time Bid Enrichment** The Permutive RTD module acts as a bridge between the Permutive Web SDK and Prebid.js: 1. The Permutive RTD module reads cohort IDs from local storage (`_pssps` for Standard/Curated/DCR cohorts) 2. Cohort data is attached to the ORTB2 object in bid requests for OpenX 3. The module updates the following locations: * `ortb2.user.data` – adds a data provider entry for `permutive.com` with the list of cohort IDs in the `segment` field * `ortb2.user.keywords` – adds keyword groups containing the cohort IDs 4. OpenX receives cohort targeting data in the bid request and makes it available to demand partners (DSPs) for real-time targeting #### Consent While Permutive is listed as a TCF vendor (ID: 361), Permutive does not typically obtain vendor consent from the TCF, but instead relies on publisher purpose consents. Publishers wishing to use TCF vendor consent instead can add 361 to their CMP and set `params.enforceVendorConsent` to `true`: ```javascript theme={"dark"} pbjs.setConfig({ realTimeData: { dataProviders: [{ name: 'permutive', params: { enforceVendorConsent: true // Require TCF vendor consent for Permutive (ID: 361) } }] } }) ``` For more details on Prebid.js configuration, see the [Prebid.js documentation](https://docs.prebid.org/dev-docs/modules/permutiveRtdProvider.html). OpenX supports in-app inventory via Prebid Mobile (Prebid SDK for iOS). Prebid Mobile passes bid requests to a Prebid Server instance, which routes them to OpenX via the `openx` bidder. Activation of Permutive cohorts on iOS in-app traffic requires customer implementation to retrieve cohort signals from the Permutive Mobile SDK and attach them to Prebid Mobile bid requests. **High-level pattern:** 1. Initialise the Permutive Mobile SDK in your iOS app. 2. Retrieve the user's active cohort IDs from the SDK. 3. Attach the cohort IDs to your Prebid Mobile bid request configuration as first-party user data — typically via `ortb2.user.data` (with a `permutive.com` data provider entry) and `ortb2.user.keywords`. This mirrors the structure used by the Permutive RTD module on web. 4. Verify that bid requests reaching OpenX (via your Prebid Server) carry Permutive cohort data. Contact your Permutive Customer Success Manager — our team will partner with you on the integration. OpenX supports in-app inventory via Prebid Mobile (Prebid SDK for Android). Prebid Mobile passes bid requests to a Prebid Server instance, which routes them to OpenX via the `openx` bidder. Activation of Permutive cohorts on Android in-app traffic requires customer implementation to retrieve cohort signals from the Permutive Mobile SDK and attach them to Prebid Mobile bid requests. **High-level pattern:** 1. Initialise the Permutive Mobile SDK in your Android app. 2. Retrieve the user's active cohort IDs from the SDK (e.g. via `TriggersProvider`). 3. Attach the cohort IDs to your Prebid Mobile bid request configuration as first-party user data — typically via `ortb2.user.data` (with a `permutive.com` data provider entry) and `ortb2.user.keywords`. This mirrors the structure used by the Permutive RTD module on web. 4. Verify that bid requests reaching OpenX (via your Prebid Server) carry Permutive cohort data. Contact your Permutive Customer Success Manager — our team will partner with you on the integration. CTV activation on OpenX typically depends on a server-side ad-serving setup (e.g. Prebid Server with SSAI). Permutive partners with your team on a tailored solution to enrich OpenX bid requests with cohort data. Contact your Permutive Customer Success Manager to discuss your CTV environment and the right activation approach. ## Data Types The OpenX SSP integration is a destination-only integration focused on cohort activation. Permutive does not collect event data from OpenX SSP. When cohorts are activated to OpenX, the following data is transmitted via the Prebid.js bidstream: Permutive passes cohort data to OpenX SSP via the ORTB2 object in Prebid.js bid requests. Different cohort types are written to specific ORTB2 locations: | ORTB2 Location | Cohort Types | | ----------------------------------------- | ---------------------------------------------- | | `ortb2.user.data` (name: `permutive.com`) | Standard Cohorts, DCR Cohorts, Curated Cohorts | | `ortb2.user.keywords` (`p_standard`) | Standard Cohorts, DCR Cohorts, Curated Cohorts | | `ortb2.user.keywords` (`p_standard_aud`) | Curated Cohorts | The Permutive Web SDK exposes cohort data via local storage, which the Permutive RTD module reads to populate bid requests. Local storage key containing Standard Cohorts, Curated Cohorts, and Data Clean Room Cohorts. ## Troubleshooting If Permutive cohorts aren't being passed to OpenX in your bid requests: * Verify that the Permutive RTD module is properly configured in your Prebid.js setup * Confirm that the Permutive SDK is loading correctly on your pages * Use browser developer tools to inspect local storage and verify that the `_pssps` key (for Standard/Curated/DCR cohorts) contains cohort data * Check your Prebid.js console logs for any errors related to the Permutive RTD module * Use the [Professor Prebid Chrome extension](https://chromewebstore.google.com/detail/professor-prebid/kdnllijdimhbledmfdbljampcdphcbdc) to inspect bid requests and verify ORTB2 data is being passed to OpenX If you see cohort data in local storage but not in bid requests, verify that your Prebid.js configuration includes `waitForIt: true` for the Permutive RTD module. If the `_pssps` local storage key is empty or missing: * Verify with your Customer Success Manager that the relevant cohort types (Standard, Curated, DCR) have been configured for your integration * Check that users actually qualify for cohorts based on cohort definitions * Ensure the Permutive SDK is loading before Prebid.js attempts to read cohort data * Verify that third-party cookies and local storage are not blocked by browser settings or extensions * Check the browser console for any Permutive SDK errors during page load If cohorts are being passed in bid requests but you don't see them available for targeting in the OpenX UI: * Verify with your OpenX account team that your SSP seat is configured to receive and process Permutive cohort data * Check that DSPs connected to your OpenX seat have access to user data signals from the bid request * Ensure that your OpenX line items or deals are configured to accept and use targeting data from the bid stream * Confirm that the ORTB2 data structure matches OpenX's expectations for user data and keywords In GDPR regions, consent management may affect cohort data transmission: * **Note:** Permutive is a TCF vendor (ID: 361), but by default relies on publisher purpose consents rather than TCF vendor consent * If you've enabled `params.enforceVendorConsent: true`, verify that Permutive (vendor ID 361) is added to your CMP and consent is being collected * Verify that your Consent Management Platform (CMP) is properly configured and Permutive has consent to process user data * Check that the Permutive SDK is receiving consent signals correctly * Ensure Prebid.js is configured to respect consent signals and only pass data when consent is granted ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # PubMatic Source: https://docs.permutive.com/integrations/advertising/ssps/pubmatic Integrate with PubMatic for targeting and campaign optimization
PubMatic

PubMatic

PubMatic is a digital advertising technology company that provides a sell-side platform for publishers to maximize their programmatic ad revenue.

## Overview PubMatic is a digital advertising technology company that provides a sell-side platform (SSP) for publishers to maximize their programmatic ad revenue. Our integration with PubMatic SSP allows you to activate Permutive cohorts for targeting on PubMatic's supply-side platform. This integration is a Destination: * **Destination:** Permutive cohorts are activated and passed to PubMatic SSP through bid requests, enabling advertisers to target your audiences programmatically. This integration supports two activation methods: * **Real-time bidstream activation:** Cohort IDs are attached to bid requests as first-party data via Prebid.js (Web), Prebid Mobile (iOS/Android), and other Prebid integrations. * **[Server-side identity-based activation](/integrations/server-side-identity-based-activation):** Can be an alternative path where client-side activation isn't viable. Permutive uploads cohort memberships to PubMatic keyed by identity values (cookie ID, IDFA, AAID, IP address, or hashed email). Cohorts appear as PubMatic segments ready for targeting. Use cases include: * **Cookie-less audience activation at scale:** Activate Permutive cohort signals programmatically via the bidstream without reliance on third-party cookies, enabling addressable reach across all users * **Private Marketplace (PMP) deals:** Use DCR Cohorts and Curated Cohorts to create premium, curated inventory packages for direct buyer relationships * **Open Marketplace (OMP) monetization:** Leverage Standard Cohorts to make your audiences available to demand partners in the open auction, increasing bid density and CPMs * **Maximize programmatic yield:** Make Permutive audiences available through PubMatic's ad exchange to increase competition and drive higher CPMs * **Cross-environment activation:** Activate audiences across Web, in-app (iOS/Android), and CTV inventory through real-time bidstream or server-side identity-based methods ## Environment Compatibility | Environment | Activation | | -------------- | -------------------------------------------------------------------- | | **Web** | Live — Permutive RTD module in Prebid.js | | **iOS** | Available — via Prebid Mobile (customer implementation) | | **Android** | Available — via Prebid Mobile (customer implementation) | | **CTV** | Discuss with your CSM | | **API Direct** | Discuss with your CSM (for SDK-less environments) | **[Server-side identity-based activation](/integrations/server-side-identity-based-activation)** can be an alternative path for this integration, particularly where client-side activation isn't viable. Permutive can support your team in setting this up — contact your Customer Success Manager to discuss. ## Supported Cohort Types PubMatic SSP activation supports multiple Permutive cohort types. Standard, Data Clean Room, and Curation Cohorts are enabled by default; Custom Cohorts can be enabled on request. Availability also depends on your workspace participating in the relevant Permutive offering. | Cohort type | Availability for PubMatic | Product required | | --------------------------- | ----------------------------- | --------------------------------------- | | **Custom Cohorts** | On request | Core Platform | | **Standard Cohorts** | Live | [Curation](/products/curation) | | **Curation Cohorts** | Live | [Curation](/products/curation) | | **Data Clean Room Cohorts** | Live | [Data Clean Room](/products/clean-room) | **Cohort type support beyond Web** The availability shown above reflects Web today. In mobile (iOS/Android) and CTV environments where this integration is available, cohort type support depends on the Permutive Mobile/CTV SDKs: * **Custom Cohorts** — supported across all environments where this integration is available. * **Data Clean Room Cohorts** — not currently exposed by the Permutive Mobile/CTV SDKs, but cohort signals can be retrieved with customer integration via an additional Permutive API call from your application code. * **Standard** and **Curation Cohorts** — not currently exposed by the Permutive Mobile/CTV SDKs. Support is coming — reach out to your Customer Success Manager if you're interested. Cohort type availability also depends on your workspace participating in the relevant Permutive offering. Contact your Customer Success Manager to enable additional cohort types or discuss your use case. ## Prerequisites **Required for all activation methods:** * **PubMatic SSP Account** — an active PubMatic SSP account **For Web (Prebid.js):** * **Permutive Web SDK** — deployed on your site * **Prebid.js with Permutive RTD module** — configured to pass cohort data to PubMatic * **PubMatic bidder adapter** — the [`pubmatic`](https://docs.prebid.org/dev-docs/bidders/pubmatic.html) bidder adapter configured in your Prebid.js setup **For iOS / Android (Prebid Mobile):** * **Permutive Mobile SDK** — deployed in your iOS / Android app * **Prebid Mobile SDK** — configured in your app * **Prebid Server** — that Prebid Mobile communicates with, with the `pubmatic` bidder configured (Prebid Mobile passes bid requests to Prebid Server, which handles bidder routing) * **Customer implementation** — to retrieve Permutive cohorts from the SDK and attach them to Prebid Mobile bid requests **For CTV and other environments:** * Contact your Permutive Customer Success Manager to discuss the integration approach for your stack. **For server-side identity-based activation:** * This connector is not built today. If your use case calls for it, identity collection would need to be configured in Permutive for the identity types you want to use (cookie ID, IDFA, AAID, IP address, hashed email). * Contact your Permutive Customer Success Manager to discuss. ## Setup The PubMatic SSP integration works automatically through Prebid.js once cohorts are configured. Standard, Curated, and Data Clean Room cohorts are enabled by default. Custom Cohort activation to PubMatic is available on request. Contact your CSM if you need to enable Custom Cohort activation, or to confirm your default cohort configuration. See [Supported Cohort Types](#supported-cohort-types) for the full list of cohort types and their availability. Ensure your Prebid.js configuration includes the Permutive RTD module with PubMatic support: Verify that the Permutive SDK is loading correctly and that cohort data is being written to local storage. After configuring Prebid.js, verify that cohort data is being passed to PubMatic in bid requests. Use browser developer tools to: 1. Check local storage for the `_pssps` key (Standard/Curated/DCR cohorts) 2. Use the [Professor Prebid Chrome extension](https://chromewebstore.google.com/detail/professor-prebid/kdnllijdimhbledmfdbljampcdphcbdc) to inspect bid requests and verify that ORTB2 data contains Permutive cohort data in `ortb2.user.data` and `ortb2.user.keywords` It may take a few minutes for cohort membership to populate after initial page load. ### Integrate with Prebid.js The PubMatic SSP integration works through the Permutive Real-Time Data (RTD) module in Prebid.js. The module reads cohort data from local storage (set by the Permutive SDK) and attaches it to bid requests as first-party data following OpenRTB 2.x conventions. #### Configuration In your Prebid.js configuration, include the Permutive RTD module: ```javascript theme={"dark"} pbjs.setConfig({ realTimeData: { dataProviders: [ { name: "permutive", waitForIt: true, params: { maxSegs: 500, }, }, ], }, }); ``` #### How it Works The PubMatic SSP integration operates through a client-side mechanism: **1. Local Storage Cohort Exposure** The Permutive Web SDK exposes cohort signals via local storage: * **Standard, Curated, and DCR Cohorts**: Stored in the `_pssps` local storage key **2. Real-Time Bid Enrichment** The Permutive RTD module acts as a bridge between the Permutive Web SDK and Prebid.js: 1. The Permutive RTD module reads cohort IDs from local storage (`_pssps` for Standard/Curated/DCR cohorts) 2. Cohort data is attached to the ORTB2 object in bid requests for PubMatic 3. The module updates the following locations: * `ortb2.user.data` – adds a data provider entry for `permutive.com` with the list of cohort IDs in the `segment` field * `ortb2.user.keywords` – adds keyword groups containing the cohort IDs 4. PubMatic receives cohort targeting data in the bid request and makes it available to demand partners (DSPs) for real-time targeting #### Consent While Permutive is listed as a TCF vendor (ID: 361), Permutive does not typically obtain vendor consent from the TCF, but instead relies on publisher purpose consents. Publishers wishing to use TCF vendor consent instead can add 361 to their CMP and set `params.enforceVendorConsent` to `true`: ```javascript theme={"dark"} pbjs.setConfig({ realTimeData: { dataProviders: [{ name: 'permutive', params: { enforceVendorConsent: true // Require TCF vendor consent for Permutive (ID: 361) } }] } }) ``` For more details on Prebid.js configuration, see the [Prebid.js documentation](https://docs.prebid.org/dev-docs/modules/permutiveRtdProvider.html). PubMatic supports in-app inventory via Prebid Mobile (Prebid SDK for iOS). Prebid Mobile passes bid requests to a Prebid Server instance, which routes them to PubMatic via the `pubmatic` bidder. Activation of Permutive cohorts on iOS in-app traffic requires customer implementation to retrieve cohort signals from the Permutive Mobile SDK and attach them to Prebid Mobile bid requests. **High-level pattern:** 1. Initialise the Permutive Mobile SDK in your iOS app. 2. Retrieve the user's active cohort IDs from the SDK. 3. Attach the cohort IDs to your Prebid Mobile bid request configuration as first-party user data — typically via `ortb2.user.data` (with a `permutive.com` data provider entry) and `ortb2.user.keywords`. This mirrors the structure used by the Permutive RTD module on web. 4. Verify that bid requests reaching PubMatic (via your Prebid Server) carry Permutive cohort data. Contact your Permutive Customer Success Manager — our team will partner with you on the integration. PubMatic supports in-app inventory via Prebid Mobile (Prebid SDK for Android). Prebid Mobile passes bid requests to a Prebid Server instance, which routes them to PubMatic via the `pubmatic` bidder. Activation of Permutive cohorts on Android in-app traffic requires customer implementation to retrieve cohort signals from the Permutive Mobile SDK and attach them to Prebid Mobile bid requests. **High-level pattern:** 1. Initialise the Permutive Mobile SDK in your Android app. 2. Retrieve the user's active cohort IDs from the SDK (e.g. via `TriggersProvider`). 3. Attach the cohort IDs to your Prebid Mobile bid request configuration as first-party user data — typically via `ortb2.user.data` (with a `permutive.com` data provider entry) and `ortb2.user.keywords`. This mirrors the structure used by the Permutive RTD module on web. 4. Verify that bid requests reaching PubMatic (via your Prebid Server) carry Permutive cohort data. Contact your Permutive Customer Success Manager — our team will partner with you on the integration. CTV activation on PubMatic typically depends on a server-side ad-serving setup (e.g. Prebid Server with SSAI). Permutive partners with your team on a tailored solution to enrich PubMatic bid requests with cohort data. Contact your Permutive Customer Success Manager to discuss your CTV environment and the right activation approach. ## Data Types The PubMatic SSP integration is a destination-only integration focused on cohort activation. Permutive does not collect event data from PubMatic SSP. When cohorts are activated to PubMatic, the following data is transmitted via the Prebid.js bidstream: Permutive passes cohort data to PubMatic SSP via the ORTB2 object in Prebid.js bid requests. Different cohort types are written to specific ORTB2 locations: | ORTB2 Location | Cohort Types | | ----------------------------------------- | ---------------------------------------------- | | `ortb2.user.data` (name: `permutive.com`) | Standard Cohorts, DCR Cohorts, Curated Cohorts | | `ortb2.user.keywords` (`p_standard`) | Standard Cohorts, DCR Cohorts, Curated Cohorts | | `ortb2.user.keywords` (`p_standard_aud`) | Curated Cohorts | The Permutive Web SDK exposes cohort data via local storage, which the Permutive RTD module reads to populate bid requests. Local storage key containing Standard Cohorts, Curated Cohorts, and Data Clean Room Cohorts. ## Troubleshooting If Permutive cohorts aren't being passed to PubMatic in your bid requests: * Verify that the Permutive RTD module is properly configured in your Prebid.js setup * Confirm that the Permutive SDK is loading correctly on your pages * Use browser developer tools to inspect local storage and verify that the `_pssps` key (for Standard/Curated/DCR cohorts) contains cohort data * Check your Prebid.js console logs for any errors related to the Permutive RTD module * Use the [Professor Prebid Chrome extension](https://chromewebstore.google.com/detail/professor-prebid/kdnllijdimhbledmfdbljampcdphcbdc) to inspect bid requests and verify ORTB2 data is being passed to PubMatic If you see cohort data in local storage but not in bid requests, verify that your Prebid.js configuration includes `waitForIt: true` for the Permutive RTD module. If the `_pssps` local storage key is empty or missing: * Verify with your Customer Success Manager that the relevant cohort types (Standard, Curated, DCR) have been configured for your integration * Check that users actually qualify for cohorts based on cohort definitions * Ensure the Permutive SDK is loading before Prebid.js attempts to read cohort data * Verify that third-party cookies and local storage are not blocked by browser settings or extensions * Check the browser console for any Permutive SDK errors during page load If cohorts are being passed in bid requests but you don't see them available for targeting in the PubMatic UI: * Verify with your PubMatic account team that your SSP seat is configured to receive and process Permutive cohort data * Check that DSPs connected to your PubMatic seat have access to user data signals from the bid request * Ensure that your PubMatic line items or deals are configured to accept and use targeting data from the bid stream * Confirm that the ORTB2 data structure matches PubMatic's expectations for user data and keywords In GDPR regions, consent management may affect cohort data transmission: * **Note:** Permutive is a TCF vendor (ID: 361), but by default relies on publisher purpose consents rather than TCF vendor consent * If you've enabled `params.enforceVendorConsent: true`, verify that Permutive (vendor ID 361) is added to your CMP and consent is being collected * Verify that your Consent Management Platform (CMP) is properly configured and Permutive has consent to process user data * Check that the Permutive SDK is receiving consent signals correctly * Ensure Prebid.js is configured to respect consent signals and only pass data when consent is granted ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Microsoft Monetize Source: https://docs.permutive.com/integrations/advertising/ssps/xandr Integrate with Microsoft Monetize (SSP) for targeting and campaign optimization
Microsoft Monetize

Microsoft Monetize (SSP)

Microsoft Monetize (formerly Xandr, AppNexus) is Microsoft's programmatic advertising platform for advanced audience targeting and campaign management.

## Overview Microsoft Monetize (formerly Xandr, AppNexus) is Microsoft's supply-side platform that provides publishers with tools to manage, deliver, and optimize programmatic advertising campaigns. Our integration with Microsoft Monetize allows you to activate Permutive cohorts for targeting on Microsoft Monetize's supply-side platform. This integration is a Destination: * **Destination:** Permutive cohorts are activated and passed to Microsoft Monetize SSP through bid requests, enabling advertisers to target your audiences programmatically. This integration supports two activation methods: * **Real-time bidstream activation:** Cohort IDs are attached to bid requests as first-party data via Prebid.js (Web), Prebid Mobile (iOS/Android), and other Prebid integrations. * **[Server-side identity-based activation](/integrations/server-side-identity-based-activation):** Can be an alternative path where client-side activation isn't viable. Permutive uploads cohort memberships to Microsoft Monetize keyed by identity values (cookie ID, IDFA, AAID, IP address, or hashed email). Cohorts appear as Microsoft Monetize segments ready for targeting. Use cases include: * **Cookie-less audience activation at scale:** Activate Permutive cohort signals programmatically via the bidstream without reliance on third-party cookies, enabling addressable reach across all users * **Private Marketplace (PMP) deals:** Use Custom Cohorts, DCR Cohorts, and Curated Cohorts to create premium, curated inventory packages for direct buyer relationships * **Open Marketplace (OMP) monetization:** Leverage Standard Cohorts to make your audiences available to demand partners in the open auction, increasing bid density and CPMs * **Maximize programmatic yield:** Make Permutive audiences available through Microsoft's ad exchange to increase competition and drive higher CPMs * **Cross-environment activation:** Activate audiences across Web, in-app (iOS/Android), and CTV inventory through real-time bidstream or server-side identity-based methods ## Environment Compatibility | Environment | Activation | | -------------- | -------------------------------------------------------------------- | | **Web** | Live — Permutive RTD module in Prebid.js | | **iOS** | Available — via Prebid Mobile (customer implementation) | | **Android** | Available — via Prebid Mobile (customer implementation) | | **CTV** | Discuss with your CSM | | **API Direct** | Discuss with your CSM (for SDK-less environments) | **[Server-side identity-based activation](/integrations/server-side-identity-based-activation)** can be an alternative path for this integration, particularly where client-side activation isn't viable. Permutive can support your team in setting this up — contact your Customer Success Manager to discuss. ## Supported Cohort Types Microsoft Monetize activation supports all current Permutive cohort types. Availability depends on your workspace participating in the relevant Permutive offering. | Cohort type | Availability for Microsoft Monetize | Product required | | --------------------------- | ----------------------------------- | --------------------------------------- | | **Custom Cohorts** | Live | Core Platform | | **Standard Cohorts** | Live | [Curation](/products/curation) | | **Curation Cohorts** | Live | [Curation](/products/curation) | | **Data Clean Room Cohorts** | Live | [Data Clean Room](/products/clean-room) | **Cohort type support beyond Web** The availability shown above reflects Web today. In mobile (iOS/Android) and CTV environments where this integration is available, cohort type support depends on the Permutive Mobile/CTV SDKs: * **Custom Cohorts** — supported across all environments where this integration is available. * **Data Clean Room Cohorts** — not currently exposed by the Permutive Mobile/CTV SDKs, but cohort signals can be retrieved with customer integration via an additional Permutive API call from your application code. * **Standard** and **Curation Cohorts** — not currently exposed by the Permutive Mobile/CTV SDKs. Support is coming — reach out to your Customer Success Manager if you're interested. Cohort type availability also depends on your workspace participating in the relevant Permutive offering. Contact your Customer Success Manager to enable additional cohort types or discuss your use case. ## Prerequisites **Required for all activation methods:** * **Microsoft Monetize SSP Account** — an active Microsoft Monetize (Xandr) SSP account **For Web (Prebid.js):** * **Permutive Web SDK** — deployed on your site * **Prebid.js with Permutive RTD module** — configured to pass cohort data to Microsoft Monetize * **Microsoft Monetize bidder adapter** — either the [`appnexus`](https://docs.prebid.org/dev-docs/bidders/appnexus.html) or [`msft`](https://docs.prebid.org/dev-docs/bidders/msft.html) bidder adapter configured in your Prebid.js setup (Microsoft is migrating from `appnexus` to `microsoft` bidder code — see Troubleshooting) **For iOS / Android (Prebid Mobile):** * **Permutive Mobile SDK** — deployed in your iOS / Android app * **Prebid Mobile SDK** — configured in your app * **Prebid Server** — that Prebid Mobile communicates with, with the `appnexus` (or `microsoft`) bidder configured (Prebid Mobile passes bid requests to Prebid Server, which handles bidder routing) * **Customer implementation** — to retrieve Permutive cohorts from the SDK and attach them to Prebid Mobile bid requests **For CTV and other environments:** * Contact your Permutive Customer Success Manager to discuss the integration approach for your stack. **For server-side identity-based activation:** * This connector is not built today. If your use case calls for it, identity collection would need to be configured in Permutive for the identity types you want to use (cookie ID, IDFA, AAID, IP address, hashed email). * Contact your Permutive Customer Success Manager to discuss. ## Setup The Microsoft Monetize SSP integration works automatically through Prebid.js once cohorts are activated. Navigate to the cohort you wish to activate in the Permutive dashboard. For **Custom Cohorts** (publisher-specific audiences), enable the Microsoft Monetize SSP activation. When activated, cohort data will be stored in the `_papns` local storage key, which the Permutive RTD module reads and passes to Microsoft Monetize. Custom Cohorts require explicit activation per cohort. Standard, Curated, and Data Clean Room cohorts are configured by your Customer Success Manager. Contact your CSM to enable these cohort types for your integration. Ensure your Prebid.js configuration includes the Permutive RTD module with Microsoft Monetize support: Verify that the Permutive SDK is loading correctly and that cohort data is being written to local storage. After activating cohorts and configuring Prebid.js, verify that cohort data is being passed to Microsoft Monetize in bid requests. Use browser developer tools to: 1. Check local storage for the `_papns` key (Custom Cohorts) or `_pssps` key (Standard/Curated/DCR cohorts) 2. Inspect network requests to Microsoft Monetize SSP endpoints (typically `/ut/v3/prebid`) 3. Verify that the ORTB2 object in bid requests contains Permutive cohort data in `ortb2.user.data` and `ortb2.user.keywords` It may take a few minutes for cohort membership to populate after initial page load. ### Integrate with Prebid.js The Microsoft Monetize SSP integration works through the Permutive Real-Time Data (RTD) module in Prebid.js. The module reads cohort data from local storage (set by the Permutive SDK) and attaches it to bid requests as first-party data following OpenRTB 2.x conventions. #### Configuration In your Prebid.js configuration, include the Permutive RTD module: ```javascript theme={"dark"} pbjs.setConfig({ realTimeData: { dataProviders: [ { name: "permutive", waitForIt: true, params: { maxSegs: 500, }, }, ], }, }); ``` #### How it Works The Microsoft Monetize SSP integration operates through a client-side mechanism: **1. Local Storage Cohort Exposure** The Permutive Web SDK exposes cohort signals via local storage: * **Custom Cohorts**: Stored in the `_papns` local storage key * **Standard, Curated, and DCR Cohorts**: Stored in the `_pssps` local storage key **2. Real-Time Bid Enrichment** The Permutive RTD module acts as a bridge between the Permutive Web SDK and Prebid.js: 1. The Permutive RTD module reads cohort IDs from local storage (`_papns` for Custom Cohorts, `_pssps` for Standard/Curated/DCR cohorts) 2. Cohort data is attached to the ORTB2 object in bid requests for Microsoft Monetize 3. For Custom Cohorts, the module updates two locations: * `ortb2.user.data` – adds a data provider entry for `permutive.com` with the list of cohort IDs in the `segment` field * `ortb2.user.keywords` – adds a keyword group called `permutive` containing the list of cohort IDs 4. Microsoft Monetize receives cohort targeting data in the bid request and makes it available to demand partners (DSPs) for real-time targeting #### Consent While Permutive is listed as a TCF vendor (ID: 361), Permutive does not typically obtain vendor consent from the TCF, but instead relies on publisher purpose consents. Publishers wishing to use TCF vendor consent instead can add 361 to their CMP and set `params.enforceVendorConsent` to `true`: ```javascript theme={"dark"} pbjs.setConfig({ realTimeData: { dataProviders: [{ name: 'permutive', params: { enforceVendorConsent: true // Require TCF vendor consent for Permutive (ID: 361) } }] } }) ``` For more details on Prebid.js configuration, see the [Prebid.js documentation](https://docs.prebid.org/dev-docs/modules/permutiveRtdProvider.html). Microsoft Monetize supports in-app inventory via Prebid Mobile (Prebid SDK for iOS). Prebid Mobile passes bid requests to a Prebid Server instance, which routes them to Microsoft Monetize via the `appnexus` (or `microsoft`) bidder. Activation of Permutive cohorts on iOS in-app traffic requires customer implementation to retrieve cohort signals from the Permutive Mobile SDK and attach them to Prebid Mobile bid requests. **High-level pattern:** 1. Initialise the Permutive Mobile SDK in your iOS app. 2. Retrieve the user's active cohort IDs from the SDK. 3. Attach the cohort IDs to your Prebid Mobile bid request configuration as first-party user data, matching the structure used by the Permutive RTD module on web — see the Web tab for the full ORTB2 mapping (`ortb2.user.data` and `ortb2.user.keywords`). 4. Verify that bid requests reaching Microsoft Monetize (via your Prebid Server) carry Permutive cohort data. Contact your Permutive Customer Success Manager — our team will partner with you on the integration. Microsoft Monetize supports in-app inventory via Prebid Mobile (Prebid SDK for Android). Prebid Mobile passes bid requests to a Prebid Server instance, which routes them to Microsoft Monetize via the `appnexus` (or `microsoft`) bidder. Activation of Permutive cohorts on Android in-app traffic requires customer implementation to retrieve cohort signals from the Permutive Mobile SDK and attach them to Prebid Mobile bid requests. **High-level pattern:** 1. Initialise the Permutive Mobile SDK in your Android app. 2. Retrieve the user's active cohort IDs from the SDK (e.g. via `TriggersProvider`). 3. Attach the cohort IDs to your Prebid Mobile bid request configuration as first-party user data, matching the structure used by the Permutive RTD module on web — see the Web tab for the full ORTB2 mapping (`ortb2.user.data` and `ortb2.user.keywords`). 4. Verify that bid requests reaching Microsoft Monetize (via your Prebid Server) carry Permutive cohort data. Contact your Permutive Customer Success Manager — our team will partner with you on the integration. CTV activation on Microsoft Monetize typically depends on a server-side ad-serving setup (e.g. Prebid Server with SSAI). Permutive partners with your team on a tailored solution to enrich Microsoft Monetize bid requests with cohort data. Contact your Permutive Customer Success Manager to discuss your CTV environment and the right activation approach. ## Data Types The Microsoft Monetize SSP integration is a destination-only integration focused on cohort activation. Permutive does not collect event data from Microsoft Monetize SSP. When cohorts are activated to Microsoft Monetize, the following data is transmitted via the Prebid.js bidstream: Permutive passes cohort data to Microsoft Monetize SSP via the ORTB2 object in Prebid.js bid requests. Different cohort types are written to specific ORTB2 locations: | ORTB2 Location | Cohort Types | | ----------------------------------------- | ---------------------------------------------- | | `ortb2.user.data` (name: `permutive.com`) | Standard Cohorts, DCR Cohorts, Curated Cohorts | | `ortb2.user.data` (name: `permutive`) | Custom Cohorts | | `ortb2.user.keywords` (`permutive`) | Custom Cohorts | | `ortb2.user.keywords` (`p_standard`) | Standard Cohorts, DCR Cohorts, Curated Cohorts | | `ortb2.user.keywords` (`p_standard_aud`) | Curated Cohorts | The Permutive Web SDK exposes cohort data via local storage, which the Permutive RTD module reads to populate bid requests. Local storage key containing Custom Cohort IDs. Local storage key containing Standard Cohorts, Curated Cohorts, and Data Clean Room Cohorts. ## Troubleshooting If Permutive cohorts aren't being passed to Microsoft Monetize in your bid requests: * Verify that the Permutive RTD module is properly configured in your Prebid.js setup * Confirm that the Permutive SDK is loading correctly on your pages * Use browser developer tools to inspect local storage and verify that the `_papns` key (for Custom Cohorts) or `_pssps` key (for Standard/Curated/DCR cohorts) contains cohort data * Check your Prebid.js console logs for any errors related to the Permutive RTD module * Inspect network requests to Microsoft Monetize endpoints (typically `/ut/v3/prebid`) to verify ORTB2 data is present If you see cohort data in local storage but not in bid requests, verify that your Prebid.js configuration includes `waitForIt: true` for the Permutive RTD module. If local storage keys are empty or missing: * Verify that Custom Cohorts have been activated in the Permutive dashboard with Microsoft Monetize SSP enabled * Check that users actually qualify for the activated cohorts based on cohort definitions * Ensure the Permutive SDK is loading before Prebid.js attempts to read cohort data * Verify that third-party cookies and local storage are not blocked by browser settings or extensions * Check the browser console for any Permutive SDK errors during page load Microsoft is in the process of migrating their Prebid bidder code from `appnexus` to `microsoft`. During this transition period: * Monitor announcements from Microsoft/Prebid regarding the deprecation timeline for the `appnexus` bidder code (see [https://docs.prebid.org/dev-docs/bidders/msft.html#table-of-contents](https://docs.prebid.org/dev-docs/bidders/msft.html#table-of-contents) for more info) * Work with your Permutive account team to understand any required configuration changes The transition from `appnexus` to `microsoft` bidder code may require updates to your Prebid.js configuration. Contact Permutive Support if you experience issues after Microsoft completes this migration. If cohorts are being passed in bid requests but you don't see them available for targeting in the Microsoft Monetize UI: * Verify with your Microsoft Monetize account team that your SSP seat is configured to receive and process Permutive cohort data * Check that DSPs connected to your Microsoft Monetize seat have access to user data signals from the bid request * Ensure that your Microsoft Monetize line items or deals are configured to accept and use targeting data from the bid stream * Confirm that the ORTB2 data structure matches Microsoft Monetize's expectations for user data and keywords In GDPR regions, consent management may affect cohort data transmission: * **Note:** Permutive is a TCF vendor (ID: 361), but by default relies on publisher purpose consents rather than TCF vendor consent * If you've enabled `params.enforceVendorConsent: true`, verify that Permutive (vendor ID 361) is added to your CMP and consent is being collected * Verify that your Consent Management Platform (CMP) is properly configured and Permutive has consent to process user data * Check that the Permutive SDK is receiving consent signals correctly * Ensure Prebid.js is configured to respect consent signals and only pass data when consent is granted ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Catalog Source: https://docs.permutive.com/integrations/contextual/catalog Browse our comprehensive catalog of ecosystem integrations. # Contextual Integrations
IBM Watson

IBM Watson

IBM Watson delivers contextual targeting capabilities, allowing publishers to serve relevant ads based on on-page content without user identifiers.

Silverbullet

Silverbullet 4D

Silverbullet 4D uses AI-powered contextual intelligence to help publishers enhance targeting and brand safety in a cookie-less environment.

OS Data Solutions

OS Data Solutions

OS Data Solutions provides geospatial and location data that publishers can use for audience targeting and segmentation.

TextRazor

TextRazor

Text Razor enables publishers to extract contextual insights from content for improved targeting and recommendation systems.

# IBM Watson Source: https://docs.permutive.com/integrations/contextual/ibm-watson Integrate with IBM Watson for contextual data enrichment.
IBM Watson

IBM Watson

IBM Watson delivers contextual targeting capabilities, allowing publishers to serve relevant ads based on on-page content without user identifiers.

## Overview IBM Watson Natural Language Understanding (NLU) analyzes page content and classifies it into standardized categories. Our integration enriches your `Pageview` events with contextual labels aligned to the IAB taxonomy, with optional advanced NLU signals (e.g. concepts, entities, sentiment) where enabled. This makes it possible to build and activate privacy-safe contextual strategies without user identifiers. This integration is a Source: * **Source:** Permutive enriches `Pageview` events with contextual classifications from IBM Watson. Use cases include: * Build contextual cohorts using IAB taxonomy labels (e.g. Sports, News/Politics). * Apply brand suitability and safety rules by including/excluding categories. * Power cookie-less targeting by using contextual cohorts across your inventory. * (Advanced) Use entities, concepts and sentiment for niche, high-intent targeting. * (Advanced) Standardize contextual labels that can be mapped into downstream activations. ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Permutive SDK**: Deployed on your web, iOS, or Android properties where you want content classified. * **Permutive enablement**: IBM Watson must be enabled for your workspace by Permutive (quotas and thresholds are configured during enablement). * **Accessible content**: Content should be accessible to classifiers. For paywalled or bot-protected pages, configure an allow mechanism (see Setup) so classifiers can fetch full content. * **Classification quota**: Ensure your monthly classification quota covers expected volume. Some sites block non-user traffic. If classifiers cannot fetch content, you can configure a URL parameter in **Contextual > Catalog > Settings** that will be appended to classification requests. Configure your paywall to allow bypass when this parameter is present. The standard parameter is `bot-access=true`, though custom parameter names and values can be configured. Alternatively, allowlist traffic based on the Autonomous System Number (ASN) and User-Agent header of Watson requests. ## Setup Contact your Customer Success Manager (CSM) to enable IBM Watson for your workspace. In the Permutive dashboard, navigate to **Contextual > Catalog** and locate IBM Watson. Configure your settings including: * **Requested Types**: Specify which classification types to request (Categories, Concepts, Emotion, Entities, Keywords, Sentiment) * **Domains**: Restrict classifications to specific domains to optimize quota usage. The domains shown are based on what you have defined under **Settings > Domains & App names** * **Quota**: Maximum monthly classification quota, managed by Permutive. Once the quota is reached, no more content will be classified until the next month * **Selective Classifications Threshold**: Set a minimum traffic threshold to classify only high-traffic URLs, optimizing quota usage. The default threshold is **20 pageviews within 24 hours** before initial classification is triggered * **XPath** (IBM Watson specific): Improve classifications by specifying a selector for the article body. This ensures other content like featured articles doesn't influence classifications. If the XPath does not return any results for a URL, a second classification request is issued without the XPath selector If your site uses a paywall or bot mitigation that blocks classifier requests, configure an allow mechanism. Navigate to **Contextual > Catalog > Settings** and configure a URL parameter (e.g., `bot-access=true`) that will be appended to classification requests. Configure your paywall to allow bypass when this parameter is present. Alternatively, your DevOps team can allowlist traffic based on the Autonomous System Number (ASN) and User-Agent header of Watson requests. After enablement, browse several article pages and check in your Permutive dashboard that `Pageview` events contain Watson classifications. You can preview classifications by hovering over the IBM Watson tile in the Catalog and clicking "Preview". Remember that pages must meet the configured threshold before they are classified. ## Data Types With IBM Watson enabled, Permutive enriches your `Pageview` events with contextual data. Permutive automatically updates your event schema to include these properties. The exact schema enabled for your workspace may vary. Standard enrichment aligned to the IAB taxonomy. List of category classifications detected for the page. Each object contains: * `provider` (string, required): "watson" * `value` (string, required): The classification category ID (e.g., "201"). The dashboard uses the taxonomy to display human-readable names * `taxonomy` (string, required): "iab\_2\_0" * `confidence` (float, required): Confidence score for the classification (e.g., 0.72) * `reason` (string, optional): Explanation for why this classification was made Additional enrichment fields may be enabled for advanced use cases. High-level concepts detected in the content. Each object contains: * `provider` (string, required): "watson" * `value` (string, required): The concept name (e.g., "Social network service") * `confidence` (float, required): Confidence score from 0 to 1 (e.g., 0.41) Named entities detected in the content. Each object contains: * `provider` (string, required): "watson" * `value` (string, required): The entity name (e.g., "Thomas J. Watson") * `confidence` (float, required): Confidence score from 0 to 1 Important keywords detected. Each object contains: * `provider` (string, required): "watson" * `value` (string, required): The keyword phrase (e.g., "curated online courses") * `confidence` (float, required): Confidence score from 0 to 1 Sentiment analysis for the content. Each object contains: * `provider` (string, required): "watson" * `value` (string, required): The sentiment label (e.g., "positive", "neutral", "negative") * `confidence` (float, required): Confidence score from 0 to 1 ## Troubleshooting Confirm the URL has met the minimum traffic threshold for classification. The default threshold is **20 pageviews within 24 hours**. Low-traffic or newly published pages may classify later. Contact Permutive Support to check if a specific URL has been classified or to verify your threshold configuration. If your monthly quota is reached, new URLs will not be classified until the next month. Contact your Customer Success Manager to review quotas. Classifier requests must be able to fetch the full article HTML. Configure your paywall to allow bypass using a URL parameter (e.g., `bot-access=true`). Set this in the Permutive dashboard under **Contextual > Catalog > Settings**, then configure your paywall system to allow access when this parameter is present in the URL. Alternatively, your DevOps team can allowlist Watson traffic based on the Autonomous System Number (ASN) and the User-Agent header. Check that the page has sufficient on-page text and a correct canonical URL. Where needed, review advanced NLU signals or retest after content updates. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # OS Data Solutions Source: https://docs.permutive.com/integrations/contextual/os-data-solutions Integrate with OS Data Solutions for contextual data enrichment.
OS Data Solutions

OS Data Solutions

OS Data Solutions provides German publishers with advanced content classification based on an independent OVK standard.

## Overview OS Data Solutions provides German publishers with advanced content classification based on an independent OVK standard. Their Contextual Classifier analyzes website content to assign IAB-standard categories and keyword-based segments. The integration enriches your `Pageview` events with contextual classifications using the IAB 2.2 taxonomy (excluding Sensitive Topics), enabling you to build contextual cohorts without relying on user identifiers. OS Data Solutions classifies content into Categories aligned with IAB standards, enabling precise contextual targeting without user data. This integration is a Source: * **Source:** Permutive enriches `Pageview` events with contextual classifications from OS Data Solutions. Use cases include: * Build contextual cohorts using IAB 2.2 category classifications for cookie-less targeting * Apply brand safety rules by including or excluding specific categories * Power privacy-safe targeting strategies across your inventory * Leverage OVK standard-based contextual intelligence for content understanding ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Permutive SDK**: Deployed on your web and mobile properties where you want content classified. * **Permutive enablement**: OS Data Solutions must be enabled for your workspace by Permutive. * **Accessible content**: Content should be accessible to classifiers. For paywalled or bot-protected pages, configure an allow mechanism (see Setup). * **Monthly classification quota**: Ensure your monthly classification quota covers expected volume. Some sites block non-user traffic. If classifiers cannot fetch content, you can configure a URL parameter in **Contextual > Catalog > Settings** that will be appended to classification requests. Configure your paywall to allow bypass when this parameter is present. The standard parameter is `bot-access=true`, though custom parameter names and values can be configured. ## Setup There are two ways to enable OS Data Solutions: * **Permutive-managed**: If OS Data Solutions is included in your Permutive contract, contact your Customer Success Manager (CSM) to enable it for your workspace. * **Your own credentials**: If you have a direct relationship with OS Data Solutions, work with your CSM to configure the integration using your own credentials. In the Permutive dashboard, navigate to **Contextual > Catalog** and locate OS Data Solutions. Configure your settings including: * **Requested Types**: Specify which classification types to request (Categories for OS Data Solutions) * **Domains**: Restrict classifications to specific domains to optimize quota usage. The domains shown are based on what you have defined under **Settings > Domains & App names** * **Quota**: Maximum monthly classification quota. You can only change this value when using your own credentials; otherwise it is managed by Permutive. Once the quota is reached, no more content will be classified until the next month * **Selective Classifications Threshold**: Set a minimum traffic threshold to classify only high-traffic URLs, optimizing quota usage. The default threshold is **20 pageviews within 24 hours** before initial classification is triggered If you are using your own credentials, you will also configure: * **Email**: The email address for authenticating with OS Data Solutions * **Password**: The password for authenticating with OS Data Solutions If your site uses a paywall or bot mitigation that blocks classifier requests, configure an allow mechanism. Navigate to **Contextual > Catalog > Settings** and configure a URL parameter (e.g., `bot-access=true`) that will be appended to classification requests. Configure your paywall to allow bypass when this parameter is present. After enablement, browse several article pages and check in your Permutive dashboard that `Pageview` events contain OS Data Solutions classifications. Remember that pages must meet the configured threshold before they are classified. You can preview classifications by hovering over the OS Data Solutions tile in the Catalog and clicking "Preview". ## Data Types With OS Data Solutions enabled, Permutive enriches your `Pageview` events with contextual classifications. Permutive automatically updates your event schema to include these properties. OS Data Solutions enriches `Pageview` events with category classifications. List of category classifications detected for the page. Each object contains: * `provider` (string, required): "osds" * `value` (string, required): The classification category ID. The dashboard uses the taxonomy to display human-readable names * `taxonomy` (string, required): "iab\_2.2" (excluding Sensitive Topics) * `confidence` (float, required): Confidence score for the classification ## Troubleshooting Confirm the URL has met the minimum traffic threshold for classification. The default threshold is **20 pageviews within 24 hours**. Low-traffic or newly published pages may classify later. Contact Permutive Support to check if a specific URL has been classified or to verify your threshold configuration. If your monthly quota is reached, new URLs will not be classified until the next month. Contact your Customer Success Manager to review quotas. Classifier requests must be able to fetch the full article content. Configure your paywall to allow bypass using a URL parameter (e.g., `bot-access=true`). Set this in the Permutive dashboard under Contextual > Catalog > Settings, then configure your paywall system to allow access when this parameter is present in the URL. If the Preview feature in the Contextual Catalog is not returning classifications, verify that the URL is publicly accessible and not blocked by bot protection. The Preview feature uses the same classification process as production, so access issues will prevent preview from working. If you are using your own OS Data Solutions credentials and experiencing authentication failures, verify that your email and password are correctly configured. Contact your Customer Success Manager and OS Data Solutions support to validate your credentials. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Silverbullet 4D Source: https://docs.permutive.com/integrations/contextual/silverbullet-4d Integrate with Silverbullet 4D for contextual data enrichment.
Silverbullet

Silverbullet 4D

Silverbullet 4D uses AI-powered contextual intelligence to help publishers enhance targeting and brand safety in a cookie-less environment.

## Overview Silverbullet 4D provides AI-powered contextual intelligence to help publishers enhance targeting and brand safety in a privacy-first environment. The integration enriches your `Pageview` events with contextual classifications using the IAB 3.0 taxonomy, enabling you to build contextual cohorts without relying on user identifiers. Silverbullet 4D classifies content into Categories aligned with IAB standards. This integration is a Source: * **Source:** Permutive enriches `Pageview` events with contextual classifications from Silverbullet 4D. Use cases include: * Build contextual cohorts using IAB 3.0 category classifications for cookie-less targeting * Apply brand safety rules by including or excluding specific categories * Power privacy-safe targeting strategies across your inventory * Leverage AI-powered contextual intelligence for content understanding ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Permutive SDK**: Deployed on your web and mobile properties where you want content classified. * **Permutive enablement**: Silverbullet 4D must be enabled for your workspace by Permutive. * **Accessible content**: Content should be accessible to classifiers. For paywalled or bot-protected pages, configure an allow mechanism (see Setup). * **Monthly classification quota**: Ensure your monthly classification quota covers expected volume. Some sites block non-user traffic. If classifiers cannot fetch content, you can configure a URL parameter in **Contextual > Catalog > Settings** that will be appended to classification requests. Configure your paywall to allow bypass when this parameter is present. The standard parameter is `bot-access=true`, though custom parameter names and values can be configured. ## Setup There are two ways to enable Silverbullet 4D: * **Permutive-managed**: If Silverbullet 4D is included in your Permutive contract, contact your Customer Success Manager (CSM) to enable it for your workspace. * **Your own credentials**: If you have a direct relationship with Silverbullet 4D, work with your CSM to configure the integration using your own API credentials. In the Permutive dashboard, navigate to **Contextual > Catalog** and locate Silverbullet 4D. Configure your settings including: * **Requested Types**: Specify which classification types to request (Categories for Silverbullet 4D) * **Domains**: Restrict classifications to specific domains to optimize quota usage. The domains shown are based on what you have defined under **Settings > Domains & App names** * **Quota**: Maximum monthly classification quota. You can only change this value when using your own credentials; otherwise it is managed by Permutive. Once the quota is reached, no more content will be classified until the next month * **Selective Classifications Threshold**: Set a minimum traffic threshold to classify only high-traffic URLs, optimizing quota usage. The default threshold is **20 pageviews within 24 hours** before initial classification is triggered If your site uses a paywall or bot mitigation that blocks classifier requests, configure an allow mechanism. Navigate to **Contextual > Catalog > Settings** and configure a URL parameter (e.g., `bot-access=true`) that will be appended to classification requests. Configure your paywall to allow bypass when this parameter is present. After enablement, browse several article pages and check in your Permutive dashboard that `Pageview` events contain Silverbullet 4D classifications. Remember that pages must meet the configured threshold before they are classified. You can preview classifications by hovering over the Silverbullet 4D tile in the Catalog and clicking "Preview". ## Data Types With Silverbullet 4D enabled, Permutive enriches your `Pageview` events with contextual classifications. Permutive automatically updates your event schema to include these properties. Silverbullet 4D enriches `Pageview` events with category classifications. List of category classifications detected for the page. Each object contains: * `provider` (string, required): "silverbullet\_4d" * `value` (string, required): The classification category ID. The dashboard uses the taxonomy to display human-readable names * `taxonomy` (string, required): "iab\_3\_0" ## Troubleshooting Confirm the URL has met the minimum traffic threshold for classification. The default threshold is **20 pageviews within 24 hours**. Low-traffic or newly published pages may classify later. Contact Permutive Support to check if a specific URL has been classified or to verify your threshold configuration. If your monthly quota is reached, new URLs will not be classified until the next month. Contact your Customer Success Manager to review quotas. Classifier requests must be able to fetch the full article content. Configure your paywall to allow bypass using a URL parameter (e.g., `bot-access=true`). Set this in the Permutive dashboard under Contextual > Catalog > Settings, then configure your paywall system to allow access when this parameter is present in the URL. If the Preview feature in the Contextual Catalog is not returning classifications, verify that the URL is publicly accessible and not blocked by bot protection. The Preview feature uses the same classification process as production, so access issues will prevent preview from working. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # TextRazor Source: https://docs.permutive.com/integrations/contextual/text-razor Integrate with TextRazor for contextual data enrichment.
TextRazor

TextRazor

TextRazor provides advanced text analytics and content classification using natural language processing and machine learning.

## Overview TextRazor provides advanced text analytics and content classification using natural language processing and machine learning. It extracts entities, concepts and categories from unstructured text, enabling precise content understanding and contextual targeting. The integration enriches your `Pageview` events with contextual classifications using the IAB 3.0 taxonomy, enabling you to build contextual cohorts without relying on user identifiers. TextRazor classifies content into Categories, Entities, and Concepts, enabling precise contextual targeting without user data. This integration is a Source: * **Source:** Permutive enriches `Pageview` events with contextual classifications from TextRazor. Use cases include: * Build contextual cohorts using IAB 3.0 category classifications for cookie-less targeting * Apply brand safety rules by including or excluding specific categories * Power privacy-safe targeting strategies across your inventory * Leverage entity and concept extraction for advanced content understanding * Enable precise audience targeting based on contextual intelligence ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Permutive SDK**: Deployed on your web and mobile properties where you want content classified. * **Permutive enablement**: TextRazor must be enabled for your workspace by Permutive. * **Accessible content**: Content should be accessible to classifiers. For paywalled or bot-protected pages, configure an allow mechanism (see Setup). * **Monthly classification quota**: Ensure your monthly classification quota covers expected volume. Some sites block non-user traffic. If classifiers cannot fetch content, you can configure a URL parameter in **Contextual > Catalog > Settings** that will be appended to classification requests. Configure your paywall to allow bypass when this parameter is present. The standard parameter is `bot-access=true`, though custom parameter names and values can be configured. ## Setup There are two ways to enable TextRazor: * **Permutive-managed**: If TextRazor is included in your Permutive contract, contact your Customer Success Manager (CSM) to enable it for your workspace. * **Your own credentials**: If you have a direct relationship with TextRazor, work with your CSM to configure the integration using your own API key. In the Permutive dashboard, navigate to **Contextual > Catalog** and locate TextRazor. Configure your settings including: * **Requested Types**: Specify which classification types to request (Categories, Entities, and/or Concepts for TextRazor) * **Domains**: Restrict classifications to specific domains to optimize quota usage. The domains shown are based on what you have defined under **Settings > Domains & App names** * **Quota**: Maximum monthly classification quota. You can only change this value when using your own credentials; otherwise it is managed by Permutive. Once the quota is reached, no more content will be classified until the next month * **Selective Classifications Threshold**: Set a minimum traffic threshold to classify only high-traffic URLs, optimizing quota usage. The default threshold is **20 pageviews within 24 hours** before initial classification is triggered If you are using your own credentials, you will also configure: * **API Key**: Your TextRazor API key for authentication If your site uses a paywall or bot mitigation that blocks classifier requests, configure an allow mechanism. Navigate to **Contextual > Catalog > Settings** and configure a URL parameter (e.g., `bot-access=true`) that will be appended to classification requests. Configure your paywall to allow bypass when this parameter is present. After enablement, browse several article pages and check in your Permutive dashboard that `Pageview` events contain TextRazor classifications. Remember that pages must meet the configured threshold before they are classified. You can preview classifications by hovering over the TextRazor tile in the Catalog and clicking "Preview". ## Data Types With TextRazor enabled, Permutive enriches your `Pageview` events with contextual classifications. Permutive automatically updates your event schema to include these properties. TextRazor enriches `Pageview` events with category, entity, and concept classifications. List of category classifications detected for the page. Each object contains: * `provider` (string, required): "textrazor" * `value` (string, required): The classification category ID. The dashboard uses the taxonomy to display human-readable names * `taxonomy` (string, required): "iab\_3\_0" * `confidence` (float, required): Confidence score for the classification List of entity classifications detected for the page. Each object contains: * `provider` (string, required): "textrazor" * `value` (string, required): The entity name * `confidence` (float, required): Confidence score for the entity extraction List of concept classifications detected for the page. Each object contains: * `provider` (string, required): "textrazor" * `value` (string, required): The concept name * `confidence` (float, required): Confidence score for the concept extraction ## Troubleshooting Confirm the URL has met the minimum traffic threshold for classification. The default threshold is **20 pageviews within 24 hours**. Low-traffic or newly published pages may classify later. Contact Permutive Support to check if a specific URL has been classified or to verify your threshold configuration. If your monthly quota is reached, new URLs will not be classified until the next month. Contact your Customer Success Manager to review quotas. Classifier requests must be able to fetch the full article content. Configure your paywall to allow bypass using a URL parameter (e.g., `bot-access=true`). Set this in the Permutive dashboard under Contextual > Catalog > Settings, then configure your paywall system to allow access when this parameter is present in the URL. If the Preview feature in the Contextual Catalog is not returning classifications, verify that the URL is publicly accessible and not blocked by bot protection. The Preview feature uses the same classification process as production, so access issues will prevent preview from working. If you are using your own TextRazor API key and experiencing authentication failures, verify that your API key is correctly configured. Contact your Customer Success Manager and TextRazor support to validate your credentials. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # AWS S3 Source: https://docs.permutive.com/integrations/data-collaboration/data-warehouses/aws-s3 Export first-party event data to AWS S3 via streaming or batch routing and import audience data for cohort building and activation
AWS S3

AWS S3

AWS S3 allows publishers to securely store and manage large volumes of advertising and audience data in the cloud.

## Overview The AWS S3 integration enables publishers to leverage Permutive's bi-directional data capabilities with their S3 storage. This integration operates in two modes: **Routing (Destination)**: Export first-party event data from Permutive to S3 buckets. Permutive offers two distinct routing modes: * **S3 Streaming**: Near real-time streaming, ideal for low-latency data pipelines and analytics * **S3 Batch**: Daily scheduled exports, suitable for data warehouse ingestion and batch processing workflows Read more in [Routing](/products/connectivity/routing) documentation. Routing capability requires the Routing package in addition to Core Platform. Contact your Customer Success Manager to enable Routing. **Connectivity (Source)**: Import audience data from your S3 storage into Permutive for cohort building and activation across your publisher inventory. Both Routing modes support exporting event data, identity data (aliases), and segment metadata to customer-owned S3 buckets with Hive-style partitioning and compression. ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites For **Routing (exporting data to S3)**: * AWS account with permissions to create S3 buckets and IAM users/policies * S3 bucket created in the appropriate AWS region * IAM user with programmatic access credentials (Access Key ID and Secret Access Key) * Ability to configure S3 bucket policies with specific permissions * Secure method to share AWS credentials with Permutive (1Password or GPG encryption recommended) ## Setup S3 Streaming routing exports your first-party event data to an S3 bucket in near real-time as GZIP-compressed JSONL files with Hive-style partitioning. Data arrives with approximately 5-minute latency, making it ideal for low-latency data pipelines and ingestion into AWS services such as Athena, Redshift, or EMR. Setup requires coordination with Permutive support. You will need to prepare your AWS environment (S3 bucket, IAM user with programmatic access) and then share your bucket details and credentials with the Permutive team. ### Prerequisites * AWS account with permissions to create S3 buckets and IAM users/policies * An S3 bucket in a region-specific location (e.g., `us-east-1`, `eu-west-1`) with public access blocked * A dedicated IAM user with `s3:List*`, `s3:Get*`, `s3:Delete*`, and `s3:Put*` permissions on the bucket * A secure method to share AWS credentials with Permutive (1Password or GPG encryption recommended) For complete setup steps, see [Setting up S3 Streaming Routing](/guides/connectivity/routing/setting-up-s3-streaming-routing). ### What Happens After Setup Once routing is active: 1. **Files stream to S3** in near real-time with approximately 5-minute latency 2. **Hive-style partitions** are created automatically by hour 3. **Event data** is written as GZIP-compressed JSONL files 4. **File naming** follows the pattern `{timestamp}-{hash}-{worker_id}.jsonl.gz` See the [Streaming Schema](#streaming-schema) section below for detailed schema information. S3 Batch routing exports your first-party event data to an S3 bucket on a scheduled 24-hour cycle. You can choose between JSON (GZIP compressed) or Parquet (Snappy compressed) format based on your data processing needs. Batch exports are suitable for data warehouse ingestion and batch processing workflows. Setup requires coordination with Permutive support. You will need to prepare an S3 bucket, choose your preferred data format, and apply a Permutive-provided bucket policy. ### Prerequisites * AWS account with permissions to create S3 buckets * An S3 bucket with public access blocked * Understanding of your data format requirements (JSON vs Parquet) **Organization-Level Scope:** S3 Batch routing operates at the organization level, exporting data for all workspaces within your organization, unlike streaming routing which is workspace-specific. For complete setup steps, see [Setting up S3 Batch Routing](/guides/connectivity/routing/setting-up-s3-batch-routing). ### What Happens After Setup Once batch routing is active: 1. **Daily exports run automatically** on the configured 24-hour schedule 2. **Event data is partitioned** by event type and date 3. **Snapshot tables are replaced** each export cycle 4. **Files are written** in your chosen format (JSON or Parquet) See the [Batch Schema](#batch-schema) section below for detailed schema information. ### Prerequisites * An AWS account with access to the S3 bucket you want to connect * Permission to modify the S3 bucket policy * Your data organized in the required directory structure ### Step 1: Set Up Your Bucket Structure Permutive uses the concept of a **Schema** (containing multiple **Tables**) to organize your data. Structure your bucket so that each table is a directory under your schema prefix: ``` s3:///// s3:///// ``` **Partitioned tables (recommended):** ``` s3://///=/.parquet ``` **Non-partitioned tables:** ``` s3://///.parquet ``` We recommend using Parquet format with ZSTD compression for optimal performance. CSV (including gzipped) is also supported. ### Step 2: Configure Bucket Permissions In the Permutive dashboard, go to **Connectivity > Catalog** and select **Amazon S3**. Begin entering your connection details. Once you enter your bucket name, Permutive will generate a bucket policy for you. Copy the generated S3 Bucket Policy from the Permutive dashboard: 1. Open the AWS S3 Console and navigate to your bucket 2. Go to the **Permissions** tab 3. Under **Bucket policy**, click **Edit** 4. Paste the policy provided by Permutive 5. Click **Save changes** ### Step 3: Create the Connection in Permutive Fill in the following fields: | Field | Description | | :-------------------- | :---------------------------------------------------- | | **Name** | A descriptive name for your connection | | **S3 Bucket Name** | The bucket name (without `s3://` prefix) | | **S3 Bucket Region** | The AWS region where your bucket is located | | **Schema Prefix** | The path within your bucket that contains your tables | | **Data Format** | Choose Parquet (recommended) or CSV | | **Data Partitioning** | Select whether tables are partitioned or not | Click **Save** to create the connection. It will appear on your **Connections** page with a "Processing" status while Permutive validates access. ### Step 4: Create an Import Once your connection is active, go to **Connectivity > Imports** and click **Create Import**, then select your S3 connection. For the complete setup guide with detailed instructions, see [Connecting to Amazon S3](/guides/connectivity/sources/connecting-to-amazon-s3). ## Data Types ### Streaming Schema Events in S3 Streaming are exported in newline-delimited JSON format with the following structure: Unix timestamp in milliseconds as a string Unique identifier for this event Permutive user ID Name of the event (e.g., `Pageview`, `slotclicked`) Organization identifier Workspace/project identifier Session identifier (optional) Page view identifier (optional) Source URL (optional) Array of segment IDs the user belongs to Custom event properties as key-value pairs ### Example Event ```json theme={"dark"} { "time": "1665851625945", "event_id": "c0b8266d-3c4d-43d6-8855-6f42d657adda", "user_id": "87bcd76b-5eb6-4c46-afa8-017d1e7148ca", "event_name": "slotclicked", "organization_id": "be668577-07f5-444d-98e0-222b990951b1", "project_id": "72f6d4b5-1e85-4c79-b4f9-da2dd1f3be6d", "session_id": "4a96de87-f8b1-4240-a1a8-7b9c6cff569a", "view_id": "16f2af62-f38d-44d1-bcea-ba5b4da39be2", "source_url": null, "segments": [], "properties": { "campaign_id": 2387641642, "line_item_id": 4792767025 } } ``` Identity synchronization events contain cross-device and identity mapping data. Unix timestamp in milliseconds as a string Permutive user ID Organization identifier Workspace/project identifier Array of alias objects, each containing: * `id`: The alias identifier value * `tag`: The alias type (e.g., `email_sha256`, `device_id`) ### Example Sync Alias ```json theme={"dark"} { "time": "1665663771749", "user_id": "b5653712-26ee-41a8-8b30-c128092df93b", "organization_id": "be668577-07f5-444d-98e0-222b990951b1", "project_id": "be668577-07f5-444d-98e0-222b990951b1", "aliases": [ {"id": "a1b2c3d4e5f6...", "tag": "email_sha256"}, {"id": "device_12345", "tag": "device_id"} ] } ``` Segment metadata snapshots containing segment definitions. These files are NOT date-partitioned. Segment UUID Segment number/ID used in the segments array of events Human-readable segment name Array of tags associated with the segment Additional segment metadata Workspace identifier Array of ancestor workspace/organization IDs State of the workspace (e.g., "Active", "Deleted") Whether the segment has been deleted ### Example Segment ```json theme={"dark"} { "id": "5289b895-4ee7-44f8-81a6-1899142ed2d2", "code": 1057, "name": "High Value Users", "tags": [], "metadata": {}, "workspace": "45582cb9-bb5c-4eb4-9c0d-7a2cebf4eeb1", "ancestors": ["45582cb9-bb5c-4eb4-9c0d-7a2cebf4eeb1", "be668577-07f5-444d-98e0-222b990951b1"], "workspaceState": "Active", "deleted": false } ``` ### Batch Schema Batch exports create separate tables for each event type (e.g., `pageview_events`, `videoview_events`). All event tables share a common structure: Timestamp for when the event was received by Permutive (in UTC) Unique identifier for each individual event Identifier unique to a particular user Identifier unique to a user's session. Sessions last 30 minutes unless a user stays on site Identifier unique to a particular page or screen view Identifier for the workspace which the event originated from A list of all segment IDs the user was in when the event fired A list of all cohort codes the user was in when the event fired Event-specific properties as a nested object. Structure varies by event type. ### Example Pageview Event ```json theme={"dark"} { "time": "2026-01-15T14:30:00Z", "event_id": "c0b8266d-3c4d-43d6-8855-6f42d657adda", "user_id": "87bcd76b-5eb6-4c46-afa8-017d1e7148ca", "session_id": "4a96de87-f8b1-4240-a1a8-7b9c6cff569a", "view_id": "16f2af62-f38d-44d1-bcea-ba5b4da39be2", "workspace_id": "72f6d4b5-1e85-4c79-b4f9-da2dd1f3be6d", "segments": [123, 456], "cohorts": ["abc123", "def456"], "properties": { "client": { "domain": "example.com", "type": "web", "url": "https://example.com/article", "referrer": "https://google.com", "title": "Example Article", "user_agent": "Mozilla/5.0..." } } } ``` Identity data and alias mappings for cross-device tracking. Timestamp when the alias was captured Type of alias event Permutive user identifier External identity value (e.g., hashed email, device ID) Identity tag or namespace (e.g., `email_sha256`, `device_id`) Workspace identifier ### Example Alias ```json theme={"dark"} { "time": "2026-01-15T14:30:00Z", "event_type": "alias_sync", "permutive_id": "87bcd76b-5eb6-4c46-afa8-017d1e7148ca", "id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "tag": "email_sha256", "workspace_id": "72f6d4b5-1e85-4c79-b4f9-da2dd1f3be6d" } ``` Domain-level metadata. This is a snapshot table that is fully replaced with each export. Domain name Workspace identifier ### Example Domain ```json theme={"dark"} { "name": "example.com", "workspace_id": "72f6d4b5-1e85-4c79-b4f9-da2dd1f3be6d" } ``` Segment definitions and metadata. This is a snapshot table that is fully replaced with each export. Segment ID number Segment name Array of tags associated with the segment JSON string containing additional segment metadata Workspace identifier ### Example Segment Metadata ```json theme={"dark"} { "number": 123, "name": "High Value Users", "tags": ["advertising", "premium"], "metadata": "{\"description\": \"Users with high engagement\"}", "workspace_id": "72f6d4b5-1e85-4c79-b4f9-da2dd1f3be6d" } ``` ### File Formats and Compression * **Format**: Newline-delimited JSON (`.jsonl`) * **Compression**: GZIP (`.gz`) * **File Extension**: `.jsonl.gz` * **Character Encoding**: UTF-8 #### JSON Format * **Format**: Newline-delimited JSON * **Compression**: GZIP * **File Extension**: `.json.gz` * **Character Encoding**: UTF-8 #### Parquet Format * **Format**: Apache Parquet columnar format * **Compression**: Snappy * **File Extension**: `.snappy.parquet` * **Schema**: Derived from BigQuery table structure Parquet format is recommended for data warehouse ingestion and analytics workloads due to better compression and query performance. ## Troubleshooting **Symptom**: Files are not appearing in S3 bucket, or logs show permission errors. **Solution**: 1. Verify the IAM user has all required permissions: * `s3:PutObject` * `s3:GetObject` * `s3:DeleteObject` * `s3:ListBucket` 2. Check that the bucket policy includes the correct bucket ARN: ```json theme={"dark"} "Resource": [ "arn:aws:s3:::YOUR_BUCKET_NAME/*", "arn:aws:s3:::YOUR_BUCKET_NAME" ] ``` 3. Verify the `bucket-owner-full-control` ACL condition is correctly configured 4. Ensure the IAM user credentials (Access Key ID and Secret Access Key) are current and not expired If you recently rotated AWS credentials, contact Permutive support at [technical-services@permutive.com](mailto:technical-services@permutive.com) to update the stored credentials. **Symptom**: No files appearing in S3 bucket after setup, or files stopped appearing. **Solution**: 1. Verify the Permutive SDK is properly deployed and events are being collected (check Event Inspector in the Dashboard) 2. Low-traffic sites may see longer delays between files due to batch size thresholds 3. Verify the bucket region matches the configured region: * Region must be specific (e.g., `eu-central-1`, not just `EU`) 4. Verify bucket path structure is correct: ``` s3://{bucket}/{prefix}/type=events/year=YYYY/month=MM/day=DD/hour=HH/ ``` 5. If issues persist, contact Permutive support at [technical-services@permutive.com](mailto:technical-services@permutive.com) with your integration details **Symptom**: Daily batch exports are missing or delayed. **Solution**: 1. Batch exports run on 24-hour cycles. Check if sufficient time has passed since the last export window. 2. Verify the Permutive SDK is properly deployed and events are being collected 3. Contact Permutive support at [technical-services@permutive.com](mailto:technical-services@permutive.com) to check batch export job logs and status **Symptom**: Files appearing in unexpected locations or wrong folder structure. **Solution**: 1. Verify the `bucketPrefix` configuration: * Should NOT include leading `/` unless intentional * Should NOT include bucket name * Example: `permutive/` not `/permutive/` or `s3://bucket/permutive/` 2. For Streaming, data uses Hive-style partitioning: * `type=events/year=2026/month=01/day=15/hour=14/` * This is expected behavior and cannot be customized 3. For Batch, data is organized by table name: * `data/{table_name}/year=2026/month=1/day=15/` * This is expected behavior and cannot be customized **Symptom**: AWS returns validation errors when applying bucket policy. **Solution**: 1. Ensure the bucket policy JSON is valid: * Check for missing commas, brackets, or quotes * Use AWS Policy Generator or an online JSON validator 2. Verify ARN format is correct: * Bucket ARN: `arn:aws:s3:::BUCKET_NAME` * Object ARN: `arn:aws:s3:::BUCKET_NAME/*` * Note the three colons `:::` before bucket name 3. Confirm the `StringEquals` condition is correctly formatted: ```json theme={"dark"} "Condition": { "StringEquals": {"s3:x-amz-acl": "bucket-owner-full-control"} } ``` **Symptom**: Some event types or fields are not appearing in exported data. **Solution**: 1. Verify events are being collected in Permutive: * Check Event Inspector in the Dashboard to confirm events are tracked * Use browser developer console to verify SDK is firing events 2. Check event schema matches expected structure: * Events must include required fields: `event_id`, `user_id`, `event_name`, etc. * Custom properties are in the `properties` object 3. Schema changes may require integration reconfiguration: * Contact Permutive support if you've made significant schema changes **Symptom**: Errors related to KMS encryption when writing to S3. **Solution**: 1. If using customer-managed KMS keys, verify the Permutive IAM user has KMS permissions: ```json theme={"dark"} { "Effect": "Allow", "Action": [ "kms:Decrypt", "kms:Encrypt", "kms:GenerateDataKey" ], "Resource": "arn:aws:kms:REGION:ACCOUNT_ID:key/KEY_ID" } ``` 2. Confirm the KMS key policy allows the Permutive IAM user to use the key 3. Verify the S3 bucket's default encryption settings are compatible AWS-managed S3 encryption (SSE-S3) is supported by default. Customer-managed KMS keys require additional configuration. Contact Permutive support for KMS requirements. ## Changelog No changes listed yet. For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Google BigQuery Source: https://docs.permutive.com/integrations/data-collaboration/data-warehouses/bigquery Stream first-party event data to Google BigQuery via routing and import audience data for cohort building and activation
Google BigQuery

Google BigQuery

Google BigQuery enables publishers to analyze large datasets for audience insights, campaign performance, and ad optimization.

## Overview The BigQuery integration enables publishers to leverage Permutive's bi-directional data capabilities with their BigQuery data warehouse. This integration operates in two modes: **Routing (Destination)**: Stream first-party event data from Permutive to BigQuery. Read more in [Routing](/products/connectivity/routing) documentation. Routing capability requires the Routing package in addition to Core Platform. Contact your Customer Success Manager to enable Routing. **Connectivity (Source)**: Import audience data from your BigQuery warehouse into Permutive for cohort building and activation across your publisher inventory. Key Routing capabilities include: * Automatic schema generation and updates for new event types and properties * Day-partitioned tables for efficient querying * Support for all Permutive event data including user events, identities, and segment metadata * Self-service setup through the Permutive Dashboard ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites For **Routing (exporting data to BigQuery)**: * **Google Cloud Platform project**: An active GCP project where BigQuery will store your data * **BigQuery API enabled**: The BigQuery API must be enabled on your GCP project (enabled by default for new projects) * **IAM permissions**: Ability to grant IAM roles to service accounts at the project level in Google Cloud Console * **Project-level access**: Permissions to add service accounts with the BigQuery User role to your GCP project Do NOT manually create the BigQuery dataset before configuring the integration. Permutive will automatically create the dataset during setup. If you manually create the dataset, you must grant the Permutive service account the **BigQuery Data Owner** role instead of BigQuery User. ## Setup ### Overview BigQuery Routing enables self-service setup through the Permutive Dashboard. The setup involves configuring a BigQuery destination in the Dashboard and granting a Permutive service account the **BigQuery User** role at the project level in your GCP IAM settings. Permutive automatically creates the dataset and tables -- no manual dataset creation is required. ### Prerequisites * An active Google Cloud Platform project with the BigQuery API enabled * Your GCP **Project ID**, a unique dataset name, and your preferred data location (**US** or **EU**) * Permissions to grant IAM roles to service accounts at the project level in Google Cloud Console Do NOT manually create the BigQuery dataset before configuring the integration. If you do, you must grant the Permutive service account the **BigQuery Data Owner** role instead of BigQuery User. For complete setup steps, see [Setting up BigQuery Routing](/guides/connectivity/routing/setting-up-bigquery-routing). ### What Happens After Setup Once routing is active: 1. **Tables are created automatically** for each event type (e.g., `Pageview_events`, `VideoView_events`) 2. **Events stream in near real-time** with approximately 5-minute latency 3. **Schemas update automatically** when you add new event types or properties 4. **Daily partitions** organize data by event date for efficient querying ### Overview Connecting BigQuery as a source allows you to import audience data into Permutive for cohort building and activation. The setup involves granting Permutive's service account (`connection@permutive.com`) the **BigQuery Data Viewer** role on your dataset, then creating a connection in the Permutive Dashboard with your GCP Project ID and Dataset name. ### Prerequisites * A Google Cloud Platform (GCP) account with BigQuery enabled * Access to manage IAM permissions on your BigQuery dataset * Knowledge of your GCP Project ID and Dataset name For the complete setup guide with detailed instructions, see [Connecting to BigQuery](/guides/connectivity/sources/connecting-to-bigquery). ### Create an Import Once your connection is active, go to **Connectivity > Imports** and click **Create Import**, then select your BigQuery connection. For more details on configuring imports, see [Imports](/products/connectivity/imports). ## Data Types The BigQuery Routing integration creates the following tables in your dataset to store different types of data: Permutive creates separate tables for each event type. Tables are named `{event_name}_events` and contain daily partitions based on event date. Example tables: * `Pageview_events` * `VideoView_events` * `AffiliateLinkClick_events` **Schema**: Event timestamp in UTC Unique identifier for the event Permutive user identifier Session identifier Page view identifier Array of segment IDs the user belongs to at the time of the event Event-specific properties stored as a JSON record. Schema varies by event type and is automatically updated when new properties are added. Workspace identifier Stores identity resolution data. The table is named `identities` and contains daily partitions. **Schema**: Timestamp when the identity was captured Type of identity event Permutive user identifier External identity value Identity tag or namespace Workspace identifier Stores segment definitions and metadata. Permutive creates two objects: * **Table**: `segment_metadata_snapshots` - Raw snapshots of segment metadata * **View**: `segment_metadata` - Deduplicated view of the latest segment metadata **Schema**: Segment ID number Segment name Array of tags associated with the segment JSON string containing segment configuration and metadata Workspace identifier Use the `segment_metadata` view for queries to automatically get deduplicated, up-to-date segment information. **Key Characteristics**: * **Automatic Schema Management**: New event types and properties are automatically added to tables without manual intervention * **Partitioning**: Event and identity tables are partitioned by day for efficient querying and cost optimization ## Troubleshooting **Cause**: The Permutive service account was not granted the correct permissions, or permissions were granted after clicking "Confirm account access granted". **Solution**: 1. Verify the service account has been granted the **BigQuery User** role at the PROJECT level in the [Google Cloud IAM console](https://console.cloud.google.com/iam-admin/iam) 2. Ensure you selected the correct GCP project 3. If permissions were granted incorrectly, you will need to restart the integration configuration from the beginning 4. Contact [Support](mailto:support@permutive.com) if the issue persists after verifying permissions **Cause**: A dataset with the same name already exists in your BigQuery project. **Solution**: * **Option 1 (Recommended)**: Use a different dataset name that doesn't already exist in your project * **Option 2**: If you must use an existing dataset, grant the Permutive service account the **BigQuery Data Owner** role at the dataset level (not just BigQuery User at project level) **Cause**: Your organization's IAM policies may restrict adding external service accounts, or permissions were not granted at the correct level. **Solution**: 1. Check with your GCP administrator about organization policies that may block external service accounts 2. Ensure the **BigQuery User** role was granted at the PROJECT level, not the dataset level 3. Verify your user account has permissions to grant IAM roles in the GCP project 4. If organization policies block external service accounts, work with your security team to add an exception for `@permutive-routing-production.iam.gserviceaccount.com` domains ## Changelog No changes listed yet. For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Catalog Source: https://docs.permutive.com/integrations/data-collaboration/data-warehouses/catalog Browse our comprehensive catalog of data warehouse integrations. # Data Warehouse Integrations
Snowflake

Snowflake

Snowflake is a cloud-based data warehouse that enables publishers to store, analyze, and share data at scale with advanced security features.

BigQuery

BigQuery

BigQuery is Google's fully managed, serverless data warehouse that enables publishers to analyze large datasets with SQL queries.

AWS S3

AWS S3

Amazon S3 is a scalable object storage service that enables publishers to store and retrieve data from anywhere on the web.

Google Cloud Storage

Google Cloud Storage

Google Cloud Storage is a unified object storage service that enables publishers to import audience data for cohort building and activation.

# Google Cloud Storage Source: https://docs.permutive.com/integrations/data-collaboration/data-warehouses/gcs Import audience data from Google Cloud Storage for cohort building and activation
Google Cloud Storage

Google Cloud Storage

Google Cloud Storage enables publishers to import audience data stored in GCS buckets for cohort building and activation across your publisher inventory.

## Overview The Google Cloud Storage integration enables publishers to import audience data from GCS buckets into Permutive for cohort building and activation. Permutive offers two connection options: connecting to your own GCS bucket, or having Permutive provision a bucket for you. Key capabilities include: * Support for customer-owned or Permutive-provisioned buckets * Hive-style partitioning for efficient data organization * Support for Parquet (recommended) and CSV data formats * Self-service setup through the Permutive Dashboard ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Setup Setting up the GCS integration involves preparing your bucket with the correct directory structure, granting Permutive the necessary permissions, and creating the connection in the Permutive Dashboard. You can either connect your own GCS bucket or have Permutive provision one for you. ### Prerequisites * A Google Cloud Platform (GCP) account * For customer-owned buckets: ability to manage IAM permissions on your GCS bucket (granting `roles/storage.objectViewer` and `roles/storage.bucketViewer` to Permutive's service account) * Your data organized using Hive-style partitioning (recommended) in Parquet or CSV format ### Quick Reference | Step | Description | | :--------------------------- | :--------------------------------------------------------------------------------------------------------------------------- | | **1. Prepare your bucket** | Structure your GCS bucket with a schema prefix containing table subdirectories, using Hive-style partitions where possible | | **2. Grant permissions** | For customer-owned buckets, grant Permutive's service account (`connection@permutive.com`) read access via IAM roles | | **3. Create the connection** | In the Permutive Dashboard, go to **Connectivity > Catalog**, select **Google Cloud Storage**, and enter your bucket details | | **4. Create an import** | Go to **Connectivity > Imports** to import data from your new connection | Follow the complete step-by-step guide for connecting to Google Cloud Storage, including detailed bucket structure requirements, data format recommendations, IAM configuration, and connection setup for both customer-owned and Permutive-provisioned buckets. For more details on configuring imports, see [Imports](/products/connectivity/imports). ## Limitations **Important limitations to be aware of:** * **Partitioning Standard**: Only Hive-style partitioning is supported * **Mixed Partitioning**: Not supported in a single schema connection. All tables must either be partitioned or non-partitioned * **Schema Evolution**: Column changes (additions/removals) are not supported for GCS imports. If your column structure changes, you'll need to create a new connection ## Troubleshooting **Cause**: The IAM permissions may not be correctly applied, or the bucket details are incorrect. **Solution**: 1. Verify the IAM permissions have been correctly applied to `connection@permutive.com` 2. Check that the bucket name and project ID are correct 3. Ensure the schema prefix exists and contains table directories 4. Double-check your IAM settings in the Google Cloud Console **Cause**: Directory structure doesn't match the required format, or data files are missing. **Solution**: 1. Verify your directory structure matches the required format 2. Check that data files exist under each table directory 3. Ensure the data format setting matches your actual file format 4. Review your GCS bucket structure and ensure each table is a direct subdirectory of the schema prefix 5. After making changes, run a schema resync in Permutive to refresh the available tables **Cause**: Partitioning settings don't match your data structure. **Solution**: 1. Verify "All tables are partitioned" is selected in Data Partitioning 2. Check that partition directories use the correct Hive format (`column=value`) 3. Update your connection settings or restructure your partition directories The bucket name is generated upon connection creation. You can find the full GCS Bucket Name on the **Connection Details** page immediately after setup is complete. ## Changelog No changes listed yet. For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Snowflake Source: https://docs.permutive.com/integrations/data-collaboration/data-warehouses/snowflake Stream first-party event data to Snowflake via routing and import audience data for cohort building and activation
Snowflake

Snowflake

Snowflake provides a cloud data platform where publishers can unify and analyze audience and advertising data securely.

## Overview The Snowflake integration enables publishers to leverage Permutive's bi-directional data capabilities with their Snowflake data warehouse. This integration operates in two modes: **Routing (Destination)**: Stream first-party event data from Permutive to Snowflake. Read more in [Routing](/products/connectivity/routing) documentation. Routing capability requires the Routing package in addition to Core Platform. Contact your Customer Success Manager to enable Routing. **Connectivity (Source)**: Import audience data from your Snowflake warehouse into Permutive for cohort building and activation across your publisher inventory. Use cases include: * Unifying first-party audience data with advertising and CRM data in Snowflake * Building a single view of your audience by combining Permutive data with other data sources * Creating segments and insights in Snowflake for activation in Permutive * Running custom analytics and reporting on raw Permutive event data ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites For **Routing (exporting data to Snowflake)**: * Active Snowflake account with appropriate permissions * Ability to create database, schema, tables, stages, and pipes in Snowflake * Ability to create users with key-pair authentication (password authentication is not supported) * Access to a Snowflake warehouse for data loading operations * Snowflake account URL in the format: `https://-.snowflakecomputing.com` ## Setup Setting up Snowflake routing involves creating a dedicated database, schema, user, and role in your Snowflake instance, then configuring key-pair authentication so Permutive can stream event data via Snowpipe. Once configured, Permutive will complete the storage integration and begin streaming data automatically. ### Prerequisites * Active Snowflake account with administrative permissions * Ability to create databases, schemas, users, roles, and storage integrations * Access to a Snowflake warehouse * Snowflake account URL in the format: `https://-.snowflakecomputing.com` Snowflake Routing requires key-pair authentication. Password authentication is not supported. For the complete step-by-step setup guide with SQL scripts and permissions configuration, see [Setting Up Snowflake Routing](/guides/connectivity/routing/setting-up-snowflake-routing). ### What Happens After Setup Once routing is active: 1. **Tables are created automatically** for events, aliases, and segment metadata 2. **Events stream via Snowpipe** in approximately 5-minute or 500MB batches 3. **All event types appear** in a single `EVENTS` table with an `EVENTNAME` column to distinguish types 4. **Schemas update automatically** when you add new event types or properties ### Prerequisites * A Snowflake account with `SECURITYADMIN` and `ACCOUNTADMIN` roles * Access to run SQL scripts in your Snowflake instance * Knowledge of the database, schema, and warehouse you want to connect ### Step 1: Set Up Your Snowflake Instance Run a setup script to create a dedicated user and role with read-only permissions. Choose between password authentication or key pair authentication. Replace the placeholders and run the following script: ```sql theme={"dark"} begin; set role_name = 'PERMUTIVE_ROLE'; set user_name = 'PERMUTIVE_USER'; set user_password = ''; set warehouse_name = ''; set database_name = ''; set schema_name = ''; use role securityadmin; create role if not exists identifier($role_name); create user if not exists identifier($user_name) password = $user_password default_role = $role_name default_warehouse = $warehouse_name; grant role identifier($role_name) to user identifier($user_name); use role ACCOUNTADMIN; grant usage on warehouse identifier($warehouse_name) to role identifier($role_name); grant usage on database identifier($database_name) to role identifier($role_name); use database identifier($database_name); grant usage on schema identifier($schema_name) to role identifier($role_name); grant select on all TABLES in schema identifier($schema_name) to role identifier($role_name); grant select on future TABLES in schema identifier($schema_name) to role identifier($role_name); commit; ``` First generate a key pair, then run the setup script with the public key. See [Connecting to Snowflake](/guides/connectivity/sources/connecting-to-snowflake) for complete key pair setup instructions. ### Step 2: Configure Network Policy (Optional) If you use Network Policies, add Permutive's IP addresses to your allowlist. See the [complete guide](/guides/connectivity/sources/connecting-to-snowflake#step-2-configure-network-policy-optional) for the full list of IPs. ### Step 3: Create the Connection in Permutive In the Permutive dashboard, go to **Connectivity > Catalog** and select **Snowflake**. | Field | Description | | :------------ | :----------------------------------------------------- | | **Database** | The database name (use UPPERCASE) | | **Host** | Your Snowflake account URL (without `https://` prefix) | | **Port** | Leave as default `443` | | **User** | The user created by the script (`PERMUTIVE_USER`) | | **Role** | The role created by the script (`PERMUTIVE_ROLE`) | | **Password** | The password from your script | | **Warehouse** | The Snowflake compute warehouse to use | Snowflake uses UPPERCASE for all database, schema, user, and role names. Click **Save** to create the connection. It will appear on your **Connections** page with a "Processing" status while Permutive validates the credentials. ### Step 4: Create an Import Once your connection is active, go to **Connectivity > Imports** and click **Create Import**, then select your Snowflake connection. For the complete setup guide with detailed instructions, see [Connecting to Snowflake](/guides/connectivity/sources/connecting-to-snowflake). ## Data Types When Routing is enabled, Permutive creates the following tables in your Snowflake instance to store event data and metadata: The main events table contains all user interaction data collected by Permutive. The timestamp when the event occurred. Your Permutive organization identifier. The workspace (project) ID where the event was collected. Unique identifier for the page view session. Unique identifier for the user session. The Permutive user identifier. Unique identifier for this specific event. The name/type of the event (e.g., PageView, Pageview, SlotViewable). Array of segment IDs (integers) that the user belongs to at the time of the event. JSON object containing event-specific properties and metadata. Structure varies by event type. Contains user identity synchronization data for cross-device and cross-context tracking. The timestamp when the alias sync occurred. Type of sync event (e.g., "alias\_sync"). The Permutive user identifier. The workspace (project) ID. The external identifier being synced (e.g., hashed email, device ID). The type/tag of the external identifier (e.g., "email\_sha256", "device\_id"). Metadata table containing information about segments configured in your Permutive workspace. When the segment metadata was inserted/updated in Snowflake. The workspace (project) ID. Human-readable name of the segment. Array of tags associated with the segment. JSON object containing additional segment metadata (e.g., description, creation date). The segment ID number as a string. ## Troubleshooting **Symptoms**: No data visible in the EVENTS table after completing Routing setup. **Solutions**: 1. Wait at least 5-10 minutes after setup completion for initial data flow 2. Verify Snowpipe is running: ```sql theme={"dark"} SHOW PIPES IN SCHEMA PERMUTIVE.DATA; ``` 3. Check pipe status - it should show as "RUNNING" 4. Verify the storage integration was created successfully: ```sql theme={"dark"} SHOW INTEGRATIONS; ``` 5. Contact [Support](mailto:support@permutive.com) if no data appears after 15 minutes **Symptoms**: Authentication failures when connecting to Snowflake, errors like "Invalid key" or "Authentication failed". **Solutions**: 1. Ensure the private key is in PKCS8 PEM format: ```bash theme={"dark"} openssl pkcs8 -topk8 -inform PEM -in old_key.pem -out new_key.p8 -nocrypt ``` 2. When setting `RSA_PUBLIC_KEY` on the Snowflake user, ensure you: * Remove the `-----BEGIN PUBLIC KEY-----` header * Remove the `-----END PUBLIC KEY-----` footer * Concatenate all remaining lines into a single string (no line breaks) 3. Verify the public key is correctly attached to the user: ```sql theme={"dark"} DESC USER permutive_routing; ``` Check the `RSA_PUBLIC_KEY_FP` field is populated 4. If using an existing key pair, regenerate a fresh key pair following the setup instructions **Symptoms**: Errors indicating insufficient permissions when Permutive attempts to create tables, stages, or pipes. **Solutions**: 1. Verify the role has all required permissions: ```sql theme={"dark"} SHOW GRANTS TO ROLE permutive_routing_role; ``` 2. Ensure the following grants are present: * `USAGE` on database * `USAGE, CREATE TABLE, CREATE STAGE, CREATE PIPE` on schema * `CREATE INTEGRATION` on account * `OPERATE` on future pipes * `USAGE` on warehouse 3. If any grants are missing, re-run the permission grant commands from the setup guide 4. Ensure the user's default role is set correctly: ```sql theme={"dark"} ALTER USER permutive_routing SET DEFAULT_ROLE = permutive_routing_role; ``` ## Changelog No changes listed yet. For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Catalog Source: https://docs.permutive.com/integrations/identity/catalog Browse our comprehensive catalog of Identity Signal integrations. # Identity Integrations
UID2

UID2

UID2 is an identity solution that enables publishers and advertisers to identify users across the digital advertising ecosystem.

ID5

ID5

ID5 provides privacy-compliant identity solutions that help publishers and advertisers identify users across devices and platforms.

RampID

RampID

RampID is LiveRamp's Identity Signal solution that enables publishers and advertisers to identify users across the digital ecosystem.

# ID5 Source: https://docs.permutive.com/integrations/identity/id5 Integrate with ID5 for universal identity and programmatic advertising
ID5

ID5

ID5 is a privacy-first universal identity platform that helps publishers maximize programmatic revenue through persistent cross-site user recognition.

## Overview ID5 provides a universal identifier that enables publishers to identify users across different websites and platforms without relying on third-party cookies or deterministic identifiers like hashed emails. The ID5 integration with Permutive allows publishers to deploy ID5's identity solution through the Permutive SDK without additional implementation work. This integration is a Destination integration: * **Destination:** Permutive retrieves ID5's ephemeral identity envelopes and passes them to the programmatic bidstream via Prebid or Amazon Publisher Services (APS), enabling demand-side platforms (DSPs) to recognize users and improve match rates. Key features of the ID5 integration: * **Probabilistic Matching:** Identifies users across domains without requiring hashed emails * **No-Code Integration:** Enable via Permutive dashboard without additional implementation * **Privacy-First:** Ephemeral envelopes for bidstream use only, with automatic TCF consent handling Use cases include: * **Yield Optimization:** Enrich programmatic bids with ID5 identifiers to increase CPMs and fill rates * Maximize addressability in a cookie-restricted environment ID5 identifiers are not ingested into Permutive's identity graph. They are ephemeral signals generated at bid time and passed directly to the bidstream. These IDs cannot be used for Permutive cohort building, identity resolution, or data collaboration features. **Amazon Publisher Services (APS) Support** In addition to Prebid, this integration also supports Amazon Publisher Services (APS) for bidstream activation. APS support is currently available on request. If you're interested in using APS, please contact your Customer Success Manager to discuss setup and requirements. ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ------------------------------------------------- | | **Web** | | Full support via Permutive SDK and Prebid module | | **iOS** | | Not supported - ID5 focuses on web-based identity | | **Android** | | Not supported - ID5 focuses on web-based identity | | **CTV** | | Not supported | | **API Direct** | | Not supported | ## Prerequisites Before enabling the ID5 integration, ensure you have the following. These prerequisites are for Prebid integration; for APS integration, contact your Customer Success Manager. * **ID5 Partner Account:** You must sign up for an ID5 account to obtain your Partner ID. While ID5 is free for publishers to use (advertisers pay to decrypt the IDs), you need to register and accept their terms of service. * Sign up at [id5.io](https://id5.io/universal-id/permutive) or contact ID5 directly * Obtain your unique Partner ID from your ID5 account dashboard * **Consent Management:** ID5 must be added to your Consent Management Platform (CMP) as an approved vendor in your TCF vendor list. Without proper consent configuration, ID5 will not generate identifiers. * Add ID5 as a vendor in your TCF implementation * Ensure required TCF purposes are configured (consult ID5 documentation for specific requirements) * **Prebid Integration:** The ID5 integration requires the Permutive Identity Manager Prebid module to be enabled. This is a specialized Prebid module separate from the standard Permutive RTD module. * The Permutive Identity Manager ID System module must be compiled into your Prebid build * See our [Prebid integration guide](/integrations/advertising/bidstream/prebid) for complete setup instructions including the Identity Manager module configuration * **Permutive SDK:** The Permutive Web SDK must be deployed and operational on your website ## Setup Register for an ID5 account and obtain your Partner ID: 1. Visit [id5.io](https://id5.io) and complete the publisher registration process 2. Accept ID5's terms of service 3. Navigate to your account dashboard and copy your Partner ID (you'll need this in the next step) ID5 is free for publishers to use. Advertisers pay to decrypt the identity envelopes in the bidstream. Add ID5 to your Consent Management Platform: 1. Log in to your CMP dashboard 2. Add ID5 to your TCF vendor list 3. Ensure the required TCF purposes are enabled for ID5 4. Test that consent is properly collected and ID5 appears in your consent string If ID5 is not properly configured in your CMP, the integration will not generate identifiers, even if enabled in Permutive. Ensure the Permutive Identity Manager ID System module is included in your Prebid build: 1. Check your current Prebid configuration or contact your Prebid implementation team 2. If the module is not already included, rebuild Prebid with the `permutiveIdentityManagerIdSystem` module 3. Deploy the updated Prebid build to your website The Permutive Identity Manager module is separate from the standard Permutive RTD module. Both can coexist in your Prebid setup. For detailed Prebid configuration instructions including the Identity Manager module setup, see our [Prebid integration guide](/integrations/advertising/bidstream/prebid). Activate the ID5 integration in Permutive: 1. Navigate to **Identity** → **Catalog** in your Permutive dashboard 2. Locate **ID5** in the identity provider catalog 3. Click **Connect** to begin the configuration process 4. Enter your ID5 Partner ID when prompted 5. Save the configuration The Permutive SDK will automatically begin retrieving ID5 identifiers on your next SDK build. Confirm that ID5 identifiers are being generated and passed to Prebid: 1. Open your website in a browser with developer tools enabled 2. Navigate to the **Console** tab 3. Look for Permutive SDK log messages indicating ID5 initialization 4. Check the **Network** tab for API calls to ID5's endpoints 5. Inspect Prebid bid requests to verify that `eids` array contains ID5 identifiers **Expected behavior:** * ID5 API calls should complete successfully (HTTP 200) * Prebid bid requests should include ID5 in the `user.eids` array with source `id5-sync.com` ```json theme={"dark"} { "user": { "eids": [ { "source": "id5-sync.com", "uids": [ { "id": "ID5*encrypted-id-envelope", "atype": 1 } ] } ] } } ``` ### Web Implementation Details The ID5 integration operates entirely through the Permutive SDK and requires no additional code implementation from publishers. Once enabled in the dashboard: 1. **SDK Initialization:** The Permutive SDK automatically detects the ID5 configuration during initialization 2. **Consent Check:** The SDK verifies user consent via the TCF consent string 3. **ID Retrieval:** If consent is granted, the SDK makes a client-side API call to ID5's servers to retrieve an ephemeral identity envelope 4. **Prebid Activation:** The ID5 identifier is passed to the Permutive Identity Manager Prebid module 5. **Bidstream Injection:** The Prebid module includes the ID5 identifier in the `user.eids` array of all bid requests ### Technical Flow ```mermaid theme={"dark"} sequenceDiagram participant Page as Publisher Page participant SDK as Permutive SDK participant CMP as Consent Management participant ID5 as ID5 API participant Prebid as Prebid.js Page->>SDK: SDK Initializes SDK->>CMP: Check TCF Consent CMP-->>SDK: Consent Granted SDK->>ID5: Request ID Envelope ID5-->>SDK: Return Encrypted ID SDK->>Prebid: Pass ID to Identity Manager Prebid->>Prebid: Include in bid requests ``` ## Data Types The ID5 integration does not collect event data into Permutive. Instead, it provides ephemeral identity signals that are passed directly to the programmatic bidstream via Prebid. ### Identity Signals Transmitted An encrypted identity envelope generated by ID5 and passed to demand-side platforms through Prebid's Extended ID (EID) format. Always set to `id5-sync.com` to identify the ID5 provider in the bidstream The encrypted ID5 identity envelope. This is an ephemeral identifier that varies across requests and cannot be used for long-term tracking Authentication type indicator. Typically set to `1` for ID5 identifiers ID5 identifiers are not stored in Permutive's identity graph or made available for cohort building. They exist solely for bidstream activation. ## Troubleshooting If ID5 identifiers are not showing up in your Prebid bid requests: **Check consent configuration:** * Verify ID5 is listed in your TCF vendor list * Ensure required TCF purposes are granted * Check the browser console for consent-related errors * Test in a fresh browser session or incognito mode to rule out cached consent states **Verify Prebid module:** * Confirm the Permutive Identity Manager ID System module is compiled into your Prebid build * Check that no Prebid configuration is blocking ID5 from the `eids` array * Review Prebid console logs for initialization errors **Confirm dashboard configuration:** * Navigate to Identity → Catalog in Permutive dashboard * Verify ID5 shows as "Connected" or "Enabled" * Ensure your Partner ID is correctly entered * Check that an SDK build has occurred since enabling ID5 **Network connectivity:** * Check browser developer tools Network tab for failed API calls to ID5 endpoints * Verify no ad blockers or content security policies are blocking ID5 requests * Test from different networks to rule out firewall or proxy issues If you're having trouble obtaining your ID5 Partner ID: 1. Ensure you've completed the full registration process at id5.io 2. Check your email for a confirmation or welcome message from ID5 with account details 3. Log in to your ID5 dashboard (the Partner ID is typically displayed prominently) 4. Contact ID5 support directly if you cannot locate your Partner ID **Common issue:** Some publishers confuse the Partner ID with other ID5 identifiers. The Partner ID is a configuration value you provide to Permutive, not an identifier generated for users. If you're seeing lower than expected ID5 coverage: **User consent factors:** * ID5 only generates identifiers for users who grant consent * In regions with strict privacy regulations (EU, California), consent rates may be lower * Review your consent management strategy and consent request UI **Technical factors:** * ID5 uses probabilistic matching, which may have inherent limitations in certain scenarios * Users with strict browser privacy settings may block ID5's signals * Safari's Intelligent Tracking Prevention (ITP) may limit ID5's effectiveness **Measurement:** * Use the Identity Insights dashboard in Permutive to view ID5 coverage metrics * Compare ID5 coverage to other identity solutions to establish a baseline * Monitor coverage trends over time to identify sudden changes that may indicate technical issues ID5 coverage naturally varies by geography, browser type, and user privacy preferences. Typical coverage rates depend on your specific audience characteristics. If you already have ID5 deployed directly on your site: The Permutive ID5 integration can coexist with direct ID5 implementations, but there may be redundant API calls. Consider: 1. **Keep existing implementation:** If your direct ID5 integration is working well, you may not need the Permutive integration 2. **Migrate to Permutive:** Remove your direct ID5 implementation and manage everything through Permutive for simplified operations 3. **Coordinate with ID5:** Contact ID5 support to ensure both implementations are configured optimally **Performance consideration:** Running duplicate ID5 implementations may result in unnecessary API calls. Consolidating to a single implementation is recommended. If you've enabled ID5 but haven't seen changes in CPMs or fill rates: **Measurement timeframe:** * Allow at least 2-4 weeks for measurable impact * Programmatic revenue fluctuates naturally; compare periods before and after ID5 enablement **Demand-side adoption:** * Not all DSPs and advertisers decrypt and utilize ID5 identifiers * The value of ID5 depends on your specific demand partners * Contact your SSP or programmatic partners to confirm they support ID5 **Analysis approach:** * Review Prebid analytics to see bid density changes in auctions with ID5 present * Compare win rates for bid requests with vs. without ID5 identifiers * Consult with your ad operations team or programmatic partner for detailed analysis ID5's value proposition varies by publisher based on audience composition, demand partners, and existing identity solutions. Some publishers see significant uplift, while others see minimal impact. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # LiveRamp RampID Source: https://docs.permutive.com/integrations/identity/rampid Integrate with LiveRamp RampID for authenticated identity and programmatic advertising
LiveRamp RampID

LiveRamp RampID

LiveRamp RampID is a privacy-first, people-based identity solution that enables publishers to maximize programmatic revenue through deterministic and probabilistic user recognition across the open web.

## Overview LiveRamp RampID is an industry-leading identity solution that provides publishers with a people-based identifier to improve addressability in programmatic advertising. The RampID integration with Permutive allows publishers to deploy LiveRamp's Authenticated Traffic Solution (ATS) through the Permutive SDK without additional implementation work. This integration is a Destination integration: * **Destination:** Permutive retrieves RampID ephemeral identity envelopes and passes them to the programmatic bidstream via Prebid or Amazon Publisher Services (APS), enabling demand-side platforms (DSPs) to recognize users and improve match rates. Key features of the RampID integration: * **Deterministic and Probabilistic Matching:** Uses both hashed emails and probabilistic signals for comprehensive coverage * **No-Code Integration:** Enable via Permutive dashboard without additional implementation * **Privacy-First:** Ephemeral envelopes for bidstream use only, with automatic TCF consent handling * **Industry Standard:** Widely adopted across the advertising ecosystem with strong DSP and SSP support Use cases include: * **Yield Optimization:** Enrich programmatic bids with RampID envelopes to increase CPMs and fill rates * Maximize addressability in a cookie-restricted environment with authenticated and probabilistic signals RampID identifiers are not ingested into Permutive's identity graph. They are ephemeral signals generated at bid time and passed directly to the bidstream. These IDs cannot be used for Permutive cohort building, identity resolution, or data collaboration features. **Amazon Publisher Services (APS) Support** In addition to Prebid, this integration also supports Amazon Publisher Services (APS) for bidstream activation. APS support is currently available on request. If you're interested in using APS, please contact your Customer Success Manager to discuss setup and requirements. ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ---------------------------------------------------- | | **Web** | | Full support via Permutive SDK and Prebid module | | **iOS** | | Not supported - RampID focuses on web-based identity | | **Android** | | Not supported - RampID focuses on web-based identity | | **CTV** | | Not supported | | **API Direct** | | Not supported | ## Prerequisites Before enabling the RampID integration, ensure you have the following. These prerequisites are for Prebid integration; for APS integration, contact your Customer Success Manager. * **LiveRamp Partner Account:** You must have a LiveRamp partnership agreement to use RampID. This typically requires: * Signing up for a LiveRamp account at [liveramp.com](https://liveramp.com) * Accepting LiveRamp's terms of service * Obtaining your unique Partner ID from your LiveRamp account dashboard * Note: While publishers can use RampID for free (advertisers pay to decrypt the IDs), you need to register and establish a partnership * **Consent Management:** RampID must be added to your Consent Management Platform (CMP) as an approved vendor in your TCF vendor list. Without proper consent configuration, RampID will not generate identifiers. * Add LiveRamp as a vendor in your TCF implementation * Ensure required TCF purposes are configured (consult LiveRamp documentation for specific requirements) * LiveRamp typically requires Purpose 1 (Store and/or access information on a device) and may require additional purposes * **Prebid Integration:** The RampID integration requires the Permutive Identity Manager Prebid module to be enabled. This is a specialized Prebid module separate from the standard Permutive RTD module. * The Permutive Identity Manager ID System module must be compiled into your Prebid build * See our [Prebid integration guide](/integrations/advertising/bidstream/prebid) for complete setup instructions including the Identity Manager module configuration * **Permutive SDK:** The Permutive Web SDK must be deployed and operational on your website * **Hashed Email Collection (Optional but Recommended):** While RampID can work with probabilistic signals alone, maximum effectiveness is achieved when you collect and hash user email addresses: * Implement email collection through registration, newsletter signup, or authentication * Hash emails using SHA-256 before passing to Permutive * Store hashed emails in Permutive's identity graph via the SDK's `identify()` method ## Setup Register for a LiveRamp account and establish your partnership: 1. Visit [liveramp.com](https://liveramp.com) and complete the publisher registration process 2. Work with your LiveRamp account representative to set up your partnership agreement 3. Accept LiveRamp's terms of service for the Authenticated Traffic Solution (ATS) 4. Navigate to your LiveRamp account dashboard and copy your Partner ID (you'll need this in the next step) RampID is free for publishers to use. Advertisers pay LiveRamp to decrypt the identity envelopes in the bidstream. Add LiveRamp to your Consent Management Platform: 1. Log in to your CMP dashboard 2. Add LiveRamp to your TCF vendor list (LiveRamp's TCF Vendor ID is typically 97) 3. Ensure the required TCF purposes are enabled for LiveRamp: * Purpose 1: Store and/or access information on a device (required) * Additional purposes as specified by LiveRamp 4. Test that consent is properly collected and LiveRamp appears in your consent string If LiveRamp is not properly configured in your CMP, the integration will not generate identifiers, even if enabled in Permutive. Ensure the Permutive Identity Manager ID System module is included in your Prebid build: 1. Check your current Prebid configuration or contact your Prebid implementation team 2. If the module is not already included, rebuild Prebid with the `permutiveIdentityManagerIdSystem` module 3. Deploy the updated Prebid build to your website The Permutive Identity Manager module is separate from the standard Permutive RTD module. Both can coexist in your Prebid setup. For detailed Prebid configuration instructions including the Identity Manager module setup, see our [Prebid integration guide](/integrations/advertising/bidstream/prebid). Activate the RampID integration in Permutive: 1. Navigate to **Identity** → **Catalog** in your Permutive dashboard 2. Locate **LiveRamp RampID** in the identity provider catalog 3. Click **Connect** to begin the configuration process 4. Enter your LiveRamp Partner ID when prompted 5. Configure any additional settings as needed 6. Save the configuration The Permutive SDK will automatically begin retrieving RampID identifiers on your next SDK build. Confirm that RampID identifiers are being generated and passed to Prebid: 1. Open your website in a browser with developer tools enabled 2. Navigate to the **Console** tab 3. Look for Permutive SDK log messages indicating RampID initialization 4. Check the **Network** tab for API calls to LiveRamp's endpoints (typically `ats.rlcdn.com` or `api.rlcdn.com`) 5. Inspect Prebid bid requests to verify that `eids` array contains RampID identifiers **Expected behavior:** * LiveRamp API calls should complete successfully (HTTP 200) * Prebid bid requests should include RampID in the `user.eids` array with source `liveramp.com` ```json theme={"dark"} { "user": { "eids": [ { "source": "liveramp.com", "uids": [ { "id": "AjfowMFS4A96...", "atype": 3, "ext": { "rtiPartner": "idl" } } ] } ] } } ``` To maximize RampID effectiveness, collect and pass hashed email addresses to Permutive: 1. **Collect Email Addresses:** Implement email collection through: * User registration flows * Newsletter subscriptions * Authentication/login * Commerce transactions 2. **Hash Emails Client-Side:** Before passing to Permutive, hash emails using SHA-256: ```javascript theme={"dark"} // Example: Hashing email with SubtleCrypto API async function hashEmail(email) { const normalized = email.toLowerCase().trim(); const msgBuffer = new TextEncoder().encode(normalized); const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); return hashHex; } // Pass hashed email to Permutive const hashedEmail = await hashEmail(userEmail); permutive.identify([{ tag: 'email_sha256', id: hashedEmail }]); ``` 3. **Privacy Considerations:** * Always obtain proper user consent before collecting and hashing emails * Never send unhashed (plaintext) emails to Permutive or any third-party * Ensure your privacy policy covers identity provider integrations * Respect opt-out requests and delete identifiers as required by privacy regulations Hashed emails significantly improve RampID match rates by providing deterministic signals to LiveRamp's identity graph. ### Web Implementation Details The RampID integration operates entirely through the Permutive SDK and requires no additional code implementation from publishers. Once enabled in the dashboard: 1. **SDK Initialization:** The Permutive SDK automatically detects the RampID configuration during initialization 2. **Consent Check:** The SDK verifies user consent via the TCF consent string 3. **Identity Graph Lookup:** The SDK checks if hashed emails or other identifiers are available in the Permutive identity graph 4. **ID Retrieval:** If consent is granted, the SDK makes a client-side API call to LiveRamp's ATS endpoints to retrieve an ephemeral RampID envelope 5. **Prebid Activation:** The RampID identifier is passed to the Permutive Identity Manager Prebid module 6. **Bidstream Injection:** The Prebid module includes the RampID identifier in the `user.eids` array of all bid requests ### Technical Flow ```mermaid theme={"dark"} sequenceDiagram participant Page as Publisher Page participant SDK as Permutive SDK participant CMP as Consent Management participant LR as LiveRamp ATS API participant Prebid as Prebid.js Page->>SDK: SDK Initializes SDK->>CMP: Check TCF Consent CMP-->>SDK: Consent Granted SDK->>SDK: Check Identity Graph SDK->>LR: Request RampID Envelope LR-->>SDK: Return Encrypted ID SDK->>Prebid: Pass ID to Identity Manager Prebid->>Prebid: Include in bid requests ``` ## Data Types The RampID integration does not collect event data into Permutive. Instead, it provides ephemeral identity signals that are passed directly to the programmatic bidstream via Prebid. ### Identity Signals Transmitted An encrypted identity envelope generated by LiveRamp and passed to demand-side platforms through Prebid's Extended ID (EID) format. Always set to `liveramp.com` to identify the LiveRamp RampID provider in the bidstream The encrypted RampID identity envelope. This is an ephemeral identifier that varies across requests and cannot be used for long-term tracking. The envelope is encrypted specifically for programmatic use and can only be decrypted by authorized advertisers who pay LiveRamp for access. Authentication type indicator. Typically set to `3` (authenticated) for RampID identifiers derived from hashed emails, or `1` for probabilistic signals LiveRamp's RTI (Real-Time Identity) partner identifier, typically set to `idl` (Identity Link) RampID identifiers are not stored in Permutive's identity graph or made available for cohort building. They exist solely for bidstream activation. The encrypted envelopes ensure privacy while enabling advertiser recognition. ## Troubleshooting If RampID identifiers are not showing up in your Prebid bid requests: **Check consent configuration:** * Verify LiveRamp is listed in your TCF vendor list (typically Vendor ID 97) * Ensure required TCF purposes are granted (at minimum Purpose 1) * Check the browser console for consent-related errors * Test in a fresh browser session or incognito mode to rule out cached consent states * Verify the consent string includes LiveRamp using browser developer tools **Verify Prebid module:** * Confirm the Permutive Identity Manager ID System module is compiled into your Prebid build * Check that no Prebid configuration is blocking RampID from the `eids` array * Review Prebid console logs for initialization errors * Ensure your Prebid version is up to date (module requires Prebid.js 5.0+) **Confirm dashboard configuration:** * Navigate to Identity → Catalog in Permutive dashboard * Verify RampID shows as "Connected" or "Enabled" * Ensure your LiveRamp Partner ID is correctly entered * Check that an SDK build has occurred since enabling RampID **Network connectivity:** * Check browser developer tools Network tab for failed API calls to LiveRamp endpoints (`ats.rlcdn.com`, `api.rlcdn.com`) * Verify no ad blockers or content security policies are blocking LiveRamp requests * Test from different networks to rule out firewall or proxy issues * Ensure HTTPS is enabled (LiveRamp requires secure connections) If you're having trouble obtaining your LiveRamp Partner ID: 1. Ensure you've completed the full partnership agreement with LiveRamp (not just a basic account registration) 2. Check your email for confirmation or welcome messages from LiveRamp with account details 3. Log in to your LiveRamp dashboard - the Partner ID is typically displayed in account settings or integration sections 4. Contact your LiveRamp account representative if you cannot locate your Partner ID 5. Verify you have access to the Authenticated Traffic Solution (ATS) product specifically **Common issue:** Publishers sometimes confuse different LiveRamp products. The RampID integration requires the ATS (Authenticated Traffic Solution) product, not the Data Marketplace or other LiveRamp offerings. If you're new to LiveRamp, your account representative can provide detailed onboarding guidance and help you locate all necessary credentials. If you're seeing lower than expected RampID coverage: **User consent factors:** * RampID only generates identifiers for users who grant consent * In regions with strict privacy regulations (EU, California), consent rates may be lower * Review your consent management strategy and consent request UI * Consider A/B testing different consent messaging to improve opt-in rates **Email collection factors:** * RampID performs best when publishers collect and pass hashed email addresses * If you're not collecting emails, consider implementing registration or newsletter signup * Ensure emails are properly hashed (SHA-256, lowercase, trimmed of whitespace) * Verify hashed emails are being passed to Permutive via the `identify()` method with tag `email_sha256` **Technical factors:** * Users with strict browser privacy settings may block RampID's signals * Safari's Intelligent Tracking Prevention (ITP) may limit effectiveness in some scenarios * Incognito/private browsing modes may reduce match rates * Ad blockers may interfere with LiveRamp API calls **Measurement:** * Use the Identity Insights dashboard in Permutive to view RampID coverage metrics * Compare RampID coverage to other identity solutions to establish a baseline * Monitor coverage trends over time to identify sudden changes that may indicate technical issues * Analyze coverage by user segment (authenticated vs. anonymous, new vs. returning) **Optimization strategies:** * Implement hashed email collection to significantly boost match rates * Ensure your consent UI clearly explains the value exchange for users * Work with LiveRamp to understand their recommended best practices for your specific use case * Consider implementing progressive registration to gradually collect user data Publishers who collect and pass hashed emails typically see 2-3x higher RampID coverage compared to those relying on probabilistic signals alone. If you already have LiveRamp ATS deployed directly on your site: The Permutive RampID integration can coexist with direct LiveRamp implementations, but there may be redundant API calls and potential conflicts. Consider: 1. **Keep existing implementation:** If your direct LiveRamp integration is working well and meets all your needs, you may not need the Permutive integration 2. **Migrate to Permutive:** Remove your direct LiveRamp implementation and manage everything through Permutive for: * Simplified operations (single dashboard) * Reduced JavaScript overhead * Unified identity management across all providers * Better performance monitoring 3. **Coordinate with LiveRamp:** Contact LiveRamp support to ensure both implementations are configured optimally if you need both 4. **Avoid duplicate calls:** Ensure you're not making duplicate API calls for the same user, which can impact performance **Performance consideration:** Running duplicate RampID implementations may result in unnecessary API calls and increased page load time. Consolidating to a single implementation is recommended. **Migration path:** If migrating from direct integration to Permutive: * Disable the direct LiveRamp ATS script * Enable RampID in Permutive dashboard * Wait for new SDK build * Test thoroughly before removing old implementation entirely If you've enabled RampID but haven't seen changes in CPMs or fill rates: **Measurement timeframe:** * Allow at least 2-4 weeks for measurable impact * Programmatic revenue fluctuates naturally; compare equivalent time periods before and after RampID enablement * Consider seasonal factors that may affect baseline performance **Demand-side adoption:** * Not all DSPs and advertisers decrypt and utilize RampID identifiers * The value of RampID depends on your specific demand partners and their LiveRamp integrations * Contact your SSP or programmatic partners to confirm they support RampID * Ask which DSPs in your auctions are actively bidding on RampID-enabled inventory **Coverage factors:** * Low RampID coverage (percentage of impressions with RampID) will limit revenue impact * Review coverage metrics in Permutive Identity Insights * If coverage is below 30-40%, consider implementing hashed email collection **Analysis approach:** * Review Prebid analytics to see bid density changes in auctions with RampID present * Compare win rates for bid requests with vs. without RampID identifiers * Analyze CPM lift for impressions with RampID vs. without * Segment analysis by geography, device type, and user segment * Consult with your ad operations team or programmatic partner for detailed analysis **Partnership optimization:** * Work with LiveRamp to ensure your partnership is properly configured * Verify that your inventory is eligible for LiveRamp's demand partnerships * Discuss with SSPs about enabling RampID-specific deal IDs or PMPs * Consider participating in LiveRamp's publisher monetization programs RampID's value proposition varies by publisher based on audience composition, demand partners, email collection rates, and existing identity solutions. Publishers with high email collection rates and strong authenticated audiences typically see the most significant uplift. If you're passing hashed emails but RampID isn't recognizing them: **Email normalization:** * Emails must be lowercase before hashing * Remove all leading and trailing whitespace * Do not include any additional characters or encoding **Correct hashing format:** ```javascript theme={"dark"} // CORRECT - proper normalization and hashing const email = " User@Example.COM "; const normalized = email.toLowerCase().trim(); // "user@example.com" const hashed = await sha256(normalized); // proper SHA-256 hash ``` **Common mistakes:** * Using MD5 or SHA-1 instead of SHA-256 * Hashing the email without normalizing (lowercase, trim) * Double-hashing the email * Including salt or pepper in the hash * Base64 encoding the hash (LiveRamp expects hexadecimal) **Verification:** * Test your hashing implementation with a known email * Use online SHA-256 tools to verify your output matches * Check Permutive SDK logs for identifier rejection messages * Verify the tag is set to `email_sha256` when calling `identify()` Always hash emails client-side before sending to Permutive. Never send plaintext emails to any third-party service. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # UID2 (Unified ID 2.0) Source: https://docs.permutive.com/integrations/identity/uid2 Integrate with UID2 for privacy-safe identity and programmatic advertising
UID2

UID2

UID2 (Unified ID 2.0) is an open-source, industry-backed identity framework that enables privacy-safe advertising through deterministic, email-based identity resolution across the programmatic ecosystem.

**Work in Progress** The UID2 integration is currently under development. If you're interested in this integration, please contact your Customer Success Manager to learn more about availability and timelines. # Catalog Source: https://docs.permutive.com/integrations/marketing/cdps/catalog Browse our comprehensive catalog of customer data platform integrations. # CDP Integrations
mParticle

mParticle

mParticle enables publishers to unify customer data from multiple sources for better personalization and ad targeting.

# mParticle Source: https://docs.permutive.com/integrations/marketing/cdps/mparticle Integrate with mParticle for customer data platform functionality
mParticle

mParticle

mParticle enables publishers to unify customer data from multiple sources for better personalization and ad targeting.

## Overview mParticle is a customer data platform (CDP) that enables publishers to collect, unify, and activate customer data across multiple channels and platforms. The Permutive integration with mParticle allows publishers to import audience segments from mParticle into Permutive for use in cohort building and activation. This integration is a Source integration: * **Source:** Permutive receives audience membership data from mParticle, allowing you to use mParticle audiences as segments within Permutive for targeting and activation. Use cases include: * Import audience segments from mParticle to combine with first-party data in Permutive * Use mParticle audiences as building blocks for more complex cohorts in Permutive * Activate mParticle-sourced audiences through Permutive's activation integrations (GAM, SSPs, DSPs) ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **mParticle Account**: You must have an active mParticle account with access to the mParticle dashboard and the ability to configure integrations. * **mParticle Audience Outputs**: You should have audiences configured in mParticle that you want to send to Permutive. * **Permutive Workspace**: You must have access to the Permutive dashboard with permissions to configure integrations. * **BigQuery or GCS Access**: The integration uses BigQuery or Google Cloud Storage (GCS) to transfer audience data from mParticle to Permutive. ## Setup Contact your Permutive Customer Success team to enable the mParticle integration for your workspace. The integration requires backend configuration to establish the connection between mParticle and Permutive. Technical Services will provide you with the necessary connection details and configure the data pipeline to receive audience updates from mParticle. In the mParticle dashboard, configure your audience outputs to send data to Permutive: 1. Navigate to **Audiences** in mParticle 2. Select the audience you want to send to Permutive 3. Configure the output to send audience membership updates to the designated BigQuery dataset or GCS bucket provided by Permutive 4. Enable the audience output Permutive receives audience membership changes as add and delete updates. When a user enters an mParticle audience, an `add` update is sent. When a user exits an audience, a `delete` update is sent. Ensure that your mParticle configuration includes user identifiers that can be matched with Permutive data: * The integration uses the **mParticle ID (mpId)** as the primary identifier * Additional identifiers (email, customer ID, etc.) can be used for identity resolution Work with your Permutive Customer Success team to confirm the identifier mapping strategy. Once the integration is configured: 1. Navigate to the **Data Imports** section in the Permutive dashboard 2. Verify that mParticle audiences are appearing as data sources 3. Check that audience membership data is being received and updated Audience data updates may take up to 24 hours to propagate from mParticle to Permutive, depending on your mParticle configuration. ## Data Types With your mParticle integration setup, Permutive receives audience membership data from mParticle. The integration imports audience segments rather than individual events. mParticle sends audience membership updates to Permutive when users enter or exit audiences. These updates include: The mParticle user identifier. This is the primary identifier used to match users between mParticle and Permutive. The unique identifier for the mParticle audience. The human-readable name of the mParticle audience. Indicates whether the user is being added to or removed from the audience. Values are `add` or `delete`. The time when the audience membership change occurred. ## Troubleshooting If your mParticle audiences are not appearing in the Permutive dashboard: * Verify that the mParticle audience output is correctly configured and enabled in the mParticle dashboard * Confirm that the BigQuery dataset or GCS bucket connection is properly configured * Check that audience updates are being sent from mParticle (you can verify this in mParticle's audience output logs) * Contact your Permutive Customer Success team to verify the backend integration configuration Audience data may take up to 24 hours to appear in Permutive after initial configuration. If audience membership updates are not being reflected in Permutive: * Verify that users have valid mParticle IDs (mpId) that can be matched in Permutive * Check the data staleness SLO: audience updates should be available within 1 hour under normal conditions * Review the mParticle output configuration to ensure both `add` and `delete` updates are being sent * Confirm that the audience is actively receiving membership changes in mParticle If issues persist, contact Permutive support with specific audience IDs and user identifiers for investigation. Identity resolution issues can prevent audience data from being properly associated with Permutive users: * Ensure that the mParticle ID (mpId) is being sent with audience updates * Verify that the same user identifiers are being collected in both mParticle and Permutive * Review your identity resolution strategy with your Permutive Customer Success team * Consider implementing additional identifier mapping (email, customer ID) for improved match rates The integration supports standard identifiers including mpId, email, and custom customer IDs. Work with [Technical Services](mailto:technical-services@permutive.com) to configure the optimal identifier strategy for your use case. When users exit an mParticle audience, the delete update should remove the audience membership from Permutive: * Verify that mParticle is configured to send `delete` updates (not just `add` updates) * Check the deletion SLO: audience membership deletions should be processed within 7 days * Confirm that the user-to-audience mapping exists in Permutive before the delete update is sent * Review mParticle's audience configuration to ensure proper exit conditions are defined Contact Permutive support if audience memberships are not being removed after the expected timeframe. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Adestra Source: https://docs.permutive.com/integrations/marketing/esps/adestra Integrate with Adestra for email service provider functionality
Adestra

Adestra

Adestra enables publishers to deliver personalized email campaigns and newsletters using audience segmentation and analytics.

## Overview Adestra is an email service provider (ESP) that enables publishers to deliver personalized email campaigns, newsletters, and automated marketing communications. The Adestra integration is a Destination integration that allows you to activate Permutive cohorts directly into Adestra for use in email marketing campaigns. This integration is a Destination: * **Destination:** Permutive sends cohort memberships to Adestra, where they are represented as Contact lists. These lists can be used to trigger automated email campaigns or target users in marketing workflows. Use cases include: * Send Permutive cohorts to Adestra to trigger automated email campaigns when users enter a cohort * Use behavioral and demographic cohorts for personalized email experiences * Leverage Permutive's first-party data for email targeting and segmentation * Create timely, relevant email campaigns based on real-time user actions and cohort membership ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | -------------------------------------------------------- | | **Web** | | Requires Contact ID to be passed via identity management | | **iOS** | | Requires Contact ID to be passed via identity management | | **Android** | | Requires Contact ID to be passed via identity management | | **CTV** | | Requires Contact ID to be passed via identity management | | **API Direct** | | Requires Contact ID to be passed via identity management | ## Prerequisites * **Adestra Account** - You must have an active Adestra account with API access enabled. * **Adestra API Key** - You will need your Adestra API key to configure the integration in the Permutive dashboard. This key should have permissions to manage contacts and lists. * **Adestra Contact Lists** - You must create Contact lists in Adestra ahead of time. The integration will add and remove users from these pre-existing lists based on cohort membership, but it will not automatically create new lists. * **Contact ID Configuration** - You must configure Permutive to collect Adestra Contact IDs (Adestra's internal user identifier) and pass them to Permutive via identity management. The identity type must match the "Alias tag" configured in the Permutive dashboard. * **Identity Management Setup** - You need to implement identity management to associate Permutive users with Adestra Contact IDs. This is typically done using the Permutive Identify API or SDK identify methods. ## Setup Before enabling the integration in Permutive, create the Contact lists in Adestra that you want to sync cohorts to. Make note of each list's List ID, as you'll need these when activating cohorts. You can find List IDs in the Adestra UI by navigating to your lists and viewing the list details. In the Permutive dashboard, navigate to your workspace's integrations page. Click *Add Integration* and select *Adestra*. You will be asked to provide: * **Adestra API Key** - Your Adestra API authentication key * **Alias tag** - The identity type name you'll use to sync Contact IDs with Permutive (defaults to `adestra`) The "Alias tag" field specifies the name of the identity type that Permutive will use to match users with their Adestra Contact IDs. This must correspond to the identity type you use when calling Permutive's identity management APIs. Implement identity management to pass Adestra Contact IDs to Permutive. You can use the Permutive SDK or the Identify API. **Example using Web SDK:** ```javascript theme={"dark"} permutive.identify([ { id: "12345", // Adestra Contact ID tag: "adestra", // Must match the Alias tag from Step 2 priority: 0 } ]); ``` **Example using Identify API:** ```bash theme={"dark"} curl -XPOST https://api.permutive.com/v2.0/identify \ -H 'X-API-Key: ' \ -H 'Content-Type: application/json' \ -d '{ "user_id": "", "aliases": [ { "id": "", "tag": "adestra", "priority": 0 } ] }' ``` The Contact ID is Adestra's internal identifier for a user. You'll need to retrieve this from Adestra and pass it to Permutive whenever you identify a user. In the Permutive Dashboard, navigate to one of your cohorts. You should see the Adestra activation option available. When enabling a cohort activation, you'll need to provide: * **Adestra List ID** - The ID of the Adestra Contact list you want to sync this cohort to Toggle the activation to *On*. Once enabled, Permutive will begin sending cohort membership updates to Adestra in near real-time (typically within 10 seconds of a user entering or exiting a cohort). After activating a cohort, verify that users are being added to the corresponding Contact list in Adestra: 1. Check the Contact list in the Adestra UI to see if users are appearing 2. Monitor the Permutive Events page to ensure identity events are being collected with the correct Adestra Contact IDs 3. Verify that cohort membership changes are reflected in Adestra within the expected timeframe For web properties, implement identity management using the Permutive Web SDK: ```javascript theme={"dark"} // Identify users with their Adestra Contact ID permutive.identify([ { id: "12345", // Adestra Contact ID tag: "adestra", // Must match the Alias tag configured in dashboard priority: 0 } ]); ``` You should call `permutive.identify()` whenever you have the user's Adestra Contact ID available (typically after login or when the Contact ID is retrieved from your authentication system). For iOS applications, implement identity management using the Permutive iOS SDK: ```swift theme={"dark"} // Identify users with their Adestra Contact ID Permutive.shared.identify( identifiers: [ Identifier( id: "12345", // Adestra Contact ID tag: "adestra", // Must match the Alias tag configured in dashboard priority: 0 ) ] ) ``` You should call `Permutive.shared.identify()` whenever you have the user's Adestra Contact ID available. For Android applications, implement identity management using the Permutive Android SDK: ```kotlin theme={"dark"} // Identify users with their Adestra Contact ID Permutive.getInstance().identify( listOf( Identifier( id = "12345", // Adestra Contact ID tag = "adestra", // Must match the Alias tag configured in dashboard priority = 0 ) ) ) ``` You should call `Permutive.getInstance().identify()` whenever you have the user's Adestra Contact ID available. For CTV applications, implement identity management using the relevant Permutive CTV SDK to pass Adestra Contact IDs to Permutive. The specific implementation will depend on your CTV platform. Use the SDK's identify method to pass the user's Adestra Contact ID with the identity type configured in the Permutive dashboard (Alias tag). You should call the identify method whenever you have the user's Adestra Contact ID available. For server-side implementations or environments without an SDK, use Permutive's Identify API directly to pass Adestra Contact IDs to Permutive: ```bash theme={"dark"} curl -XPOST https://api.permutive.com/v2.0/identify \ -H 'X-API-Key: ' \ -H 'Content-Type: application/json' \ -d '{ "user_id": "", "aliases": [ { "id": "", "tag": "adestra", "priority": 0 } ] }' ``` The `tag` value must match the Alias tag configured in the Permutive dashboard. You can retrieve your Public API Key from the Permutive dashboard. ## Data Types The Adestra integration activates cohort memberships to Adestra. Permutive sends cohort membership data to Adestra by adding and removing users from Contact lists. Permutive sends user cohort memberships to Adestra by managing Contact list memberships via the Adestra API. The integration uses the Adestra Contact ID (configured via the Alias tag) to identify users. **API Operations:** When a user enters a cohort: * **POST** `/contacts/{contact_id}/lists/{list_id}` - Adds the user to the specified Contact list When a user exits a cohort: * **DELETE** `/contacts/{contact_id}/lists/{list_id}` - Removes the user from the specified Contact list The Adestra Contact ID for the user. This is Adestra's internal identifier that you pass to Permutive via identity management. The Adestra List ID of the Contact list to add/remove the user from. This is configured when you activate a cohort in the Permutive dashboard. For the integration to work, Permutive must be able to match users with their Adestra Contact IDs. This requires implementing identity management. The Adestra Contact ID for the user. This must be passed to Permutive using the `identify` method in the SDK or via the Identify API. The identity type name. This must exactly match the "Alias tag" configured in the Permutive dashboard (defaults to `adestra`). The priority of this identifier. Set to `0` for most use cases. ## Troubleshooting If users are not being added to Adestra Contact lists, verify the following: * Ensure you have implemented identity management correctly and are passing Adestra Contact IDs to Permutive * Verify that the "Alias tag" configured in the Permutive dashboard matches the `tag` value you're using in identity management calls * Check that the Adestra List ID is correct when activating cohorts * Confirm that the Adestra API key is valid and has the correct permissions to manage contacts and lists * Verify that the Contact lists exist in Adestra before activating cohorts (the integration will not create lists automatically) * Check the Permutive Events page to ensure identity events are being collected with Adestra Contact IDs If you receive an invalid API key error when setting up the integration: * Verify that you have copied the full API key from Adestra without any extra spaces or characters * Confirm that the API key has not expired or been revoked in Adestra * Check that the API key has the necessary permissions to access the Adestra API (specifically, permissions to manage contacts and lists) * Contact your Adestra account administrator if you're unsure about API key permissions If you receive an error about an invalid List ID when activating a cohort: * Verify that you have entered the correct List ID from Adestra * Confirm that the Contact list still exists in Adestra and has not been deleted * Check that the API key has permission to access and manage the specified list * Ensure you're using the List ID (numeric identifier) and not the list name Cohort membership updates are processed in near real-time, typically within 10 seconds of a user entering or exiting a cohort. If you're experiencing significant delays: * Check the Permutive Events page to ensure events are being collected properly * Verify that the integration is enabled and properly configured * Confirm that identity management is implemented correctly and Contact IDs are being passed to Permutive * Contact Permutive Support if delays persist beyond expected timeframes If some users are not being synced to Adestra, it may be because their Contact IDs are not available in Permutive: * Verify that you're calling `identify` with the Adestra Contact ID for all authenticated users * Check that the Contact ID is available before the user enters a cohort (identity must be established before cohort membership) * Review your identity management implementation to ensure Contact IDs are being passed consistently across all platforms (Web, iOS, Android, CTV) * Consider implementing server-side identity resolution if Contact IDs are only available in your backend systems Adestra has API usage limits (10 concurrent connections). If you're experiencing throttling or rate limit errors: * Contact Permutive Support to review your integration configuration * Consider reducing the number of cohorts synced to Adestra if you're activating a very large number of cohorts * Review Adestra's API documentation for current rate limits and usage restrictions ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Catalog Source: https://docs.permutive.com/integrations/marketing/esps/catalog Browse our comprehensive catalog of email service provider integrations. # ESP Integrations
Salesforce Marketing Cloud

Salesforce Marketing Cloud

Salesforce Marketing Cloud allows publishers to manage email, social, and digital campaigns with audience segmentation.

Adestra

Adestra

Adestra enables publishers to deliver personalized email campaigns and newsletters using audience segmentation and analytics.

Sailthru

Sailthru

Sailthru helps publishers personalize emails and on-site experiences using behavioral and demographic data.

# Sailthru Source: https://docs.permutive.com/integrations/marketing/esps/sailthru Integrate with Sailthru for email service provider functionality
Sailthru

Sailthru

Sailthru helps publishers personalize emails and on-site experiences using behavioral and demographic data.

## Overview Sailthru is an email service provider (ESP) and marketing automation platform that enables publishers to personalize customer experiences across email and other channels. The Sailthru integration is a Destination integration that allows you to activate Permutive cohorts directly into Sailthru for use in email marketing campaigns and other marketing inventory. This integration is a Destination: * **Destination:** Permutive is able to send cohort memberships to Sailthru for targeting in email campaigns. Use cases include: * Send Permutive cohorts to Sailthru to target users in email marketing campaigns * Use behavioral and demographic cohorts for personalized email experiences * Leverage Permutive's first-party data for email targeting and segmentation ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | -------------------------------------------------------------------- | | **Web** | | Sailthru cookie ID (`sailthru_hid`) is collected from web properties | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Sailthru Account** - You must have Sailthru installed and configured on your website prior to enabling the Permutive integration. * **Sailthru Cookie** - The Sailthru cookie (`sailthru_hid`) must be present on your web properties for Permutive to collect the Sailthru user ID. * **Sailthru API Credentials** - You will need your Sailthru API credentials to configure the integration in the Permutive dashboard. * **IP Whitelist** - Depending on the type of API key, you may need to whitelist Permutive IPs within Sailthru for the integration to work. Contact Permutive to get the IP list. ## Setup In the Permutive dashboard, navigate to your workspace's integrations page. Click *Add Integration* and select *Sailthru*. You will be asked to enter your Sailthru API credentials at this stage. Depending on the type of API key, you may need to whitelist Permutive IPs within Sailthru for the integration to work. Contact Permutive to get the IP list. To whitelist the IPs in Sailthru, navigate to **Settings → Setup → API & Postbacks** in the Sailthru UI. In the Permutive Dashboard, navigate to one of your cohorts. Check that the Sailthru activation sync is visible. You can try toggling this to *On*. Once enabled, Permutive will begin sending cohort membership updates to Sailthru in near real-time (typically within 10 seconds of a user entering or exiting a cohort). No additional web-specific configuration is required. The Permutive Web SDK will automatically collect the Sailthru cookie ID (`sailthru_hid`) from your web properties once the integration is enabled. ## Data Types The Sailthru integration activates cohort memberships to Sailthru. Permutive sends cohort membership data to Sailthru in the following format: Permutive sends user cohort memberships to Sailthru by updating user profiles via the Sailthru API. The integration uses the Sailthru cookie ID (`sailthru_hid`) to identify users. The Sailthru user ID (from the `sailthru_hid` cookie). An array of Permutive cohort IDs that the user is a member of. This field is updated on the user's Sailthru profile and can be used for targeting in Sailthru campaigns. ## Troubleshooting If cohort memberships are not appearing in Sailthru user profiles, verify the following: * Ensure the Sailthru cookie (`sailthru_hid`) is present on your web properties * Verify that Permutive's fixed IP address has been added to your Sailthru allowlist * Check that the cohort activation toggle is enabled in the Permutive dashboard * Confirm that your Sailthru API credentials are correctly configured in the Permutive dashboard Cohort membership updates are processed in near real-time, typically within 10 seconds of a user entering or exiting a cohort. If you're experiencing significant delays: * Check the Permutive Events page to ensure events are being collected properly * Verify that the integration is enabled and properly configured * Contact Permutive Support if delays persist beyond expected timeframes ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Salesforce Marketing Cloud Source: https://docs.permutive.com/integrations/marketing/esps/sfmc Integrate with Salesforce Marketing Cloud for email campaign management and audience activation
Salesforce Marketing Cloud

Salesforce Marketing Cloud

Salesforce Marketing Cloud is a comprehensive digital marketing platform that enables publishers to deliver personalized email campaigns and customer journeys at scale.

## Overview Salesforce Marketing Cloud (SFMC) is a leading marketing automation platform that helps organizations manage customer relationships and deliver targeted email communications. Our integration with Salesforce Marketing Cloud enables you to activate Permutive cohorts directly into SFMC Data Extensions for use in email campaigns and automated customer journeys. This integration is a Destination: * **Destination:** Permutive can activate your cohorts into SFMC Data Extensions, allowing you to target email campaigns with audience segments built in Permutive. Use cases include: * Send targeted email campaigns to Permutive cohorts based on user behavior and interests. * Create personalized customer journeys in SFMC using Permutive audience segments. * Retarget users via email based on their on-site activity and cohort membership. * Leverage first-party data from Permutive to enhance email marketing performance. ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ------------------------------------------------------------ | | **Web** | | Requires Subscriber Key to be passed via identity management | | **iOS** | | Requires Subscriber Key to be passed via identity management | | **Android** | | Requires Subscriber Key to be passed via identity management | | **CTV** | | Requires Subscriber Key to be passed via identity management | | **API Direct** | | Requires Subscriber Key to be passed via identity management | ## Prerequisites * **SFMC Account Access** - You must have access to your Salesforce Marketing Cloud account with permissions to create API integrations and manage Data Extensions. * **SFMC Subdomain** - You must determine the subdomain specific to your Marketing Cloud instance. This can be found in the SFMC dashboard: 1. Navigate to Administration (under your email address in top-right, where logout is) 2. Click the General Settings box 3. Identify the text after "Portfolio Base URL" or "SOAP WSDL" (just the parts between `https://` and the first full stop) 4. Example: `https://my-subdomain.soap.marketingcloudapis.com/Service.asmx` would be `my-subdomain` * **Business Unit Configuration** - For each Business Unit you want to send data to, you must configure API integrations in SFMC. You will need: * A unique name for each Business Unit (alphanumeric, spaces allowed) * Client ID (generated when you create the API integration package) * Client Secret (generated when you create the API integration package) * An identity type/alias tag that should be synced from Permutive to SFMC * **Subscriber Key Type** - You must determine whether your SFMC Subscriber Keys are text or numeric values. This configuration is required when setting up the integration in the Permutive dashboard. * **User ID Alignment** - The IDs being synced to Permutive (via `permutive.identify` in the SDK or API) must match the Subscriber Keys used in SFMC. The Subscriber Key is the primary identifier SFMC uses for contacts, and it must align with the identity type you configure in Permutive. ## Setup Before configuring API access, determine your SFMC subdomain. This is required for the integration setup in the Permutive dashboard. 1. Log in to Salesforce Marketing Cloud 2. Navigate to "Administration" (under your email address in top-right, where logout is) 3. Click the "General Settings" box 4. Locate the "Portfolio Base URL" or "SOAP WSDL" field 5. Extract the subdomain (the text between `https://` and the first `.`) * Example: `https://my-subdomain.soap.marketingcloudapis.com/Service.asmx` → subdomain is `my-subdomain` The subdomain is consistent across all Business Units in your Marketing Cloud account. For each Business Unit you want to activate cohorts to, you must configure API access in Salesforce Marketing Cloud. Repeat these steps for every Business Unit. 1. In SFMC, navigate to "Installed Packages" under the "Account" dropdown (top-left) 2. Click the "New" button in the top right 3. Enter a name and description (e.g., "Permutive \{name-of-business-unit}") 4. Click "Add Component" and select "API Integration" (should be automatically selected) 5. Ensure "Perform server-to-server requests" is checked (should be auto-selected) 6. Scroll down to "Data" section 7. Under "Data Extensions", grant both "Read" and "Write" permissions 8. In the new "API Integration" box under "Components", record the following for each Business Unit: * **Client ID** - Specific to this Business Unit * **Client Secret** - Specific to this Business Unit * **Subdomain** - Should match the subdomain you determined in Step 1 9. Keep these credentials secure and note which Business Unit they correspond to Each Business Unit requires its own API Integration package with unique Client ID and Client Secret credentials. If you need to revoke Permutive's access to a specific Business Unit later, delete the corresponding package in "Installed Packages". Configure the Salesforce Marketing Cloud integration in the Permutive dashboard with your subdomain and Business Unit credentials. 1. Navigate to the Permutive dashboard 2. Go to "Settings" → "Integrations" 3. Find "Salesforce Marketing Cloud" and click "Add Integration" 4. **Enter your SFMC subdomain** (from Step 1) 5. **Select Subscriber Key Type** - Choose whether your SFMC Subscriber Keys are "text" or "number" 6. **Configure each Business Unit** you want to activate cohorts to: * **Name** - Enter a descriptive name for the Business Unit (alphanumeric, spaces allowed) * This name is used in the Permutive dashboard for reference * It's also used in the Data Folder name created in SFMC (e.g., `Permutive (Sales)`) * Does not need to match the MID in Marketing Cloud * **Client ID** - Enter the Client ID from the API Integration (from Step 2) * **Client Secret** - Enter the Client Secret from the API Integration (from Step 2) * **Identity Type / Alias Tag** - Specify the identity type that should be synced from Permutive to SFMC * This must match the identity you pass via `permutive.identify` in the SDK * Example: if you use `permutive.identify([{id: "12345", tag: "sfmc", priority: 0}])`, enter `sfmc` here 7. To add additional Business Units, click the "+ Business unit" button and repeat the configuration 8. Click "Save" to complete the integration setup The Identity Type/Alias Tag must match the Subscriber Key you're using in Salesforce. This is how Permutive maps users to SFMC contacts. Once the integration is enabled, you can configure individual cohorts for activation to SFMC. 1. In the Permutive dashboard, navigate to one of your cohorts 2. Look for the Salesforce Marketing Cloud activation option 3. Select the Business Unit you want to send the cohort data to 4. Set the activation to fire "Every Time" a user enters or exits the cohort 5. Enable the activation When you enable a cohort activation, Permutive will automatically create a Data Extension in SFMC with the same name as your cohort. The Data Extension will be placed in a Data Folder named `Permutive (\{business-unit-name\})`. In the Salesforce Marketing Cloud dashboard, verify that the integration is working correctly. 1. Navigate to "Data Extensions" in SFMC 2. Look for the Data Folder named `Permutive (\{business-unit-name\})` 3. Inside the folder, you should see Data Extensions corresponding to your activated cohorts 4. Each Data Extension should contain: * `subscriber_key`: The user ID from Permutive * `timestamp`: When the user entered the cohort 5. As users enter and exit cohorts, rows will be added and removed from the Data Extensions The Data Extension name will match your Permutive cohort name. The description will contain the cohort code and description (if specified). For web properties, implement identity management using the Permutive Web SDK to pass SFMC Subscriber Keys to Permutive: ```javascript theme={"dark"} // Identify users with their SFMC Subscriber Key permutive.identify([ { id: "subscriber-key-12345", // SFMC Subscriber Key tag: "sfmc", // Must match the Alias tag configured in dashboard priority: 0 } ]); ``` You should call `permutive.identify()` whenever you have the user's SFMC Subscriber Key available. The Subscriber Key is SFMC's primary identifier for contacts and must match the key used in your SFMC account. The Subscriber Key type (text or number) must be configured correctly in the Permutive dashboard to match your SFMC configuration. For iOS applications, implement identity management using the Permutive iOS SDK to pass SFMC Subscriber Keys to Permutive: ```swift theme={"dark"} // Identify users with their SFMC Subscriber Key Permutive.shared.identify( identifiers: [ Identifier( id: "subscriber-key-12345", // SFMC Subscriber Key tag: "sfmc", // Must match the Alias tag configured in dashboard priority: 0 ) ] ) ``` You should call `Permutive.shared.identify()` whenever you have the user's SFMC Subscriber Key available. For Android applications, implement identity management using the Permutive Android SDK to pass SFMC Subscriber Keys to Permutive: ```kotlin theme={"dark"} // Identify users with their SFMC Subscriber Key Permutive.getInstance().identify( listOf( Identifier( id = "subscriber-key-12345", // SFMC Subscriber Key tag = "sfmc", // Must match the Alias tag configured in dashboard priority = 0 ) ) ) ``` You should call `Permutive.getInstance().identify()` whenever you have the user's SFMC Subscriber Key available. For CTV applications, implement identity management using the relevant Permutive CTV SDK to pass SFMC Subscriber Keys to Permutive. The specific implementation will depend on your CTV platform. Use the SDK's identify method to pass the user's SFMC Subscriber Key with the identity type configured in the Permutive dashboard (Alias tag). You should call the identify method whenever you have the user's SFMC Subscriber Key available. For server-side implementations or environments without an SDK, use Permutive's Identify API directly to pass SFMC Subscriber Keys to Permutive: ```bash theme={"dark"} curl -XPOST https://api.permutive.com/v2.0/identify \ -H 'X-API-Key: ' \ -H 'Content-Type: application/json' \ -d '{ "user_id": "", "aliases": [ { "id": "", "tag": "sfmc", "priority": 0 } ] }' ``` The `tag` value must match the Alias tag configured in the Permutive dashboard for the relevant Business Unit. You can retrieve your Public API Key from the Permutive dashboard. ## Data Types When you activate a cohort for SFMC, Permutive creates a Data Extension with the following schema: Each Data Extension created by Permutive contains user identifiers and membership timestamps for a specific cohort. **Naming Convention:** * **Data Extension Name:** Same as your Permutive cohort name (e.g., "Travel Enthusiasts") * **Data Extension Description:** Cohort code followed by cohort description (e.g., "12345 - Users interested in travel content") * **Data Folder:** All Data Extensions for a Business Unit are organized in a folder named `Permutive ({business-unit-name})` (e.g., `Permutive (Sales)`) * **External Key:** Each Data Extension is assigned a UUID as its External Key, which Permutive uses to identify it **Fields:** The user identifier synced to Permutive that maps to SFMC's Subscriber Key. This is the Alias Tag/Identity Type you configured in the integration setup. * Field type matches the Subscriber Key Type configured in Permutive (text or number) * Configured as the Subscriber Key attribute in SFMC to make the Data Extension sendable * Must match the IDs you pass via `permutive.identify` in the SDK or API * Primary Key of the Data Extension The timestamp when the user entered the cohort in Permutive. * Stored without timezone information in SFMC * Used to track when users became members of the cohort **Behavior:** * When a user enters the cohort in Permutive, a row is added to the Data Extension with their `subscriber_key` and entry `timestamp` * When a user exits the cohort in Permutive, the corresponding row is removed from the Data Extension * Data Extensions are configured as both **sendable** and **testable** for use in email campaigns and automated journeys * Segment entries and exits are synced in near real-time (typically within seconds) Understanding the relationship between Permutive's Alias Tag and SFMC's Subscriber Key is crucial for successful cohort activation. **What is a Subscriber Key?** The Subscriber Key is SFMC's primary identifier for contacts. It's the unique ID that SFMC uses to identify and target individual users across all email campaigns and customer journeys. **What is an Alias Tag?** In Permutive, an Alias Tag is the name of an identity type. It's how you label different types of identifiers when you call `permutive.identify`. For the SFMC integration, you configure an Alias Tag (e.g., "sfmc") that represents your SFMC Subscriber Keys. **How They Work Together:** 1. You configure the Alias Tag in the Permutive dashboard when setting up the SFMC integration (e.g., "sfmc") 2. You identify users in Permutive with their SFMC Subscriber Key using this Alias Tag: ```javascript theme={"dark"} permutive.identify([{ id: "subscriber-12345", // The actual SFMC Subscriber Key tag: "sfmc", // The Alias Tag configured in dashboard priority: 0 }]); ``` 3. When a user enters a cohort, Permutive looks up their identifier for the configured Alias Tag 4. Permutive sends this identifier to SFMC as the `subscriber_key` in the Data Extension 5. SFMC can now use this Data Extension to target the user in email campaigns **Important Notes:** * The Alias Tag is just a label in Permutive - the actual ID values must be valid SFMC Subscriber Keys * The Subscriber Key type (text or number) must be configured correctly in Permutive to match your SFMC setup * Users must be identified with the Alias Tag before entering a cohort for activation to work * Different Business Units can use different Alias Tags if they use different identifier schemes ## Troubleshooting If you see a 500 error after approximately 10 seconds when setting up the integration, this is likely due to slow SFMC API response times. The integration should still complete successfully. **Solution:** * Refresh your browser (hard refresh recommended) * Navigate to Settings → Integrations * Check if Salesforce Marketing Cloud appears in your list of active integrations * If the integration appears, setup was successful despite the timeout * If it doesn't appear, try the setup process again If you receive permission errors when trying to enable the integration, check your API integration configuration in SFMC. **Solution:** * Verify that you've granted "Read" and "Write" permissions for Data Extensions in your SFMC API Integration package * Ensure you're using the correct Client ID and Client Secret for each Business Unit * Confirm that the API Integration is set to "Perform server-to-server requests" * Check that the subdomain is correct If a Data Extension is not created when you activate a cohort, there may be a configuration issue. **Solution:** * Verify that the cohort activation is set to fire "Every Time" (this is the only valid option for SFMC) * Check that you've selected a Business Unit for the activation * Ensure there are users in the cohort who have been identified with your SFMC identifier type * Look for any error messages in the Permutive dashboard when enabling the activation Once a Business Unit is added to the integration configuration, it cannot be removed or renamed through the dashboard. This is a limitation of the integration design. **Solution:** * Business Units cannot be removed after being added to the integration configuration * The name of an existing Business Unit cannot be changed after creation * You can edit Client ID, Client Secret, and Alias Tag for existing Business Units, but not the name * If you need to remove a Business Unit entirely, contact Permutive Support for assistance * To revoke Permutive's access to a specific Business Unit without removing it from Permutive, delete the API Integration package in SFMC's "Installed Packages" section If you configured the wrong Subscriber Key type (text vs. number) in the Permutive dashboard, cohort activations may not work correctly. **Solution:** * Verify the Subscriber Key type in your SFMC account (check if keys are numeric or text-based) * The Subscriber Key type configured in Permutive must match your SFMC configuration * If you selected the wrong type, contact Permutive Support to update the configuration * Check that the IDs you're passing via `permutive.identify` match the expected format (text or number) If you accidentally activated a cohort to the wrong Business Unit, you can change it. **Solution:** * In the Permutive dashboard, navigate to the cohort's activation settings * Select a different Business Unit from the dropdown * The Data Extension will be created in the newly selected Business Unit * The old Data Extension in the previous Business Unit will remain but will no longer receive updates * To clean up, manually delete the old Data Extension in SFMC if desired If users in your Permutive cohorts are not appearing in SFMC Data Extensions, there may be an ID mismatch. **Solution:** * Verify that the IDs being sent to Permutive via `permutive.identify` match the Subscriber Keys in SFMC * Check that you've specified the correct identifier type in the integration configuration * Ensure users have been identified in Permutive using the identifier type you configured * Confirm that the Data Extension's `subscriber_key` column is set as the Subscriber Key attribute in SFMC If you've moved or renamed a Data Extension created by Permutive, activations should continue to work. **Solution:** * Moving Data Extensions to different folders is supported - activations will continue to sync * Renaming Data Extensions is supported - activations use the External Key (UUID) to identify the Data Extension, not the name * Do NOT change the `External Key` of the Data Extension, as this is used by Permutive to identify the correct Data Extension * New activations will still be created in the original Permutive Data Folder Timestamps in SFMC Data Extensions may not display in the timezone you expect. **Solution:** * Timestamps are stored without timezone information in SFMC * The displayed timezone may differ from UTC depending on SFMC's internal handling * This is a known limitation and does not affect the functionality of the integration * Consider the timestamp as a relative indicator of when users entered cohorts rather than an absolute time You have flexibility in how you organize Data Folders and Data Extensions created by Permutive in SFMC. **Data Folders:** * **Moving:** Yes, you can move Data Folders created by Permutive. New Data Extensions will still be placed in the moved folder. * **Renaming:** Yes, you can rename Data Folders. New Data Extensions will still be created in the renamed folder. * **Sharing across Business Units:** No, Data Folders and Data Extensions cannot be automatically shared across Business Units when created by Permutive. You would need to manually configure sharing in SFMC if needed. **Data Extensions:** * **Moving:** Yes, you can move Data Extensions to different folders. Activations will continue to sync correctly using the External Key. * **Renaming:** Yes, you can rename Data Extensions. Segment transitions will continue to sync because Permutive uses the External Key (UUID) to identify the Data Extension, not the name. * **External Key:** Do NOT change the External Key of a Data Extension, as this is how Permutive identifies it for updates. * **New activations:** If you move or rename Data Extensions, new cohort activations will still be created in the original Permutive Data Folder. **Disabling activations:** * Disabling a cohort activation in Permutive will NOT delete the corresponding Data Extension in SFMC * You must manually delete Data Extensions in SFMC if you want to remove them entirely No, SFMC cohort activations cannot be automatically enabled when you create a new segment in Permutive. **Solution:** * You must manually enable the SFMC activation for each cohort you want to sync * Navigate to the cohort in the Permutive dashboard * Toggle the SFMC activation to "On" * Select the Business Unit and configure the activation settings ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Overview Source: https://docs.permutive.com/integrations/overview Connect with 50+ partners across advertising, marketing, and data platforms ## Featured Integrations
Google Ad Manager

Google Ad Manager

Industry-leading ad server for publishers with comprehensive monetization capabilities

Prebid

Prebid

Open-source header bidding solution for maximizing programmatic revenue

The Trade Desk

The Trade Desk

Leading DSP for programmatic advertising and audience targeting

DV360

DV360

Google's demand-side platform for programmatic media buying

Magnite

Magnite

Independent sell-side advertising platform for publishers

Snowflake

Snowflake

Cloud data warehouse for secure analytics and data collaboration

## Quick Start by Use Case

Pass signals to your ad server across web, app, and CTV

Google Ad Manager FreeWheel Equativ Microsoft Monetize Nativo AdsWizz

Make cohorts available to demand partners via OMP, PMP, and curated deals

Magnite Index Exchange PubMatic OpenX

Enrich audience profiles with contextual intelligence from your content

IBM Watson SilverBullet OS Data Solutions TextRazor

Enrich bidstream with identity signals for yield optimization

ID5 UID2 RampID

Unify and analyze audience and advertising data

Snowflake BigQuery AWS S3

Activate cohorts for programmatic campaigns across display, video, and CTV

The Trade Desk DV360 Adform StackAdapt

Collect viewing behavior from video players to build engaged audiences

Brightcove JW Player Dailymotion

Build custom audiences on social media platforms using cohort data

Facebook TikTok YouTube LinkedIn X Pinterest

Enrich bid requests with cohort and identity signals

Prebid Amazon Publisher Services

Collect declared data through interactive survey integrations

Collective Audience Apester Brand Metrics Opinary Qualifio
# Catalog Source: https://docs.permutive.com/integrations/social-media/catalog Browse our comprehensive catalog of social media platform integrations. # Social Media Integrations
Facebook

Facebook

Facebook is the world's largest social media platform, enabling publishers to reach billions of users with targeted content and advertising.

Instagram

Instagram

Instagram is a visual social media platform that enables publishers to share photos, videos, and stories with engaged audiences.

X (Twitter)

X (Twitter)

X (formerly Twitter) is a social media platform that enables publishers to target promoted posts and ads at Permutive cohort audiences in real-time.

LinkedIn

LinkedIn

LinkedIn is a professional networking platform that enables publishers to reach business audiences with professional content.

Pinterest

Pinterest

Pinterest is a visual discovery platform that enables publishers to share and discover content through pins and boards.

TikTok

TikTok

TikTok is a short-form video platform that enables publishers to create and share engaging content with global audiences.

YouTube

YouTube

YouTube is the world's largest video platform, enabling publishers to share and monetize video content with global audiences.

# Facebook Source: https://docs.permutive.com/integrations/social-media/facebook Activate Permutive cohorts to Facebook for custom audience targeting
Facebook

Facebook

Activate Permutive cohorts to Facebook for custom audience targeting and advertising campaigns.

## Overview The Permutive Facebook Pixel integration enables you to send rich cohort information to Facebook in real-time for audience targeting. These audiences are passed to Facebook via custom pixel events and can be used to build custom audiences in Facebook Ads Manager. This integration is a Destination: * **Destination:** Permutive sends cohort membership data to Facebook via the Facebook pixel, enabling you to build custom audiences based on Permutive cohorts. Use cases include: * Build custom audiences in Facebook Ads Manager based on your Permutive cohorts * Retarget users across Facebook, Instagram, and Meta Audience Network * Target users with precision using real-time cohort membership data * Create lookalike audiences based on Permutive cohorts ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ------------------------------------- | | **Web** | | Requires Meta pixel installed on site | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Facebook Pixel:** You must have the Facebook pixel already installed and active on your website. Verify this by searching for `_fbq` in the browser console. The Permutive SDK integrates with the existing Facebook pixel implementation to send cohort membership data. * **Facebook Business Manager Access:** You should have access to your Facebook Business Manager account to view and manage custom audiences created from Permutive cohorts. * **Facebook Account Type:** Your Facebook account administrator or Facebook representative should confirm whether you have a Core Setup or Non-Core Setup account, as this determines the pixel event configuration. * **Permutive SDK:** The Permutive Web SDK must be deployed on your site. The Facebook integration is an add-on that works with the core Permutive SDK. The Facebook pixel integration works by firing custom events to the Facebook pixel SDK that is already present on your page. Permutive does not install the Facebook pixel for you. The integration automatically works with multiple Facebook pixels on your site. ## Setup Before enabling the Permutive integration, verify that the Facebook pixel is properly installed on your site: 1. Open your website in a browser 2. Open Developer Tools (F12 or right-click → Inspect) 3. In the Console tab, search for `_fbq` 4. If the Facebook pixel is installed, you should see the `_fbq` object defined 5. If you see an error message that `_fbq` is not defined, please refer to the [Facebook pixel implementation documentation](https://www.facebook.com/business/help/952192354843755) and contact Facebook support if needed You can also use the [Meta Pixel Helper Chrome extension](https://chrome.google.com/webstore/detail/meta-pixel-helper/fdgfkebogiimcoedlicjlajpkdmockpc) to verify pixel installation. 1. Navigate to **Settings > Integrations** in the Permutive Dashboard 2. Click **+ Add Integration** 3. Select **Facebook Pixel** from the list of available integrations 4. You will see a configuration screen where you need to select your Facebook account type Depending on the type of Facebook account you have, select either **Core Setup** or **Non-Core Setup**. This configuration determines the type of pixel event that will be sent by the Facebook pixel. **Core Setup:** If you have a Core Setup Facebook account, you will see two unique pixel events collected for every cohort: `PermutiveSegmentEntry_` and `PermutiveSegmentExit_`. For example, if a user is segmented into Permutive cohort 123, the following pixel event will fire: `PermutiveSegmentEntry_123` **Non-Core Setup:** If you have a Non-Core Setup Facebook account, you will see two generalized pixel events for all cohorts — only one event named `PermutiveSegmentEntry` will represent all cohort entries and only one event named `PermutiveSegmentExit` will represent all cohort exits. An event property named `segment_id` stored within each of those generalized events will contain the Permutive cohort code. Your account administrator or Facebook representative will be able to confirm what type of account you have. Once you have selected your Facebook account type, click **Add Integration**. The integration will be available for use in under 20 minutes. Once activated, you can begin sending cohorts to Facebook. For each cohort you want to activate to Facebook: 1. Navigate to the cohort in the Permutive dashboard 2. In the Activation Syncs section, toggle the Facebook Pixel activation to *On* 3. The cohort will now be sent to Facebook when users enter or exit the cohort To verify that cohorts are being sent to Facebook: 1. Use the Meta Pixel Helper Chrome extension to monitor pixel events on your site 2. For Core Setup accounts, look for custom events named `PermutiveSegmentEntry_` when users enter cohorts 3. For Non-Core Setup accounts, look for events named `PermutiveSegmentEntry` with a `segment_id` property 4. In Facebook Business Manager, check that custom audiences are being populated with data from the pixel events ### Core Setup vs Non-Core Setup The Facebook account type you select during integration setup determines how Permutive sends cohort data to Facebook: **Core Setup Accounts:** * Send unique pixel events for each cohort * Event naming format: `PermutiveSegmentEntry_` and `PermutiveSegmentExit_` * Example: A user entering cohort 123 fires the event `PermutiveSegmentEntry_123` * This approach is required because Core Setup accounts do not collect custom parameters in pixel events **Non-Core Setup Accounts:** * Send generalized pixel events for all cohorts * Event names: `PermutiveSegmentEntry` and `PermutiveSegmentExit` * The specific cohort code is passed as a property named `segment_id` within the event * This allows for more efficient event tracking across multiple cohorts In a Meta Pixel context, "Core Setup" is a data restriction mode that Meta automatically or manually applies to a pixel when a website is identified as being in a sensitive industry (e.g., healthcare, financial services) or has violated data-sharing terms. When a pixel is in Core Setup, Meta limits the type and amount of data it collects to reduce the potential for sharing prohibited information. ### Working with Multiple Facebook Pixels This integration works seamlessly with multiple Facebook pixels on your site. Permutive will automatically detect whichever Facebook pixel is available on the page for the domain where it is running. **How it works:** For example, if `https://www.sport.com/` has a pixel with one account ID and `https://www.gamebyte.com/` has a pixel with a different account ID: * When a cohort in your project is sent to Facebook, users who entered that cohort from `https://www.sport.com/` will be sent to the associated Facebook account * Users who entered the segment from `https://www.gamebyte.com/` will be sent to the associated Facebook account The integration automatically routes cohort data to the correct Facebook pixel based on the domain. **Selective Pixel Firing:** If you want to control which specific pixels receive Permutive cohort activations: This feature is available on request. Contact your Customer Success Manager to enable selective pixel firing for your workspace. Once enabled, you can: 1. Configure a list of pixel IDs in your Facebook integration settings 2. Toggle cohort activations per pixel when creating or editing activations 3. Control precisely which pixels receive which cohort activations This is useful if you deploy pixels for different purposes (e.g., agency pixels, partner pixels) and don't want to send Permutive cohorts to all of them. ### Web Implementation The Facebook pixel integration works automatically once enabled in the dashboard. The Permutive SDK fires custom events to the Facebook pixel when users enter or exit cohorts that are configured for Facebook activation. The integration automatically detects and works with all Facebook pixels on your site, sending cohort data to the appropriate pixel based on the domain. ### Technical Details The event structure depends on your Facebook account type: **Non-Core Setup Accounts:** When a user enters a cohort configured for Facebook activation: ```javascript theme={"dark"} fbq("trackCustom", "PermutiveSegmentEntry", { segment_id: "", }); ``` When a user exits a cohort: ```javascript theme={"dark"} fbq("trackCustom", "PermutiveSegmentExit", { segment_id: "", }); ``` **Core Setup Accounts:** When a user enters cohort 123: ```javascript theme={"dark"} fbq("trackCustom", "PermutiveSegmentEntry_123"); ``` When a user exits cohort 123: ```javascript theme={"dark"} fbq("trackCustom", "PermutiveSegmentExit_123"); ``` These events are captured by Facebook and can be used to build custom audiences in Facebook Business Manager. ## Data Types With your Facebook integration setup, Permutive fires custom events to your Meta pixel to enable audience building: Custom event fired when a user enters a cohort that has Facebook activation enabled. This event is used by Facebook to build custom audiences. The custom event name: `PermutiveSegmentEntry` The Permutive cohort code that the user entered. This property contains the unique identifier for the cohort. Custom event fired when a user exits a cohort that has Facebook activation enabled. The custom event name: `PermutiveSegmentExit` The Permutive cohort code that the user exited. For Core Setup accounts, a unique custom event is fired for each cohort when a user enters that cohort. The custom event name: `PermutiveSegmentEntry_[COHORT_CODE]` where `[COHORT_CODE]` is the unique identifier for the cohort. For example: `PermutiveSegmentEntry_123` For Core Setup accounts, a unique custom event is fired for each cohort when a user exits that cohort. The custom event name: `PermutiveSegmentExit_[COHORT_CODE]` where `[COHORT_CODE]` is the unique identifier for the cohort. For example: `PermutiveSegmentExit_123` Custom events sent to Facebook are subject to Facebook's data processing and matching. Match rates depend on Facebook's ability to match users based on cookies and other identifiers. Third-party cookies may be blocked in some environments, which can affect match rates. ## Troubleshooting First, verify that the Facebook pixel is properly installed on your site by checking that `_fbq` is defined in the browser console. If the pixel is installed but Permutive events are not appearing: * Check that the Facebook integration is enabled in the Permutive dashboard and has been active for at least 20 minutes * Verify that cohorts are configured with Facebook activation enabled * Ensure that the Permutive SDK has loaded successfully on the page * Check the browser console for any JavaScript errors that might be preventing the Permutive SDK from executing * For Non-Core Setup accounts, look for `PermutiveSegmentEntry` events with a `segment_id` property * For Core Setup accounts, look for `PermutiveSegmentEntry_` events Facebook custom audiences built from pixel events can take 24-48 hours to populate after pixel events start firing. If audiences are still not populating after this time: * Verify that pixel events are firing correctly using the Meta Pixel Helper * Check that the pixel ID in your pixel implementation matches the pixel ID in Facebook Business Manager * For Non-Core Setup accounts, ensure your custom audience definition in Facebook includes the `PermutiveSegmentEntry` custom event with the correct `segment_id` value * For Core Setup accounts, ensure your custom audience definition includes the specific `PermutiveSegmentEntry_` event * Check Facebook's Event Match Quality score, which indicates how well Facebook can match pixel data to Facebook users. Low match quality may be due to cookie blocking or other privacy restrictions. By default, Permutive automatically detects and works with all Facebook pixels on your site. The integration will pick up whichever Facebook pixel is available on the page for the domain where it is running. This means: * Different domains with different pixel IDs will automatically send data to their respective Facebook accounts * Users who enter a cohort from one domain will be sent to that domain's associated Facebook account * Users who enter a cohort from another domain will be sent to that domain's associated Facebook account If you want to control which specific pixels receive Permutive data, this requires custom configuration. Please contact your Customer Success Manager to discuss options for selective pixel activation. Facebook uses third-party cookies for user matching, which are increasingly blocked by browsers and privacy tools. If you're experiencing low match rates: * Consider using Facebook's Conversion API for server-side data transfer (contact Permutive Support for guidance) * Ensure users are logged into Facebook when visiting your site, as this improves matching * Review Facebook's Event Match Quality metrics in Facebook Business Manager to understand matching performance If you selected the wrong account type (Core Setup vs Non-Core Setup) during integration setup and cohort data isn't appearing in Facebook: 1. Verify your Facebook account type with your account administrator or Facebook representative 2. Core Setup accounts require unique events per cohort (`PermutiveSegmentEntry_`) 3. Non-Core Setup accounts use generalized events with a `segment_id` property (`PermutiveSegmentEntry`) 4. If you selected the wrong account type, you will need to remove and re-add the Facebook integration with the correct configuration 5. Contact Permutive Support if you need assistance reconfiguring the integration ## Changelog ### April 2025 * Added support for selective pixel firing to control which Meta pixels receive Permutive activations ### October 2024 * Enhanced configuration options for Meta Core Setup accounts to support separate custom events per cohort For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Instagram Source: https://docs.permutive.com/integrations/social-media/instagram Activate Permutive cohorts to Instagram for custom audience targeting via Facebook integration
Instagram

Instagram

Activate Permutive cohorts to Instagram for custom audience targeting via the Facebook Pixel integration.

## Overview Instagram advertising is powered by the Meta ecosystem and uses the Permutive Facebook Pixel integration. There is no separate Instagram integration required. Instagram targeting is powered by the Permutive [Facebook Pixel integration](/integrations/social-media/facebook). No separate Instagram integration is required. This integration is a Destination: * **Destination:** Permutive sends cohort membership data to Facebook via the Facebook pixel, which can then be used for Instagram advertising campaigns. Use cases include: * Target Instagram users with ads based on Permutive cohorts * Retarget users who visited your website on Instagram * Build custom audiences for Instagram campaigns using real-time cohort data * Create lookalike audiences for Instagram based on Permutive cohorts ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ------------------------------ | | **Web** | | Via Facebook Pixel integration | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Facebook Pixel Integration:** The [Facebook Pixel integration](/integrations/social-media/facebook) must be set up and configured in your Permutive workspace. Instagram targeting uses the same Facebook pixel infrastructure. * **Facebook Business Manager Access:** You must have access to Facebook Business Manager to create and manage audiences for Instagram campaigns. * **Instagram Business Account:** Your Instagram account must be connected to Facebook Business Manager. You can link your Instagram account by navigating to **Business Settings > Instagram Accounts** in Facebook Business Manager. * **Meta Pixel Installed:** The Meta (Facebook) pixel must be installed and active on your website to collect cohort data. ## Setup Instagram targeting uses the Facebook Pixel integration. Follow the complete setup guide in the [Facebook integration documentation](/integrations/social-media/facebook#setup). This includes: * Verifying the Meta pixel is installed on your site * Adding the Facebook Pixel integration in Permutive * Configuring your Facebook account type (Core Setup or Non-Core Setup) * Enabling cohort activations to Facebook 1. Navigate to [Facebook Business Manager](https://business.facebook.com/) 2. Go to **Business Settings** in the menu 3. Select **Instagram Accounts** from the left sidebar 4. Click **Add** and follow the prompts to connect your Instagram Business Account 5. Confirm that your Instagram account is linked and shows as active 1. In Facebook Ads Manager, create a new campaign or edit an existing one 2. In the **Audience** section, select **Custom Audiences** or **Lookalike Audiences** 3. Choose audiences that were built from Permutive cohorts (these are custom audiences created from the Facebook pixel events that Permutive sends) 4. In the **Placements** section, ensure **Instagram** is selected as a placement option 5. Continue with your ad creative and campaign settings Custom audiences built from Facebook pixel events can take 24-48 hours to populate after the integration is enabled. Instagram targeting is configured entirely through Facebook Business Manager and Ads Manager. There is no additional web implementation required beyond the Facebook Pixel integration. Refer to the [Facebook integration Web tab](/integrations/social-media/facebook#setup) for technical details on how cohort data is sent to the Meta pixel. Instagram targeting via Permutive cohorts is currently only supported on Web through the Facebook Pixel integration. Instagram targeting via Permutive cohorts is currently only supported on Web through the Facebook Pixel integration. ## Troubleshooting If Instagram is not available as a placement option when creating campaigns in Facebook Ads Manager: * Verify that your Instagram Business Account is properly linked to Facebook Business Manager (**Business Settings > Instagram Accounts**) * Ensure your Instagram account is a Business Account, not a Personal Account * Check that your Facebook Business Manager account has the appropriate permissions for the Instagram account * Confirm that your ad campaign objective supports Instagram placements (some campaign objectives may have limited placement options) If issues persist, refer to [Meta's documentation on Instagram placements](https://www.facebook.com/business/help/1695926137115781). Instagram campaigns use the same custom audiences as Facebook campaigns. If your Permutive-based audiences are not populating: * Check the [Facebook integration troubleshooting](/integrations/social-media/facebook#troubleshooting) section for guidance on custom audience population * Verify that pixel events are firing correctly using the Meta Pixel Helper Chrome extension * Allow 24-48 hours for custom audiences to populate after the Facebook integration is enabled * Confirm that your custom audience definition in Facebook includes the correct pixel events (`PermutiveSegmentEntry` or `PermutiveSegmentEntry_` depending on your account type) Since Instagram targeting relies on the Facebook Pixel integration, any pixel-related issues will affect Instagram campaigns. For troubleshooting: * Review the complete [Facebook integration troubleshooting guide](/integrations/social-media/facebook#troubleshooting) * Common issues include: * Facebook pixel not properly installed on your website * Incorrect Facebook account type configuration (Core Setup vs Non-Core Setup) * Cohort activations not enabled for Facebook in the Permutive dashboard * Third-party cookie blocking affecting match rates Match rates for Instagram audiences depend on Facebook's ability to match website visitors to Instagram users. To improve match rates: * Ensure users are logged into Facebook/Instagram when visiting your site * Review Facebook's Event Match Quality metrics in Facebook Business Manager * Consider implementing Facebook's Conversion API for server-side data transfer (contact Permutive Support for guidance) * Note that third-party cookie blocking can significantly impact match rates # LinkedIn Source: https://docs.permutive.com/integrations/social-media/linkedin Activate Permutive cohorts to LinkedIn for professional audience targeting
LinkedIn

LinkedIn

LinkedIn is the world's largest professional network, offering unique targeting capabilities for reaching business decision-makers.

## Overview LinkedIn is the world's largest professional network, offering unique targeting capabilities for reaching business decision-makers and professionals. Permutive can integrate with LinkedIn to enable cohort activation through LinkedIn's Matched Audiences feature. This integration is currently available on a case-by-case basis. It is not yet available as a self-serve integration in the Permutive Dashboard. If you are interested in enabling this integration for your organization, please reach out to your Customer Success Manager. Use cases include: * Activate Permutive cohorts for targeting LinkedIn advertising campaigns * Retarget website visitors on LinkedIn using first-party data * Reach professional audiences with content aligned to their interests ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Permutive SDK**: The Permutive SDK must be installed on your website * **Email Collection**: Your site must collect user email addresses for audience matching * **LinkedIn Campaign Manager Access**: You must have access to LinkedIn Campaign Manager * **Customer Success Manager Contact**: This integration requires assisted setup - contact your CSM to begin ## Setup This integration is not yet available as a self-serve option in the Permutive Dashboard. To get started, reach out to your Customer Success Manager who will work with you to enable and configure the integration for your specific needs. Technical Services will provide technical guidance and support throughout the implementation process. Once implementation is complete, your CSM and technical team will verify that Permutive cohorts are correctly being synced to LinkedIn Matched Audiences. ## Data Types Permutive cohorts are exported as hashed email addresses and synced to LinkedIn as Matched Audiences, enabling audience targeting in LinkedIn Campaign Manager. SHA-256 hashed email addresses of users in the Permutive cohort. ## Troubleshooting Since this integration requires assisted setup, please contact your Customer Success Manager for any troubleshooting needs or questions about implementation. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Pinterest Source: https://docs.permutive.com/integrations/social-media/pinterest Activate Permutive cohorts to Pinterest for custom audience targeting in real-time
Pinterest

Pinterest

Activate Permutive cohorts to Pinterest for custom audience targeting in real-time. Send cohorts to multiple Pinterest accounts for promoted pin campaigns.

## Overview Using the Pinterest Pixel integration, you can send rich cohort information to multiple Pinterest accounts for targeting. These audiences are passed to Pinterest in real-time, enabling you to run custom audiences and target promoted pins at Permutive cohorts. This integration is a Destination: * **Destination:** Permutive sends cohort membership data to Pinterest via the Pinterest pixel, which can then be used for targeting in Pinterest advertising campaigns. Use cases include: * Target Pinterest users with promoted pins based on Permutive cohorts * Build custom audiences for Pinterest campaigns using real-time cohort data * Activate cohorts to multiple Pinterest accounts for different brands or campaigns * Retarget users who visited your website on Pinterest ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Pinterest Ads Account:** You must have an active Pinterest Ads account. * **Pinterest Tag ID:** You need to obtain your unique Tag ID from Pinterest. To find this: 1. Log in to your Pinterest Ads account 2. Click **Ads** in the top left-hand corner and select **Conversion tags** 3. Choose to **Create new Pinterest Tag** (if you do not have one created already) 4. You'll receive a Tag ID - save this for the integration setup ## Setup Navigate to the Permutive Dashboard and go to **Settings** > **Integrations**. Click **Add Integration** and select **Pinterest**. You'll need to configure the integration with your unique Tag ID obtained from Pinterest (see Prerequisites). Click **Add Pinterest** to complete the setup. The integration will be available for use in approximately 20 minutes. Once the integration is enabled, you can activate cohorts to your default Pinterest account: 1. Navigate to a cohort in the Permutive Dashboard 2. Toggle on the Pinterest activation for that cohort 3. The cohort will be sent to Pinterest in real-time 1. In your Pinterest Ads account, navigate to **Ads** > **Audiences** 2. You should see audiences corresponding to your activated Permutive cohorts 3. These audiences can now be used for targeting in your Pinterest ad campaigns If you want to send cohorts to multiple Pinterest accounts (for example, different brands or campaigns), you can configure this at the cohort activation level: 1. In the Permutive Dashboard, go to **Cohort Activation** 2. Click **+ Add Activation** 3. Select the **Pinterest** tile When creating the activation, you'll see a field to input a Tag ID. * To send to an alternative Pinterest account, enter that account's Tag ID here * If you leave this field blank, the default Tag ID configured during integration setup will be used Choose the cohort(s) you want to push to this Pinterest account and save the activation. This allows you to send different cohorts to different Pinterest accounts without needing to set up multiple integrations in the Permutive Dashboard. The Pinterest Pixel integration is configured entirely through the Permutive Dashboard. Once enabled, the Permutive Web SDK will automatically send cohort membership data to Pinterest via the Pinterest pixel. No additional web implementation is required beyond the standard Permutive SDK deployment. Pinterest cohort activation via Permutive is currently only supported on Web through the Pinterest Pixel integration. Pinterest cohort activation via Permutive is currently only supported on Web through the Pinterest Pixel integration. ## Data Types The Pinterest integration is a Destination integration that sends cohort membership data from Permutive to Pinterest. It does not collect events into Permutive. When a cohort is activated to Pinterest, Permutive sends cohort membership information to Pinterest via the Pinterest pixel, which Pinterest uses to build custom audiences for targeting in ad campaigns. ## Troubleshooting If the Pinterest integration does not become available after the expected 20-minute waiting period: * Verify that you entered the correct Tag ID during setup * Check that you saved the integration configuration in the Permutive Dashboard * Ensure your Pinterest Ads account is active and in good standing * Contact Permutive Support if the issue persists If activated cohorts are not showing up as audiences in Pinterest: * Confirm that you've toggled on the Pinterest activation for the cohort in Permutive * Allow up to 24 hours for audiences to populate in Pinterest after first activation * Verify that the Tag ID configured in Permutive matches the one in your Pinterest Ads account * Check that your Pinterest Ads account has the necessary permissions to create custom audiences * Ensure your cohort has a sufficient number of users (Pinterest may have minimum audience size requirements) If cohorts are being sent to the wrong Pinterest account when using multiple accounts: * When creating a cohort activation for an alternative Pinterest account, ensure you've entered the correct Tag ID in the activation configuration * Remember that leaving the Tag ID field blank will use the default Tag ID from the integration setup * Verify the Tag ID by checking your Pinterest Ads account under **Ads** > **Conversion tags** * If you need to change which account receives cohorts, update the activation configuration with the correct Tag ID If the audience size in Pinterest is significantly smaller than your cohort size in Permutive: * Pinterest can only match users who have Pinterest accounts and have been identified by the Pinterest pixel * Match rates vary based on your audience demographics and Pinterest usage patterns * Consider the cookie-based nature of the integration - users need to be identified by both Permutive and Pinterest pixels * Allow 24-48 hours for the audience to fully populate after first activation ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # TikTok Source: https://docs.permutive.com/integrations/social-media/tiktok Integrate with TikTok for cohort activation and audience targeting
TikTok

TikTok

TikTok is a leading social media platform for short-form video content. Permutive's integration enables publishers to activate cohorts for targeted advertising on TikTok Ads Manager.

## Overview TikTok is a global social media platform that allows users to create, share, and discover short-form video content. Our integration with TikTok enables publishers to activate Permutive cohorts for targeted advertising campaigns on TikTok Ads Manager. This integration is a Destination: * **Destination:** Permutive can activate your cohorts in TikTok for audience targeting and campaign optimization. Use cases include: * Activate Permutive cohorts in TikTok Ads Manager to build custom audiences for targeted advertising campaigns * Leverage hashed email matching to improve match rates and reduce reliance on third-party cookies * Create lookalike audiences in TikTok based on your Permutive cohorts * Export user lists from Permutive for manual upload to TikTok as custom audiences ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ------------------------------------------------- | | **Web** | | Requires TikTok Pixel installed on site | | **iOS** | | User export with IDFA supported via manual upload | | **Android** | | User export with GAID supported via manual upload | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **TikTok Pixel Installed**: You must have the TikTok Pixel installed on your website. The pixel should be available as a global object (`ttq`). To verify, open your browser console and run `ttq` - you should see the TikTok Analytics object. * **TikTok Ads Manager Account**: You must have an active TikTok Ads Manager account with permissions to create custom audiences. * **User Alias Collection**: You must be collecting at least one of the following user aliases via Permutive's identity framework for enhanced matching: * Hashed email address (SHA-256) * Hashed mobile number (SHA-256) * IDFA (Identifier for Advertisers) * GAID (Google Advertising ID) Phone and email matching in TikTok may require coordination with your TikTok Account Manager. Advanced Matching via email and phone number takes precedence over matching via IP address and user agent. ## Setup Contact your Customer Success Manager to enable the TikTok integration for your workspace. Once enabled, navigate to your cohort configuration page in the Permutive Dashboard. For each cohort you want to activate in TikTok, toggle on the TikTok activation. You can enable this when creating a new cohort or by editing an existing cohort. When a user enters a cohort that has TikTok activation enabled, Permutive will fire a pixel event via the installed TikTok Pixel. The event name will have the format `Permutive-{cohort_code}` where `{cohort_code}` is your cohort's unique identifier. In TikTok Ads Manager, navigate to Tools → Events to verify that Permutive events are being received. Then create a custom audience: 1. Go to your TikTok Ads Manager 2. Navigate to Assets → Audiences 3. Click "Create Audience" → "Custom Audience" 4. Select "Website Activity" 5. Choose your TikTok Pixel 6. Select the Permutive events corresponding to your cohorts (e.g., `Permutive-1234`) 7. Configure your audience retention period (default is 30 days) 8. Save your custom audience For detailed instructions, refer to the [TikTok Custom Audiences documentation](https://ads.tiktok.com/help/article/custom-audiences). In TikTok Ads Manager, navigate to Tools → Events. You should see Permutive events appearing in the event list. Check that the events correspond to your activated cohorts. In the Audience Library, verify that your custom audiences are populating with matched users. ### User Export Setup If you prefer to manually export user lists and upload them to TikTok (useful for mobile app audiences or file-based matching): In the Permutive Dashboard, navigate to Cohort Activations → Exports and click "Add Export". Configure your export: - **ID Type**: Select the identifier type for matching (email\_sha256, mob\_sha256, IDFA, or GAID) - **Date Range**: Specify the time period for users to include - **Segments**: Select the cohorts you want to export for TikTok targeting Once the export is generated, download the file containing your user identifiers. In TikTok Ads Manager: 1. Navigate to Assets → Audiences 2. Click "Create Audience" → "Customer File" 3. Upload your exported file 4. Map the columns to the appropriate identifier types (email, phone, IDFA, GAID) 5. Save your custom audience For detailed instructions, refer to the [TikTok Customer File Audience documentation](https://ads.tiktok.com/help/article?aid=9609). ### Web Implementation Details The TikTok integration for web leverages the TikTok Pixel that is already installed on your site. Permutive does not add the TikTok Pixel - you must have it installed independently. When a user enters a cohort with TikTok activation enabled: 1. Permutive fires a custom event via the TikTok Pixel with the event name `Permutive-{cohort_code}` 2. If the user has a hashed email (`email_sha256`) in Permutive's identity framework, the pixel event is enriched with this identifier using TikTok's `identify` method 3. TikTok receives the event and can match the user for audience building **How it works:** * Event names contain the cohort code because TikTok builds audiences based on event names, not event parameters * If multiple TikTok Pixels are installed on the page, Permutive will trigger events on each pixel * Enhanced matching with hashed email improves match rates and reduces reliance on cookies The TikTok Pixel must be present and initialized before Permutive attempts to fire events. Verify the pixel is loaded by checking for the global `ttq` object in your browser console. ### iOS Implementation For iOS app audiences, use the User Export approach described in the "Alternative: User Export for Manual Upload" section. Export user lists with IDFA (Identifier for Advertisers) from Permutive and upload them to TikTok Ads Manager as a Customer File audience. ### Android Implementation For Android app audiences, use the User Export approach described in the "Alternative: User Export for Manual Upload" section. Export user lists with GAID (Google Advertising ID) from Permutive and upload them to TikTok Ads Manager as a Customer File audience. ## Data Types With your TikTok integration setup, Permutive will send cohort activation events to TikTok via the TikTok Pixel: Custom events fired via the TikTok Pixel when a user enters an activated cohort. These events can be used to build custom audiences in TikTok Ads Manager. The event name follows the format `Permutive-{cohort_code}` where ` {cohort_code}` is the unique identifier for your Permutive cohort. Hashed email address (SHA-256) passed to TikTok's identify method for enhanced user matching. Only included if available in Permutive's identity framework. ## Troubleshooting Verify that the TikTok Pixel is installed on your website by opening your browser console and running `ttq`. You should see the TikTok Analytics object. If not, you'll need to install the TikTok Pixel before using this integration. Contact your TikTok Account Manager or refer to the [TikTok Pixel installation guide](https://ads.tiktok.com/help/article?aid=10000357). Check the following: * Verify that the TikTok integration is enabled for your workspace in Permutive * Confirm that cohorts are activated for TikTok in the Permutive Dashboard * Ensure users are entering the activated cohorts (check cohort membership in Permutive) * Allow up to 24 hours for events to appear in TikTok Ads Manager after initial setup * Navigate to Tools → Events in TikTok Ads Manager to verify event receipt To improve match rates: * Ensure you're collecting hashed email addresses (SHA-256) via Permutive's identity framework * Verify that hashed emails are being passed to TikTok (check browser console for identify calls) * Consider using the User Export approach with multiple identifier types (email, mobile, IDFA, GAID) * Allow sufficient time for audience population - TikTok requires a minimum of 1,000 matched users for most custom audiences If you have multiple TikTok Pixels installed on your website, Permutive will trigger events on each pixel. This is expected behavior. Ensure that you're selecting the correct pixel when creating custom audiences in TikTok Ads Manager. To verify how many pixels are installed, check the `ttq` object in your browser console or review your site's tag management configuration. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # X (Twitter) Source: https://docs.permutive.com/integrations/social-media/twitter Activate Permutive cohorts to X for custom audience targeting in real-time
X (Twitter)

X (Twitter)

Activate Permutive cohorts to X (formerly Twitter) for custom audience targeting in real-time. Send rich cohort information to X for promoted posts and ad campaigns.

## Overview Using the X pixel integration (labeled as "Twitter" in the Permutive dashboard), you can send rich cohort information to X (formerly Twitter) for targeting. These audiences are passed to X in real-time, enabling you to run custom audiences and target promoted posts or ads at Permutive cohorts. In the Permutive dashboard, this integration is still labeled as "Twitter" even though the platform is now called X. When following setup instructions, look for "Twitter" in your Permutive dashboard UI. This integration is a Destination: * **Destination:** Permutive sends cohort membership data to X via a pixel-based integration, which can then be used for targeting in X advertising campaigns. Use cases include: * Target promoted posts and ads at Permutive cohort audiences on X's timeline and ad network * Build custom audiences for X campaigns using real-time cohort data * Retarget users who visited your website on X * Create tailored audiences based on website visitor behavior for X advertising ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **X Ads Account:** You must have an active X (formerly Twitter) Ads account with a Business X account. * **Access to X Ads Manager:** You need access to the Ads section of your X account to create tailored audiences and manage campaigns. * **Audience Tag ID:** For each cohort activation, you'll need to obtain unique audience tag IDs from X. These IDs are generated when you create tailored audiences in X's Audience Manager (see Setup steps for details). ## Setup Navigate to the Permutive Dashboard and go to **Settings** > **Integrations**. Click **Add Integration** and select **Twitter** (note that the integration is still labeled as "Twitter" in the Permutive dashboard, even though the platform is now called X). Click **Add Integration** to add the Twitter Pixel. The integration will be available for use in approximately 20 minutes. For each Permutive cohort you want to activate to X, you'll need to create a corresponding tailored audience in X Ads: 1. Log in to your Business X account 2. Select your Account Icon (Profile and Settings) in the top right corner and click on the **Ads** section 3. Choose the correct Account, then navigate to **Tools** > **Audience Manager** 4. Click **Create New Audience** > **Make a tag to collect website visitors** 5. Create a new tailored audience with a relevant name * Suggested naming convention: `Permutive_[SegmentName]` (e.g., `Permutive_Dog_Lovers`) 6. Check **Use a single event website tag** 7. Click **Save** 8. When saved, you'll receive a Website tag. From this JavaScript tag, save the **audience tag ID** (this ID typically begins with the letter 'o', such as `o1234` in the `trackPid` call or `txn_id` value) 9. Keep this audience tag ID - you'll need it for creating activations in Permutive You need to create a separate tailored audience in X for each Permutive cohort you want to activate. After creating tailored audiences in X, create corresponding activations in Permutive: 1. In the Permutive Dashboard, go to the **Activations** section 2. Click **Add Activation** 3. Under **Action**, select **Twitter** 4. Give the activation a descriptive name 5. Add tags for easier discovery (optional) 6. Add a description (optional) 7. Enter the **audience tag ID** (the ID beginning with 'o' that you obtained from X's Audience Manager) 8. Select the Permutive cohort you want to target 9. Set the trigger to **On Entry** 10. Click **Create** Currently, only one Twitter activation per cohort is supported. You cannot create multiple activations to different X audiences for the same Permutive cohort. Once activations are created and users start entering your cohorts, you can use these audiences in X Ads: 1. The Website events tag will appear in the **Conversion tracking** list in the X Ads dashboard 2. As soon as a user triggers the activation (by entering the cohort), the audience status changes from "unverified" to "verified" 3. Check audience size by navigating to **Tools** > **Audience Manager** in X Ads 4. When creating a campaign in X Ads, find your custom audiences under **Tailored Audiences (web)** in the **Targeting** section 5. Select the relevant Permutive-powered audiences to target your promoted posts or ads It may take a few hours for audiences to populate after the first users are added, and audience sizes will update as more users enter the cohort. The X pixel integration is configured entirely through the Permutive Dashboard and X Ads Manager. There is no additional web implementation required beyond the standard Permutive SDK integration. Once the integration is enabled in Permutive and activations are created, the Permutive SDK automatically fires pixel calls to X when users enter activated cohorts, adding them to the corresponding tailored audiences in real-time. The pixel integration works by making HTTP requests to X's tracking endpoints with the audience tag ID and user information when cohort entry triggers occur. X targeting via Permutive cohorts is currently only supported on Web through the pixel integration. X targeting via Permutive cohorts is currently only supported on Web through the pixel integration. ## Troubleshooting If your tailored audience remains "unverified" after creating the activation in Permutive: * Ensure that the activation has been enabled for at least 20 minutes (the time it takes for Permutive integrations to become active) * Verify that users are actually entering the cohort by checking the cohort's **Live Audience Size** in the Permutive Dashboard. Note that Live Audience Size starts at zero when a cohort is first deployed and grows over time as users are evaluated — if the cohort was recently created, allow time for users to be evaluated. See [Understanding Audience Size](/products/signals/cohorts/custom#whats-the-difference-between-predicted-audience-size-and-live-audience-size) for details. * Confirm that the audience tag ID entered in Permutive exactly matches the ID from X's Website tag (typically starts with 'o') * Check that the Permutive SDK is properly installed and firing on your website * Wait for at least one user to enter the cohort and trigger the activation - the audience will only become verified after the first successful pixel call If issues persist after verifying the above, contact Permutive support. If the audience size in X Ads is significantly smaller than the cohort size in Permutive: * Remember that only users who enter the cohort **after** the activation is created will be added to the X audience (historical cohort members are not backfilled) * Ensure the activation trigger is set to "On Entry" to capture users as they join the cohort * Check that the Permutive SDK is deployed across all pages where cohort entry events might occur * Be aware that some users may not have X accounts or may have ad tracking disabled, which can reduce the match rate * Allow 24-48 hours for audience sizes to stabilize as X processes the incoming pixel data Audience sizes typically range from 60-80% of the Permutive cohort size due to match rates and user privacy settings. The X pixel integration currently only supports one activation per Permutive cohort. If you need to send the same cohort to multiple X audiences: * Create duplicate cohorts in Permutive with the same logic * Create separate activations for each duplicate cohort, targeting different X audience tag IDs * Alternatively, use the same X audience tag ID across multiple campaigns in X Ads Manager This is a known limitation of the current integration implementation. Contact Permutive support if this limitation significantly impacts your use case. If you cannot find your Permutive-powered audience when setting up targeting in X Ads: * Verify that the tailored audience has been created in X's Audience Manager * Ensure the audience status shows as "verified" (see "Audience not showing as verified" above) * Check that the audience has a minimum size (X typically requires at least 500 users before an audience can be targeted) * Confirm you're looking in the correct section: **Tailored Audiences (web)** under Targeting in the campaign setup * Verify that you're logged into the correct X Ads account that owns the tailored audience If the audience still doesn't appear, it may not have reached the minimum size threshold for targeting. The Permutive dashboard still refers to this integration as "Twitter" even though the platform has been rebranded to X. This is expected behavior: * When adding the integration, look for "Twitter" in the integration list * When creating activations, select "Twitter" as the action * This naming convention exists to maintain compatibility with existing customer configurations The integration functions identically regardless of the label - it sends data to X (formerly Twitter) as expected. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # YouTube Source: https://docs.permutive.com/integrations/social-media/youtube Target ads on YouTube.com and YouTube channels using Google Audience within Google Ad Manager
YouTube

YouTube

Target ads on YouTube.com and your YouTube channel using Google Audience within Google Ad Manager.

Looking to target ads in an embedded YouTube player on your website or app? See the [Embedded YouTube Player Integration](/integrations/video/youtube). ## Overview YouTube is the world's largest video platform, enabling publishers to distribute and monetize video content through YouTube.com and YouTube channels. This integration enables targeting of syndicated YouTube videos where your content is hosted on YouTube.com (outside your own domains) using Google Audience within Google Ad Manager. This integration is a Destination: * **Destination:** Permutive cohorts are synced to Google Audience segments in GAM, which can then be targeted on YouTube inventory. Use cases include: * Target ads on your YouTube channel videos with Permutive cohorts * Retarget users who visited your website when they watch videos on YouTube.com * Extend your audience reach to YouTube inventory beyond your owned properties * Monetize your YouTube channel with advanced audience targeting **How DV360 Fits Into This Workflow** Display & Video 360 (DV360) is Google's demand-side platform (DSP) that advertisers use to programmatically buy video inventory, including YouTube. When you create Google Audience segments in GAM using Permutive cohorts: 1. **You create segments in GAM** using Permutive key-values (as described in this guide) 2. **Segments can be shared** with linked DV360 accounts via your Ad Exchange connection 3. **Advertisers target these segments** in DV360 when buying YouTube inventory The "Addressable Users" metric in DV360 shows how many users in your audience segment can actually be reached on YouTube—this is where PPID becomes critical for achieving meaningful scale. This integration focuses on the publisher side (creating segments in GAM). The advertiser-side workflow in DV360 is managed by your advertising partners or internal ad ops team. ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | --------------------------------------------------------------- | | **Web** | | SDK required to build cohorts; targeting applies to YouTube.com | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Google Ad Manager Integration**: The [GAM integration](/integrations/advertising/ad-servers/google-ad-manager) must be enabled in Permutive. * **Google Audience Access**: You must have access to Google Audience within your Google Ad Manager instance. * **Cohorts with GAM Activation**: Cohorts must have GAM Activation Sync enabled in the Permutive dashboard. * **PPID Implementation (Strongly Recommended)**: Publisher Provided ID (PPID) should be configured in GAM for meaningful audience scale on YouTube.com. Without PPID, match rates will be very limited. See the [Understanding PPID](#understanding-ppid) section below. ## Setup Before setting up YouTube targeting, you must enable the Google Ad Manager integration in the Permutive dashboard. See the [Google Ad Manager integration documentation](/integrations/advertising/ad-servers/google-ad-manager) for detailed setup instructions. Navigate to the cohorts you want to target on YouTube in the Permutive dashboard. Enable the Google Ad Manager Activation Sync for each cohort. This will create targeting key-values in GAM that can be used in Google Audience rules. In your Google Ad Manager dashboard, navigate to **Audience** > **Google Audience**. You will create a first-party audience segment for each Permutive cohort you want to target on YouTube. Follow [Google's documentation](https://support.google.com/admanager/answer/2423498) to create each segment: 1. Click **New Audience** 2. **Name** your segment (recommended: use the same name as in Permutive for consistency) 3. Set the segment **Status** to **Active** 4. In the **Rules** section, click **Key-values and Audience** 5. Select the **permutive** key-value 6. Select the specific cohort ID value that corresponds to your Permutive cohort 7. Set **Page Views** to **1** 8. Set **Membership Expiration** to **90 days** (standard setting) 9. Click **Save** Repeat this process for each Permutive cohort you want to target on YouTube. When creating line items in Google Ad Manager for YouTube inventory: 1. In the **Targeting** section of your line item, add **Audience Segments** 2. Select the Google Audience segments you created that map to your Permutive cohorts 3. Configure your YouTube inventory targeting as needed 4. Complete your line item setup and activate The segments will be matched to signed-in Google users on YouTube based on their activity on your site. This approach relies on third-party cookie matching between your site and YouTube. Users must be logged into Google and have visited your site for matching to occur. You will not achieve 100% scale compared to on-site targeting. YouTube.com targeting is configured entirely within Google Ad Manager using Google Audience segments. There is no additional web implementation required beyond: 1. Having the Permutive SDK deployed on your website to build cohorts 2. Enabling GAM Activation Sync for cohorts in the Permutive dashboard All targeting configuration happens in the Google Ad Manager interface as described in the Primary Setup Steps tab. ## Understanding PPID Publisher Provided ID (PPID) is critical for achieving meaningful audience scale when targeting on YouTube.com. This section explains why PPID matters and how it affects your YouTube targeting. ### Why PPID Matters for YouTube Targeting Google Audience segments in GAM can be built using key-values from Permutive cohorts. However, **the effectiveness of targeting these audiences on YouTube.com depends heavily on whether you have PPID configured**. Without PPID, your Google Audience segments are populated using browser-based identifiers (such as Google's `__gpi` cookie). These identifiers have significant limitations: * **Website-only scope**: Browser cookies only identify users on your website * **No cross-site linking**: When users visit YouTube.com, they don't carry your website's cookies with them * **Poor match rates**: Google cannot reliably link your website visitors to their YouTube sessions * **Limited scale**: You may see very low "Addressable Users" counts in DV360 for your audience lists In essence, without PPID, Google Audience segments act as "folders" filled with identifiers that only work on your own properties. With PPID configured, your Google Audience segments are populated with stable, persistent identifiers that Google can link to users' Google Accounts: * **Deterministic matching**: PPIDs provide a stable identifier that Google can match to signed-in users * **Cross-platform recognition**: When users visit YouTube.com while signed into Google, they can be recognized as members of your audience * **Higher match rates**: Significantly improved "Addressable Users" counts in DV360 * **Meaningful scale**: Your first-party audiences become actionable on YouTube inventory PPID can be based on: * A first-party cookie for anonymous traffic * A login ID for authenticated users (provides the best cross-device matching) Without PPID, you may technically be able to create Google Audience segments and target them on YouTube, but the actual reach and match rate will be very limited. For meaningful YouTube.com targeting, PPID implementation is strongly recommended. ### Setting Up PPID PPID configuration is done within Google Ad Manager. For detailed instructions on implementing PPID, refer to [Google's PPID documentation](https://support.google.com/admanager/answer/2880055). Key considerations: * PPID must be passed consistently in ad requests from your website * The same PPID should be used across all platforms where possible for cross-device matching * For authenticated users, consider using a hashed login ID as your PPID for the best results ## Data Types Permutive cohorts are mapped to Google Audience segments in GAM, which can then be targeted on YouTube.com inventory. Permutive cohorts are mapped to Google Audience first-party segments within GAM for targeting on YouTube inventory. The name of the Google Audience segment. Recommended to match the Permutive cohort name for consistency. The targeting rule that defines membership in the Google Audience segment. Uses the `permutive` key with a specific cohort ID value. Example: `permutive = abc123def456` The number of days a user remains in the audience segment. Standard is 90 days. The minimum number of page views required for segment membership. Typically set to 1. The status of the audience segment. Must be set to "Active" for targeting. When you enable GAM Activation Sync for a cohort in Permutive, the cohort ID is automatically created as a targeting value under the `permutive` key in Google Ad Manager. The targeting key used in GAM for all Permutive cohorts. The unique identifier for each Permutive cohort, created as a value under the `permutive` key when GAM Activation Sync is enabled. Example: `abc123def456` ## Troubleshooting If your Google Audience segments created in GAM are not building membership: * **Verify key-value configuration**: Ensure the segment rule is correctly configured to target the `permutive` key with the specific cohort ID value * **Check segment status**: Confirm the segment status is set to "Active" in Google Audience * **Allow time for population**: Audience segments may take 24-48 hours to begin populating after creation * **Verify Permutive cohort is active**: Ensure the cohort is active in Permutive and has GAM Activation Sync enabled * **Check key-value exists in GAM**: Navigate to **Inventory > Key-Values** in GAM and confirm the specific cohort ID value exists under the `permutive` key * **Verify site traffic**: Google Audience requires sufficient site traffic for meaningful audience building. Check that your site has adequate daily unique users. If you're seeing lower-than-expected reach for YouTube targeting: * **PPID not configured**: This is the most common cause of limited scale. Without PPID, Google cannot reliably match your website visitors to their YouTube sessions. See the [Understanding PPID](#understanding-ppid) section above * **Audience size minimums**: Google Audience segments may have minimum size requirements before they're eligible for targeting on YouTube * **Membership expiration**: Check your segment's membership duration setting. 90 days is recommended for sufficient scale * **Traffic requirements**: Ensure your site has sufficient daily traffic for meaningful audience building * **User sign-in status**: Users must be signed into Google for matching to occur—anonymous YouTube viewers cannot be targeted * **Consider alternative approaches**: For better scale, consider using Google Customer Match with CRM data or leveraging Google's Optimized Targeting features with your first-party segments as a seed If you cannot find the `permutive` key or specific cohort ID values in Google Ad Manager: * **Verify GAM integration is enabled**: Check that the Google Ad Manager integration is properly configured in the Permutive dashboard * **Check cohort activation**: Ensure GAM Activation Sync is enabled for the specific cohorts in Permutive * **Verify GAM permissions**: Confirm that [dfp@permutive.com](mailto:dfp@permutive.com) has the required permissions in your GAM account (View and Edit key-values) * **Allow time for sync**: After enabling GAM Activation Sync, allow a few minutes for the key-value to be created in GAM * **Check correct GAM network**: Ensure you're viewing the correct GAM network that's configured in Permutive The YouTube.com integration requires Google Ad Manager to be set up first: * **Prerequisite**: Complete the [Google Ad Manager integration setup](/integrations/advertising/ad-servers/google-ad-manager) before configuring YouTube targeting * **Verify GAM connection**: In the Permutive dashboard, check that GAM is listed under Integrations and shows as connected * **Check permissions**: Ensure [dfp@permutive.com](mailto:dfp@permutive.com) has the required permissions in your GAM account * **Verify network code**: Confirm the correct GAM network code is configured in Permutive If you cannot target YouTube inventory in your GAM line items: * **Check ad exchange access**: YouTube inventory is typically available through Google Ad Exchange (AdX). Verify your GAM account has AdX access * **Verify inventory targeting**: Ensure your line item targeting includes YouTube placements or uses the appropriate inventory targeting settings * **Check geographic targeting**: Some YouTube inventory may have geographic restrictions. Verify your targeting settings * **Contact Google support**: If YouTube inventory is not appearing, you may need to work with your Google account manager to enable access ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Apester Source: https://docs.permutive.com/integrations/survey/apester Integrate with Apester for survey data collection
Apester

Apester

Apester enables publishers to collect first-party survey data through multiple-choice surveys on their website.

## Overview Apester allows you to run multiple-choice surveys on your site. This declared first-party data can be useful in building cohorts, whether for demographics or purchase intent. Unlike other survey platform integrations where you enable the integration from within the survey platform, Apester requires you to deploy custom JavaScript code on your website to enable the integration. You cannot initiate this integration from within the Apester platform. Use cases include: * Build cohorts based on survey responses (e.g., demographic segments, purchase intent) * Track survey question-level responses for advanced segmentation * Create audiences based on specific answer patterns ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Permutive SDK Deployment**: The Permutive JavaScript SDK must be deployed on your website where Apester surveys will run. * **Custom ApesterSurveyResponse Event**: Contact your Permutive Customer Success Manager (CSM) to request the setup of the `ApesterSurveyResponse` custom event on your workspace. This event must be preconfigured before you can use the integration. * **Access to Both Platforms**: You need access to both your website codebase (to deploy the JavaScript code) and the Apester platform (to create and manage surveys). * **Developer Resources**: You will need developer support to add the integration code to your website. * **Verify with your CSM**: Confirm that this collector is part of your Permutive plan. ## Setup Contact your Permutive Customer Success Manager (CSM) to request the setup of the `ApesterSurveyResponse` custom event in your workspace. This step must be completed before proceeding with the code deployment. Add the following JavaScript code to your website where Apester surveys are displayed. This code listens for survey responses and sends them to Permutive as `ApesterSurveyResponse` events. ```javascript theme={"dark"} window.addEventListener('message', (event) => { if (event && event.data && event.data.type === 'picked_answer') { var permutiveEventContent = {}; [ 'interactionId', 'interactionIndex', 'interactionTitle', 'answerId', 'answerText', 'slideId', 'slideTitle', 'externalId', ].forEach(function(property) { if (!event.data.data.hasOwnProperty(property) || !event.data.data[property]) { return; } permutiveEventContent[property] = event.data.data[property] + ''; }); window.permutive.track('ApesterSurveyResponse', permutiveEventContent); } }); ``` This code is provided "as is", without warranty of any kind, express or implied. In no event shall Permutive be liable for any claim, damages, updates, or other liability. Ensure this code is placed on all pages where Apester surveys will be displayed and that it loads after the Permutive SDK. After deploying the integration code, run a test survey and submit responses. In your Permutive dashboard, navigate to the Events page and verify that `ApesterSurveyResponse` events are being collected. You should see recent events appearing in the Events list with the survey response data. Once event collection is verified, you can build cohorts based on survey responses. In the Permutive cohort builder, the `ApesterSurveyResponse` event will appear in the event dropdown list. A common use case is to build a cohort of users who have answered a question in a certain way. For example, you can create cohorts for: * Users whose gender is "female" * Users interested in specific products or categories * Users who completed a particular survey * Demographic segments based on age, location, or other attributes The integration code provided in the Primary Setup Steps is specifically for Web environments. No additional web-specific configuration is required beyond deploying the code snippet and ensuring the Permutive SDK is present on the page. The code uses the browser's `window.addEventListener` API to listen for Apester survey response messages and forwards them to Permutive via the `window.permutive.track()` method. ## Data Types With your Apester integration setup, you'll see the following event type collected in Permutive: This event is triggered when a user responds to an Apester survey question. The event captures details about the survey interaction, question, and answer provided. The Apester interaction ID. Example: `5cb48901b712a358aeb0180b` The interaction index. Title is taken from the first text element on the first slide. Example: `My great story` The answer ID. Example: `19174477-5b29-4044-b5bf-b683024aa420` Text of the answer provided by the user. Example: `definitely gonna buy the new iPhone` The question ID. Example: `5cb48901b712a35acbb0180a` Text of the question. Example: `What do you think of the new iPhone?` The external ID is taken from the external-id attribute on the embedded element. Example: `9864` ApesterSurveyResponse events will also contain default Permutive event properties such as page URL (`client.url`) and page title (`client.title`). ## Troubleshooting Verify the following: * The custom `ApesterSurveyResponse` event has been configured in your Permutive workspace. Contact your CSM if this step was missed. * The integration code has been deployed to all pages where Apester surveys are displayed. * The Permutive SDK is deployed and loading correctly on those pages. Use the Permutive Chrome Extension to verify SDK presence. * The integration code is loading after the Permutive SDK (check that `window.permutive` is available). * Check your browser's developer console for any JavaScript errors that might prevent the code from executing. The `ApesterSurveyResponse` event is a custom event that must be preconfigured by Permutive before the integration will work. If you deploy the integration code without this custom event being set up first, survey data will not be collected. Contact your Customer Success Manager to request the custom event setup. If the integration code is deployed but not capturing responses: * Verify that Apester surveys are actually loading on the page and users are submitting responses. * Check that the message event data structure matches what the code expects (type: 'picked\_answer'). * Use your browser's developer console to monitor message events and verify that Apester is sending the expected data structure. * Ensure the `window.permutive.track()` method is available when the code executes. The integration code only captures fields that are present in the Apester message data. If certain fields like `interactionIndex` or `externalId` are not populated by Apester, they will not be included in the Permutive event. This is expected behavior. The code checks if each field exists and has a value before including it in the event data. Not all Apester survey configurations will populate all available fields. For questions about this integration, contact [Technical Services](mailto:technical-services@permutive.com). ## Changelog For the latest updates and changes to this integration, visit [changelog.permutive.com](https://changelog.permutive.com). # Brand Metrics Source: https://docs.permutive.com/integrations/survey/brandmetrics Integrate with Brand Metrics for brand lift measurement data collection
Brand Metrics

Brand Metrics

Brand Metrics enables publishers to measure brand lift delivered by digital ad campaigns through automated survey measurement at scale.

## Overview Brand Metrics is a SaaS platform that enables the automated measurement of brand lift delivered by digital ad campaigns at true scale. The approach firstly tracks exposure frequency and time-in-view of the creative and then retargets a single question survey to a sub-set of those exposed. Based on their answer to the question and their level of exposure to the message, Brand Metrics' regression-based algorithm calculates brand lift and delivers the brand lift results for four key metrics: awareness, consideration, preference and action intent, benchmarked against thousands of other campaigns measured the same way. Unlike other survey platform integrations where you enable the integration from within the survey platform, Brand Metrics requires you to deploy custom JavaScript code on your website to enable the integration. You cannot initiate this integration from within the Brand Metrics platform. Use cases include: * Build cohorts based on survey responses for brand lift analysis * Track survey response patterns for campaign effectiveness measurement * Create audiences based on specific brand awareness or consideration answers * Measure advertiser impact through brand uplift metrics ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Permutive SDK Deployment**: The Permutive JavaScript SDK must be deployed on your website where Brand Metrics surveys will run. * **Custom BrandMetricsSurveyResponse Event**: Contact your Permutive Customer Success Manager (CSM) to request the setup of the `BrandMetricsSurveyResponse` custom event on your workspace. This event must be preconfigured before you can use the integration. * **Access to Both Platforms**: You need access to both your website codebase (to deploy the JavaScript code) and the Brand Metrics platform (to create and manage surveys). * **Developer Resources**: You will need developer support to add the integration code to your website. * **Verify with your CSM**: Confirm that this collector is part of your Permutive plan. ## Setup Contact your Permutive Customer Success Manager (CSM) to request the setup of the `BrandMetricsSurveyResponse` custom event in your workspace. This step must be completed before proceeding with the code deployment. Add the following JavaScript code to your website where Brand Metrics surveys are displayed. This code listens for Brand Metrics survey responses and sends them to Permutive as `BrandMetricsSurveyResponse` events. ```javascript theme={"dark"} window._brandmetrics = window._brandmetrics || []; window._brandmetrics.push({ cmd: '_addeventlistener', val: { event: 'surveyanswered', handler: function(ev) { try { if (window.permutive && window.permutive.track) { ev.answers.split(';').forEach(function(questionAnswers) { questionAnswers = questionAnswers.split('-'); var question = null; var answers = null; if (questionAnswers[0]) { question = questionAnswers[0]; } if (questionAnswers[1]) { answers = questionAnswers[1].split(','); } window.permutive.track('BrandMetricsSurveyResponse', { mid: ev.mid, question: question, answers: answers, }); }); } } catch (e) { console.error('Error sending Brand Metrics data to Permutive', e); } }, }, }); ``` This code is provided "as is", without warranty of any kind, express or implied. In no event shall Permutive be liable for any claim, damages, updates, or other liability. Ensure this code is placed on all pages where Brand Metrics surveys will be displayed and that it loads after the Permutive SDK. After deploying the integration code, run a test survey and submit responses. In your Permutive dashboard, navigate to the Events page and verify that `BrandMetricsSurveyResponse` events are being collected. You should see recent events appearing in the Events list with the survey response data. Once event collection is verified, you can build cohorts based on survey responses. In the Permutive cohort builder, the `BrandMetricsSurveyResponse` event will appear in the event dropdown list. A common use case is to build a cohort of users who have answered a question in a certain way. For example, you can create cohorts for: * Users who demonstrated brand awareness after exposure to campaigns * Users showing purchase consideration or intent * Users who answered specific brand preference questions * Segments based on action intent responses The integration code provided in the Primary Setup Steps is specifically for Web environments. No additional web-specific configuration is required beyond deploying the code snippet and ensuring the Permutive SDK is present on the page. The code uses Brand Metrics' event listener API to listen for survey response events and forwards them to Permutive via the `window.permutive.track()` method. ## Data Types With your Brand Metrics integration setup, you'll see the following event type collected in Permutive: This event is triggered when a user responds to a Brand Metrics survey question. The event captures details about the measurement, question, and answers provided. The measurement ID for the Brand Metrics survey. Question identifier. Example: `q1` (represents question + question id) Array of answer values, typically numeric values from 1-5. Example: `['3', '4', '5']` BrandMetricsSurveyResponse events will also contain default Permutive event properties such as page URL (`client.url`) and page title (`client.title`). ## Troubleshooting Verify the following: * The custom `BrandMetricsSurveyResponse` event has been configured in your Permutive workspace. Contact your CSM if this step was missed. * The integration code has been deployed to all pages where Brand Metrics surveys are displayed. * The Permutive SDK is deployed and loading correctly on those pages. Use the Permutive Chrome Extension to verify SDK presence. * The integration code is loading after the Permutive SDK (check that `window.permutive` is available). * Check your browser's developer console for any JavaScript errors that might prevent the code from executing. The `BrandMetricsSurveyResponse` event is a custom event that must be preconfigured by Permutive before the integration will work. If you deploy the integration code without this custom event being set up first, survey data will not be collected. Contact your Customer Success Manager to request the custom event setup. If the integration code is deployed but not capturing responses: * Verify that Brand Metrics surveys are actually loading on the page and users are submitting responses. * Check that the Brand Metrics `surveyanswered` event is firing correctly. * Use your browser's developer console to monitor for the `surveyanswered` event and verify that Brand Metrics is sending the expected data structure. * Ensure the `window.permutive.track()` method is available when the code executes. The integration code parses the Brand Metrics answer format (`ev.answers`) which uses semicolons and dashes as delimiters. The format is typically: `question-answer1,answer2,answer3`. If you see parsing issues: * Verify that Brand Metrics is sending answers in the expected format. * Check the browser console for any parsing errors logged by the integration code. * Contact Brand Metrics support to confirm the answer format matches your survey configuration. For questions about this integration, contact [Technical Services](mailto:technical-services@permutive.com). ## Changelog For the latest updates and changes to this integration, visit [changelog.permutive.com](https://changelog.permutive.com). # Catalog Source: https://docs.permutive.com/integrations/survey/catalog Browse our comprehensive catalog of survey platform integrations. # Survey Integrations
Collective Audience

Collective Audience

Collective Audience (formerly BeOp) enables publishers to collect first-party survey data and optionally retarget Permutive cohorts within their survey platform.

Apester

Apester

Apester enables publishers to collect first-party survey data through multiple-choice surveys on their website.

Brand Metrics

Brand Metrics

Brand Metrics enables publishers to measure brand lift delivered by digital ad campaigns through automated survey measurement at scale.

Opinary

Opinary

Opinary helps publishers embed interactive opinion tools that boost engagement and enable contextual ad targeting.

Qualifio

Qualifio

Qualifio allows publishers to create interactive experiences (polls, contests) to boost engagement and collect first-party data.

# Collective Audience Source: https://docs.permutive.com/integrations/survey/collective-audience Integrate with Collective Audience (formerly BeOp) for survey data collection and cohort retargeting
Collective Audience

Collective Audience

Collective Audience (formerly BeOp) enables publishers to collect first-party survey data and optionally retarget Permutive cohorts within their survey platform.

## Overview Collective Audience (formerly BeOp) allows you to run multiple-choice surveys on your site. This declared first-party data is useful for building cohorts for demographics or purchase intent targeting. This is a Source integration where Collective Audience has integrated with Permutive, meaning you enable the integration from within the Collective Audience platform after Permutive configures a custom event in your workspace. Use cases include: * Build cohorts based on survey responses (e.g., demographic segments, purchase intent) * Track survey completion and question-level responses for advanced segmentation * Optionally activate Permutive cohorts to Collective Audience for targeted surveys ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Permutive Project ID**: Available in your [Permutive dashboard project settings](https://dash.permutive.com/settings/keys/). * **Custom SurveyResponse Event**: Contact your Permutive Customer Success Manager (CSM) to configure the SurveyResponse custom event on your workspace. This event must be preconfigured before using this integration. * **Access to Collective Audience Platform**: You must have access to the Collective Audience dashboard where you can configure integrations. * **Verify with your CSM**: Confirm that this collector is part of your Permutive plan. ## Setup Contact your Permutive Customer Success Manager (CSM) to configure the SurveyResponse custom event on your workspace. The SurveyResponse event is collected for every survey answer. If a survey has multiple questions, an event is emitted for each question. This is a prerequisite that must be completed before proceeding with the Collective Audience configuration. In the Collective Audience platform: 1. Navigate to the DMP integration settings. 2. Add your Permutive Project ID (found in your Permutive dashboard [project settings](https://dash.permutive.com/settings/keys/)). 3. Save the configuration. For detailed instructions, refer to [Collective Audience's DMP Configuration documentation](https://docs.collectiveaudience.co/dmp-configuration/#permutive). Once configured, survey responses from Collective Audience will automatically be sent to Permutive as SurveyResponse events. To verify: 1. Navigate to the Permutive dashboard. 2. Go to **Events** and look for the SurveyResponse event type. 3. Check that survey response data is being collected. Once event collection is verified, you can build cohorts based on survey responses. In the Permutive cohort builder, the `SurveyResponse` event will appear in the event dropdown list. For example, you can create cohorts for: * Users who completed a specific survey * Users who answered a particular question with a specific response (e.g., users who answered "Female" to a gender question) * Demographic segments (e.g., users who answered "25-34" to an age question) * Purchase intent (e.g., users interested in specific product categories) To enable Permutive cohort retargeting within Collective Audience (optional feature): **Prerequisites for Cohort Retargeting:** * Ensure 'UpdateAll' access to the Cohort API at the Organizational, Workspace, or API-Key level. * Contact [Technical Services](mailto:technical-services@permutive.com) to verify API access. **Steps:** 1. Generate an API key from your Permutive platform dashboard. 2. In Collective Audience settings, enable "Activate Permutive retargeting". 3. Select "Permutive API" mode. 4. Enter your generated API key. 5. Save the configuration. For detailed instructions, see [Collective Audience's Permutive Retargeting documentation](https://docs.collectiveaudience.co/dmp-configuration/#permutive-retargeting). No additional Web SDK configuration is required. The Permutive SDK must be deployed on your site, and Collective Audience will automatically send survey response data to Permutive once configured in their platform. ## Data Types With your Collective Audience integration setup, you'll see the following additional event types collected in Permutive: The SurveyResponse event is emitted for each answer to a survey question. If a survey contains multiple questions, a SurveyResponse event is sent for each question answered. Collective Audience ID of the answer. Text of the answer provided by the user. Whether the answer is valid. Index of the answer within the question options. Collective Audience ID of the question. Text of the question. Collective Audience ID of the survey. Text or title of the survey. Whether the survey is completed (true when the last question is answered). Audience question match ID (Collective Audience technical field). Audience question match tag (Collective Audience technical field). Audience answer match ID (Collective Audience technical field). Audience answer match tag (Collective Audience technical field). Audience survey match ID (Collective Audience technical field). Audience survey match tag (Collective Audience technical field). List of audience survey match details (Collective Audience technical field). List of audience answer match details (Collective Audience technical field). List of audience question match details (Collective Audience technical field). SurveyResponse events also contain default Permutive event properties such as page URL (`client.url`) and page title (`client.title`). ## Troubleshooting **Solution:** * Verify that the SurveyResponse custom event has been created on your Permutive workspace. Contact your CSM if this step was missed. * Confirm that you have entered the correct Permutive Project ID in the Collective Audience DMP integration settings. * Check that the Permutive SDK is deployed on the pages where Collective Audience surveys are running. * Use your browser's developer console to check for any JavaScript errors that might prevent event collection. **Solution:** * Verify that you have 'UpdateAll' access to the Cohort API. Contact [Technical Services](mailto:technical-services@permutive.com) to confirm API permissions. * Check that you have generated a valid API key from the Permutive dashboard and entered it correctly in Collective Audience. * Ensure "Activate Permutive retargeting" is enabled in Collective Audience settings and "Permutive API" mode is selected. * Confirm that the cohorts you want to retarget are active and have users. **Solution:** * Not all survey types may populate all fields in the SurveyResponse event. The `audience_question_match`, `audience_answer_match`, and `audience_survey_match` fields are Collective Audience technical fields that may not be populated for all survey configurations. * Verify the survey configuration in Collective Audience to ensure the expected fields are being sent. * Check the [Collective Audience documentation](https://docs.collectiveaudience.co/) for details on which fields are supported for your survey type. ## Changelog For the latest updates and changes to this integration, visit [changelog.permutive.com](https://changelog.permutive.com). # Opinary Source: https://docs.permutive.com/integrations/survey/opinary Integrate with Opinary for survey functionality
Opinary

Opinary

Opinary helps publishers embed interactive opinion tools that boost engagement and enable contextual ad targeting.

## Overview Opinary is a software platform that allows publishers to engage their audience by using interactive widgets on their website. The widgets include various types of polls that are designed to gather user opinions and generate insights. Unlike other survey integrations where the survey platform handles the integration setup, Opinary requires you to deploy custom JavaScript on your website to forward poll responses to Permutive. This declared first-party data is useful for building cohorts for demographics or purchase intent targeting. Use cases include: * Build cohorts based on poll responses (e.g., demographic segments, opinion-based targeting) * Track poll completion and question-level responses for advanced segmentation * Enrich audience profiles with opinion data collected through Opinary widgets ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Permutive SDK Deployment**: The Permutive JavaScript SDK must be deployed on your website where Opinary widgets will run. * **Custom OpinarySurveyResponse Event**: Contact your Permutive Customer Success Manager (CSM) to set up the OpinarySurveyResponse custom event on your workspace. This event must be preconfigured before using this integration. * **Access to Opinary Platform**: You must have access to the Opinary platform and polls configured on your website. * **JavaScript Implementation Access**: You need the ability to add custom JavaScript code to your website pages where Opinary polls are displayed. * **Verify with your CSM**: Confirm that this collector is part of your Permutive plan. ## Setup Contact your Permutive Customer Success Manager (CSM) to configure the OpinarySurveyResponse custom event on your workspace. This step must be completed before proceeding with the JavaScript implementation. Add the following JavaScript code to your website where Opinary polls are displayed. This code listens for Opinary vote events and forwards them to Permutive. This code is provided "as is", without warranty of any kind, express or implied. In no event shall Permutive be liable for any claim, damages, updates, or other liability. ```javascript theme={"dark"} (function () { function listenToOpinary() { Opinary.on('opinary.vote', function (vote, poll) { if (permutive && poll.dmpIntegration) { permutive.track('OpinarySurveyResponse', { survey: { id: poll.pollId, type: poll.type, }, question: { text: poll.header }, answer: { text: vote.label, posX: vote.x || 0.0, posY: vote.y || 0.0, optionIdentifier: vote.optionID || "", optionPosition: vote.position || 0, rawValue: vote.value || 0.0, unit: vote.unit || "" } }); } }); } if (Opinary && Opinary.on) { listenToOpinary() } else { window.addEventListener('OpinaryReady', function () { listenToOpinary() }); } })(); ``` This code should be placed on pages where Opinary polls are displayed, ensuring it loads after both the Permutive SDK and Opinary scripts. Once configured, poll responses from Opinary will be sent to Permutive as OpinarySurveyResponse events. To verify: 1. Navigate to the Permutive dashboard. 2. Go to **Events** and look for the OpinarySurveyResponse event type. 3. Check that poll response data is being collected. 4. Interact with an Opinary poll on your site and verify the event appears in your dashboard. Once event collection is verified, you can build cohorts based on poll responses. In the Permutive cohort builder, the `OpinarySurveyResponse` event will appear in the event dropdown list. For example, you can create cohorts for: * Users who answered a specific poll question in a certain way (e.g., users whose gender is "female") * Users who completed specific polls * Demographic segments based on poll responses * Opinion-based segments for targeted content or advertising The integration code provided in the Primary Setup Steps is specifically for Web environments. No additional web-specific configuration is required beyond deploying the code snippet and ensuring the Permutive SDK is present on the page. The code listens for Opinary vote events and forwards them to Permutive via the `window.permutive.track()` method. ## Data Types With your Opinary integration setup, you'll see the following event type collected in Permutive: The OpinarySurveyResponse event is triggered when a user votes on an Opinary poll. The event captures details about the survey, question, and answer provided. The unique identifier for the Opinary poll. Example: `12345` The type of Opinary poll (e.g., single choice, multiple choice, rating scale). The text of the poll question presented to the user. Example: `What is your preferred news category?` The user's text response or label for their answer. Example: `Politics` The X position of the answer (used for spatial poll types like image grids). Defaults to `0.0` if not applicable. The Y position of the answer (used for spatial poll types). Defaults to `0.0` if not applicable. The unique identifier for the selected answer option. Empty string if not applicable. The position/index of the selected option within the poll choices. Defaults to `0` if not applicable. The raw numeric value of the answer (used for rating scales or numeric inputs). Defaults to `0.0` if not applicable. The unit associated with the answer (e.g., "years", "dollars"). Empty string if not applicable. OpinarySurveyResponse events also contain default Permutive event properties such as page URL (`client.url`) and page title (`client.title`). ## Troubleshooting Verify the following: * The custom `OpinarySurveyResponse` event has been configured in your Permutive workspace. Check the Events page in your dashboard to confirm the event exists. * The Permutive SDK is deployed and loading correctly on the pages where your Opinary polls are displayed. Use the Permutive Chrome Extension or check for `window.permutive` in the browser console. * The Opinary SDK is loaded before the collector JavaScript runs. Check for `window.Opinary` in the browser console. * The collector JavaScript has been added to the page and is executing without errors. Check the browser console for JavaScript errors. * The `poll.dmpIntegration` flag is set to `true` in your Opinary poll configuration. This flag controls whether poll responses are sent to DMPs. If you see JavaScript errors in the console: * Verify that both `permutive` and `Opinary` objects are available before the collector code runs. * Check that the Opinary SDK version you're using supports the `opinary.vote` event and the `OpinaryReady` event. * Ensure there are no conflicts with other JavaScript libraries on the page. * Test the integration on a clean page with minimal JavaScript to isolate conflicts. The `OpinarySurveyResponse` event is a custom event that must be preconfigured before the integration will work. If you attempt to deploy the JavaScript collector without this custom event being set up first, poll data will not be collected. Contact your Permutive Customer Success Manager (CSM) to request the custom event setup. If the `OpinarySurveyResponse` event is not appearing in the cohort builder dropdown: * Confirm that events are being collected by checking the Events page in your dashboard. * Ensure at least one event has been received. Events only appear in the cohort builder after data has been collected. * Wait a few minutes after the first event is received, then refresh the cohort builder page. * Verify that the event schema includes the fields you want to use for cohort building. If events are being collected but still not appearing, contact your Permutive CSM for assistance. Not all Opinary poll types populate all fields in the OpinarySurveyResponse event: * Simple choice polls may only populate `answer.text` and `answer.optionPosition`. * Rating scale polls will populate `answer.rawValue` and may include `answer.unit`. * Spatial polls (like image grids) will populate `answer.posX` and `answer.posY`. This is expected behavior. The collector code uses default values (0.0 for numbers, empty strings for text) for fields that don't apply to the specific poll type. If polls display but votes are not being tracked: * Verify that the `poll.dmpIntegration` setting is enabled in your Opinary poll configuration. * Check that you're using a compatible Opinary SDK version that supports the `opinary.vote` event. * Test with different poll types to determine if the issue is specific to certain poll formats. * Contact Opinary support to verify that your polls are configured correctly for DMP integration. ## Changelog For the latest updates and changes to this integration, visit [changelog.permutive.com](https://changelog.permutive.com). # Qualifio Source: https://docs.permutive.com/integrations/survey/qualifio Integrate with Qualifio for survey functionality
Qualifio

Qualifio

Qualifio allows publishers to create interactive experiences (polls, contests) to boost engagement and collect first-party data.

## Overview Qualifio is the leading first- and zero-party data collection platform for media groups and consumer brands. The platform enables marketing teams to generate engagement and capture data through quizzes, games, contests, and over 50 other interactive formats via Qualifio Engage, and to reward and segment members with interaction-based programs through Qualifio Loyalty. The Permutive integration with Qualifio Engage allows you to deliver survey response data from any Qualifio campaign directly into your Permutive workspace. This is a Source integration where Qualifio has integrated with Permutive, meaning you enable the integration from within the Qualifio platform after Permutive configures a custom event in your workspace. Use cases include: * Build cohorts based on survey responses to understand audience preferences and interests * Target users who answered specific questions in particular ways * Enrich audience profiles with zero-party data collected through interactive campaigns * Segment audiences based on engagement with quizzes and surveys ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Permutive SDK Deployment** - The Permutive JavaScript SDK must be deployed on your website where Qualifio campaigns will run. * **Custom Event Configuration** - This integration requires a custom `QualifioSurveyResponse` event to be preconfigured in your Permutive workspace. Contact your Permutive Customer Success Manager (CSM) to request this custom event setup. * **Qualifio Account** - You must have an active Qualifio account with access to campaign configuration settings. ## Setup Contact your Permutive Customer Success Manager (CSM) to request the setup of the `QualifioSurveyResponse` custom event in your workspace. This step must be completed before you can proceed with enabling the integration. The event schema will be configured to accept the following data fields: * Survey site URL * Survey ID * Survey completion status * Question text * Answer text Once the custom event has been configured in your Permutive workspace, follow the instructions on the Qualifio support website to enable the Permutive integration for your campaigns: * **English**: [How does Permutive DMP work with my Qualifio campaigns](https://support.qualifio.com/hc/en-us/articles/7457736575516-How-does-Permutive-DMP-work-with-my-Qualifio-campaigns) * **French**: [Comment fonctionne Permutive DMP avec mes campagnes Qualifio](https://support.qualifio.com/hc/fr/articles/7457736575516) During this process, you will need to provide your Permutive Project ID, which can be found in your Permutive dashboard. After enabling the integration in Qualifio, run a test campaign and submit survey responses. In your Permutive dashboard, navigate to the Events page and verify that `QualifioSurveyResponse` events are being collected. You should see recent events appearing in the Events list with the survey response data. Once event collection is verified, you can build cohorts based on survey responses. In the Permutive cohort builder, the `QualifioSurveyResponse` event will appear in the event dropdown list. For example, you can create cohorts for: * Users who completed a specific survey * Users who answered a particular question with a specific response * Users who engaged with any Qualifio campaign The Qualifio integration works automatically once the Permutive SDK is deployed and the custom event is configured. No additional web-specific implementation is required beyond the standard Permutive SDK deployment. Ensure that the Permutive SDK is loaded on all pages where Qualifio campaigns will be displayed to capture survey response data. ## Data Types With your Qualifio integration setup, you'll see the following event type collected in Permutive: This event is triggered when a user responds to a Qualifio survey question. The event captures details about the survey, question, and answer provided. The URL of the site where the Qualifio campaign is running. Example: `https://my-survey.com` The unique identifier for the Qualifio campaign. Example: `12345` Indicates whether this response completes the survey. Set to `true` when this is the last question in the survey, `false` otherwise. The text of the survey question presented to the user. Example: `How old are you?` The user's response to the question. Example: `30` ## Troubleshooting Verify the following: * The custom `QualifioSurveyResponse` event has been configured in your Permutive workspace. Check the Events page in your dashboard to confirm the event exists. * The Permutive integration is enabled in your Qualifio campaign settings with the correct Project ID. * The Permutive SDK is deployed and loading correctly on the pages where your Qualifio campaigns are displayed. Use the Permutive Chrome Extension to verify SDK presence. * Test with a simple survey to isolate any configuration issues. The `QualifioSurveyResponse` event is a custom event that must be preconfigured before the integration will work. If you attempt to enable the integration in Qualifio without this custom event being set up first, survey data will not be collected. Contact your Permutive Customer Success Manager (CSM) to request the custom event setup. If the `QualifioSurveyResponse` event is not appearing in the cohort builder dropdown: * Confirm that events are being collected by checking the Events page in your dashboard. * Ensure at least one event has been received. Events only appear in the cohort builder after data has been collected. * Wait a few minutes after the first event is received, then refresh the cohort builder page. If events are being collected but still not appearing, contact Permutive support for assistance. ## Changelog For the latest updates and changes to this integration, visit [changelog.permutive.com](https://changelog.permutive.com). # Brightcove Source: https://docs.permutive.com/integrations/video/brightcove Integrate with Brightcove for video event collection and cohort activation
Brightcove

Brightcove

Brightcove allows publishers to manage and monetize video content with robust ad insertion and analytics tools.

## Overview Brightcove is a high-performance, cross-platform HTML5-first video player that loads quickly, delivers high-quality video across desktop and mobile platforms. Our integration with Brightcove enables video event collection and targeting. This integration is both a Source and Destination: * **Source:** Permutive is able to track video events from the Brightcove player. * **Destination:** Permutive is able to activate your cohorts for video ads served in the player. Use cases include: * Track video events in Permutive, for insights, advanced segmentation or modeling. * Add Permutive cohorts to your Google Ad Manager (GAM) ad requests so that they can be used for decisioning and targeting in realtime. ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----------------------------------------------- | | **Web** | | Targeting supported for ads served via GAM only | | **iOS** | | Not supported | | **Android** | | Not supported | | **CTV** | | Not supported | | **API Direct** | | Not supported | ## Prerequisites * **Public API key**: This can be found in the Permutive dashboard, in your [project settings](https://dash.permutive.com/settings/keys/). We recommend provisioning a new one, by clicking *Add Key* and choosing *Public*. * **Brightcove Video Player**: You should already have a Brightcove video player installed on your site. You must also have access to the Brightcove Cloud Studio dashboard, where you can configure your player. ## Setup You must enable the Brightcove Video Player integration in the Permutive Dashboard, if this has not already be done. In the Permutive dashboard, navigate to your workspace's integrations page. Click *Add Integration* and select *Brightcove Video Player*. There is nothing to configure in the Permutive Dashboard for this integration. The Permutive plugin for Brightcove can be installed into your player using the Brightcove Cloud Studio dashboard. The following steps must be performed for every player where you want Permutive enabled: 1. Open the player in Brightcove Cloud Studio. 2. Scroll down to *Plugins* and press *Edit*. 3. Click on *JavaScript* and paste our plugin URL:\ `https://cdn.permutive.com/integrations/videojs-permutive-1.4.2.js` 4. Select the '+' to the right. 5. Click on *Name*, *Options (JSON)* and paste in the following configuration: ```javascript theme={"dark"} { "apiKey": "", "platform": "" } ``` * Replace `` with your Permutive public API key. * Replace `` with either `web`, `amp` or `fia`, based on whether your player is to be deployed on Web, AMP or Instant Articles. 6. Click *Save*. You can now configure the player to make Permutive cohorts available for targeting video ads served in your the player via the GAM ad server. 1. Open the player in Brightcove Cloud Studio 2. Go to *Advertising* 3. Modify the URL in the *Ad Tag* section to include `&cust_params=permutive%3D{permutive}` in its query part Be aware that `cust_params` may already be part of the URL, if you are already appending custom targeting data to ad requests. In this case just prepend the `cust_params` value with `permutive%3D{permutive}%26`. Targeting for pre-roll ads is only supported when using the "on play" Brightcove *Request Ads* setting. If "on load" is used, Permutive cohorts will not be included on the ad request. In the Permutive Dashboard, navigate to your events page. Having completed the above steps, plugin installation is now complete. You should start seeing video event data appear in the Permutive Dashboard. For your cohorts with a GAM activation configured, you should now see these cohorts available for targeting on video ad requests from the player. ## Data Types With your Brightcove integration setup, you'll see the following additional event types collected in Permutive: Tracks each time a video is played. Unique ID representing this video engagement. Indicates whether the player auto started. Unique ID for this item of video content. Name (title) of the video. More detailed description of the video. List of video tags (categories). The video duration, in seconds. Number of times the user has watched this video. Datetime for when the video content was first published. Tracks progress of a video play, as a percentage watched. Unique ID representing this video engagement. Percentage of the video content watched so far. Indicates whether the user has seeked through the video content. Unique ID for this item of video content. Name (title) of the video. More detailed description of the video. List of video tags (categories). The video duration, in seconds. Number of times the user has watched this video. Datetime for when the video content was first published. Tracks additional events emitted by the player, such as when a user enters full screen mode or presses pause. Unique ID representing this video engagement. Name of the additional event that was fired by the player (e.g. `PressedPause`, `ResumedPlay`) Unique ID for this item of video content. Name (title) of the video. More detailed description of the video. List of video tags (categories). The video duration, in seconds. Number of times the user has watched this video. Datetime for when the video content was first published. Tracks the first time a video loads on the page. Unique ID representing this video engagement. Indicates whether the player auto started. Unique ID for this item of video content. Name (title) of the video. More detailed description of the video. List of video tags (categories). The video duration, in seconds. Number of times the user has watched this video. Datetime for when the video content was first published. Tracks when a video ad is played, includes all ad information available from IMA. Unique ID representing this video engagement. The ID of the ad, or the empty string if this information is unavailable. The source ad server of the ad, or the empty string if this information is unavailable. The ID of the selected creative for the ad, or the empty string if this information is unavailable. Returns the first deal ID present in the wrapper chain for the current ad, starting from the top. Returns the empty string if unavailable. The advertiser name, or the empty string if this information is unavailable. The registry associated with cataloging the UniversalAdId of the selected creative for the ad. The UniversalAdId of the selected creative for the ad, or "unknown" if unavailable. Returns the title of this ad from the VAST response. Returns the description of this ad from the VAST response. Returns the duration of the selected creative, or -1 for non-linear creatives. Returns the minimum suggested duration in seconds that the nonlinear creative should be displayed. Returns -2 if the minimum suggested duration is unknown. For linear creative it returns the entire duration of the ad. Unique ID for this item of video content. Name (title) of the video. More detailed description of the video. List of video tags (categories). The video duration, in seconds. Number of times the user has watched this video. Datetime for when the video content was first published. Tracks progress of a video ad play, as a percentage watched. Unique ID representing this video engagement. Percentage of the video ad content watched so far. The ID of the ad, or the empty string if this information is unavailable. The source ad server of the ad, or the empty string if this information is unavailable. The ID of the selected creative for the ad, or the empty string if this information is unavailable. Returns the first deal ID present in the wrapper chain for the current ad, starting from the top. Returns the empty string if unavailable. The advertiser name, or the empty string if this information is unavailable. The registry associated with cataloging the UniversalAdId of the selected creative for the ad. The UniversalAdId of the selected creative for the ad, or "unknown" if unavailable. Returns the title of this ad from the VAST response. Returns the description of this ad from the VAST response. Returns the duration of the selected creative, or -1 for non-linear creatives. Returns the minimum suggested duration in seconds that the nonlinear creative should be displayed. Returns -2 if the minimum suggested duration is unknown. For linear creative it returns the entire duration of the ad. Unique ID for this item of video content. Name (title) of the video. More detailed description of the video. List of video tags (categories). The video duration, in seconds. Number of times the user has watched this video. Datetime for when the video content was first published. Tracks additional events emitted by the player whilst an ad is being watched, such as when a user enters full screen mode, pressed pause or resumed play. Unique ID representing this video engagement. Name of the additional event that was fired by the player (e.g. `PressedPause`, `ResumedPlay`) The ID of the ad, or the empty string if this information is unavailable. The source ad server of the ad, or the empty string if this information is unavailable. The ID of the selected creative for the ad, or the empty string if this information is unavailable. Returns the first deal ID present in the wrapper chain for the current ad, starting from the top. Returns the empty string if unavailable. The advertiser name, or the empty string if this information is unavailable. The registry associated with cataloging the UniversalAdId of the selected creative for the ad. The UniversalAdId of the selected creative for the ad, or "unknown" if unavailable. Returns the title of this ad from the VAST response. Returns the description of this ad from the VAST response. Returns the duration of the selected creative, or -1 for non-linear creatives. Returns the minimum suggested duration in seconds that the nonlinear creative should be displayed. Returns -2 if the minimum suggested duration is unknown. For linear creative it returns the entire duration of the ad. Unique ID for this item of video content. Name (title) of the video. More detailed description of the video. List of video tags (categories). The video duration, in seconds. Number of times the user has watched this video. Datetime for when the video content was first published. Tracks when a user clicks on a video ad. Unique ID representing this video engagement. The ID of the ad, or the empty string if this information is unavailable. The source ad server of the ad, or the empty string if this information is unavailable. The ID of the selected creative for the ad, or the empty string if this information is unavailable. Returns the first deal ID present in the wrapper chain for the current ad, starting from the top. Returns the empty string if unavailable. The advertiser name, or the empty string if this information is unavailable. The registry associated with cataloging the UniversalAdId of the selected creative for the ad. The UniversalAdId of the selected creative for the ad, or "unknown" if unavailable. Returns the title of this ad from the VAST response. Returns the description of this ad from the VAST response. Returns the duration of the selected creative, or -1 for non-linear creatives. Returns the minimum suggested duration in seconds that the nonlinear creative should be displayed. Returns -2 if the minimum suggested duration is unknown. For linear creative it returns the entire duration of the ad. Unique ID for this item of video content. Name (title) of the video. More detailed description of the video. List of video tags (categories). The video duration, in seconds. Number of times the user has watched this video. Datetime for when the video content was first published. ## Troubleshooting Targeting for pre-roll ads is only supported when using the "on play" Brightcove Request Ads setting. If "on load" is used, Permutive targeting will not be included on the ad request. Check your settings in Brightcove Cloud Studio. ## Changelog ### November 2019 * Improved tracking of `VideoPlay`, to include details of video duration and whether auto start was used. For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Catalog Source: https://docs.permutive.com/integrations/video/catalog Browse our comprehensive catalog of video platform integrations. # Video Integrations
YouTube

YouTube

YouTube is the world's largest video platform, enabling publishers to share and monetize video content with global audiences.

Brightcove

Brightcove

Brightcove provides enterprise video solutions that help publishers deliver, monetize, and measure video content across all devices.

JW Player

JW Player

JW Player is a video platform that helps publishers deliver high-quality video experiences with advanced analytics and monetization.

Dailymotion

Dailymotion

Dailymotion is a video-sharing platform that enables publishers to upload, share, and monetize video content with global audiences.

# Dailymotion Source: https://docs.permutive.com/integrations/video/dailymotion Integrate with Dailymotion for targeting and campaign optimization
Dailymotion

Dailymotion

Dailymotion is a global video platform that enables content creators and advertisers to reach diverse audiences worldwide.

## Overview Dailymotion is a global video platform that enables publishers to deliver video content and monetize through advertising. Permutive can integrate with Dailymotion to enable cohort activation for video ad targeting, as well as video event collection for audience building. This integration is currently available on a case-by-case basis. It is not yet available as a self-serve integration in the Permutive Dashboard. If you are interested in enabling this integration for your organization, please reach out to your Customer Success Manager. Use cases include: * Activate Permutive cohorts for targeting video ads served through Dailymotion * Collect video engagement events (e.g. play, pause, progress) for audience building * Improve video ad performance using audience segments built in Permutive * Enhance programmatic video advertising with first-party data ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Permutive SDK**: The Permutive SDK must be installed on your website * **Dailymotion Video Player**: You must have Dailymotion video player deployed on your site * **Customer Success Manager Contact**: This integration requires assisted setup - contact your CSM to begin ## Setup This integration is not yet available as a self-serve option in the Permutive Dashboard. To get started, reach out to your Customer Success Manager who will work with you to enable and configure the integration for your specific needs. Technical Services will provide technical guidance and support throughout the implementation process, including configuration of your Dailymotion player to pass Permutive cohorts for ad targeting. Once implementation is complete, your CSM and technical team will verify that Permutive cohorts are correctly being passed to your Dailymotion video ad requests. ## Data Types Permutive cohorts are passed to Dailymotion as key-value pairs on video ad requests, enabling real-time targeting based on your audience segments. Comma-separated list of Permutive cohort IDs that the user belongs to. ## Troubleshooting Since this integration requires assisted setup, please contact your Customer Success Manager for any troubleshooting needs or questions about implementation. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # JW Player Source: https://docs.permutive.com/integrations/video/jw-player Integrate with JW Player for targeting and campaign optimization
JW Player

JW Player

JW Player is a leading video platform that helps publishers deliver and monetize video content across multiple platforms.

## Overview JW Player is a leading video platform that helps publishers deliver and monetize video content across multiple platforms. Permutive can integrate with JW Player to enable cohort activation for video ad targeting. This integration is currently available on a case-by-case basis. It is not yet available as a self-serve integration in the Permutive Dashboard. If you are interested in enabling this integration for your organization, please reach out to your Customer Success Manager. Use cases include: * Activate Permutive cohorts for targeting video ads served through JW Player * Improve video ad performance using audience segments built in Permutive * Enhance programmatic video advertising with first-party data * Enable key-value targeting for web player implementations ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----- | | **Web** | | -- | | **iOS** | | -- | | **Android** | | -- | | **CTV** | | -- | | **API Direct** | | -- | ## Prerequisites * **Permutive SDK**: The Permutive SDK must be installed on your website * **JW Player**: You must have JW Player deployed on your site * **Customer Success Manager Contact**: This integration requires assisted setup - contact your CSM to begin ## Setup This integration is not yet available as a self-serve option in the Permutive Dashboard. To get started, reach out to your Customer Success Manager who will work with you to enable and configure the integration for your specific needs. Technical Services will provide technical guidance and support throughout the implementation process, including configuration of your JW Player to pass Permutive cohorts as key-value pairs for ad targeting. Once implementation is complete, your CSM and technical team will verify that Permutive cohorts are correctly being passed to your JW Player video ad requests. ## Data Types Permutive cohorts are passed to JW Player as key-value pairs on video ad requests, enabling real-time targeting based on your audience segments. Comma-separated list of Permutive cohort IDs that the user belongs to. ## Troubleshooting Since this integration requires assisted setup, please contact your Customer Success Manager for any troubleshooting needs or questions about implementation. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # YouTube Source: https://docs.permutive.com/integrations/video/youtube Integrate with YouTube for targeting and campaign optimization
YouTube

YouTube

YouTube is the world's largest video platform, enabling content creators and advertisers to reach global audiences.

Looking to target ads on YouTube.com or your YouTube channel instead? See the [YouTube.com Integration](/integrations/social-media/youtube). ## Overview YouTube is the world's largest video platform, enabling publishers to embed and monetize video content through various advertising formats. This integration enables cohort activation for video ads served in YouTube players embedded on your own website or app. This integration is a Destination: * **Destination:** Permutive cohorts are passed to YouTube IFrame Player ad requests as key-value parameters for real-time targeting. Use cases include: * Target video ads in embedded YouTube players with Permutive cohorts * Deliver personalized video advertising based on user behavior and interests * Optimize video ad inventory monetization with audience targeting * Enable real-time cohort activation for pre-roll, mid-roll, and post-roll video ads ## Environment Compatibility | Environment | Supported | Notes | | -------------- | ------------ | ----------------------------------------------- | | **Web** | | Embedded YouTube player with IFrame API | | **iOS** | | Custom implementation required—contact your CSM | | **Android** | | Custom implementation required—contact your CSM | | **CTV** | | Custom implementation required—contact your CSM | | **API Direct** | | -- | ## Prerequisites * **Google Ad Manager Integration**: The [GAM integration](/integrations/advertising/ad-servers/google-ad-manager) must be enabled in Permutive before setting up YouTube targeting. * **Permutive SDK**: The Permutive SDK must be installed on your website or app where the YouTube player is embedded. * **Cohorts with GAM Activation**: Cohorts must have GAM Activation Sync enabled in the Permutive dashboard. * **YouTube IFrame API** (Web): You must be using the [YouTube IFrame Player API](https://developers.google.com/youtube/iframe_api_reference) to embed videos. * **Access to Player Configuration**: You need the ability to modify your YouTube player configuration code to pass custom parameters. ## Setup Before setting up YouTube targeting, you must enable the Google Ad Manager integration in the Permutive dashboard. See the [Google Ad Manager integration documentation](/integrations/advertising/ad-servers/google-ad-manager) for detailed setup instructions. Navigate to the cohorts you want to target in embedded YouTube players in the Permutive dashboard. Enable the Google Ad Manager Activation Sync for each cohort. This will make the cohorts available in localStorage for targeting. Follow the environment-specific tab (Web, iOS, Android, or CTV) for detailed implementation steps for your YouTube player. Each platform requires passing Permutive cohort IDs as custom parameters in video ad requests. Test your implementation to ensure cohort IDs are being passed correctly in ad requests from the YouTube player. Use your browser's developer tools or platform-specific debugging tools to inspect ad request parameters. ### Embedded YouTube Player Setup (YouTube for Publishers) When embedding a YouTube video using the YouTube IFrame API, you can pass Permutive cohorts as custom key-value pairs for ad targeting. Permutive stores cohort IDs in the localStorage variable `_pdfps`. You can retrieve these dynamically when initializing your YouTube player: ```javascript theme={"dark"} var cohortIds; try { cohortIds = JSON.parse(window.localStorage._pdfps || '[]') .slice(0, 250) .toString(); } catch (e) { cohortIds = ''; } ``` When initializing the YouTube IFrame Player, pass cohort IDs through the `embedConfig` object. Use `adsConfig.adTagParameters.cust_params` to pass custom targeting parameters. The `cust_params` value must be URI-encoded as required by Google Ad Manager. ```javascript theme={"dark"} var player; function onYouTubeIframeAPIReady() { // Retrieve cohort IDs var cohortIds; try { cohortIds = JSON.parse(window.localStorage._pdfps || '[]') .slice(0, 250) .toString(); } catch (e) { cohortIds = ''; } // Initialize player with cohort targeting player = new YT.Player('player', { height: '390', width: '640', videoId: 'YOUR_VIDEO_ID', events: { 'onReady': onPlayerReady, 'onStateChange': onPlayerStateChange }, embedConfig: { adsConfig: { adTagParameters: { cust_params: encodeURIComponent('permutive=' + cohortIds) } } } }); } ``` Verify that cohort IDs are being passed correctly: * Check your browser's network tab for ad requests from the YouTube player * Look for the `cust_params` parameter in ad request URLs * Confirm the permutive key-values are present and properly encoded If the YouTube player loads before the Permutive SDK has run on the page, the cohort IDs retrieved from localStorage may not be updated from the previous pageview. Consider delaying player initialization until after Permutive has loaded. ### Embedded YouTube Player Setup for iOS For iOS apps embedding YouTube players with Permutive cohort targeting, the typical approach involves using Google's Interactive Media Ads (IMA) SDK to pass cohort IDs as custom parameters in video ad requests. Contact your Permutive Customer Success Manager for guidance on implementing cohort targeting in embedded YouTube players on iOS. They can provide recommendations based on your specific app architecture and ad serving configuration. ### Embedded YouTube Player Setup for Android For Android apps embedding YouTube players with Permutive cohort targeting, the typical approach involves using Google's Interactive Media Ads (IMA) SDK to pass cohort IDs as custom parameters in video ad requests. Contact your Permutive Customer Success Manager for guidance on implementing cohort targeting in embedded YouTube players on Android. They can provide recommendations based on your specific app architecture and ad serving configuration. ### Embedded YouTube Player Setup for CTV For CTV applications embedding YouTube players with Permutive cohort targeting, the typical approach involves using Google's Interactive Media Ads (IMA) SDK to pass cohort IDs as custom parameters in video ad requests. Implementation details will vary by platform (e.g., Roku, Fire TV, Apple TV). Contact your Permutive Customer Success Manager for guidance on implementing cohort targeting in embedded YouTube players on CTV. They can provide recommendations based on your specific CTV platform and ad serving configuration. ## Data Types Permutive cohorts are passed to YouTube player ad requests as key-value parameters, enabling real-time targeting for video ads in embedded players. Permutive cohorts are passed to YouTube player ad requests as key-value pairs, enabling real-time targeting based on your audience segments. Comma-separated list of Permutive cohort IDs that the user belongs to. Passed via `cust_params` parameter in video ad requests. Example: `permutive=abc123,def456,ghi789` For Web implementations, cohort IDs are retrieved from the localStorage variable `_pdfps` which contains an array of cohort IDs the user belongs to. Updated in real-time as users qualify for cohorts. The custom parameters field in YouTube IFrame API's `adsConfig.adTagParameters` or IMA SDK's ad request. Must be URI-encoded. Example: `cust_params=permutive%3Dabc123%2Cdef456%2Cghi789` For Web implementations using YouTube IFrame API, the configuration object passed to the player that includes ad targeting parameters. ## Troubleshooting If cohort IDs are not being passed to your YouTube player ad requests: * **Check localStorage**: Open your browser's developer console and inspect `localStorage._pdfps` to verify cohort IDs are being stored * **Verify GAM Activation**: Ensure the cohorts have GAM Activation Sync enabled in the Permutive dashboard * **Check timing**: The YouTube player may be loading before Permutive has run. Consider delaying player initialization until after Permutive loads * **Verify encoding**: Ensure `cust_params` values are properly URI-encoded using `encodeURIComponent()` * **Inspect network requests**: Use your browser's network tab to inspect ad requests and verify the `permutive` key-values are present in `cust_params` If the YouTube player is showing cohort data from a previous pageview: * **Problem**: The video/player loads before Permutive SDK has executed on the current page, so `localStorage._pdfps` contains stale cohort IDs * **Solution**: Delay YouTube player initialization until after Permutive has loaded. You can use Permutive's ready callback: ```javascript theme={"dark"} window.permutive = window.permutive || []; window.permutive.push(['ready', function() { // Initialize YouTube player here onYouTubeIframeAPIReady(); }]); ``` If cohort IDs are not being passed in mobile or CTV implementations: * **Verify SDK integration**: Ensure the Permutive SDK is properly integrated on your mobile/CTV platform * **Check cohort retrieval**: Verify you're correctly retrieving cohort IDs from the Permutive SDK using the platform-specific method * **Verify parameter encoding**: Ensure parameters are properly URI-encoded before including in ad requests * **Test with debugging tools**: Use platform-specific debugging tools to inspect ad requests and verify parameters are included * **Contact your CSM**: Mobile and CTV implementations can vary significantly—reach out to your Permutive Customer Success Manager for platform-specific guidance The YouTube integration requires Google Ad Manager to be set up first: * **Prerequisite**: Complete the [Google Ad Manager integration setup](/integrations/advertising/ad-servers/google-ad-manager) before configuring YouTube targeting * **Verify GAM connection**: In the Permutive dashboard, check that GAM is listed under Integrations and shows as connected * **Check permissions**: Ensure [dfp@permutive.com](mailto:dfp@permutive.com) has the required permissions in your GAM account ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Introduction Source: https://docs.permutive.com/introduction Learn about Permutive's predictive data collaboration platform Permutive Understand the key concepts & abstractions behind Permutive. How to achieve tasks and operate your workflows using Permutive. Dive into our products in-depth. All the data tools you can use with Permutive. Technical specifications for Permutive's APIs. Understand the privacy & security posture of Permutive's platform. Permutive is the **predictive data collaboration platform** for the **open internet**. Leading media companies and advertisers rely on Permutive to deliver incremental outcomes through first-party signals. ## Why Permutive? Media owners have large audiences and enjoy a first-party relationship with their users, but have historically served advertisers through an open marketplace that relies on third parties to aggregate their data, activate audiences, and provide insights. With increasing privacy regulation and consumer expectations, traditional ad-tech can only address a fraction of a media owner's true audience, leaving advertisers unable to reach their consumers on the open internet. Permutive's edge technology provides a **first-party signals** foundation that enables media owners to activate & address **100% of their audience** in real-time—including users in privacy-safe environments like Safari, Firefox, and iOS. By modeling advertiser data to publisher signals, Permutive's predictive data collaboration unlocks outcomes with direct and programmatic campaigns that **reach an advertiser's incremental consumers** through a clean supply path. ## What does Permutive offer? Permutive provides a unified platform for: * **Data Management**: Build and activate publisher signals for cohort, identity, and contextual data that drive advertiser outcomes. Plan campaigns, optimize targeting, and report on performance—all powered by AI-driven workflows. * **Data Clean Room**: Collaborate securely between media companies and advertisers to match, model, and activate audiences without sharing personally identifiable information or moving data. * **Curation**: Package publisher signals with AI into curated audiences available in SSP marketplaces. Enable buyers with stronger targeting on brand-safe inventory across premium media owners to drive measurable outcomes. ## Key capabilities * **100% addressability**: Reach your entire audience regardless of browser, device, or environment. * **Real-time activation**: Process data and activate audiences in milliseconds, not hours. * **Privacy by design**: Built on edge computing, Permutive processes user data on-device without exposing it to third-party ad-tech. * **Unified signals**: Combine behavioral, contextual, and identity signals for precision targeting. * **Seamless connectivity**: Connect your data ecosystem—warehouses, CDPs, ad servers, and partners—through no-code workflows. # Clean Room Source: https://docs.permutive.com/products/clean-room Enable secure data collaboration between publishers and advertisers without exposing raw user data ## Overview **Clean Room** enables publishers and advertisers to connect and share aggregated audience data without exposing raw personally identifiable information (PII). First-party data from advertisers is matched with publisher data in a secure environment, allowing for the creation of insights and audiences, and the execution of targeted campaigns while maintaining strict data privacy controls. With Clean Room, advertisers can upload their first-party data and match users with the Permutive publisher ecosystem without having to expose raw PII or sign lengthy Data Protection Agreements. Once users are matched, audiences can be created either by direct matching or using Permutive lookalike technology. These audiences can then be activated for campaign execution within the ad server or via curation channels. ## Why Use Clean Room? **Secure data collaboration** — Clean Room enables publishers and advertisers to collaborate on audience targeting without exposing raw user data. All matching happens in a secure environment where neither party can access the other's raw identifiers. **No lengthy DPAs required** — Advertisers can onboard their first-party data without signing lengthy Data Protection Agreements with each publisher, accelerating campaign setup and reducing legal overhead. **Flexible audience creation** — Create audiences through direct matching (users known to both parties) or extend reach using Permutive's lookalike technology to find similar users across the publisher's audience. **End-to-end data control** — Data owners retain full control throughout the workflow. Advertisers can unlink their data at any point without leaving residual data in the publisher's environment. **Privacy-preserving activation** — Only cohort membership signals are passed to ad servers, never individual user IDs. This protects user privacy while enabling effective targeting. ## Concepts ### Definitions * **Supply-side workspace**: The publisher's workspace where they manage connections with advertisers, configure ad server targeting, and handle deal information. * **Demand-side workspace**: The advertiser's workspace where they manage data sources and create clean room audiences. In publisher-driven workflows, publishers also have demand-side access. * **Matched Cohort**: An audience of users who appear in both the publisher's and advertiser's datasets. Created by matching common identifiers. * **Modeled Cohort**: A lookalike audience that extends reach beyond matched users by finding similar users based on behavioral patterns. * **Data Source**: The advertiser's first-party data uploaded to Clean Room, containing user identifiers and segment assignments. ### Workspaces In publisher-driven workflows, publishers access two separate dashboards: | Workspace | Purpose | | ------------------------ | -------------------------------------------------- | | Supply-side (DMP) | Manage connections, ad server targeting, deal info | | Demand-side (Clean Room) | View advertiser data sources, create audiences | These are separate logins. When Clean Room is enabled for publisher-driven workflows, you receive access to both dashboards. You must switch between them during setup. ## Workflows Clean Room supports two workflow models based on your business relationship: ### Permutive-Driven (One-to-Many) Permutive brings advertiser demand to publishers. Publishers manage connections and ad server targeting while advertisers have full control over audience creation. | Party | Workspace Access | Responsibilities | | ---------- | ---------------- | -------------------------------------------------------------------- | | Publisher | Supply-side | Set up connections, configure ad server targeting, provide deal info | | Advertiser | Full demand-side | Manage data sources, create audiences, full platform access | ### Publisher-Driven (One-to-One) Publishers work directly with their own advertiser customers and maintain control over audience creation. | Party | Workspace Access | Responsibilities | | ---------- | ------------------------- | ---------------------------------------------------------------------------- | | Publisher | Supply-side + Demand-side | Set up connections, create audiences, configure targeting, provide deal info | | Advertiser | Limited demand-side | Manage data sources, permission data to publisher | ## Setup ### Prerequisites * Access to the Permutive Platform * For publishers: Active Audience Platform account with Permutive to collect first-party data * For identity matching: Common identifiers between publisher and advertiser (e.g., hashed emails, mobile advertising IDs) * For enabling Clean Room: Contact your Customer Success Manager (CSM) to discuss commercial terms ### Publisher Setup (Supply-side workspace) Clean Room is a paid product. Reach out to your Customer Success Manager (CSM) to discuss enabling Clean Room for your organization and complete the order form. Upon fulfillment, Permutive will create a Vault for you. Connect with advertisers who have a Permutive account. You'll provide them access to your supply-side data through connections. See the [Setting Up Advertiser Connections](/guides/clean-room/setting-up-advertiser-connections) guide. Once the advertiser has created audiences in their demand-side workspace, configure targeting in your ad server. See the [Configuring Ad Server Targeting](/guides/clean-room/configuring-ad-server-targeting) guide. Share proposal details and deal IDs with the advertiser for campaign execution. See the [Sharing Proposal and Deal Information](/guides/clean-room/sharing-proposal-and-deal-information) guide. ### Advertiser Setup (Full demand-side workspace) Sign a Data Processing Agreement with Permutive and receive your demand-side workspace access. Contact your Permutive representative to begin the onboarding process. Upload your first-party data (hashed emails, mobile IDs, etc.) to your data sources for matching with publisher data. See the [Uploading Advertiser Data](/guides/clean-room/uploading-advertiser-data) guide. Build matched audiences (users known to both parties) or modeled audiences (lookalike expansion) from the matched data. See the [Creating Clean Room Audiences](/guides/clean-room/creating-clean-room-audiences) guide. Access insights and analytics on your audiences and campaign performance. **Two dashboards required:** This workflow uses both your supply-side workspace (existing DMP) and a separate demand-side workspace (Clean Room). Both are provisioned when Clean Room is enabled. See [Workspaces](#workspaces) for details. ### Publisher Setup (Supply-side + Demand-side workspaces) Clean Room is a paid product. Reach out to your Customer Success Manager (CSM) to discuss enabling Clean Room for your organization and complete the order form. Upon fulfillment, Permutive will create a Vault for you with both supply-side and demand-side access. **(Supply-side dashboard)** Log into your supply-side workspace at dashboard.permutive.com to complete this step. Connect with your advertiser customers. In this workflow, advertisers will only be able to upload data, not create audiences. See the [Setting Up Advertiser Connections](/guides/clean-room/setting-up-advertiser-connections) guide. Provide your advertiser with access to upload their first-party data to your demand-side workspace. **(Demand-side dashboard)** Log into your demand-side workspace (Clean Room dashboard) to complete this step. Once the advertiser has uploaded their data, use your demand-side workspace to create matched audiences or modeled audiences. See the [Creating Clean Room Audiences](/guides/clean-room/creating-clean-room-audiences) guide. **(Supply-side dashboard)** Log into your supply-side workspace at dashboard.permutive.com to complete this step. Configure targeting in your ad server for the audiences you've created. See the [Configuring Ad Server Targeting](/guides/clean-room/configuring-ad-server-targeting) guide. **(Supply-side dashboard)** Log into your supply-side workspace at dashboard.permutive.com to complete this step. Share proposal details and deal IDs with the advertiser for campaign execution. See the [Sharing Proposal and Deal Information](/guides/clean-room/sharing-proposal-and-deal-information) guide. ### Advertiser Setup (Limited demand-side workspace) Sign a Data Processing Agreement with Permutive and accept the publisher's invitation to their demand-side workspace. Upload your first-party data (hashed emails, mobile IDs, etc.) to data sources. See the [Uploading Advertiser Data](/guides/clean-room/uploading-advertiser-data) guide. Grant the publisher access to your data source by providing their Vault Organization ID. The publisher will then create audiences on your behalf. See the [Uploading Advertiser Data](/guides/clean-room/uploading-advertiser-data) guide for permission details. ## Guides Step-by-step instructions for working with Clean Room. For publishers: Connect with advertisers in the Clean Room Set up targeting in GAM or Microsoft Monetize Configure PMP deals in GAM For advertisers: Upload first-party data to data sources Build matched and modeled audiences Set up frequency caps for campaigns ## Troubleshooting This typically occurs when there are no common identifiers between the publisher and advertiser datasets. **Solution:** * Verify that both parties are using the same identifier types (e.g., both using hashed emails or both using mobile advertising IDs) * Check that identifier formatting is consistent (e.g., emails are lowercase and trimmed before hashing) * Ensure the publisher is collecting the necessary identifiers in their DMP setup * Consider using an ID Bridge provider to enable matching when parties don't share common identifiers The advertiser's organization ID may be incorrect or the advertiser may not have a Permutive account set up yet. **Solution:** * Ask the advertiser to verify their organization ID by logging into Settings in the Permutive dashboard (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) * Ensure the advertiser has completed their Permutive account setup and signed the required Data Processing Agreement * Contact [Technical Services](mailto:technical-services@permutive.com) if you need to grant provisional access to a vault when publishers are ready to work with an advertiser ## Environment Compatibility #### Core Product | Environment | Supported | Notes | | ----------- | ------------ | ------------- | | Web | | Full support | | iOS | | Not supported | | Android | | Not supported | | CTV | | Not supported | | API Direct | | Not supported | ## Dependencies Clean Room relies on the following products and configurations: | Dependency | Required | Description | | :-------------------- | :------- | :---------------------------------------------------------------------------------------------------------------- | | DMP | ✓ | Publishers must have an active DMP setup to collect first-party data for matching | | Custom Cohorts | ✓ | Custom cohorts are used as features for building modeled audiences and as the basis for matched cohorts | | Identity Solution | \~ | When publisher and advertiser don't share common identifiers, an ID Bridge provider (WiNR, Tapad) may be required | | Ad Server Integration | ✓ | An active integration with an ad server (GAM, Xandr) is required to activate and target clean room cohorts | ## FAQ Clean Room supports matching on hashed emails (SHA-256), mobile advertising IDs, and IP addresses. Both parties must use the same identifier type for matching to occur. Data processing can take several hours depending on dataset size. You can monitor the data source status in the interface. **Matched cohorts** contain only users who appear in both the publisher's and advertiser's datasets. **Modeled cohorts** extend reach by using lookalike technology to find similar users beyond the matched set. ## Changelog ### 2025 **December 2025** * Documentation restructured to follow new product documentation standards For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Events (Audience) Source: https://docs.permutive.com/products/connectivity/events Capture and manage user behavior data across all touchpoints ## Overview **Events** are the foundation of audience creation in Permutive. An event captures a user action or behavior on your website or app—such as viewing content, engaging with media, watching a video, or completing an action. Events are enriched with **properties** that provide contextual information about the action, enabling you to build precise, behavior-based cohorts for targeting and activation. Permutive automatically collects standard events appropriate to each platform (such as content view and engagement events), and you can extend this with custom events specific to your business needs. All events are tracked in real-time and stored for historical analysis and cohort building. ## Why Use Events? **Build cohorts based on real user behavior** — Events enable you to create audience segments based on what users actually do on your properties, not just demographic assumptions. By tracking content views, engagement, video plays, and custom interactions, you gain a complete picture of user behavior that powers precise targeting for advertisers. **Enrich events with custom properties for granular targeting** — Beyond standard event data, you can include custom properties that capture business-specific context—like article categories, product brands, video titles, or content authors. These properties allow you to build highly specific cohorts that meet advertiser briefs and campaign requirements. **Real-time data collection for immediate activation** — Events are collected and processed in real-time by the Permutive SDK, enabling immediate cohort evaluation and activation. Users are added to cohorts as soon as they meet the criteria, ensuring your targeting is always current and responsive to user behavior. ## Concepts ### Definitions * **Event**: A captured action or behavior that a user takes on your site or app. Events have a name (e.g., "Pageview", "VideoPlay", "ContentView") and are associated with a specific user, session, and timestamp. * **Event Properties**: Additional information associated with an event that provides context about the action. Properties can be standard (collected automatically by Permutive) or custom (defined by you). Examples include `client.url`, `article.title`, `product.brand`, or `video.duration`. * **Event Schema**: The defined structure for an event, specifying which properties are allowed and their data types (e.g., String, Integer, List). The schema ensures data consistency and enables validation. * **Standard Events**: Events automatically collected by the Permutive SDK. Standard events vary by platform—for example, web includes page views and engagement, CTV/audio includes video views or audio play events. All platforms collect cohort entry and exit events. * **Custom Events**: Events you define to track specific behaviors unique to your business, such as video plays, social shares, article comments, or e-commerce transactions. Custom events are tracked using platform-specific SDK methods or direct API calls. * **Automatic Collection**: The Permutive SDK automatically collects standard content view events and their associated properties appropriate to each platform (e.g., Pageview on web, Video View on CTV). ## Workflows ### Collecting Standard Events The Permutive SDK automatically collects standard events as users interact with your properties. No additional configuration is required beyond deploying the SDK. Standard events vary by platform and include content views, engagement metrics, and ad events from your configured ad server. ### Defining Custom Events To track behaviors beyond standard events, you can define custom events with their own schemas. Custom events and schemas are configured by your Permutive team based on your specific business requirements. Work with your Customer Success Manager to identify the events you need to track, and once agreed, Permutive will configure the event schemas in your workspace. You then implement tracking using the appropriate SDK method or API for your platform. ### Viewing Event Data The Events page in the Permutive Dashboard displays all events being collected for your workspace, including standard and custom events. You can inspect event schemas, view property definitions, and see sample data to verify tracking is working correctly. ## Troubleshooting Common issues when working with Events: If events are not appearing in the Permutive Dashboard, this may be due to: * The Permutive SDK is not properly deployed or initialized * The SDK is blocked by ad blockers or privacy tools (primarily on web) * Events are being rejected due to schema validation errors * There is a delay in data processing (typically events appear within a few minutes) **Solution**: Verify the SDK is deployed and initialized correctly using platform-specific debugging tools (browser developer console for web, platform logs for mobile/CTV). Check for API calls to Permutive and review any error messages. For web, temporarily disable ad blockers to test. If events are still missing after 10-15 minutes, contact [Support](mailto:support@permutive.com). Events can be rejected if the data sent does not match the event schema. Common causes include: * Sending a property that is not defined in the schema * Sending a property with the wrong data type (e.g., String instead of Integer) * Sending a required property with a null or undefined value * Property name typos or case sensitivity issues **Solution**: Inspect the event schema in the Permutive Dashboard to verify property names and types. Check your tracking code to ensure properties match the schema exactly. Use your browser's console to inspect the data being sent before it reaches the Permutive API. Add missing properties to the schema if needed, or update your tracking code to match the schema. If custom event properties are showing null or undefined values, check: * The data layer or variables are populated before the event fires * The property names in your tracking code match the schema exactly * There are no errors preventing the property values from being set * The timing of the event firing—data may not be available yet **Solution**: Use platform-specific debugging tools to verify that the data you're trying to capture is available when the event fires. Add logging to your tracking code to debug property values. Ensure asynchronous data is loaded before firing events. Consider delaying event tracking until required data is available. Multiple content view events can occur when: * The SDK is initialized multiple times * Single-page applications are firing view events on client-side route changes (web) * The platform is using a tag manager that fires the SDK more than once (web) **Solution**: Verify the SDK is only initialized once. For single-page applications, ensure view tracking is intentionally configured for route changes (this is often desired behavior). For web, review tag manager configuration to prevent duplicate SDK loads. If expected properties are not appearing in your event data: * The properties may not be defined in the event schema * The properties may be optional and not being sent in some cases * Data layer values may not be available when the event fires * Property names may have been misspelled in the tracking code **Solution**: Verify the property exists in the event schema and note whether it is required or optional. Check that the data source (data layer, page variables) is populated correctly. Review tracking code for spelling and case sensitivity. Add missing required properties or update the schema to include new optional properties. ## Environment Compatibility #### Core Product Events are collected across all Permutive SDK platforms: | Functionality | Web | iOS | Android | CTV | API Direct | | :------------------------- | :----------- | :----------- | :----------- | :----------- | :----------- | | Standard event collection | | | | | | | Custom event collection | | | | | | | Real-time event processing | | | | | | #### Typical Data by Platform Events will be defined with your team during onboarding, based on your environments and ad-tech stack. Typical data points collected in each environment include: **Web:** * Page View, Engagement (engaged time, scroll depth), Link Click, Ad Impression, Ad Click, Cohort Entry, Cohort Exit **iOS / Android:** * Content View, Engagement (engaged time, scroll depth), Ad Impression, Ad Click, Cohort Entry, Cohort Exit **CTV:** * Video View, Engagement (engaged time), Video Completion, Ad Play, Ad Completion, Cohort Entry, Cohort Exit **Audio:** * Audio Play, Engagement (engaged time), Audio Completion, Ad Play, Ad Completion, Cohort Entry, Cohort Exit ## Dependencies Events rely on the following products and configurations: | Dependency | Required | Description | | :------------ | :---------- | :------------------------------------------------------------------------------------------------------------------------------------------------------- | | Permutive SDK | Recommended | The Permutive SDK is typically used and makes it easy to automate collection of standard events. Events can also be tracked by calling our API directly. | | Event Schemas | Yes | Event schemas must be defined in the Permutive Dashboard before events can be collected and validated. | ## Limits Events adhere to the following specifications and limits. #### Feature Limits | Feature | Description | Limit | | :-------------------------- | :------------------------------------------------------------------------- | :---------------- | | Custom events per workspace | Maximum number of custom event types that can be defined. | Based on contract | | Properties per event | Maximum number of properties that can be defined on a single event schema. | No hard limit | #### Usage Limits | SKU | Description | Limit | | :----------- | :-------------------------------------------------------- | :---------------- | | Event volume | Maximum number of events that can be collected per month. | Based on contract | ## FAQ The Permutive SDK automatically collects several standard events without any additional configuration. Standard events vary by platform: **Web:** * **Pageview**: Fired on every page load * **Pageview Engagement**: Tracks time on page, scroll depth, and completion * **Cohort Entry/Exit**: Tracks when users join or leave cohorts **Mobile (iOS/Android):** * **Content View**: Fired on content navigation * **Engagement**: Tracks time on screen and scroll depth * **Cohort Entry/Exit**: Tracks when users join or leave cohorts **CTV/Audio:** * **Video View / Audio Play**: Fired on content playback * **Engagement**: Tracks playback duration * **Cohort Entry/Exit**: Tracks when users join or leave cohorts If you have ad server integration enabled, additional ad events are collected (e.g., Slot Rendered, Slot Clicked, Slot Viewable). All other events (video plays, form submissions, link clicks, etc.) must be configured or implemented as custom events. To track a custom event: 1. Define the event schema in the Permutive Dashboard under Events > Create Event 2. Specify the event name and properties with their data types 3. Implement tracking using the appropriate method for your platform (e.g., `permutive.track('EventName', { property: 'value' })` on web, or platform-specific SDK methods for mobile/CTV) Define the event schema in the Permutive Dashboard under Events > Create Event and implement tracking using the appropriate method for your platform. Yes, you can add new optional properties to an event schema at any time. However, changing property data types or removing properties can cause validation errors for events that are already being tracked. It's recommended to add new properties rather than modifying existing ones. Contact [Support](mailto:support@permutive.com) for assistance with schema migrations. * **Required properties** must be included every time the event is tracked. If a required property is missing, the entire event will be rejected. * **Optional properties** can be omitted. Events will still be accepted even if optional properties are not provided. It's best practice to make properties optional unless they are truly essential to the event's meaning. Event data retention policies vary by customer agreement. Typically: * Recent event data (last 30-90 days) is stored in hot storage for fast querying * Historical event data is retained for longer periods based on your data retention policy Contact your Customer Success Manager to discuss your specific data retention requirements. Yes, you can send events to Permutive via server-side integration using: * **Pixel API**: For simple server-side event tracking * **API Direct**: For batch event ingestion without SDK deployment * **Connectivity Imports**: For importing historical event data See our [API documentation](/api/introduction) for details on server-side event tracking. To inspect events being collected, use platform-specific debugging tools: **Web:** 1. Open your browser's Developer Tools (F12) 2. Go to the Network tab 3. Filter for requests to Permutive's API (look for `permutive.com`) 4. Reload the page and inspect the API calls to see event payloads **Mobile/CTV:** * Use platform logs and network debugging tools to inspect API calls to Permutive * Check the Permutive Dashboard's Event Inspector for all platforms You can also use the Permutive browser extension (if available for web) or check the Permutive Dashboard's Event Inspector. Events are rejected when they fail schema validation. Common reasons: * Property name does not exist in the schema * Property value does not match the expected data type * Required property is missing * Property value exceeds maximum length Check your browser console for error messages, and verify your event tracking code matches the schema exactly. Yes, events are the primary data source for building Custom Cohorts in Permutive. You can create cohorts based on: * Which events users have triggered (e.g., "Viewed at least 3 articles") * Event property values (e.g., "Viewed articles in the Sports category") * Recency (e.g., "In the past 7 days") * Frequency (e.g., "At least 5 times") See our [Custom Cohorts](/products/signals/cohorts/custom) documentation for more details. Standard properties collected on all events include: * `client.domain`: The domain where the event occurred (web) * `client.url`: The URL where the event occurred (web) * `client.title`: The page or screen title * `client.referrer`: The referring URL (web) * `client.type`: The platform type (web, iOS, Android, CTV) * `client.user_agent`: The browser or app user agent Note: Some properties (like `client.domain`, `client.url`, `client.referrer`) are web-specific. Mobile and CTV platforms collect equivalent properties appropriate to their environment. Plus internal properties like `event_id`, `user_id`, `session_id`, `view_id`, and `time`. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Imports (Audience) Source: https://docs.permutive.com/products/connectivity/imports Import audience data from external sources like Google Cloud Storage and LiveRamp. ## Overview **Imports** (also known as Audience Imports) enables publishers to import audience segments from external sources directly into Permutive. Publishers can bring in data from Customer Data Platforms (CDPs), data partners, DMPs, or their own CRM systems and use these imported segments to build cohorts and activate campaigns. Permutive supports two import sources: * **Google Cloud Storage (GCS)**: Upload user ID and segment files directly to a Permutive-managed GCS bucket * **LiveRamp**: Receive audience data through LiveRamp's data distribution network Imported segments appear in the Cohort Builder under the "Audience Imports" condition type, allowing publishers to combine external data with their first-party behavioral data. ## Why Use Imports? **Interoperability** — Monetize all data points from external sources alongside your first-party data. Import segments from any system that can export user lists, enabling a unified view of your audience. **Second-party data partnerships** — Receive audience data from trusted partners. Partners can share their audience segments with you through GCS file uploads or LiveRamp distribution, enabling collaborative data strategies. **CRM integration** — Import subscriber lists, customer segments, or membership tiers from your CRM. Target known users across your properties with personalized campaigns based on their customer status. **Third-party enrichment** — Layer data provider segments onto your audiences. Import demographic, interest, or intent data from third-party providers to enhance your targeting capabilities. ## Concepts ### Definitions * **Import**: A configuration that defines the source of external audience data, access permissions, and segment lifetime. Each import represents a connection to a specific data provider or partner. * **Taxonomy**: A mapping that associates segment codes with human-readable names, descriptions, and optional metadata like CPM pricing. The taxonomy defines what each segment code means and how it appears in the Dashboard. * **Data Provider / Audience Set**: A logical grouping mechanism for organizing segments from different sources. Each import is associated with a data provider, which helps separate segments from different partners or use cases. * **Segment Code**: A unique identifier for a segment within an import. Segment codes are defined in the taxonomy and referenced in data files. Codes are alphanumeric strings — best practice is to use sequences (e.g., "0001", "s001") rather than human-readable words. * **Import Lifetime**: The time-to-live (TTL) for imported segment memberships. After the lifetime expires, users are removed from the segment unless refreshed by a new data upload. Default is 60 days, but can be overridden per segment in the taxonomy. ### Data Flow The import process follows this sequence: 1. **Import Creation**: Configure an import in the Dashboard, specifying the source type (GCS or LiveRamp) and data provider details 2. **Taxonomy Setup**: Upload or configure the taxonomy to define segment codes and names 3. **Data Upload**: Upload data files (GCS) or receive data (LiveRamp) containing user IDs and segment memberships 4. **Processing**: Permutive processes the files and matches user IDs to users in your workspace 5. **Activation**: Imported segments become available in the Cohort Builder ## Workflows ### Creating an Import Publishers navigate to **Connectivity > Imports** in the Permutive Dashboard and click "Add Import" to begin. They select the import source (GCS or LiveRamp) and provide configuration details such as the data provider name and default segment lifetime. For **GCS imports**: Permutive generates a unique GCS bucket path and service account credentials. Publishers use these credentials to upload data files to the specified bucket. For **LiveRamp imports**: The advertiser configures LiveRamp to distribute data to Permutive using your **Permutive Organization ID** (found in **Settings** in the Dashboard). See the [LiveRamp guide](/guides/connectivity/imports/ingesting-data-via-liveramp) for detailed setup steps. ### Setting Up Taxonomy The taxonomy maps segment codes to human-readable names and metadata. Publishers can configure the taxonomy in two ways: **CSV Upload**: Upload a CSV file with columns for segment ID, name, description, CPM, and lifetime. This is ideal for initial setup or bulk updates. **Taxonomy API**: Use the Taxonomy API to programmatically manage segments. This supports adding, updating, and removing individual segments with batch operations of up to 5,000 operations per request. ### Uploading Data Files For GCS imports, publishers upload data files containing user ID and segment mappings: **Manual Upload**: Use the Google Cloud Console or `gsutil` CLI to upload files directly to the Permutive-managed bucket. **Programmatic Upload**: Use the GCS service account credentials provided by Permutive to automate file uploads from your data pipeline. ### Data File Format Data files must follow this tab-separated format: ``` USER_IDSEGMENT_CODES 76E5F445-1993 0002,0007,0012 5E824DCF-2C6D 0010,0011 69E0985B-50C0 0009,0005 69E0985B-50C0 0012 2DABE6C1-07DD 0001,0008,0010,0012,0013 ``` The same user ID can appear on multiple rows. Each row adds the specified segment memberships for that user. **Requirements:** * Tab-separated values (USER\_ID \t SEGMENTS) * Segment codes comma-separated (no spaces) * Files should be gzip compressed with `.gz` extension (NOT `.gzip`) * No whitespace in segment codes * User IDs should match identifiers tracked by your Permutive SDK ### Taxonomy CSV Format The taxonomy CSV defines your segments: ```csv theme={"dark"} ID,Name,Description,CPM (USD),Lifetime (days) 0001,Country - France,Users living in France,0,45 0002,Country - Spain,Users living in Spain,0,45 0009,Gender - Female,People that identify as Female,0,30 0012,Subscriber - Premium,Paying subscribers,0,30 ``` **Fields:** * `ID` (required): Unique segment code (alphanumeric, no spaces). Best practice is to use a sequence (e.g., `0001`, `0002`, `s001`) rather than human-readable words. * `Name` (required): Display name in Dashboard. Use hyphens to delimit category levels (e.g., `Demographic - Inferred Gender - Female`). * `Description` (optional): Segment description * `CPM (USD)` (optional): Cost per mille for third-party segments. Leave blank or `0` for self-sourced data. * `Lifetime (days)` (optional): Per-segment TTL override in days. If omitted, the import's default lifetime is used. ## Troubleshooting Permission errors occur when the upload credentials don't have write access to the Permutive-managed GCS bucket. **Solution**: Verify you are using the correct service account credentials provided by Permutive. Check that the credentials have not expired. If uploading via the GCS Console, ensure you are signed in with an account that has been granted access to the bucket. Contact [Technical Services](mailto:technical-services@permutive.com) if credentials need to be regenerated. File format errors occur when data files don't match the expected format. Common issues: * Using spaces instead of tabs as the delimiter * Using `.gzip` extension instead of `.gz` * Whitespace in segment codes * Missing or malformed user IDs **Solution**: Verify your files are tab-separated (not comma or space separated). Ensure files are compressed with gzip and use the `.gz` extension. Remove any whitespace from segment codes. Validate a sample of your file format before uploading large batches. If uploaded segment codes don't appear in the Dashboard, the taxonomy may not include those codes. **Solution**: Upload segment codes to the taxonomy before uploading data files. The taxonomy defines which segments are recognized. Codes in data files that don't match taxonomy entries will be ignored. Use the Taxonomy API or CSV upload to add missing segment codes. Low or zero match rates indicate that user IDs in the data file don't match users in your Permutive workspace. Possible causes: * User ID format mismatch (e.g., lowercase vs uppercase) * Using a different identifier type than what's tracked * Users haven't visited your site/app yet * Segment lifetime has expired **Solution**: Verify the user ID format matches exactly what your Permutive SDK tracks. Check that you're using the correct identifier type (e.g., Permutive user ID, RampID, or custom identifier). Upload fresh data to reset segment lifetimes for expired memberships. Note that even when matching is working correctly, the **Live Audience Size** of cohorts built on imported data will start at zero and grow over time as users visit your properties. This is expected behavior — see [Understanding Audience Size for Import Cohorts](/guides/signals/cohorts/custom/using-audience-imports#understanding-audience-size-for-import-cohorts) for details. If a LiveRamp import is configured but not receiving data, there may be a configuration mismatch. **Solution**: Verify that the correct **Permutive Organization ID** was provided during LiveRamp setup (found in **Settings** in the Dashboard). Confirm that LiveRamp has been configured to distribute data to Permutive. Check with your LiveRamp representative that the distribution is active. Contact Permutive support at [technical-services@permutive.com](mailto:technical-services@permutive.com) for assistance troubleshooting the connection. The Taxonomy API supports a maximum of 5,000 operations per request. Requests exceeding this limit will be rejected. **Solution**: Split large taxonomy updates into multiple requests of 5,000 operations or fewer. Consider using CSV upload for initial bulk taxonomy creation, then use the API for incremental updates. ## Environment Compatibility #### Import Sources Imports can receive data from the following sources: | Source | Description | Configuration | | :------------------- | :------------------------------------------------------ | :----------------------------------- | | Google Cloud Storage | Upload files directly to a Permutive-managed GCS bucket | Service account credentials provided | | LiveRamp | Receive data through LiveRamp's distribution network | Permutive Organization ID required | #### Identifier Support Imports support matching on various identifier types: | Identifier Type | GCS Import | LiveRamp Import | | :----------------- | :----------- | :-------------- | | Permutive User ID | | | | RampID | | | | Custom Identifiers | | | ## Guides Understand how second-party data works in Permutive Map segment codes to human-readable names Set up LiveRamp to send cohorts to Permutive Browse the LiveRamp Data Marketplace and distribute audience segments to Permutive ## Dependencies Imports require the following products and infrastructure: | Dependency | Required | Description | | :------------------- | :----------- | :------------------------------------------------------------------------------------------------------------------ | | Permutive SDK | ✓ | The Permutive SDK must be deployed to track user identifiers that imported segments will match against. | | Identity Graph | ✓ | User identifiers used in import files must be configured in Identity Graph for matching to work correctly. | | Cloud Storage Access | For GCS | GCS imports require the ability to upload files to Google Cloud Storage using provided service account credentials. | | LiveRamp Account | For LiveRamp | LiveRamp imports require an active LiveRamp account with data distribution configured. | ## Limits Imports adhere to the following product specifications and limits. #### File Limits | Feature | Description | Limit | | :---------------- | :---------------------------------------------------- | :--------- | | User IDs per file | Maximum number of user ID rows in a single data file. | 10,000,000 | | Daily data volume | Maximum total file size uploaded per day. | 40 GB | | File compression | Required compression format for data files. | gzip (.gz) | #### Taxonomy Limits | Feature | Description | Limit | | :---------------------- | :------------------------------------------ | :------------- | | Taxonomy API batch size | Maximum operations per API request. | 5,000 | | Segment code length | Maximum character length for segment codes. | 256 characters | #### Segment Limits | Feature | Description | Limit | | :----------------------- | :-------------------------------------------- | :------- | | Default segment lifetime | Default TTL for imported segment memberships. | 60 days | | Minimum segment lifetime | Minimum configurable segment lifetime. | 1 day | | Maximum segment lifetime | Maximum configurable segment lifetime. | 365 days | ## FAQ You can use any identifier that is tracked by your Permutive SDK and configured in Identity Graph. Common identifiers include Permutive user IDs, RampIDs, and custom identifiers like CRM IDs or hashed emails. The identifier format in your import files must exactly match what's tracked by the SDK, including case sensitivity. Imported segments typically become available within 15-30 minutes after file upload. Processing time depends on file size and current system load. Large files (approaching the 10M user ID limit) may take longer to process. When a segment lifetime expires, users are automatically removed from that segment. To maintain segment membership, upload fresh data before the lifetime expires. Each new upload resets the lifetime clock for the users included in that upload. Yes, you can update the taxonomy at any time. Changes to segment names or descriptions take effect immediately in the Dashboard. Adding new segment codes makes them available for future data uploads. Removing segment codes does not delete existing user memberships—users will remain in the segment until the lifetime expires. Users are automatically removed when their segment lifetime expires. To immediately remove users, you would need to wait for the lifetime to expire or contact [Support](mailto:support@permutive.com) for assistance with manual removal. There is no mechanism to upload a "removal" file. Yes, create a separate import for each partner or data provider. Each import has its own taxonomy, bucket path (for GCS), and configuration. This keeps partner data organized and allows different segment lifetimes or settings per partner. **GCS imports** give you direct control over data uploads. You manage the files, upload schedule, and data format. This is ideal for CRM data, partner file exchanges, or any data you can export to files. **LiveRamp imports** receive data automatically through LiveRamp's network. This is ideal if your data partners already distribute through LiveRamp or if you want to receive third-party data segments via RampID matching. Low match rates usually indicate an identifier mismatch. Check that: 1. The identifier type in your file matches what's tracked by the SDK 2. The format is exactly correct (case sensitivity, hyphens, etc.) 3. The users have actually visited your site/app (new users won't match) 4. The identifier is configured in Identity Graph Contact [Support](mailto:support@permutive.com) if you need help diagnosing match rate issues. Yes, imported segments can be used in cohorts that are activated for real-time targeting. Once a user matches an imported segment, they are added to any cohorts that include that segment, and those cohorts flow through to your ad server and SSP activations. No, imports are point-in-time. Only data uploaded after the import is created will be processed. If you need to backfill historical segment memberships, you'll need to upload a file containing those memberships. Similarly, when you create a cohort using imported segments, the cohort's **Live Audience Size** is not backfilled — it starts at zero and grows as users are evaluated. The **Predicted Audience Size** shown in the Cohort Builder is a historical estimate and may be significantly higher initially. See [Predicted and Live Audience Size](/products/signals/cohorts/custom#whats-the-difference-between-predicted-audience-size-and-live-audience-size) for more details. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Routing Source: https://docs.permutive.com/products/connectivity/routing Export first-party event data to data warehouses and cloud storage. ## Overview **Routing** enables publishers to export all of their first-party event data from web and mobile applications directly into data warehouses like Google BigQuery, Snowflake, or Amazon S3. Publishers can route Permutive data into their warehouses to build a single view of their audience, create custom reporting and analytics, and integrate with other data sources using Permutive's [Identity Graph](/products/signals/identity/identity-graph). ## Why Use Routing? **Build a single view of your audience** — Routing allows publishers to consolidate all their first-party event data from Permutive into their own data warehouse, where they can link it with other data sources using Permutive's [Identity Graph](/products/signals/identity/identity-graph). **Create custom reporting and analytics** — With full access to raw event data in your warehouse, you can build custom reports, dashboards, and analytics tailored to your specific business needs. Run complex queries, generate insights, and integrate with your existing BI tools without limitations. ## Concepts ### Definitions * **Events**: Actions performed by users on your website or app that are tracked by the Permutive SDK. Events include standard events like Pageview, SlotClicked, and custom events you define. Each event captures contextual properties about the user's action, session, and identity. * **Routing Integration**: A configuration that specifies the destination for event data. Each routing integration defines where events should be sent, authentication credentials, and data format preferences. BigQuery integrations can be configured via the Dashboard self-service; Snowflake and S3 integrations require assistance from Permutive support. * **Streaming Routing**: Event data is streamed to the destination in near real-time as events are collected. Available for BigQuery, Snowflake, and S3 Streaming destinations. * **Batch Routing**: Event data is exported on a scheduled basis (typically 24-hour cycles). Available for S3 destinations. Batch routing operates at the Organization level rather than individual workspaces. * **Schema Generation**: Routing automatically generates and updates table schemas based on the events and properties being tracked. When new event types or properties are added, the schema is automatically updated without manual intervention. * **Data Partitioning**: Tables are organized by date to optimize query performance and data management. Partitioning strategy varies by destination—see [How is data partitioned in my destination?](#how-is-data-partitioned-in-my-destination) for details on each platform. ### Data Types Routing supports exporting different types of data to your destination. The availability of each data type depends on the destination: | Data Type | Description | BigQuery | Snowflake | S3 Streaming | S3 Batch | | :----------------- | :----------------------------------------------------------------------- | :----------- | :----------- | :----------- | :----------- | | `events` | User events tracked by the Permutive SDK (Pageview, custom events, etc.) | | | | | | `aliases` | Identity data linking user identifiers (sync\_aliases) | | | | | | `domains` | Domain-level metadata and configuration | | | | | | `segment_metadata` | Segment definitions and metadata | | | | | ## Workflows ### Setting Up a Routing Integration For **BigQuery**: Publishers navigate to the Routing section in the Permutive Dashboard and click "Add Integration" to begin. They select BigQuery as the destination and provide configuration details such as project ID, dataset name, and region. Permutive generates a unique service account that the publisher must grant access to in their GCP project. For **Snowflake and S3**: Contact [Technical Services](mailto:technical-services@permutive.com) to set up routing integrations. The support team will work with you to configure the destination and provide the necessary authentication details. ### Monitoring Routing Status Once configured, routing integrations display their current status in the Dashboard. Status values include "Running" (active and operational), "Created" or "Updating" (in progress), and "Failed" (setup error occurred). Publishers can monitor the health of their routing integrations and troubleshoot issues using the status information and error messages provided. ### Querying Routed Data After routing is enabled, event data begins flowing to the destination. Publishers can query their routed data using standard SQL tools for BigQuery and Snowflake, or process files from S3 using their preferred data processing framework. ## Troubleshooting This error typically occurs when the service account does not have the correct permissions in the destination platform. **Solution**: For BigQuery: Ensure you have granted the Permutive service account the **BigQuery User** role at the PROJECT level (not just the dataset level). If you are manually creating the dataset, you must grant **BigQuery Data Owner** role instead. For Snowflake: Verify that key-pair authentication is configured correctly and that the Permutive user has appropriate permissions on the target database and schema. For S3: Confirm that the AWS IAM user has been granted write permissions to the specified bucket and path prefix. Review the permissions in your cloud platform's IAM console and grant the required roles to the Permutive service account or user. After granting permissions, click "Confirm account access granted" in the Permutive Dashboard. Integration failures can occur due to: * Invalid project ID, dataset name, or bucket name * Permission issues (see above) * IAM policy restrictions (e.g., domain restrictions in GCP) * Network or firewall rules blocking access **Solution**: Contact Permutive support at [technical-services@permutive.com](mailto:technical-services@permutive.com) with your integration details. Provide the integration ID (found in the Dashboard URL) and any error messages displayed. Common errors include "Project not found", "IAM setPolicy failed", or "Access Denied" messages. If events are not appearing or are delayed, first verify: * The integration status is "Running" in the Permutive Dashboard * Events are being tracked successfully - check Event Inspector in the Dashboard * The Permutive SDK is properly deployed on your site/app For BigQuery: Check that tables named `{event_name}_events` exist (e.g., `Pageview_events`). Verify you can query recent data. For Snowflake: Events are batched before delivery. Check the EVENTS table for recent records. Note that there may be reporting lag due to Snowflake COPY\_HISTORY refresh. For S3 Streaming: Check bucket permissions and verify files are being written to the expected path. For S3 Batch: Exports run on 24-hour cycles. Verify the last successful export completed. **Solution**: If the integration shows "Running" but data is not appearing, contact [Technical Services](mailto:technical-services@permutive.com) with your integration details and the time range of missing data. Routing automatically updates schemas when new event types or properties are added. If schema changes are not appearing: * There may be a delay in schema propagation (typically a few minutes) * The event or property may not be firing correctly * For customer-created datasets in BigQuery, permissions may prevent schema updates **Solution**: Verify the new events and properties are firing in Event Inspector. Wait a few minutes for schema changes to propagate. If using a manually created dataset in BigQuery, ensure the Permutive service account has **BigQuery Data Owner** role (not just BigQuery User). Permutive generates and manages routing table schemas automatically. Do not modify the tables created by routing, as this can break the routing pipeline. **Solution**: If tables have been modified, contact Permutive support at [technical-services@permutive.com](mailto:technical-services@permutive.com) to restore the correct schema and resume routing. In the future, avoid making any changes to routing-created tables. If you need custom table structures, create views or separate tables based on the routing data instead of modifying the source tables. This error occurs when your GCP organization has domain restriction policies that prevent external service accounts from being granted access. **Solution**: Review your organization's [IAM domain restriction policies](https://cloud.google.com/resource-manager/docs/organization-policy/restricting-domains). You may need to add Permutive's domain to the allowlist or adjust the policy to permit the specific service account. Contact your GCP organization administrator for assistance. ## Environment Compatibility #### Core Product Routing supports event collection from all Permutive SDK platforms: | Functionality | Web | iOS | Android | CTV | API Direct | | :----------------- | :----------- | :----------- | :----------- | :----------- | :----------- | | Event routing | | | | | | | Identity routing | | | | | | | Segment routing | | | | | | | Schema auto-update | | | | | | #### Destinations Routing supports the following destination platforms: **Streaming Routing:** * Google BigQuery * Snowflake (via [Snowpipe](https://docs.snowflake.com/en/user-guide/data-load-snowpipe-intro)) * Amazon S3 Streaming (GZIP compressed JSONL files) **Batch Routing:** * Amazon S3 Batch (scheduled exports, JSON or Parquet format) ## Guides Step-by-step instructions for working with Routing. Configure near real-time event streaming to Google BigQuery Set up automated data loading to Snowflake using Snowpipe Stream events to Amazon S3 in near real-time Configure scheduled daily exports to S3 in JSON or Parquet format ## Dependencies Routing requires the following products and infrastructure: | Dependency | Required | Description | | :--------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------- | | Permutive SDK | ✓ | The Permutive SDK (Web, iOS, Android, CTV, or API Direct) must be deployed to track events that will be routed to the destination. | | Cloud Platform Account | ✓ | A Google Cloud Platform project (for BigQuery), Snowflake account, or AWS account (for S3) is required to receive routed data. | | Event Tracking | ✓ | Events must be properly configured and firing. | ## Limits Routing adheres to the following product specifications and limits. #### Feature Limits | Feature | Description | Limit | | :------------------- | :----------------------------------------------------------------------------------- | :------------------------------------ | | Routing integrations | The number of active routing integrations. | 1 per destination type | | Historical data | Routing is point-in-time; no historical backfill is available when enabling routing. | No backfill | | Table modification | Do not modify routing-created tables, as this can break the pipeline. | No manual modifications are supported | #### Performance Limits | Metric | Description | Expected Latency | | :------------------- | :---------------------------------------------------------------------- | :--------------- | | BigQuery latency | Expected time for events to appear in BigQuery after being tracked. | \~5 minutes | | Snowflake latency | Expected time for events to appear in Snowflake after being tracked. | \~5 minutes | | S3 Streaming latency | Expected time for events to appear in S3 Streaming after being tracked. | \~5 minutes | | S3 Batch frequency | Frequency of scheduled batch exports to S3. | 24-hour cycles | ## FAQ Only **User Events** (events managed via the Events UI) are routed to your destination. These are the custom events you configure and include events like Pageview, SlotClicked, FormSubmission, LinkClick, VideoView, and any custom events you create. No, Routing is a completely separate system and does not impact any other Permutive features. Enabling or disabling Routing does not affect cohort creation, activation, reporting, or any other functionality. Routing simply creates a copy of your event data in your own data warehouse. No, Permutive employees do **NOT** have access to your routed data. The data is written to your own cloud infrastructure (your BigQuery project, Snowflake account, or S3 bucket), and Permutive employees would need explicit permission from you to access it. You have full control over who can access your routed data. Routed data includes: * All User Events with full properties * User identities and aliases * Segment IDs that users belong to * Cohort memberships * Domain information * Segment metadata See the [Data Types](#data-types) section for a breakdown of what each destination supports. No, Routing is point-in-time only. When you enable Routing, only new events going forward will be routed to your destination. There is no historical backfill of past data. If you need historical data, contact [Support](mailto:support@permutive.com) to discuss options. Cloud platforms charge for data ingestion and storage. Costs depend on your event volume and the destination you choose: * **BigQuery**: Google Cloud charges for streaming inserts and storage separately * **Snowflake**: Snowflake charges for compute (data loading) and storage * **S3**: AWS charges for storage and data transfer For specific pricing information about Routing, contact your Customer Success Manager. They can provide guidance on expected costs based on your event volume and help you understand cost optimization options. Yes, you can enable routing to one destination of each type (one BigQuery integration, one Snowflake integration, and one S3 integration). Each integration operates independently. Data format depends on the destination: * **BigQuery**: Native BigQuery tables with automatically managed schemas * **Snowflake**: Three tables (EVENTS, SYNC\_ALIASES, SEGMENTS) with structured schemas * **S3 Streaming**: GZIP compressed newline-delimited JSON (.jsonl.gz) with Hive-style partitioning * **S3 Batch**: JSON (GZIP compressed) or Parquet (Snappy compressed) with Hive-style partitioning Disabling or deleting Routing integrations requires assistance from Permutive. Contact support at [technical-services@permutive.com](mailto:technical-services@permutive.com) to request disabling routing. Data partitioning varies by destination: * **BigQuery**: Tables named `{event_name}_events` (e.g., `Pageview_events`) containing daily partitions based on event date. * **Snowflake**: Tables organized by data type (EVENTS, SYNC\_ALIASES, SEGMENTS) * **S3 Streaming**: Hive-style partitioning for events, sync\_aliases, and segments with structure `type={data_type}/year=YYYY/month=MM/day=DD/hour=HH/` * **S3 Batch**: Hive-style partitioning for events, sync\_aliases, and segments with structure `{data_type}/year=YYYY/month=MM/day=DD/hour=HH/` **Hive-style partitioning** organizes data into a folder hierarchy based on column values. For example, an events file from January 15, 2026 at 14:00 UTC would be stored at: ``` type=events/year=2026/month=01/day=15/hour=14/data.jsonl.gz ``` This structure enables efficient querying by allowing you to scan only the relevant time partitions. BigQuery routing can be configured via the Dashboard self-service. For Snowflake and S3 integrations, contact Permutive support at [technical-services@permutive.com](mailto:technical-services@permutive.com). The support team will work with you to configure the destination and provide the necessary authentication details. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Sources (Beta) Source: https://docs.permutive.com/products/connectivity/sources Set up connections to data warehouses and data lakes and import your data into Permutive ## Overview Sources is the area within the Connectivity suite where you manage your external data platform connections and imports. From Sources, you can establish connections to your data warehouses and data lakes, configure imports from your chosen tables, and bring that data into Permutive for audience building and activation. **Beta Program** We're still in the process of fine-tuning and building out this product, so we're grateful for your participation in our Beta phase. We aim to provide a smooth experience, however, you may encounter some minor hiccups or unexpected behavior as we continue to refine and improve the product. Your feedback is invaluable during this time, so please don't hesitate to share your thoughts and experiences with us. ## Why Use Sources? **Centralize your data connections** — Sources provide a single point of management for all your external data platforms. Rather than managing multiple integration methods, you configure your connections once and reuse them across multiple imports. **Enable rich audience building** — By connecting your data warehouses and storage systems, you unlock the ability to build cohorts based on data that lives outside of Permutive's behavioral collection. This includes CRM data, purchase history, offline conversions, and any other user data you store externally. **Maintain security and control** — Source connections use secure authentication methods and allow you to control exactly which data Permutive can access. You choose which tables and columns to import, keeping sensitive data protected. **Support flexible import types** — A single source connection can power multiple types of imports: user profile data for trait-based targeting, activity data for time-bound events, user identity data for Identity Graph enrichment, and group identity data for household cohorts. ## Concepts ### Definitions * **Source**: An external data platform (such as a data warehouse or cloud storage) that Permutive connects to for importing data. Sources are configured through the Catalog and managed on the Connections page. * **Catalog**: The interface within the Connectivity Suite where you browse available source platforms and initiate new connections. The Catalog displays all supported sources with their categories and connection status. * **Connection**: A configured link between Permutive and a source platform. Connections store authentication credentials and settings needed to access your data. A single platform can have multiple connections (e.g., connections to different BigQuery projects). * **Import**: A configured data pipeline that pulls specific data from a source connection into Permutive. Multiple imports can be created from a single connection. ### Supported Sources Permutive supports connections to the following source platforms: | Platform | Category | Description | | :------------------- | :------------- | :----------------------------------------------------------------- | | Google BigQuery | Data Warehouse | Connect to BigQuery datasets to import tables containing user data | | Snowflake | Data Warehouse | Connect to Snowflake databases and schemas for data imports | | Amazon S3 | Cloud Storage | Connect to S3 buckets containing data files | | Google Cloud Storage | Cloud Storage | Connect to GCS buckets containing data files | We're working towards making more source connection types available. If there is a specific data source you'd like to connect, please contact your Customer Success Manager. ## Workflows ### Browsing the Catalog The Catalog page is your starting point for discovering and connecting to new source platforms. Use the Catalog to: * Browse available source connectors * Search and filter platforms by name or category When you find a platform you want to connect to, click the **Connect** button on that platform's card to begin the connection workflow. You can create multiple connections to a single platform. Even if you already have a connection to a platform (e.g., BigQuery), you can create additional connections to other BigQuery projects or datasets. ### Creating a Source Connection To create a new source connection: 1. Navigate to the **Catalog** page within Connectivity 2. Find and click on the platform you want to connect to 3. Click the **Connect** button 4. Provide a descriptive name for your connection 5. Enter the required authentication credentials for the platform 6. Configure any additional connection settings 7. Click **Save** to create the connection Once created, your connection will appear on the **Connections** page with a "Processing" status while Permutive validates the credentials. After processing completes, the status changes to "Active" and the connection is ready for use. ### Managing Connections The **Connections** page provides a centralized view of all your source and destination connections. From here you can: * View connection status (Active, Inactive, Processing, Deprecated) * See which platform and type each connection belongs to * Access connection details and settings | Column | Description | | :------ | :---------------------------------------------------------------- | | Name | The name you provided when creating the connection | | Source | The platform name (e.g., BigQuery, Snowflake) | | Type | The connection type (currently "Source" for imports) | | Created | Date the connection was created | | Status | Current availability: Active, Inactive, Processing, or Deprecated | ### Connection Statuses Source connections have one of four statuses: * **Active**: The connection is available and can be used to create imports * **Inactive**: The connection has been disabled and cannot be used for imports * **Processing**: A new connection is being validated. This typically completes within a few minutes * **Deprecated**: The connection is no longer supported or has been retired ### Import Types Once a source connection is active, you can create imports to bring different types of data into Permutive: * **User Profile Data** — Import static user attributes such as demographics and subscription tiers for trait-based cohort building and targeting. * **User Activity Data** — Import time-stamped event or behavioral data such as purchase history or content interactions for time-bound audience building. * **Identity Graph Data** — Import user identity mappings and household graphs to enrich Permutive's Identity Graph with identifiers and group relationships from your data warehouse. See [Importing User Identity](/guides/signals/identity/importing-user-identity) and [Importing User Group Memberships](/guides/signals/identity/importing-user-group-memberships) for step-by-step guides. ## Guides Step-by-step instructions for working with Sources. Set up a connection to Google BigQuery Set up a connection to Snowflake Set up a connection to Amazon S3 Set up a connection to Google Cloud Storage Create and configure a data import from an active connection Add new columns to an existing import when your source schema changes ## Troubleshooting The following issues may occur when working with Sources: If your connection remains in "Processing" status for more than 15 minutes: * Verify that the authentication credentials are correct * Check that the source platform is accessible and not experiencing outages * Ensure any required network permissions (IP allowlisting, firewall rules) are configured **Solution**: Try creating the connection again with verified credentials. If the issue persists, contact [Support](mailto:support@permutive.com) with your connection details. A connection may become inactive if: * Authentication credentials have expired or been revoked * The source platform configuration has changed **Solution**: If a connection becomes inactive, you'll need to create a new connection with valid credentials. Connections cannot be reactivated once they become inactive. If you can connect but cannot see expected tables: * Verify the service account or user has read permissions on the specific tables * Check that you're connecting to the correct project, dataset, or schema * Confirm the tables contain data and are not empty **Solution**: Review and update permissions in your source platform, ensuring the Permutive service account has appropriate read access. Authentication failures typically occur due to: * Incorrect credentials (wrong password, expired token, invalid key file) * Missing or incorrect project/account identifiers * Service account not enabled or activated **Solution**: Double-check all credential fields, regenerate credentials if needed, and ensure the service account is properly configured in your source platform. If you don't see your desired platform in the Catalog: * The connector may not yet be supported * The connector may be in limited availability or beta * Your workspace may not have access to certain connectors **Solution**: Contact your Customer Success Manager to inquire about connector availability and request access if needed. ## Environment Compatibility #### Core Product Sources functionality is available in the Permutive Dashboard: | Functionality | Web Dashboard | API | | :--------------------- | :------------ | :---------- | | Browse Catalog | | | | Create connections | | | | Manage connections | | | | View connection status | | | Source management is currently available only through the Permutive Dashboard. API support for programmatic connection management is planned for future releases. #### Supported Source Platforms | Platform | Availability | Notes | | :------------------- | :------------------------------- | :------------------------------------------------------- | | Google BigQuery | Generally Available | Full support for standard and partitioned tables | | Snowflake | Generally Available | Supports all Snowflake cloud providers (AWS, Azure, GCP) | | Amazon S3 | Generally Available | Supports CSV and Parquet files | | Google Cloud Storage | Generally Available | Direct GCS bucket access | ## Dependencies Sources rely on the following products and features being configured for your organization: | Dependency | Required | Description | | :--------------------- | :------- | :------------------------------------------------------------------------------------------------------------------ | | Connectivity Suite | ✓ | Sources are part of the Connectivity Suite. Access to Connectivity must be enabled for your workspace. | | Source Platform Access | ✓ | You must have appropriate permissions and credentials in your source platform to establish connections. | | Imports | \~ | While not required for creating connections, Imports are needed to actually bring data from Sources into Permutive. | ## Limits Sources adhere to the following product specifications and limits. #### Feature Limits | Feature | Description | Limit | | :------------------------ | :------------------------------------------------------------- | :------------ | | Connections per platform | Number of connections you can create to a single platform type | No limit | | Connections per workspace | Total number of source connections per workspace | No hard limit | #### Performance Limits | Metric | Description | Limit | | :------------------------- | :----------------------------------------------------- | :-------------------- | | Connection validation time | Time to validate a new connection | Typically 1-5 minutes | | Schema refresh time | Time to refresh available tables/columns from a source | Varies by source size | #### Usage Limits | SKU | Description | Limit | | :--------------- | :-------------------------------------------------- | :---------------- | | Data capacity | Amount of data that can be imported through sources | Based on contract | | Import frequency | How often imports sync from sources | Every 24 hours | For specific limits related to your contract, contact your Customer Success Manager. ## FAQ A **Source** refers to the external platform type (e.g., BigQuery, Snowflake), while a **Connection** is a specific configured link to that platform with your credentials. You might have one BigQuery source type but multiple BigQuery connections to different projects. Yes, you can create connections to multiple different data warehouse platforms simultaneously. For example, you can have active connections to both BigQuery and Snowflake, and create imports from either. Permutive requires read-only access to the specific tables you want to import. We recommend creating a dedicated service account with minimal permissions scoped to only the necessary datasets or schemas. When you create an import from a source, Permutive reads and processes the specified data to make it available for audience building. The data is processed according to your import configuration and Permutive's data handling policies. For composable setups, data may remain in your cloud environment. Deleting connections is not currently available in the dashboard but is coming soon. If you need to delete a connection, please contact [Permutive support](mailto:support@permutive.com). Note that deleting a connection will cause any imports using that connection to fail. Editing connections is not currently supported. If something is wrong with a connection or credentials need to be updated, you'll need to create a new connection with the correct details and migrate your imports to the new connection. If your source platform is unavailable, scheduled imports will fail and retry according to the configured retry policy. Connection status may not change immediately, but you can check import run history to see if syncs are failing. Currently, Sources only support importing data into Permutive. Exporting data back to your data warehouse or storage is planned for a future release. Permutive can detect and apply certain types of schema changes to an existing import: * **Add new columns** to an existing import and choose which of the new columns to include * See detected changes flagged as **Supported** (can be accepted from the dashboard) or **Unsupported** (require reverting the change at source) Unsupported changes — removing, renaming, reordering, or changing the data type of existing columns — must be reverted at source. Leaving unsupported changes in place can cause cohorts referencing the affected import to stop functioning correctly. For the end-to-end workflow, detection details, and CSV vs Parquet differences, see [Actioning Updates to Your Source Schema](/guides/connectivity/sources/actioning-updates-to-your-source-schema). ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Curation Source: https://docs.permutive.com/products/curation Package and monetize publisher signals across multi-publisher curated deals ## Overview **Curation** enables publishers to package and monetize their first-party audience signals across premium multi-publisher deals within SSP Curation Marketplaces. Permutive aggregates publisher cohorts into standardized audiences and curated packages that advertisers can access through Monetize, Index Exchange Marketplaces, Pubmatic, Google Curated Deals, and other SSP curation seats. Publishers participate in curation by opting in their signals, which are then made available to advertisers as part of Permutive-branded curated deals. This allows publishers to unlock incremental programmatic revenue from their open marketplace inventory without the operational overhead of managing curation themselves. ## Why Use Curation? **Unlock new programmatic revenue** — Curation provides access to advertiser budgets that would otherwise be unreachable due to signal loss and cookie deprecation. By participating in Permutive's curated audiences, publishers can monetize 100% of their audience, not just the 30% of cookied inventory. **Maintain data control and transparency** — Unlike traditional curation intermediaries, Permutive gives publishers full control over which cohorts participate in curation. Publishers can exclude specific signals from curated deals to avoid channel conflict with their direct-sold motions, and Permutive maintains transparent fee structures capped at 40%. **Zero operational overhead** — Permutive handles all demand generation, audience packaging, deal creation, campaign optimization, and buyer reporting. Publishers participate passively once technical setup is complete, with no ongoing management required. ## Workflows ### Publisher Opt-In Publishers sign Permutive's Curation Order Form to participate in the curation offering. This grants Permutive permission to package the publisher's cohorts into multi-publisher curated audiences and make them available to advertisers through SSP curation seats. ### Technical Setup Publishers configure Prebid.js integration (for Monetize, Index Exchange, Pubmatic) and/or Google Curate integration to enable cohort signals to flow to SSP curation seats. The Permutive SDK automatically handles passing cohort memberships to these platforms in real-time. ### Cohort Controls Configuration Publishers review their Standard Cohorts and Custom Cohorts to determine which signals should be excluded from curation. Standard Cohorts can be toggled on/off directly in the UI, while Custom Cohorts require applying a `curation_exclusion` tag to prevent inclusion in curated deals. ## Setup ### Prerequisites * Active Permutive SDK deployment (Web platform required) * Standard or Custom Cohorts enabled for Curation * Prebid.js integration and/or Google Ad Manager with Google Curate enabled * Signed Curation Order Form (contact your Customer Success Manager) ### Enablement Process Curation is a publisher opt-in product. To enable Curation for your organization: Contact your Customer Success Manager to discuss the curation opportunity, review commercial terms, and receive the Curation Order Form. Review and execute the Curation Order Form. This grants Permutive permission to package your cohorts into multi-publisher curated audiences. Configure Prebid.js submodule and/or Google Curate integration to enable cohort signals to flow to SSP curation seats. See the [Configuring Prebid for Curation](/guides/curation/configuring-prebid-for-curation) guide. Review and configure which cohorts should be excluded from curation to avoid channel conflict with direct-sold campaigns. See the [Managing Cohort Exclusions](/guides/curation/managing-cohort-exclusions) guide. ## Guides Step-by-step instructions for working with Curation. Set up Prebid.js RTD module to enable cohort signals for SSP curation marketplaces Enable Google Curate in Google Ad Manager to share signals with Permutive's curation seat Control which cohorts participate in curation using UI toggles and exclusion tags ## Environment Compatibility #### Core Product | Functionality | Web | iOS | Android | CTV | API Direct | | :----------------------- | :----------- | :---------- | :---------- | :---------- | :---------- | | Cohort signal activation | | | | | | | Standard Cohorts | | | | | | | Custom Cohorts | | | | | | #### Activation Curation requires Prebid.js integration or Google Ad Manager with Google Curate. Supported SSP curation marketplaces: * Monetize * Index Exchange Marketplaces * Pubmatic * Google Curate (via Google Ad Manager) * OpenX ## Dependencies | Dependency | Required | Description | | :---------------- | :------- | :----------------------------------------------------------------------------------------------------------------- | | Permutive SDK | ✓ | Web SDK deployment required to collect events and enable real-time cohort evaluation. | | Standard Cohorts | ✓ | Standard Cohorts must be enabled and populating to provide the primary signals for curated audiences. | | Prebid.js | \~ | Required for Monetize, Index Exchange, and Pubmatic curation activation. Not required if using Google Curate only. | | Google Ad Manager | \~ | Required for Google Curate activation. Not required if using Prebid-based curation only. | ## Changelog No changes listed yet. For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Products Source: https://docs.permutive.com/products/overview Explore Permutive's suite of products for audience activation, data collaboration, and curation Permutive provides a unified platform that enables publishers to build, activate, and monetize their first-party audiences. Our products work together to address 100% of your audience in real-time while maintaining privacy by design. ## Core Products Connect your data ecosystem with Permutive. Ingest data from websites, apps, and external sources, then route activated audiences to your advertising and marketing platforms. Build and manage audience signals using cohorts, identity resolution, and marketplace data. Create custom, contextual, and modeled audiences for precise targeting. Plan campaigns, optimize targeting, and measure performance. Access audience, campaign, prospecting, and revenue insights powered by AI-driven workflows. Collaborate securely with advertisers to match, model, and activate audiences without sharing personally identifiable information or moving data. Package publisher signals into curated audiences available through SSP marketplaces, enabling buyers to transact on premium, multi-publisher inventory via deal IDs. # Contextual Cohorts Source: https://docs.permutive.com/products/signals/cohorts/contextual Target ads based on page content without processing user data ## Overview **Contextual Cohorts** enable publishers to target ads based on contextual data derived from page content without processing or storing any user data. Since no user consent is required, Contextual Cohorts allow publishers to reach users who have not given consent and cannot be targeted with behavioral data today. Contextual Cohorts work by analyzing page-level signals such as automated content classifications, editorial metadata, and affinity scores from consented users. This enables publishers to create a differentiated contextual targeting offering that combines the precision of content understanding with the unique insights from their first-party data. ## Why Use Contextual Cohorts? **Reach users without consent requirements** — Contextual Cohorts operate entirely on page-level data, requiring no user consent. This enables publishers to monetize inventory from users who haven't consented to data collection, expanding addressable inventory while maintaining privacy compliance. **Leverage multiple contextual signals in one place** — Permutive's Contextual Cohorts bring together multiple data sources including NLP classifications from providers like IBM Watson, editorial tagging from your CMS, third-party classification services, and affinity insights from consented users—all in a single platform. **Build a differentiated contextual offering** — By combining automated content classifications with affinity scores derived from your first-party data, you can create contextual targeting that reflects your unique audience understanding and differentiates your advertising offering from generic contextual solutions. **Maintain unified targeting management** — Manage both user-based targeting (Custom Cohorts) and contextual targeting (Contextual Cohorts) within the same dashboard, simplifying campaign setup and enabling consistent targeting logic across consent and non-consent scenarios. ## Concepts ### Definitions * **Contextual Cohorts**: Audience segments built on page-level attributes rather than user-level data. They target content that matches specific criteria such as categories, keywords, sentiment, or affinity to Custom Cohorts. Because they operate purely on content data, they don't require user consent and can be used across all inventory. * **Content Classifications**: Automated categorizations of your web content generated by NLP (Natural Language Processing) providers. These classifications extract structured data such as categories, concepts, entities, keywords, sentiment, and emotion. * **Page Properties**: Custom metadata you collect about your content, including editorial tags (e.g., section, author, topic), in-house classifications, third-party classification data, and content metadata (e.g., word count, publish date). The available Page Properties mirror the custom properties from your Pageview event schema. * **Affinity Cohorts**: Allow you to leverage insights from consented users to make contextual targeting decisions. Permutive calculates an affinity score for each URL based on how likely users in a specific Custom Cohort are to engage with that content compared to the site average. An affinity score of 100 indicates site average engagement, scores above 100 indicate higher engagement (e.g., 200 = 2x more likely), and scores below 100 indicate lower engagement. * **Keyword Explorer**: An AI-powered feature that uses machine learning to offer keyword recommendations for building interest-based cohorts. By analyzing the similarities and connections between keywords, it suggests relevant terms that maximize both reach and relevance. ## Workflows ### Creating a Contextual Cohort Publishers use the Contextual Cohort builder to define targeting rules using Content Classifications, Page Properties, or Affinity Cohorts. They select providers, configure dimensions, and combine multiple conditions using AND/OR logic to create precise contextual targeting criteria. ### Deploying and Managing Contextual Cohorts All Contextual Cohorts are displayed in the Contextual section of the Permutive Dashboard. The cohort list provides an overview including cohort name, description, targeting logic, creation date, status (Enabled/Disabled), and pageview reach forecast (where available). ### Activating Contextual Cohorts Contextual Cohorts can be activated to advertising platforms such as Google Ad Manager, Xandr, and other destinations. The Contextual API passes cohort memberships to these platforms in real-time for ad targeting based on the content users are currently viewing. ## Guides Step-by-step instructions for working with Contextual Cohorts. Build audience segments based on page content Enable and configure NLP classification providers Test how providers classify your content Activate contextual cohorts in GAM for ad targeting Advanced use cases for Contextual Cohorts: Get AI-powered keyword recommendations Bring your own contextual classification data via a custom endpoint ## Troubleshooting **Cause**: You're trying to use Content Classifications but no classification provider is enabled for your workspace. **Solution**: * Navigate to *Contextual > Catalog* * Enable at least one provider by contacting your Customer Success Manager or entering your own API key **Cause**: You've selected a Page Property that isn't being passed to the Contextual API. **Solution**: * Review your Contextual API implementation * Ensure the property is included in the API call * Refer to the [Contextual API documentation](/api/contextual/introduction) **Cause**: You've selected a Custom Cohort that cannot be used for Affinity Cohorts (e.g., modeled cohort, standard cohort, or cohort using third-party data). **Solution**: * Select a different Custom Cohort built from first-party behavioral data * Ensure the cohort has sufficient engagement data for affinity score calculation **Cause**: Your classification provider has reached its usage quota for the current time period. **Solution**: * Wait for the quota to reset (timeframe depends on provider settings) * Contact your Customer Success Manager to increase your quota * Optimize quota usage by restricting classifications to specific domains or using selective classification thresholds **Cause**: The selected Custom Cohort doesn't have enough engagement data to calculate reliable affinity scores. **Solution**: * Allow more time for engagement data to accumulate * Use cohorts with higher traffic or engagement levels * Consider alternative targeting methods (Content Classifications or Page Properties) **Cause**: The Contextual API hasn't been integrated on your website, so contextual cohorts cannot be deployed. **Solution**: * Follow the [Contextual API implementation guide](/api/contextual/introduction) * Contact [Technical Services](mailto:technical-services@permutive.com) for implementation support * Verify implementation using browser developer tools (look for `segment?k=*API_KEY*` requests) ## Environment Compatibility #### Core Product Contextual Cohorts functionality is supported on the following platforms: | Functionality | Web | iOS | Android | CTV | API Direct | | :--------------------------- | :----------- | :----------- | :----------- | :-------------------- | :----------- | | Cohort creation & management | | | | | | | Content classification | | | | Varies by environment | | | Contextual API | | | | Varies by environment | | | Cohort activation | | | | Varies by environment | | #### Platform-Specific Notes | Platform | Support | Notes | | :------- | :-----: | :--------------------------------------------------------- | | Web | ✓ | Full support for all features | | iOS | ✓ | Full support for all features | | Android | ✓ | Full support for all features | | CTV | \~ | Support depends on specific environment and implementation | #### Activation Contextual Cohorts can currently be activated to the following destinations: * Google Ad Manager * Xandr ## Dependencies Contextual Cohorts rely on the following products and features being configured for your organization: | Dependency | Required | Description | | :--------------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------- | | Contextual API | ✓ | The Contextual API must be implemented on your website to enable real-time cohort deployment and activation. | | Content Classification | \~ | Required if building cohorts based on NLP classifications (IBM Watson, TextRazor, etc.). Not required if using only Page Properties or Affinity. | | Custom Cohorts | \~ | Required only for Affinity Cohorts. You must have Custom Cohorts created with sufficient consented user engagement data. | | Permutive SDK | \~ | Required for collecting user engagement data that powers Affinity Cohort calculations. Not required for basic Content Classification cohorts. | ## Limits Contextual Cohorts adhere to the following product specifications and limits. #### Feature Limits | Feature | Description | Limit | | :-------------------------------- | :------------------------------------------------------------------------ | :----------------- | | Content classification providers | The number of classification providers that can be enabled simultaneously | Multiple supported | | Contextual cohort rule complexity | The maximum number of conditions and AND/OR statements in a single cohort | No hard limit | | Taxonomies per provider | The number of taxonomies supported by a classification provider | Varies by provider | #### Performance Limits | Metric | Description | Limit | | :--------------------------- | :--------------------------------------------------------------------- | :----------------------------------------- | | Classification time | The time it takes to classify a new URL | Varies by provider (typically 2-5 seconds) | | Contextual API response time | The time for the Contextual API to return cohort memberships for a URL | \< 100ms | #### Usage Limits | SKU | Description | Limit | | :---------------------- | :--------------------------------------- | :------------------------------ | | Contextual Cohorts | Maximum contextual cohorts per workspace | \[Contact support] | | Content Classifications | Classification quota per provider | Varies by provider and contract | #### Data Retention Content classifications are cached and retained to optimize performance and quota usage. Classification data may be refreshed based on content update frequency and provider settings. ## Content Classification Providers Permutive supports multiple content classification providers, allowing you to choose the best solution for your content and requirements. ### Available Providers | Provider | Status | Categories | Keywords | Entities | Concepts | Sentiment | Emotion | | :---------------------------------------------------------------------------------------- | :---------- | :--------: | :------: | :------: | :------: | :-------: | :-----: | | IBM Watson | ✓ Available | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | TextRazor | ✓ Available | ✓ | | ✓ | ✓ | | | | OS Data Solutions | ✓ Available | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Silverbullet 4D | ✓ Available | ✓ | | | | | | | Permutive Brand Safety | ✓ Beta | ✓ | | | | | | | [Webhook (Custom)](/guides/signals/cohorts/contextual/custom-classifications-via-webhook) | ✓ Available | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ## FAQ **Custom Cohorts** target users based on their behavior and data, requiring user consent. **Contextual Cohorts** target page content without processing user data, requiring no consent. Contextual Cohorts allow you to reach non-consented users by targeting the content they're viewing rather than their behavioral profile. Yes! You can use both targeting approaches in parallel or combine them strategically. For example, use Custom Cohorts for consented users and fall back to Contextual Cohorts for non-consented users. Additionally, you can use Affinity Cohorts to apply insights from Custom Cohorts to contextual targeting. Affinity scores are normalized against cohort size to prevent large cohorts from dominating scores. However, for very large cohorts, affinity scores will gravitate toward 100. An extreme example: an "everyone" cohort will have an affinity score of 100 for any URL since it represents the site average. We generally recommend starting with a threshold greater than 200 (2x or 100% more likely to engage). Higher thresholds provide more relevance but less scale. You can test different thresholds based on your campaign needs and audience characteristics. The best provider depends on your content type, language, and specific requirements: * **IBM Watson**: Comprehensive dimensions including sentiment and emotion, strong multi-language support * **TextRazor**: Excellent for entity extraction and conceptual analysis * **OS Data Solutions**: Optimized for German publishers, includes BVDW taxonomy * **Permutive Brand Safety**: Focused on brand safety and suitability classifications Use the Preview feature to test different providers against your content. Content is typically classified when first discovered or when Pageview traffic exceeds your selective classification threshold. Classifications are cached to optimize quota usage. The refresh frequency depends on your provider settings and content update patterns. Editing capabilities vary based on your configuration and provider. Some cohorts can be edited directly in the dashboard, while others may require creating a new cohort. Check the individual cohort settings to see if editing is available. Pageview reach forecasts may not be available for all cohorts depending on: * Data availability for the specified conditions * Cohort complexity * Provider limitations Reach forecasts are being progressively rolled out across all contextual cohort types. Export functionality for Contextual Cohorts is currently in development. Contact your Customer Success Manager for updates on availability. Yes! Permutive's Contextual Cohorts (when configured as Standard Contextual Cohorts) can be exposed to demand platforms via Google AdX through Publisher Provided Signals, enabling buyers to bid more intelligently based on your first-party contextual signals. ## Changelog ### 2024 **November 2024** * Added provider preview functionality to the Catalog * Improved quota management and selective classification thresholds * Enhanced Affinity Cohort visualization in the dashboard **June 2024** * Expanded support for multiple classification providers * Added custom taxonomy support via Webhook integration * Introduced unified Content Classifications architecture **March 2024** * Released Permutive Brand Safety provider (beta) * Added support for confidence thresholds in cohort targeting ### 2023 **October 2023** * Introduced pageview reach forecasts for select cohort types * Enhanced Affinity Cohort methodology with normalized scoring **July 2023** * Added OS Data Solutions provider with BVDW taxonomy support * Expanded editing capabilities for existing contextual cohorts **April 2023** * Introduced TextRazor provider integration * Added support for IAB 3.0 taxonomy ### 2022 **December 2022** * Launched Affinity Cohorts for contextual targeting * Added Page Properties support for editorial metadata **September 2022** * Released Contextual Cohorts with IBM Watson integration * Introduced Contextual API for real-time targeting For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Custom Cohorts Source: https://docs.permutive.com/products/signals/cohorts/custom Build flexible audience segments based on user behavior and data imports. ## Overview **Custom Cohorts** enable publishers to build flexible, granular audience segments based on any combination of user behaviors, properties, and data sources. These cohorts are computed on device for real-time targeting and activation. Publishers can create cohorts using first-party data (behaviors on their properties), second-party data (partner data), and third-party data, allowing for highly customized audience targeting that meets specific campaign needs and advertiser requirements. ## Why Use Custom Cohorts? **Build audiences for any campaign need** — Custom Cohorts provide publishers with complete flexibility to create audience segments tailored to specific campaigns and advertiser briefs. Whether targeting users based on content engagement, recency, frequency, or custom event properties, Custom Cohorts enable publishers to meet diverse targeting requirements. **Leverage multiple data sources** — Build cohorts not just from your own first-party behavioral data, but also incorporate second-party data from partners and third-party data providers. This allows publishers to enrich their audience understanding and create more valuable, targetable segments for advertisers. **Real-time Edge Computation** — Custom Cohorts are computed on the Edge, enabling real-time user segmentation and activation on device. This ensures that your audience targeting is always current and responsive to user behavior as it happens. ## Concepts ### Definitions * **Custom Cohort**: A publisher-defined audience segment that groups users based on specific rules and conditions. Users are added to or removed from cohorts in real-time as they meet or no longer meet the cohort criteria. * **Cohort Builder**: The UI tool within the Permutive Dashboard that allows publishers to create and manage Custom Cohorts using a visual, no-code interface. * **Event**: An action or occurrence that is tracked by the Permutive SDK, such as a Pageview, VideoView, or custom event. Events form the foundation of cohort definitions. * **Properties**: Attributes associated with events that provide additional context, such as `client.url`, `client.title`, or custom properties. Properties enable fine-grained filtering when building cohorts. * **Recency**: The time window in which a user must have performed an action to qualify for a cohort (e.g., "in the past 30 days"). * **Frequency**: The number of times a user must have performed an action to qualify for a cohort (e.g., "at least 3 times"). * **User Group Cohort**: A special type of Custom Cohort that targets groups of users (such as households) rather than individual users. Behavior is aggregated across the entire group. * **Predicted Audience Size (PAS)**: An estimate of how many users would have qualified for a cohort based on the past 30 days of historical data. PAS is computed server-side when you click *Calculate* in the Cohort Builder. It is useful for forecasting whether a cohort will deliver sufficient scale for a campaign and is available immediately after defining cohort rules. * **Live Audience Size (LAS)**: The count of users who currently qualify for an active cohort. LAS is updated periodically (approximately every 4 hours) as users are evaluated against the cohort criteria. When a cohort is first deployed, LAS starts at zero and grows as qualifying users are processed. LAS represents the audience you can actually reach in live campaigns. ## Workflows ### Creating a Custom Cohort Publishers use the Cohort Builder to define rules for their audience segments. They select events, specify properties, set recency and frequency requirements, and combine multiple conditions using AND/OR logic to create precise targeting criteria. Creating a custom cohort ### Deploying a Custom Cohort Once a Custom Cohort is created and validated with an audience size calculation (this shows the [predicted audience size](#whats-the-difference-between-predicted-audience-size-and-live-audience-size)), you can control whether it is deployed to the Permutive SDK via the cohort's **status** toggle (on/off) on the cohort list page. After deployment, the cohort's [Live Audience Size](#whats-the-difference-between-predicted-audience-size-and-live-audience-size) will start at zero and grow over time as users are evaluated. The status toggle determines whether the cohort gets included for evaluation in the SDK: * **On (enabled)**: The cohort is included in the SDK and users will be evaluated against it in real-time * **Off (disabled)**: The cohort is not included in the SDK and users will not be evaluated against it Enabling or disabling a cohort triggers an automatic rebuild of your SDK. The cohort change should be available shortly thereafter (typically within 2-5 minutes). Once deployed and enabled, the SDK evaluates users in real-time against the cohort criteria as they interact with your properties. Deploying a custom cohort All Custom Cohorts are displayed in the *Custom Cohorts* section of the Permutive Dashboard. The Cohort List provides an overview including cohort name, cohort code, tags, audience size (predicted and live), precision type (User or User Group), date created (UTC), status (Enabled/Disabled), and cohort logic. You can filter and sort cohorts by various attributes to find specific cohorts or analyze your cohort inventory. ### Activating a Custom Cohort Custom Cohorts can be activated to advertising platforms such as Google Ad Manager, Xandr, or other destinations. Activations can be configured during cohort creation or added later from the cohort management interface. The Permutive SDK manages passing cohort memberships to these platforms in real-time for ad targeting. Activating a custom cohort ## Guides Step-by-step instructions for working with Custom Cohorts. Build a new audience segment using the Cohort Builder Modify existing cohort rules and settings Remove cohorts from your project Export a report of all cohorts Advanced use cases for Custom Cohorts: Create cohorts based on Internet Service Provider Build cohorts from external data sources Target households instead of individuals ## Troubleshooting The following errors may occur when working with Custom Cohorts: If your cohort fails to calculate an audience size, this may be due to: * Complex cohort rules that time out during calculation * Using unsupported event types or properties * Data processing delays **Solution**: Try simplifying your cohort rules, verify that the events and properties exist in your data, and wait a few minutes before trying again. If the issue persists, contact [Support](mailto:support@permutive.com). The [Live Audience Size (LAS)](#whats-the-difference-between-predicted-audience-size-and-live-audience-size) starts at zero for every new cohort and grows over time as users are evaluated. If LAS remains at zero or is unexpectedly small, check: * The cohort was recently deployed — allow time for users to visit and be evaluated * The Permutive SDK may not be deployed on all relevant pages * The recency window may be too narrow or frequency threshold too high * Event properties may have typos or incorrect values * For cohorts using audience imports, imported users must visit your properties before they can be counted in LAS **Solution**: Allow at least 24–48 hours after deployment for LAS to begin reflecting users. Broaden your criteria if needed, verify event tracking is working correctly, and check property values in the Event Inspector. For import-based cohorts, see [Understanding Audience Size for Import Cohorts](/guides/signals/cohorts/custom/using-audience-imports#understanding-audience-size-for-import-cohorts). This is expected behavior for newly deployed cohorts. The [Predicted Audience Size (PAS)](#whats-the-difference-between-predicted-audience-size-and-live-audience-size) reflects historical data — how many users *would have* qualified in the past 30 days. The [Live Audience Size (LAS)](#whats-the-difference-between-predicted-audience-size-and-live-audience-size) counts only users evaluated since the cohort was deployed. * LAS will converge toward PAS as more users visit your properties over the following days and weeks * For most behavioral cohorts, expect LAS to stabilize within approximately 30 days * For cohorts using audience imports, convergence may take longer because imported users must visit your properties and be identified before they can be evaluated **Solution**: This is normal — no action is required. If LAS remains at zero after several days, check that the SDK is up to date and that import data (if applicable) is being ingested correctly. Cohort save errors typically occur due to: * Missing required fields (name, tags) * Invalid cohort logic or syntax * Permissions issues **Solution**: Ensure all required fields are filled out, review your cohort logic for any issues, and verify you have permission to create cohorts in your workspace. If a cohort is not showing up in your activation platform: * The activation may not be properly configured * There may be a delay in cohort deployment (typically a few minutes) * The cohort may have no users qualifying for it * API keys or integrations may not be properly set up **Solution**: Verify activation configuration, wait a few minutes for deployment, check cohort audience size, and ensure integration credentials are correct. ## Environment Compatibility #### Core Product Custom Cohorts functionality is supported on the following platforms: | Functionality | Web | iOS | Android | CTV | API Direct | | :--------------------------- | :----------- | :----------- | :----------- | :----------- | :-------------- | | Cohort creation & management | | | | | | | Event collection | | | | | | | Real-time segmentation | | | | | | | Cohort activation | | | | | \* | \* For API Direct, activation information is returned in the response from the Permutive API, but the client is typically responsible for integrating with the activation destination. #### Activation Custom Cohorts can be activated to all standard Permutive activation destinations, including: * Google Ad Manager * Xandr * Amazon Publisher Services * Prebid-supported SSPs (Magnite, Rubicon, etc.) * And many more via our activation integrations For a complete list of supported activation destinations, please see our [Integrations documentation](/integrations/overview). #### User Group Cohorts User Group Cohorts have limited platform support: | Functionality | Web | iOS | Android | CTV | API Direct | | :--------------- | :----------- | :----------- | :----------- | :----------- | :-------------- | | Event collection | | | | | | | Activation | | | | | \* | \* Activation information is returned in the response from the Permutive API, but the client is responsible for integrating with the activation destination. User Group cohorts are activated against User IDs, not Group IDs. ## Dependencies Custom Cohorts rely on the following products and features being configured for your organization: | Dependency | Required | Description | | :--------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Permutive SDK | ✓ | The Permutive SDK (Web, iOS, Android, or CTV) must be deployed to track events and enable real-time cohort evaluation and activation. | | Event Tracking | ✓ | Events must be properly tracked and transmitted to Permutive for cohorts to function. At minimum, Pageview events are required. | | Identity Graph | \~ | When using second-party data or User Group Cohorts, appropriate identifiers must be configured in Identity Graph and shared between you and the data partner. | | Connectivity | \~ | Required for importing user group membership data when using User Group Cohorts, or for certain advanced cohort use cases. | | Audience Imports | \~ | Required only if building cohorts based on external user lists or segments (second-party or third-party data). | ## Limits Custom Cohorts adhere to the following product specifications and limits. #### Feature Limits | Feature | Description | Limit | | :-------------------------- | :---------------------------------------------------------------------------------------- | :----------------- | | Cohort rule complexity | The maximum number of conditions, AND/OR statements, and nested logic in a single cohort. | No hard limit | | User Group identifier types | The number of user group identifier types supported per organization. | Multiple supported | | Custom event types | The number of custom event types that can be used in cohort definitions. | No limit | #### Performance Limits | Metric | Description | Limit | | :----------------------------- | :----------------------------------------------------------------------------------------------- | :------------------------ | | Audience size calculation time | The time it takes to calculate historical audience size for a cohort. | \~30 seconds to 2 minutes | | Cohort deployment time | The time it takes for a newly created cohort to be available in the SDK for evaluation. | 2-5 minutes | | Activation deployment time | The time it takes for a newly created or updated cohort to be available in activation platforms. | 2-5 minutes | #### Usage Limits | SKU | Description | Limit | | :------------- | :----------------------------------------------------- | :----------------- | | Custom Cohorts | Maximum number of active Custom Cohorts per workspace. | \[Contact support] | ## FAQ PAS and LAS measure different things. PAS tells you how many users *could* be in a cohort based on past behavior — it reflects potential. LAS tells you how many users *are* in the cohort right now — it reflects reality. A newly deployed cohort will always show a higher PAS than LAS because LAS needs time to build as users are evaluated. | | Predicted Audience Size (PAS) | Live Audience Size (LAS) | | :---------------- | :--------------------------------------- | :------------------------------------------------------------------ | | What it measures | Estimated users from the past 30 days | Actual users currently qualifying | | When available | Immediately after clicking *Calculate* | Builds over time after deployment | | How it's computed | Server-side, using historical data | Updated periodically (approximately every 4 hours) as users qualify | | Best used for | Forecasting, planning, and RFP responses | Monitoring active cohort performance and activation | | Starting value | Reflects 30-day historical data | Starts at 0, grows as users are evaluated | Custom Cohorts are publisher-defined segments built flexibly based on any combination of your first-party, second-party, and third-party data. Standard Cohorts are pre-defined, common audience segments based on NLP content classification that provide a standardized taxonomy across publishers for advertiser targeting. Yes, Custom Cohorts support VideoView and VideoEngagement events, allowing you to target users based on video viewing behavior. Note that VideoEngagement events may not be supported in all query contexts. When you calculate audience size in the Cohort Builder, the result is the **Predicted Audience Size (PAS)** — an estimate based on the past 30 days of historical data. Once the cohort is deployed, the **Live Audience Size (LAS)** starts at zero and grows over time as users are evaluated against the cohort criteria (LAS is updated approximately every 4 hours). For most behavioral cohorts, LAS begins to reflect meaningful numbers within hours to days depending on your traffic volume. For cohorts using audience imports, see [Using Audience Imports in Cohorts](/guides/signals/cohorts/custom/using-audience-imports#understanding-audience-size-for-import-cohorts) for additional considerations. Yes, you can export your Custom Cohorts list from the Cohort List view in the Permutive Dashboard. See our [Exporting Custom Cohorts](/guides/signals/cohorts/custom/exporting-custom-cohorts) guide for step-by-step instructions. When you delete a Custom Cohort, it is immediately removed from all activations. Users in that cohort will no longer be targeted based on that cohort criteria. This action cannot be undone. User Group Cohorts (also called Household Cohorts) aggregate behavior across a small group of users (such as a household) rather than individual users. This allows for household-level targeting. See our [Creating User Group Cohorts](/guides/signals/cohorts/custom/creating-user-group-cohorts) guide for details. Yes, Custom Cohorts are supported by our CCS API. The API enables you to pass event data directly to Permutive's API and receive cohort IDs for a given device/user in the response. This is useful for environments where SDK deployment is not feasible. There is no hard limit on cohort complexity, but very complex cohorts with many nested conditions may take longer to calculate audience size and could impact performance. We recommend keeping cohorts as simple as possible while meeting your targeting needs. Yes, you can duplicate a cohort directly from the Custom Cohorts list. Click the menu icon (⋮) next to the cohort and select *Duplicate*. The duplicated cohort will be created with the same rules and a "(Copy)" suffix in the name. For bulk cohort duplication across workspaces, contact [Support](mailto:support@permutive.com) for assistance. ## Changelog No changes listed yet. For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Classification Cohorts Source: https://docs.permutive.com/products/signals/cohorts/modeled-classification Reach your non-endemic audience ## Overview **Classification Cohorts** enable publishers to reach their **non-endemic audience** through data modeling. Publishers select a dataset containing categorized users—e.g. by age ranges—and Permutive uses the publisher's audience data to learn which behavioral patterns predict a user's membership to a distinct category, such as those in the `18–35` age range. Classification Cohorts can be built using either partner data or first-party data, allowing publishers to scale valuable data from brands and data providers or their own declared data to make informed targeting decisions across their whole audience. ## Why Use Classification Cohorts? **Reach your non-endemic audience** — When publishers receive briefs from advertisers requesting non-endemic audiences, they need a way to prove they have these audiences and that they can reach them at sufficient scale. Building Classification Cohorts with partner data allows publishers to reach their non-endemic audience by identifying which of their users have similar behavior to those of their partners, such as brands or data providers. For example, an auto publisher may work with Rolex to identify which of their users are luxury watch intenders. **Scale your declared first-party data across your whole audience** — With the availability of third-party data decreasing, more and more publishers are investing in capturing declared first-party data, such as user information collected during sign-up or the responses to surveys. Building Classification Cohorts with first-party data allows publishers to scale their high fidelity understanding of a subset of their audience to identify and reach users across their entire audience. For example, a food and drink publisher responding to a brief from a vegan food retailer may survey a small set of their users and leverage this dataset to identify users across their total audience according to diet preference. ## Concepts ### Definitions * **Seed Dataset**: A set of users each assigned to one of a fixed set of categories, called *labels*. For example, diet information from a brand might consist of a set of hashed email addresses each assigned one label from `vegan`, `vegetarian`, `carnivore`, `other`. * **Label**: The set of categories users in the seed dataset can belong to and that the model predicts for. A user is assigned to exactly one label since Classification Cohorts are designed for use-cases where you want to enforce mutual exclusivity. * **Classification Model**: The seed dataset is trained against the publisher's custom cohorts (excluding those using third-party data) to produce a Classification Model. For a given set of custom cohorts that a user belongs to, this model predicts the confidence with which the user belongs to each of the model's categories (labels). * **Confidence Threshold**: When we evaluate the model for a user, we will make a prediction if the user should be assigned to label `A`, `B`, or `C`. Publishers can specify a confidence threshold such that users are only assigned to a label if that minimum confidence threshold is met or exceeded. For higher thresholds, confidence in the accuracy of the classification increases but audience reach decreases. Conceptually, at 0% confidence, the model assigns a label for 100% of your users but its predictions are tantamount to choosing a label at random. At 100% confidence, the model assigns labels only for those users in the seed dataset, and therefore provides 0% increase in reach. * **Classification Cohort**: At each confidence threshold, publishers can create a Classification Cohort for each label in the model. A user falls into the Classification Cohort if the minimum threshold is met for that label. ### Data Model Classification data model * **One** Classification Model can be created for **one** partner Seed Dataset. * **Many** Classification Models can be created for **one** first-party Seed Dataset, parameterized by the choice of 2–4 custom cohorts for labels. * **Many** Classification Cohorts can be created for **one** Classification Model, parameterized by the confidence threshold and the model's labels. ## Workflows ### Building a Classification Model Publishers select a seed dataset—partner data or first-party data—which is joined with the publisher's custom cohort user data to form training data. During training, the Classification Model learns patterns in the cohorts that distinguish the different labels in the seed dataset. Building a classification model ### Deploying a Classification Cohort A Classification Cohort is created from a Classification Model by first choosing a confidence threshold and then selecting the label for which the publisher would like to create a Classification Cohort. Deploying a classification model ### Activating a Classification Cohort Once a Classification Cohort is deployed, the Permutive SDK manages the evaluation of users arriving on your sites and apps against the Classification Cohort to determine whether they meet the minimum threshold to fall into the label. To evaluate a user, the model uses the set of custom cohorts the user belongs to and predicts a level of confidence that the user falls into the label—if the confidence meets or exceeds the threshold for the Classification Cohort, then the user is deemed to fall into the cohort. Activating a classification cohort ## Guides Step-by-step instructions for working with Classification Cohorts. Build a model from partner or first-party data Create cohorts from your trained model Manage and filter your models and cohorts ## Troubleshooting If your Classification Model remains in "In Progress" status for an extended period (more than 12 hours), this is typically caused by one of the following issues: **Insufficient users in seed cohorts** — The model encountered issues due to an absence of users in the cohorts when they were initially created. This commonly occurs when cohorts and models are generated on the same day, resulting in insufficient training data. **Low unique users** — The seed dataset does not have enough unique users to train a reliable model. Each Custom Cohort used as a label must have at least 1,000 users. **Solution**: * Wait 24-48 hours after creating your seed cohorts to ensure they have accumulated sufficient users before creating a Classification Model * Verify that each cohort in your seed dataset has at least 1,000 users * If the issue persists after 12 hours, contact [Support](mailto:support@permutive.com) for assistance If your deployed Classification Cohort shows lower than expected reach or accuracy, this may be due to the confidence threshold setting. **Confidence threshold too high** — Setting a very high confidence threshold (e.g., 90%+) will increase accuracy but significantly reduce reach, as fewer users will meet the minimum confidence requirement. **Confidence threshold too low** — Setting a very low confidence threshold (e.g., below 50%) will increase reach but may reduce accuracy, as the model will classify more users with lower certainty. **Solution**: * Review the confidence threshold for your Classification Cohort * Adjust the threshold to balance reach and accuracy for your use case * Create multiple cohorts from the same model at different confidence thresholds to test performance * Remember: At 0% confidence, the model assigns labels to 100% of users but predictions are essentially random; at 100% confidence, only users in the seed dataset are classified If your Classification Cohort is not appearing in your ad server or activation destination, verify the following: **Activation not enabled** — The cohort may not be enabled for activation to your desired destination. **SDK rebuild required** — After creating a Classification Cohort, the Permutive SDK may need to be rebuilt to include the classification models addon. **Unsupported platform** — Classification Cohorts are only supported on Web, iOS, and Android platforms. They are not currently available for CTV or API Direct. **Solution**: * Verify the cohort is enabled for activation in the dashboard * Check that the activation destination (Google Ad Manager or Xandr) is properly configured * Allow time for the SDK to rebuild and deploy (this can take several minutes) * Confirm the platform where you're attempting activation is supported ## Environment Compatibility #### Core Product Classification Cohorts functionality is supported on the following platforms: | Functionality | Web | iOS | Android | CTV | API Direct | | :----------------- | :----------- | :----------- | :----------- | :---------- | :---------- | | Activation | | | | | | | Live audience size | | | | | | #### Insights | Functionality | Web | iOS | Android | CTV | API Direct | | :---------------- | :----------- | :----------- | :----------- | :---------- | :---------- | | Audience Insights | | | | | | | Campaign Insights | | | | | | #### Activation Classification Cohorts can be activated against the following destinations: * [Google Ad Manager](/integrations/advertising/ad-servers/google-ad-manager) * [Xandr](/integrations/advertising/ad-servers/xandr) ## Dependencies Classification Cohorts rely on the following products being configured for your organization and workspace. | Dependency | Required | Description | | :------------- | :------- | :------------------------------------------------------------------------------------------------------------------- | | Identity Graph | \~ | When using partner data, there must be a common identity configured in Identity Graph between you and the partner. | | Custom Cohorts | ✓ | Custom Cohorts (excluding those using third-party data) are used for training and predicting Classification Cohorts. | ## Limits Classification Cohorts adhere to the following product SLAs. #### Feature Limits | Feature | Description | Limit | | :----------------------------------------- | :------------------------------------------------------------------------ | :---- | | Number of labels for first-party seed data | *The number of Custom Cohorts that can be used as labels for each model.* | 2–5 | #### Performance Limits | Metric | Description | Limit | | :---------------------------------------- | :------------------------------------------------------------------------------------------------------------------------ | :------------- | | Time to train a Classification Model | *Once created in the dashboard, Classification Models may take up to 12 hours to train before they are ready to be used.* | Up to 12 hours | | Minimum number of users per Custom Cohort | *For first-party seed datasets, a Custom Cohort must have at least 1,000 users to be used as a model label.* | 1,000 users | #### Usage Limits | SKU | Description | Limit | | :--------------------- | :--------------------------------------------------------------------- | :----------------------- | | Classification Models | *Maximum number of active models each workspace can run at any time.* | 10 models per workspace | | Classification Cohorts | *Maximum number of active cohorts each workspace can run at any time.* | 30 cohorts per workspace | ## FAQ Classification Models can take up to 12 hours to train after being created in the dashboard. The exact training time depends on the size of your seed dataset and the complexity of the model. Once training is complete, the model will be automatically deployed and available for creating Classification Cohorts. You will be notified when training completes. **Partner data** comes from external data providers (such as Fluent or OnAudience) and consists of a fixed, system-wide set of labels. Partner data allows you to reach your non-endemic audience by identifying which of your users have similar behavior to those in the partner dataset. For example, an auto publisher might use partner data from a luxury brand to identify users who are luxury watch intenders. **First-party data** comes from your own sources, such as declared data collected during sign-up or survey responses. With first-party data, you select 2-5 of your own Custom Cohorts to use as labels for the model. This allows you to scale your high-fidelity understanding of a subset of your audience to identify and reach users across your entire audience. No. Classification Models are trained using your Custom Cohorts, excluding those using third-party data. This is intentional, as the goal of Classification Models is to help you scale your own first-party data or leverage partner datasets, not to amplify third-party data. The confidence threshold determines the trade-off between accuracy and reach: * **Higher thresholds (70-90%)**: More accurate predictions but lower reach. Use when precision is critical. * **Medium thresholds (50-70%)**: Balanced accuracy and reach. Good starting point for most use cases. * **Lower thresholds (30-50%)**: Greater reach but lower confidence in predictions. Use when maximizing audience size is important. You can create multiple Classification Cohorts from the same model at different confidence thresholds to test which performs best for your specific use case. Monitor performance through Audience Insights and Campaign Insights to optimize your threshold selection. Each workspace is limited to: * **10 active Classification Models** at any time * **30 active Classification Cohorts** at any time These limits help ensure optimal performance across the platform. If you need to create new models or cohorts and have reached the limit, you'll need to delete existing ones first. For first-party seed datasets, each Custom Cohort used as a model label must have at least 1,000 users to ensure the model has sufficient training data. Models trained on cohorts with fewer users may fail to train or produce unreliable predictions. If your cohorts don't yet have 1,000 users, wait for your audience to grow before creating the Classification Model. You can check cohort sizes in the dashboard before starting model training. Classification Cohorts can be activated to: * Google Ad Manager (GAM) * Xandr (AppNexus) Classification Cohorts are supported on Web, iOS, and Android platforms. They are not currently available for CTV or API Direct environments. For more information about activation destinations, see the [Activation](/integrations/advertising/ad-servers) documentation. No. Once a Classification Model has been created and trained, it cannot be edited. If you need to make changes to the seed dataset or labels, you must create a new Classification Model. However, you can create multiple Classification Cohorts from the same trained model by adjusting the confidence threshold and selecting different labels, without needing to retrain the model. ## Changelog For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Lookalike Cohorts Source: https://docs.permutive.com/products/signals/cohorts/modeled-lookalike Expand audience reach using real-time lookalike modeling ## Overview Lookalike Modeling is a machine-learning (ML) process used to expand your audience reach by finding users who behave similarly to a set of "seed users." By identifying patterns of behavior across your first-party data, Permutive can predict the likelihood of other users belonging to a specific interest or demographic group, even if they haven't explicitly performed the actions that define the seed segment. Unlike traditional lookalike models that rely on static, batch-processed data, Permutive's lookalike models operate in real-time. They compute granular likelihood scores for every user on every page view, allowing for immediate activation and highly precise targeting. ## Why Use Lookalike Models? **Expand Niche Audiences** — Increase the reach of high-value but small first-party segments to meet campaign delivery goals without sacrificing relevance. **Find New Customers** — Expand your high-value first-party segments to find "likely converters" among your own audience, driving better campaign performance for your advertiser partners. **Real-Time Activation** — Score and activate users immediately from their first page view, ensuring no targeting opportunities are missed. ## Concepts ### Definitions * **Seed Segment**: The original "source" cohort of users that the model learns from. This is typically a high-quality, publisher first-party segment. * **Feature Space**: The set of all custom cohorts in your workspace that the model uses as inputs to identify behavioral patterns. * **Similarity Score (Propensity)**: A probability between 0 (0%) and 1 (100%) assigned to each user, representing how closely their behavior matches the seed segment. * **Precision (Confidence)**: A threshold chosen by the user to determine which users to include in a lookalike cohort based on their similarity score. * **Reach**: The estimated number of unique users that will be included in a lookalike cohort at a given precision level. ## Workflows ### Creating a Lookalike Model To start expansion, you first define a seed segment from your existing publisher cohorts. Permutive then uses logistic regression to analyze the behavioral patterns (cohort memberships) of users in that seed. The model learns which other cohorts are strong predictors of being in the seed segment. Lookalike model creation screen showing seed selection ### Evaluating Model Performance Once trained, the model produces a "Precision vs. Reach" curve. This curve shows the trade-off between how similar the users are to your seed (Precision) and how many users you can reach (Reach). You can also inspect the "weights" assigned to different cohorts to see what behavioral traits the model found most significant. Precision vs. Reach curve in the Permutive dashboard ### Activating Lookalike Cohorts From the model's curve, you can create one or more lookalike cohorts by selecting a specific similarity threshold. These cohorts can then be activated programmatically via SSPs/DSPs or directly in your publisher ad server, just like any other Permutive cohort. ## Guides Step-by-step instructions for working with Lookalike Models. Guide for setting up a new lookalike model, including seed selection and configuration options like include/exclude segments. Guide for using the precision vs. reach curve to create and name lookalike cohorts for activation. Guide for modifying the underlying seed segment and understanding how it triggers model retraining. Guide for removing a model and the impact on its associated lookalike cohorts. Advanced use cases for Lookalike Models: Guide for using advanced filtering to restrict the model's training set or target population. Guide for interpreting model weights to ensure the machine learning logic aligns with behavioral expectations. ## Troubleshooting Lookalike modeling is probabilistic, not deterministic. If seed users don't exhibit strong, shared behavioral patterns across your other cohorts, the model may not be able to confidently group them together. **Solution**: Ensure your workspace has a diverse set of custom cohorts (at least 10-20) to provide enough "features" for the model to learn from. This often happens if the seed group is too small, too diverse, or if overly restrictive "Include/Exclude" settings are used. **Solution**: Check that your seed segment has at least 1,000 unique users per day. Review any "Include" or "Exclude" segments to ensure you aren't accidentally filtering out too much of the target population. Models typically take up to 2 hours to train. Common causes of failure include: * **Insufficient Seed Data**: The seed segment has too few users. * **Over-restricted Include Segments**: Using only a single or a handful of cohorts in "Include Segments" doesn't provide enough features for the model to build. **Solution**: Ensure your seed segment is large enough (1,000+ users/day). When using "Include Segments," always add a reasonable amount of cohorts (typically 10 or more) to provide sufficient behavioral signals. If the model curve stops at a low similarity percentage, it means the model couldn't find a group of users that strongly and uniquely match the seed's behavior compared to the rest of the audience. **Solution**: Check for "overlapping" cohorts that are too similar to the seed, which can confuse the model. Try expanding your feature space with more distinct behavioral cohorts. ## Environment Compatibility #### Core Product | Functionality | Web | iOS | Android | CTV | API Direct | | :---------------- | :----------- | :----------- | :----------- | :----------- | :----------- | | Real-time Scoring | | | | | | | Model Training | | | | | | #### Activation Lookalike cohorts can be activated across all standard Permutive destinations, including: * Google Ad Manager (GAM) * Xandr * Magnite * Index Exchange * FreeWheel ## Dependencies | Dependency | Required | Description | | :-------------- | :------- | :------------------------------------------------------------------------------------------------------- | | Permutive SDK | ✓ | Required for real-time user scoring and inference on-device. | | Custom Cohorts | ✓ | At least 10-20 active custom cohorts are recommended to provide a sufficient feature space for training. | | Matched Cohorts | \~ | Required if using advertiser-imported data as a seed for a publisher lookalike model. | ## Limits #### Feature Limits | Feature | Description | Limit | | :------------- | :------------------------------------------ | :------------ | | Seed Size | Minimum recommended users in seed segment. | 1,000 per day | | Feature Space | Recommended number of cohorts in workspace. | 10 - 1,000 | | Model Training | Frequency of model updates. | Weekly | #### Performance Limits | Metric | Description | Limit | | :------------- | :-------------------------------------- | :------------ | | Inference Time | Time to score a user on-page. | \< 50ms | | Training Time | Time to complete a full model training. | Up to 2 hours | #### Usage Limits | SKU | Description | Limit | | :--------------- | :------------------------------------- | :----------------- | | Lookalike Models | Number of active models per workspace. | \[Contact support] | ## FAQ There is no hard minimum, but we recommend at least 10 segments. Having more segments increases the chance of the model finding meaningful relationships between behaviors. The model will continue to work for a while but will eventually become less accurate as it can no longer learn from new seed data. It is recommended to keep the seed segment active. Yes, if your workspace has third-party data enabled, it can be included in the feature space to significantly improve model accuracy. You can validate a model by checking the weights. For example, if a "Sports Lover" model gives a high positive weight to a "Rugby" cohort, the model is likely learning correctly. ## Changelog ### 2025 **February 2025** * Added support for Lookalike models in the Rust query runtime for improved performance. * Improved SDK handling of model state to prevent indefinite processing in edge cases. ### 2024 **December 2024** * Enabled the use of Connectivity-Imported cohorts as seeds for lookalike modeling. For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Standard Cohorts Source: https://docs.permutive.com/products/signals/cohorts/standard Pre-built standardized audience segments for common targeting needs ## Overview **Standard Cohorts** are pre-built audience segments created by Permutive based on the IAB Audience Taxonomy. These standardized cohorts segment users according to common content categories that are consistent across all publishers, enabling universal targeting criteria for advertisers and agencies. Standard Cohorts leverage Watson Natural Language Understanding to automatically classify page content and assign users to appropriate audience segments based on their content engagement. ## Why Use Standard Cohorts? **Provide standardized targeting for advertisers** — Standard Cohorts enable advertisers and agencies to target consistent audience segments across multiple publishers. By using the IAB Audience Taxonomy, these cohorts ensure that targeting criteria remain uniform regardless of which publisher properties are being used, simplifying campaign setup and execution. **Enable programmatic audience activation** — Standard Cohorts are specifically designed for programmatic advertising activation. They can be activated to SSPs and DSPs, allowing publishers to offer standardized audiences that are immediately available for buyer targeting without custom configuration. **Leverage AI-powered content classification** — Standard Cohorts use Watson Natural Language Understanding to automatically classify content and segment users, eliminating the need for manual tagging or categorization. This ensures accurate, consistent audience segmentation based on actual content consumption. ## Concepts ### Definitions * **Standard Cohort**: A Permutive-defined audience segment built according to the IAB Audience Taxonomy. Standard Cohorts follow predefined rules and are applicable to all publishers who enable the feature. * **IAB Audience Taxonomy**: The Interactive Advertising Bureau's standardized taxonomy for categorizing audiences based on content interests. Permutive uses a subset of approximately 200 categories from the IAB Audience Taxonomy 1.1 to define Standard Cohorts. * **Watson Natural Language Understanding**: IBM's AI service that analyzes page content to generate hierarchical content categories. Permutive uses Watson to automatically classify articles and assign users to Standard Cohorts based on their content engagement. * **Standard Audiences**: A broader feature built on Standard Cohorts that can include Watson-based Standard Cohorts, Affinity Cohorts, or publisher-mapped custom cohorts for programmatic activation. * **Standard Cohort Configuration**: The process of enabling and customizing which Standard Cohorts are active for a publisher's organization or workspace. Configuration can occur at any level of the organizational hierarchy. ## Workflows ### Enabling Standard Cohorts To enable Standard Cohorts, publishers must contact [Support](mailto:support@permutive.com) to request initial setup. Standard Cohorts cannot be self-enabled—the initial configuration must be performed by Permutive. Once Permutive has completed the initial setup, publishers can enable and disable Standard Cohorts themselves through the Permutive Dashboard. Once enabled, the Permutive SDK automatically evaluates users against Standard Cohort criteria in real-time as they view content. ### Configuring Standard Cohorts for Enterprise Workspaces If you require different Standard Cohort configurations for different business units, you must request this with [Support](mailto:support@permutive.com). Once the workspace-specific configurations are in place, you can manage enabling and disabling Standard Cohorts at each configured level. When Standard Cohorts are configured at a specific workspace, users will be segmented into Standard Cohorts at that workspace and all its descendant workspaces. Ancestor workspaces (higher up in the hierarchy) will not be included in the segmentation. ## Guides Manage which Standard Cohorts are active and included in ad requests ## Environment Compatibility #### Core Product Standard Cohorts functionality is supported on the following platforms: | Functionality | Web | iOS | Android | CTV | API Direct | | :------------------------- | :----------- | :---------- | :---------- | :---------- | :---------- | | Content classification | | | | | | | User segmentation | | | | | | | Standard Cohort activation | | | | | | ## Dependencies Standard Cohorts rely on the following products and features being configured: | Dependency | Required | Description | | :-------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | | Permutive SDK | ✓ | The Permutive SDK (Web, iOS, Android, or CTV) must be deployed to track pageview events and enable real-time Standard Cohort evaluation. | | Watson Classification | ✓ | Watson Natural Language Understanding must be enabled to classify page content and generate the categories that power Standard Cohorts. | | Pageview Tracking | ✓ | Pageview events must be properly tracked and transmitted to Permutive. Standard Cohorts require 3 pageviews within 30 days for qualification. | | SSP Integrations | \~ | Required only if activating Standard Cohorts to programmatic platforms for ad targeting. | ## FAQ Standard Cohorts are pre-defined audience segments created by Permutive based on the IAB Audience Taxonomy and Watson content classification. They are standardized across all publishers and designed for programmatic advertising. Custom Cohorts are publisher-defined segments built flexibly based on any combination of first-party, second-party, and third-party data sources. Publishers have complete control over the rules and criteria for Custom Cohorts. Yes, Standard Cohorts depend on Watson Natural Language Understanding to automatically classify page content into categories from the IAB Audience Taxonomy. Without Watson, Standard Cohorts cannot function. Permutive uses a subset of approximately 200 categories from the IAB Audience Taxonomy 1.1 to define Standard Cohorts. This subset was selected to provide broad coverage of common content and audience categories relevant to publishers and advertisers. Yes, publishers can configure which Standard Cohorts are active for their organization or workspace through the Standard Cohorts configuration interface. This allows publishers to enable only the cohorts relevant to their content and audience. Yes, Standard Cohort configuration can be set at any level of your organizational hierarchy (Group, Division, or Business Unit). However, there can be only one configuration per branch in the hierarchy. Users will be segmented according to the configuration applied at their workspace and its descendants. ## Changelog No changes listed yet. For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Standard Contextual Cohorts Source: https://docs.permutive.com/products/signals/cohorts/standard-contextual Ready-made contextual audience segments based on standardized taxonomies ## Overview **Standard Contextual Cohorts** are ready-made contextual audience segments based on standardized content taxonomies like the IAB Content Taxonomy 2.0. Unlike custom Contextual Cohorts which publishers must build themselves, Standard Contextual Cohorts are automatically generated based on your enabled content classification provider (such as IBM Watson). Standard Contextual Cohorts provide a consistent, standardized set of contextual categories across all publishers, making it easier for advertisers to target similar contextual segments across multiple publisher properties. These cohorts operate entirely on page-level data without processing user information, requiring no user consent. ## Why Use Standard Contextual Cohorts? **Reduce operational overhead** — Standard Contextual Cohorts eliminate the need to manually build and maintain hundreds of contextual segments. Once you enable a classification provider, you automatically receive a suite of ready-made cohorts aligned to industry-standard taxonomies that are automatically maintained and updated. **Enable advertiser demand through standardization** — By using industry-standard taxonomies like IAB Content 2.0, Standard Contextual Cohorts make it easier for advertisers to target consistent contextual categories across multiple publishers. This standardization is essential for programmatic demand and features like Google Publisher Provided Signals (PPS). **Integrate with Google PPS** — Standard Contextual Cohorts can be mapped to Google Publisher Provided Signals (PPS), enabling publishers to expose first-party contextual signals to demand platforms via Google AdX. This allows buyers to bid more intelligently on contextually-enriched inventory in the open auction. ## Concepts ### Definitions * **IAB Content Taxonomy**: A standardized classification system developed by the Interactive Advertising Bureau for categorizing web content. The IAB Content Taxonomy 2.0 contains approximately 1,500 categories. Permutive uses version 2.0 because it aligns with the output from IBM Watson Natural Language Understanding. * **Content Classification Provider**: An NLP (Natural Language Processing) service like IBM Watson that analyzes page content and categorizes it according to a standardized taxonomy. The classification provider must be enabled in your workspace for Standard Contextual Cohorts to be generated. * **Publisher Provided Signals (PPS)**: A Google Ad Manager feature that allows publishers to pass first-party contextual signals into the Ad Exchange auction. Standard Contextual Cohorts can be mapped to PPS to enable programmatic buyers to target these standardized contextual categories. ## Workflows ### Enabling Standard Contextual Cohorts Standard Contextual Cohorts are automatically generated when you enable a content classification provider in your workspace. The cohorts are created based on the taxonomy used by your classification provider (typically IAB Content Taxonomy 2.0 for IBM Watson). ### Viewing and Managing Standard Contextual Cohorts All Standard Contextual Cohorts appear in the Contextual section of the Permutive Dashboard alongside your custom Contextual Cohorts. They are automatically labeled or identified as standard cohorts. The cohort list shows the cohort name, corresponding taxonomy category, and activation status. ### Mapping to Google Publisher Provided Signals (PPS) To use Standard Contextual Cohorts with Google PPS, Permutive generates a CSV mapping file that connects your cohort codes to the Google PPS taxonomy. This file is then uploaded to Google Ad Manager to enable PPS targeting. ## Guides Step-by-step instructions for working with Standard Contextual Cohorts. Enable a content classification provider to generate Standard Contextual Cohorts Activate Standard Contextual Cohorts to ad servers and SSPs Generate PPS mapping files and upload to Google Ad Manager ## Troubleshooting **Cause**: You don't have a content classification provider enabled, or the provider hasn't classified enough content yet. **Solution**: * Navigate to *Contextual > Catalog* and enable a classification provider (e.g., IBM Watson) * Ensure the provider has API credentials configured * Allow time for content to be classified (typically a few hours after enabling) * Contact your Customer Success Manager if Standard Contextual Cohorts don't appear after 24 hours **Cause**: The mapping tool may not be able to access your Google Ad Manager key-value targeting or there's a mismatch between activated cohorts and GAM configuration. **Solution**: * Ensure Standard Contextual Cohorts are activated to Google Ad Manager * Verify GAM integration credentials are valid and have appropriate permissions * Check that the `prmtvctx` key exists in your GAM key-value targeting * Contact [Technical Services](mailto:technical-services@permutive.com) for assistance with mapping generation **Cause**: Google filters out certain sensitive categories that are not permitted in Publisher Provided Signals, including topics like war/conflicts, medical health, religion, and politics. **Solution**: * This is expected behavior—Google automatically excludes sensitive categories from PPS * The mapping tool should automatically filter these categories * Refer to Google's PPS documentation for the complete list of restricted categories ## Environment Compatibility Standard Contextual Cohorts functionality is supported on the following platforms: | Functionality | Web | iOS | Android | CTV | API Direct | | :--------------------- | :----------- | :----------- | :----------- | :----- | :---------- | | Cohort generation | | | | Varies | | | Content classification | | | | Varies | | | Cohort activation | | | | Varies | | Standard Contextual Cohorts can currently be activated to Google Ad Manager and Xandr. ## Dependencies Standard Contextual Cohorts rely on the following products and features: | Dependency | Required | Description | | :------------------------ | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Content Classification | ✓ | A content classification provider (e.g., IBM Watson) must be enabled in your workspace. Standard Contextual Cohorts are generated from the provider's taxonomy output. | | Contextual API | \~ | Standard Contextual Cohorts can work through the Permutive SDK without direct API implementation, just like regular Contextual Cohorts. | | Contextual Cohorts Add-on | ✓ | Your contract must include the Contextual Cohorts add-on, which enables both custom and standard contextual cohorts. | | Google Ad Manager | \~ | Required only if using Publisher Provided Signals (PPS). You must have GAM with AdX enabled and appropriate API access configured. | ## Limits | Feature | Limit | | :------------------------------ | :------------------------ | | Standard cohorts per taxonomy | \~1,500 (IAB Content 2.0) | | Active classification providers | 1 primary provider | ## FAQ **Standard Cohorts** are behavioral audience segments that track user engagement over time (e.g., "users who viewed 3+ sports articles in the past 30 days"). They require user consent because they process user-level data. **Standard Contextual Cohorts** are page-level segments that classify content based on standardized taxonomies without tracking users. They require no consent and target content rather than users (e.g., "this page is about sports"). **Custom Contextual Cohorts** are publisher-defined segments that you build manually using the Contextual Cohort builder, selecting specific categories, keywords, or affinity thresholds. **Standard Contextual Cohorts** are automatically generated for every category in your classification provider's taxonomy (e.g., all \~1,500 IAB Content 2.0 categories). You don't build them—they're created automatically when you enable a classification provider. Yes. Standard Contextual Cohorts are activated individually, just like other cohorts. Navigate into the cohort and enable the relevant activation destinations. In Google Ad Manager, you can run PPS lift reports that compare PPS-tagged inventory against an open AdX baseline. Calculate the eCPM delta between these two groups to measure incremental value. Permutive uses the **IAB Content Taxonomy 2.0** for Standard Contextual Cohorts when IBM Watson is the classification provider. Version 2.0 is used because it aligns with Watson's native output, eliminating the need for additional taxonomy translation. This depends on your contract. In some cases, Standard Contextual Cohorts may be excluded from cohort count limits because they are auto-generated. Contact your Customer Success Manager for details. ## Changelog ### 2025 **May 2025** * Standard Contextual Cohorts released to general availability * Integration with Google Publisher Provided Signals (PPS) For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Identity Graph Source: https://docs.permutive.com/products/signals/identity/identity-graph Manage user identity and cross-device recognition ## Overview Identity Graph is Permutive's core identity management system that collects, stores, and links identifiers to build a unified view of your users across domains, devices and sessions. All events in Permutive are tracked against users, each assigned a unique Permutive ID. The Identity Graph enables publishers to assign first and third party identifiers to track user events across devices, domains, and platforms, creating a comprehensive identity resolution system. Identity Graph supports both user-level identifiers (for individual users) and user group-level identifiers (for households or other groupings), allowing you to build sophisticated identity relationships for audience targeting, insights, measurement, and activation use cases. ## Why Use Identity Graph? **Unified User View Across Devices** — Link identifiers from different devices, sessions, and domains to create a single customer view, enabling more accurate and multi-dimensional audience targeting and measurement. **Cross-Domain Identity Resolution** — Resolve user identities across multiple domains within your organization, improving the accuracy of cross-domain audience targeting and analytics. **Flexible Identity Management** — Support for multiple identifier types (cookies, hashed emails, third-party IDs) with configurable priority for resolution, giving you control over how identities are resolved. **Foundation for Advanced Use Cases** — Power audience segmentation, activation, routing, and data collaboration by providing a reliable identity resolution layer that connects user data across the Permutive platform with data vendors and advertising partners. **Household-Level Targeting** — Support for user groups (such as households) enables segmentation, targeting, activation and measurement at the household level, expanding your audience reach and measurement capabilities. ## Concepts ### Definitions * **Identity Graph**: A database that holds customer profiles and all the known identifiers that correlate with individual consumers, enabling identity resolution across domains, devices and sessions. * **Permutive ID**: A unique, Permutive-generated identifier assigned to each user or user group entity in the identity graph. * **Identifier**: A type of first-party or third-party identity set up in the Permutive platform (e.g., `appnexus`, `email_sha256`, `user_id`). Identifiers can be designated as either user-level or user group-level. * **Identity**: A specific instance of an identifier, consisting of an identifier type and a value (the identifier string itself). For example, `appnexus: abc123` or `email_sha256: 2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae`. * **Alias**: A legacy term for Identity (used in our previous documentation). An alias consists of an identifier type and a value and can reference either a User or Group entity. * **Entity**: A fundamental object in the identity graph that serves as a generic container. An Entity can be one of two types: a User or a Group. * **User**: A type of Entity that represents an individual user within the system. Each User has a unique Permutive-generated identifier (PermutiveUserId) and can be linked to one or more Groups. * **Group**: A type of Entity that represents a collection of individual Users. The primary use case is a household. A Group has its own unique identifier (PermutiveUserGroupId) and can be referenced by its own set of Identities. * **Identity Resolution**: The process of linking multiple identifiers to the same Permutive ID, creating a unified view of a user across different touchpoints. ### Standard Identifiers Permutive supports the following standard identifier types out of the box. You can also create custom identifiers to suit your specific needs. | Identifier | Description | Auto-Collected | | :------------- | :---------------------------------------------------------------------------------------------- | :------------- | | `aaid` | Android Advertising ID (also called GAID) — Google's advertising identifier for Android devices | Yes | | `amp` | Google AMP (Accelerated Mobile Pages) Client ID for tracking users across AMP pages | Yes | | `appnexus` | A cookie-based ID from Xandr/Microsoft, often used within Permutive for data import | Yes | | `ddp` | A Google cookie-based ID, used by Permutive's DV360 integration | No | | `email_sha256` | SHA-256 hashed email address (64-character hex string) | No | | `gigya` | SAP Customer Data Cloud (formerly Gigya) user identifier for identity management | Yes | | `idfa` | Apple's Identifier for Advertisers for iOS devices | Yes | | `idfv` | Identifier for Vendor (iOS) — unique ID shared across apps from the same developer/company | Yes | | `ip_address` | IPv4 address of the user's device | Yes | | `ipv6_address` | IPv6 address of the user's device | No | | `mpId` | Identifier from the mParticle customer data platform, used by Permutive's mParticle integration | No | | `piano_id` | Piano (subscription/paywall platform) user identifier | No | | `ppid` | Publisher Provided ID — Google Ad Manager's identifier for publisher first-party data | No | | `pxid` | Permutive's own cookie-based ID, used to aid cross-domain identity resolution | Yes | | `sailthru` | Sailthru email marketing platform user identifier | Yes | | `tradedesk` | The Trade Desk's cookie-based ID, used by Permutive's Trade Desk integration | Yes | Auto-collected identifiers can be automatically captured by the Permutive SDK when available in the user's environment, if this option is configured for the identifier in the Permutive dashboard. All other identifiers must be passed explicitly via the identify endpoint or imported via Connectivity. ## Workflows ### Configuring Identifiers in the Dashboard The Identity Graph dashboard allows you to view, add, and manage identifiers for your workspace. Navigate to Identity → Identifiers to see all configured identifiers, view metrics about their scale and usage, and manage identifier settings. You can add new custom identifiers, view identifier details including volume and coverage metrics, and see where each identifier is being used across the Permutive platform. ### Adding Identities via the Identify Endpoint The `/identify` endpoint is one of the methods for adding identities to your Identity Graph. When called, it accepts a Permutive user ID (or generates a new one if none is provided) and a prioritized list of identities. The system reads through the provided identities in priority order until it finds one that already exists in the graph, then links all provided identities to that Permutive ID. If no match is found, all identities are linked to the provided or newly generated Permutive ID. For more information on the `/identify` endpoint, please consult [our "Adding Identities via Identify Endpoint" guide](/guides/signals/identity/adding-identities-via-identify) or our API documentation. ### Viewing Identifier Details The Identifier Details section provides comprehensive visibility into each identifier's scale, coverage, and usage across the Permutive platform. This view shows volume and coverage metrics, compares identifier scale against your total universe, and visualizes where each identifier is being used (Connectivity, Audience Imports, Activations, Routing, Data Collaboration). Identity section ### Importing Users, User Groups, and Memberships via Connectivity Connectivity is another method to add identities in your Identity Graph. Identity Graph supports importing users, user groups, and user group memberships (e.g., household relationships) via Connectivity. This allows you to bulk import identity data from your data warehouse, including: * **User identities**: Import user-level identifiers to build your identity graph * **User group identities**: Import group-level identifiers (e.g., household IDs) * **Group memberships**: Create relationships between users and groups for household-level identity resolution Each import requires a primary identifier (match key), a secondary identifier (the new identity to add), and a timestamp/cursor field for incremental updates. Group membership imports also support an optional deletion flag for managing changes in group membership over time. For detailed import file format requirements and column specifications, see [Importing User Group Memberships](/guides/signals/identity/importing-user-group-memberships). Identity section ### Identity Resolution Process When identities are added to the graph, the system performs identity resolution by checking if any provided identity already exists. If a match is found, all new identities are linked to the existing Permutive ID. This creates a unified view where multiple identifiers (e.g., a cookie ID and a hashed email) resolve to the same user profile. Identity section ## Guides Step-by-step instructions for working with Identity Graph. Guide for adding, viewing, and managing identifiers in the Identity Graph dashboard, including custom identifier setup Guide for accessing and interpreting the Identifier Details page, including metrics analysis and usage visualization Guide for implementing the /identify endpoint across Web, iOS, Android, and API platforms, including priority configuration Guide for setting up Connectivity imports for user group memberships, including data format requirements and configuration Guide for setting up Connectivity imports for user identity data to link identifiers in the Identity Graph ## Troubleshooting If an identity is not resolving to the expected Permutive ID, this could be due to several reasons: 1. The identity may not be properly configured in the Identifiers list (Identity > Identifiers) 2. The identity may have a different identifier type or format than expected 3. Priority order in the identify call may be causing a different resolution path **Solution**: 1. Verify the identifier is configured in the dashboard (Identity > Identifiers) 2. Check the identifier type and value format matches exactly what's configured 3. Review the priority order in your identify calls - identities are checked in priority order (0 = highest priority) 4. Use Identity Insights to view overlap and resolution patterns If an identifier you've configured is not appearing in the Identity Graph dashboard: 1. The identifier may not have any associated identities yet (no data has been collected) 2. There may be a delay in metrics calculation 3. The identifier may have been removed from the identifier list **Solution**: 1. Verify the identifier is still in your identifier list (Identity > Identifiers) 2. Check that you're sending identities with this identifier type via the identify endpoint 3. Wait a few hours for metrics to update if you've recently added the identifier 4. Contact [Support](mailto:support@permutive.com) if the identifier should have data but isn't appearing If your Connectivity import for user group memberships is failing: 1. The required identifiers may not be configured (user-level and group-level identifiers must be set up) 2. The data format may not match requirements (user\_id, group\_id, cursor columns) 3. The identifier types in your import may not match the configured identifiers **Solution**: 1. Verify both user-level and group-level identifiers are configured in Identity > Identifiers 2. Check that your import table has the required columns: user\_id, group\_id, cursor (and optional is\_deleted) 3. Ensure the identifier types in your data match the namespace configured in Permutive 4. Review Connectivity import logs for specific error messages If you're experiencing low identity resolution rates (identities not linking to existing Permutive IDs): 1. Identifiers may not be prioritized correctly in identify calls 2. Identity collection may be inconsistent across sessions or devices 3. Identifier formats may be changing or inconsistent **Solution**: 1. Review identifier priority in your identify calls - place the most stable, persistent identifiers first (priority 0) 2. Ensure identify calls are made consistently across all user touchpoints 3. Verify identifier formats are consistent (e.g., email hashing method) 4. Use Identity Insights to analyze overlap patterns and identify gaps If you're unable to add more than 5 identifier types, you may need to upgrade to the Advanced ID Graph package. **Solution**: Contact your Customer Success Manager to discuss upgrading to Advanced ID Graph, which supports additional identifier types beyond the standard 5. ## Environment Compatibility #### Core Product | Functionality | Web | iOS | Android | CTV | API Direct | | :---------------- | :-- | :-- | :------ | :-- | :--------- | | Identity Graph | ✓ | ✓ | ✓ | ✓ | ✓ | | Identify Endpoint | ✓ | ✓ | ✓ | ✓ | ✓ | ## Dependencies | Dependency | Required | Description | | :--------------- | :------- | :---------------------------------------------------------------------------------------------------------------------------------- | | Permutive SDK | ✓ | Required to collect identity data via the identify endpoint. Different SDKs are available for Web, iOS, Android, and CTV platforms. | | Workspace Access | ✓ | You must have access to a Permutive workspace to configure identifiers and view Identity Graph dashboards. | | Identity Graph | ✓ | Identifiers must be configured in the dashboard (Identity > Identifiers) before they can be used in identify calls. | | Connectivity | ✓ | Required to collect identity data via an import process. | ## Limits #### Feature Limits | Feature | Description | Limit | | :------------------------ | :-------------------------------------------------------------- | :------------------ | | Standard Identifier Types | Number of identifier types supported in standard Identity Graph | 5 identifier types | | Advanced Identifier Types | Number of identifier types supported in Advanced ID Graph | 15 identifier types | #### Usage Limits | SKU | Description | Limit | | :------------------------ | :------------------------------------------------------------------------ | :----------------------------------- | | Identity Graph (Standard) | Access to standard Identity Graph with up to 5 identifier types | Included with all publisher accounts | | Advanced ID Graph | Access to Advanced ID Graph with additional identifier types and features | 15 identifier types | ## FAQ An **identifier** is the type of identity (e.g., `appnexus`, `email_sha256`). An **identity** is a specific instance of an identifier, consisting of the type and a value (e.g., `appnexus: abc123`). You configure identifiers in the dashboard, and then send identities (identifier + value pairs) via the identify endpoint. When you call the identify endpoint with a list of identities, the system checks each identity in priority order (0 = highest priority) to see if it already exists in the graph. If a match is found, all provided identities are linked to that existing Permutive ID. If no match is found, all identities are linked to a new or provided Permutive ID. This creates a unified view where multiple identifiers resolve to the same user. Currently, a User cannot have multiple identities of the same identifier type. For example, a user cannot have two different hashed emails. Each identifier type can only appear once per user entity. User-level identifiers are used to identify individual users (e.g., `email_sha256`, `appnexus`). Group-level identifiers are used to identify user groups like households (e.g., `household_id`). When importing user group memberships, you must use a user-level identifier for the user\_id and a group-level identifier for the group\_id. Navigate to Identity > Identifiers in the dashboard to see high-level metrics for all configured identifiers. Click the ••• menu next to any identifier and select "View Details" to open the Identifier Details page, which shows volume, coverage, and usage metrics across the Permutive platform. Yes, you can delete identifiers from the Identifiers page by clicking the ••• menu next to the identifier and selecting "Delete". Note that deleting an identifier will prevent new identities of that type from being added, but existing identities will remain in the graph. Identity Insights may continue to show data for deleted identifiers for a period based on the reporting time range (Past 7 or Past 30 Days). Use Connectivity to import user group memberships from your data warehouse. Set up a Connectivity import with columns for user\_id, group\_id, cursor, and optional is\_deleted. Ensure both the user-level and group-level identifiers are configured in Identity > Identifiers before setting up the import. Identity Graph is the core identity resolution system that collects and links identifiers. Identity Manager activates alternative IDs (like RampID, UID2) into the bidstream but does not ingest them into the Identity Graph by default. Identity Insights provides analytics dashboards that visualize your Identity Graph data, showing overlap, ingestion patterns, and domain resolution effectiveness. Identity Graph data can be accessed through Routing, which provides a log of alias changes. For bulk export capabilities, contact [Support](mailto:support@permutive.com) to discuss your specific requirements. ## Changelog ### 2025 **December 2025** * Enhanced Identity Graph with Identifier Details Page, providing visibility on identifier scale, coverage metrics, and usage across the Permutive platform (Connectivity, Audience Imports, Activations, Routing, Data Collaboration) ### 2024 **August 2024** * Identity Graph made generally available with support for user-level and group-level identifiers **April 2024** * Initial release of Identity Graph with support for user-level identity resolution and the identify endpoint For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Identity Insights Source: https://docs.permutive.com/products/signals/identity/identity-insights Visualize your identity graph and gain insights on the scale and performance of every identifier. ## Overview Identity Insights provides analytics dashboards that visualize your identity graph and help you understand how first-party and third-party identifiers are performing against each other. The product features three main dashboard components that enable you to analyze identity data overlap, monitor data ingestion patterns, and assess cross-domain user stitching effectiveness. Identity Insights is available to all publisher accounts and is primarily used as a debugging tool to understand the scale of identifiers, their relationships, and to troubleshoot identity data collection issues. ## Why Use Identity Insights? **Visualize Identity Graph Performance** — Understand how different identity vendors are performing, from a scale perspective, relative to each other through overlap analysis. **Debug Identity Data Collection** — Monitor daily volume patterns to identify and troubleshoot drops in identity data collection. **Assess Cross-Domain User Stitching** — Evaluate how successful your user stitching is across different domains. ## Concepts ### Definitions * **Identity Graph**: A collection of identifiers (IDs) that Permutive uses to recognize and link users across sessions and domains. * **Identifier (ID)**: A unique identifier type that can be collected and stored in your identity graph (e.g., AppNexus ID, RampID, UID2, hashed email). * **Identity Overlap**: The percentage of users (Permutive IDs) who have both of two different identifiers, showing how identifiers relate to each other. * **Identity Data Ingestion**: The process of collecting and storing identity data from your site or app. This process can be done via Connectivity Imports and/or the `/identify` endpoint. * **Domain Overlap**: The percentage of users who appear on multiple domains within your organization, indicating cross-domain identity resolution success. ## Workflows ### Viewing Identity Overlap The Slicer tab displays overlap between different identifier types in a pivot table format. This helps you understand which identifiers are commonly found together and assess the scale of your identity graph. The table shows both percentage overlap and absolute values, allowing you to identify relationships between different identity vendors. ### Monitoring Identity Data Ingestion The Time Series tab displays identity data ingestion per day over a 7-day or 30-day period. This workflow helps you monitor the health of your identity data collection and identify any drops in data ingestion that might indicate integration issues or vendor problems. ### Analyzing Cross-Domain User Overlap The Domains view shows user overlap across different domains in your organization. This helps you assess the overlap of users across domains, which is important for cross-domain audience targeting and measurement. ## Guides Step-by-step instructions for working with Identity Insights. Guide for accessing Identity Insights in the Permutive dashboard and navigating the three main tabs Guide for interpreting the Slicer tab pivot table, including how to read overlap percentages and identify relationships between identifiers Guide for using the Ingestion tab to monitor daily data collection, identify drops in ingestion, and troubleshoot data collection issues Guide for interpreting domain overlap metrics and assessing cross-domain user stitching effectiveness ## Troubleshooting There are a few reasons why an unexpected identifier might appear in your Identity Insights reports: 1. You stopped sending the identifier to Permutive, but it's still on the Identity Graph allow-list (Settings > Identifiers). In these cases, you might see a small amount of data (10s of users) associated with the identifier. This can happen because of old, cached information on some users' devices. 2. The identifier is related to an integration you're using, but you might not recognize it. For example, an identifier like 'ddp' supports an integration between Permutive and Google's DSP (DV360), but it may not be clear from the identifier name itself that it's supporting an important function for your business. **Solution**: Review your Identity Graph allow-list in Identity > Identifiers and remove any identifiers you no longer want to collect. Note that Identity Insights reports on Past 7 and Past 30 Days, so there will be a temporary period where it continues to show identifiers you removed if data was collected from those identifiers in those time ranges. If you notice a drop in data ingestion in the Time Series tab, this could indicate an issue with identity data collection. **Solution**: 1. Check the Time Series tab to identify when the drop occurred 2. Review any recent changes to your site or app that might affect identity collection 3. Verify that identity vendors are still properly integrated 4. Contact [Support](mailto:support@permutive.com) if the drop persists or if you need assistance identifying the cause Domain overlap is calculated once per month on the first of the month due to query cost considerations. Custom date ranges are not supported for this view. **Solution**: Wait until the first of the next month for updated domain overlap data. The view will automatically refresh with new data points at the beginning of each month. The Slicer tab pivot table reads top-to-bottom rather than left-to-right, which can be counter-intuitive for some users. **Solution**: Read the table by looking at the top row identifier first, then reading down the column to see overlap percentages with other identifiers. For example, if AppNexus is in the top row, reading down shows what percentage of users with each identifier also have AppNexus. ## Environment Compatibility #### Core Product | Functionality | Web | iOS | Android | CTV | API Direct | | :-------------------------- | :----------- | :----------- | :----------- | :----------- | :----------- | | Identity Insights Dashboard | | | | | | ## Dependencies | Dependency | Required | Description | | :--------------- | :------- | :--------------------------------------------------------------------------------------------------------------------- | | Permutive SDK | ✓ | Required to collect identity data that powers Identity Insights reports. | | Identity Graph | ✓ | Identity Insights analyzes data from your Identity Graph, so you must have identifiers configured and collecting data. | | Workspace Access | ✓ | You must have access to a Permutive workspace to view Identity Insights dashboards. | ## Limits #### Feature Limits | Feature | Description | Limit | | :-------------------------- | :-------------------------------------------------------- | :---------------------------------------- | | Date Range Selection | Available date ranges for Ingestion tab | 7 days or 30 days only | | Domain Overlap Refresh | Frequency of domain overlap data updates | Once per month (on the 1st of each month) | | Identifier Overlap Analysis | Number of identifiers that can be analyzed simultaneously | Up to 10 identifiers | #### Performance Limits | Metric | Description | Limit | | :--------------------- | :--------------------------------------------- | :--------------------------- | | Query Execution Time | Time for Identity Insights queries to complete | A few seconds | | Data Processing Window | Time window for data to appear in reports | Past 7 days and Past 30 days | #### Usage Limits | SKU | Description | Limit | | :---------------- | :------------------------------------- | :---------------------------------- | | Identity Insights | Access to Identity Insights dashboards | Available to all publisher accounts | ## FAQ Identity overlap (Slicer tab) and time series data are updated daily. Domain overlap is calculated once per month on the first of the month and cannot be customized to different date ranges. No, custom date ranges are not supported. See [Troubleshooting](#troubleshooting) above for details on the domain overlap update schedule. This is a common situation with a few possible causes. See [Troubleshooting](#troubleshooting) above for a full explanation and steps to resolve this. The pivot table reads top-to-bottom. Start with the identifier in the top row, then read down the column to see what percentage of users with each identifier also have the top-row identifier. You can also view absolute values by selecting the sum view option. Use the Ingestion tab to identify when the drop occurred, then review recent changes to your site or app. Check that identity vendors are still properly integrated. If the issue persists, contact [Support](mailto:support@permutive.com) for assistance. Yes, Identity Insights is available to all publisher accounts. It was made generally available (GA) in August 2024. ## Changelog ### 2024 **August 2024** * Identity Insights made generally available (GA) to all publisher accounts **April 2024** * Identity Insights (Beta) released with three main dashboard components: Slicer tab, Ingestion tab, and Domain Overlap view For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Identity Manager Source: https://docs.permutive.com/products/signals/identity/identity-manager Activate alternative IDs into the bid stream to maximize programmatic yield. ## Overview **Identity Manager** enables publishers to integrate with third-party universal ID vendors, allowing encrypted user IDs (identity envelopes) to be passed into the programmatic bid stream. This no-code solution helps publishers increase CPMs and maximize programmatic revenue without requiring additional development resources. Identity Manager currently supports LiveRamp (RampID, PAIR ID, ATS Direct) and ID5 integrations with the Permutive SDK handling real-time ID retrieval and activation through Prebid. ## Why Use Identity Manager? **Maximize programmatic revenue with minimal effort** — Identity Manager provides a no-code workflow to integrate alternative IDs, driving higher CPMs in programmatic auctions. Publishers can enable ID vendor integrations directly from the Permutive Dashboard without requiring engineering resources or additional code deployment. **Reduce dependence on third-party cookies** — As the industry moves away from third-party cookies, Identity Manager helps publishers maintain addressability through alternative identity solutions. By supporting multiple ID vendors (LiveRamp, ID5), publishers can maximize reach and yield across different buyer preferences. **Control data exposure with geographic targeting** — Publishers can restrict ID vendor integrations by geography based on user IP location. This allows you to balance incremental value against data exposure concerns, enabling IDs only in regions where they provide the most benefit. ## Concepts ### Definitions * **Identity Envelope**: An encrypted user ID provided by ID vendors (like LiveRamp, UID2, or ID5) that is passed into the bid stream. Advertisers pay to decrypt these envelopes during the bidding process. * **Alternative ID / Universal ID**: Identity solutions designed to replace third-party cookies for programmatic advertising. These include deterministic IDs (requiring hashed email) and probabilistic IDs (using other signals). * **Prebid**: The open-source header bidding solution used to pass identity envelopes to SSPs and DSPs during ad auctions. * **Permutive User ID Module**: A Prebid module that acts as an interface between the Permutive SDK and Prebid, enabling Identity Manager integrations to work with your existing Prebid setup. * **Geo Rules**: Configuration settings that restrict ID vendor integrations to specific countries based on the user's IP location. * **Partner ID / Placement ID / API Key**: Vendor-specific credentials provided by ID vendors (LiveRamp, UID2, ID5) that are required to enable their integration in Identity Manager. ## Workflows ### Discovering Available ID Vendors Publishers access the Identity Manager catalog in the Permutive Dashboard to view available ID vendor integrations. The catalog displays supported vendors with options to connect or view details of enabled integrations. Identity Manager catalog ### Configuring an ID Integration Publishers configure ID vendor integrations by providing vendor-specific credentials (Partner ID, Placement ID, or API key) obtained from the ID vendor. Optional geo rules can be configured to restrict the integration to specific countries based on user IP location. Setting up an integration ### Activating IDs to Prebid Once configured, the Permutive SDK automatically retrieves encrypted IDs from enabled vendors and passes them into the bid stream via Prebid. The Permutive User ID Module for Prebid handles the integration, ensuring IDs are available during ad auctions in real-time. ## Guides ### Enabling Identity Manager for Your Organization Identity Manager is available as part of the Permutive platform. To enable Identity Manager: Navigate to the **Identity** section in the Permutive Dashboard. Review the catalog of available ID vendor integrations. Sign up with your chosen ID vendor(s) to obtain the required credentials (Partner ID, API key, or Placement ID). These vendors typically offer free accounts for publishers, with costs paid by advertisers who decrypt the IDs. Ensure the [Permutive User ID Module](https://github.com/prebid/Prebid.js/blob/master/modules/permutiveIdentityManagerIdSystem.md) is added to your Prebid setup. This step only needs to be done once and supports all current and future ID integrations. For managed Prebid implementations, contact your Prebid provider to add the Permutive User ID Module to your setup. ### Setting Up an ID Vendor Integration To enable an ID vendor integration in Identity Manager: In the Permutive Dashboard, go to **Identity** and access the Identity Manager catalog. Click **Connect** for the ID vendor you want to enable. Input the **Partner ID / Placement ID / API key** provided by the vendor. You receive these credentials after signing up and accepting the vendor's terms and conditions. Set geographic restrictions to limit the integration to specific countries based on user IP location. This helps balance data exposure with incremental value. Configuring geo rules Click **Save Configuration** to enable the integration. The Permutive SDK will begin retrieving and activating IDs from this vendor in real-time. **Tips for ID vendor setup:** * Start with geo rules in high-value markets to test incrementality before expanding globally. * Ensure the ID vendor is added to your consent management platform (CMP) vendor list before enabling the integration. * The Permutive SDK will automatically check TCF consent before running vendor integrations. **LiveRamp** * Supports RampID, PAIR ID, and ATS Direct segments * Requires hashed email (deterministic ID) * IDs are stored locally in the browser for 15 days (California) or 30 days (other regions) to reduce API calls and improve performance * PAIR IDs enable privacy-focused first-party data matching with advertisers using Google's Publisher Advertiser Identity Reconciliation framework **UID2 (Unified ID 2.0)** * Requires hashed email (deterministic ID) * May be restricted in GDPR regions—confirm availability for your markets * Subscription ID and public key required for setup **ID5** * Probabilistic ID solution (does not require hashed email) * Permutive downloads the ID5 JavaScript SDK on your site to retrieve IDs * Partner ID required for setup ### Managing ID Vendor Integrations To manage existing ID vendor integrations: Go to **Identity** in the Permutive Dashboard to access the Identity Manager catalog. Integrations that are already enabled display a **View Details** button instead of **Connect**. Click **View Details** to modify vendor credentials, update geo rules, or adjust other settings. To turn off an integration, access the integration details and disable the vendor. The Permutive SDK will stop retrieving IDs from that vendor. Managing integrations Changes to integration configuration take effect immediately. The Permutive SDK will begin using updated settings in real-time. ### Setting Up the Permutive User ID Module for Prebid The Permutive User ID Module for Prebid is required for Identity Manager to work. This module acts as an interface between the Permutive SDK and Prebid, passing identity envelopes into the bid stream. Identify whether you manage your own Prebid implementation (self-managed) or use a managed Prebid service provider. * **Self-managed Prebid**: Add the Permutive User ID Module to your Prebid configuration file. See the [Permutive User ID Module documentation](https://github.com/prebid/Prebid.js/blob/master/modules/permutiveIdentityManagerIdSystem.md) for implementation details. * **Managed Prebid**: Contact your Prebid provider and request that they add the Permutive User ID Module to your setup. Confirm the module is included in your Prebid build and is receiving identity envelopes from the Permutive SDK. The Permutive User ID Module only needs to be added once. It will support all current and future ID vendor integrations enabled through Identity Manager. ## Troubleshooting If identity envelopes are not being passed to Prebid, check the following: * **Prebid module not installed**: Verify the Permutive User ID Module is included in your Prebid setup * **Vendor not in CMP**: Ensure the ID vendor is added to your consent management platform's vendor list * **Geo restrictions**: Check if geo rules are blocking the integration in the user's location * **Vendor credentials incorrect**: Verify the Partner ID / API key / Placement ID is correct in the integration configuration * **SDK timing**: Ensure the Permutive SDK loads before Prebid runs to allow sufficient time for ID retrieval **Solution**: Review your Prebid configuration, verify consent settings, check geo rules, and confirm vendor credentials are correct. Identity Manager checks TCF consent before running vendor integrations. If consent requirements are not met, IDs will not be retrieved. * **Vendor missing from CMP**: The ID vendor must be listed in your consent management platform * **Required consent purposes not granted**: Each vendor has specific TCF consent requirements * **Consent string not available**: The Permutive SDK must be able to access the consent string **Solution**: Add the ID vendor to your CMP vendor list, ensure required consent purposes are configured, and verify the consent string is accessible to the Permutive SDK. If you cannot save an integration configuration, check: * **Invalid credentials**: The Partner ID / API key / Placement ID format may be incorrect * **Missing required fields**: Ensure all required configuration fields are filled out * **Permissions issues**: Verify you have permission to configure integrations in your workspace **Solution**: Verify credentials with your ID vendor, complete all required fields, and check your workspace permissions. If you're not seeing CPM increases after enabling Identity Manager: * **Insufficient time for testing**: Allow several days to a week for meaningful data collection * **Low advertiser demand**: Not all advertisers support alternative IDs; demand varies by market * **Geo restrictions limiting scale**: Narrow geo rules may limit the number of users with IDs * **Hashed email availability**: Deterministic IDs (LiveRamp, UID2) require hashed emails, which may not be available for all users **Solution**: Allow sufficient testing time, consider expanding geo rules if appropriate, review vendor match rates, and evaluate whether your audience has sufficient hashed email availability for deterministic IDs. ## Environment Compatibility #### Core Product Identity Manager functionality is currently supported on the following platforms: | Functionality | Web | iOS | Android | CTV | API Direct | | :--------------------- | :----------- | :---------- | :---------- | :---------- | :---------- | | ID vendor integration | | | | | | | Real-time ID retrieval | | | | | | | Prebid activation | | | | | | | Geo-based restrictions | | | | | | App (iOS, Android) and CTV support is planned for future releases. API Direct is not currently on the roadmap for Identity Manager. #### Activation Identity Manager currently supports activation through: * **Prebid**: All SSPs and DSPs that support alternative IDs through Prebid * **LiveRamp**: RampID, PAIR ID, and ATS Direct segments * **\[Coming Soon] UID2**: Unified ID 2.0 framework participants * **ID5**: ID5 Universal ID ecosystem Google Secure Signals activation is planned for a future release. ## Dependencies Identity Manager relies on the following products and configurations: | Dependency | Required | Description | | :-------------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------- | | Permutive SDK (Web) | ✓ | The Permutive Web SDK must be deployed to retrieve IDs from vendors and pass them to Prebid in real-time. | | Prebid | ✓ | Prebid is required for passing identity envelopes into the bid stream for programmatic activation. | | Permutive User ID Module | ✓ | The Permutive User ID Module for Prebid acts as the interface between the Permutive SDK and Prebid. | | Consent Management Platform | ✓ | ID vendors must be added to your CMP vendor list. The Permutive SDK checks TCF consent before running vendor integrations. | | ID Vendor Account | ✓ | Publishers must sign up with each ID vendor (LiveRamp, UID2, ID5) to obtain credentials (Partner ID, API key, Placement ID). | | Hashed Email Collection | \~ | Required only for deterministic ID vendors (LiveRamp, UID2). Not required for probabilistic vendors (ID5). | ## Limits Identity Manager adheres to the following product specifications and limits. #### Feature Limits | Feature | Description | Limit | | :------------------------ | :------------------------------------------------------------------------------------- | :------------------------ | | Supported ID vendors | The number of ID vendor integrations currently available in Identity Manager. | 2 vendors (LiveRamp, ID5) | | Active integrations | The number of ID vendor integrations that can be enabled simultaneously per workspace. | No limit | | Geo rules per integration | The number of countries that can be configured in geo rules for a single integration. | No limit | #### Performance Limits | Metric | Description | Limit | | :--------------------------- | :----------------------------------------------------------------------------- | :-------------------------------------------- | | ID retrieval time | The time it takes for the Permutive SDK to retrieve IDs from vendors. | Varies by vendor (typically under 500ms) | | Local storage TTL (LiveRamp) | Time-to-live for locally stored LiveRamp identifiers. | 15 days (California), 30 days (other regions) | | Prebid integration latency | Additional latency introduced to ad auctions by Identity Manager ID retrieval. | Minimal (under 100ms in most cases) | #### Usage Limits | SKU | Description | Limit | | :--------------- | :---------------------------------------------- | :------------------------------------------------- | | Identity Manager | Usage limits for Identity Manager integrations. | \[Contact [Support](mailto:support@permutive.com)] | ## FAQ **LiveRamp** and **UID2** are deterministic ID solutions that require hashed email addresses. **ID5** is a probabilistic ID solution that does not require hashed email and can work with anonymous users. No, Identity Manager currently only supports activation through Prebid. The Permutive User ID Module for Prebid is required to pass identity envelopes into the bid stream. Google Secure Signals activation is planned for a future release. Yes, Identity Manager works with managed Prebid implementations. You will need to contact your Prebid provider and ask them to add the Permutive User ID Module to your Prebid configuration. Once the module is added, Identity Manager will work with your existing setup. Geo rules allow you to restrict ID vendor integrations to specific countries based on the user's IP location. When a user visits your site, the Permutive SDK determines their country from their IP address. If the country is not included in your geo rules for a particular vendor, that vendor's IDs will not be retrieved for that user. This helps you balance data exposure with incremental value. Identity Manager does not currently provide built-in measurement tools for CPM impact. Publishers should monitor their programmatic revenue metrics through their ad server or SSPs to evaluate the impact of enabling alternative IDs. Many publishers conduct A/B tests by enabling IDs in specific geos or for a percentage of traffic to measure incrementality. No, identity envelopes from Identity Manager are not currently added to the Permutive Identity Graph. Identity Manager retrieves ephemeral, encrypted IDs that are only used for passing into the bid stream via Prebid. These IDs cannot be used for identity resolution, cross-domain tracking, or audience imports. Integration with the Identity Graph is being considered for future development. All three are LiveRamp identity solutions with different use cases: * **RampID**: LiveRamp's core encrypted identifier passed into the bid stream for programmatic activation * **PAIR ID**: Google's Publisher Advertiser Identity Reconciliation (PAIR) framework identifier, enabling privacy-focused first-party data matching with advertisers * **ATS Direct**: LiveRamp's Authenticated Traffic Solution for direct publisher-advertiser data matching Identity Manager supports all three LiveRamp identity types and stores them locally for improved performance. Identity Manager currently supports LiveRamp and ID5, with UID2 coming soon. Additional ID vendor integrations may be added in future releases. If you have specific vendor integration requests, please share your feedback with your Customer Success Manager. ## Changelog ### 2025 **December 2025** * Enhanced LiveRamp integration with local storage for RampID, PAIR ID, and ATS Direct segments * Added PAIR ID activation to Prebid for Google's Publisher Advertiser Identity Reconciliation framework * Implemented region-aware TTL management (15 days for California, 30 days for other regions) ### 2024 **November 2024** * Initial release of Identity Manager with support for LiveRamp and ID5 integrations (UID2 coming soon) * No-code integration workflow in Permutive Dashboard * Geo-based restrictions for ID vendor integrations * Prebid activation via Permutive User ID Module For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Marketplace Source: https://docs.permutive.com/products/signals/marketplace Discover and access third-party audience data ## Overview Marketplace enables publishers to access and activate third-party data segments from leading data providers and aggregators like Eyeota and LiveRamp through Permutive's platform. This integration allows you to enrich your first-party audience data with high-quality third-party segments for enhanced targeting, audience expansion, and audience insights. Marketplace provides seamless access to thousands of pre-built audience segments across demographics, interests, intent, purchase behavior, and industry verticals. These segments can be imported into Permutive, combined with your first-party data, and activated across your preferred advertising destinations. ## Why Use Marketplace? **Expand Audience Reach** — Access 60,000+ global audience segments from leading data providers to expand your targeting capabilities beyond first-party data. **Enhance Targeting Precision** — Combine third-party data segments with your first-party audiences to create more precise targeting strategies and improve campaign performance. **Seamless Integration** — Marketplace integrates directly with third-party providers and aggregators and allows custom audiences via partners like LiveRamp to be imported directly into Permutive for access in the Cohort Builder. **Global Coverage** — Access audience segments across 180 countries from multiple data providers, enabling international campaign targeting and audience expansion. ## Concepts ### Definitions * **Marketplace**: Permutive's integration with third-party data providers that enables access to pre-built audience segments for targeting and activation. * **Third-Party Data**: Audience segments and data provided by external data providers (e.g., Eyeota, LiveRamp) that are not collected directly by your organization. * **Data Provider**: A third-party company that creates and maintains audience segments based on aggregated data from multiple sources. Examples include Eyeota and LiveRamp. * **Audience Segment**: A pre-defined group of users sharing common characteristics (demographics, interests, behaviors, purchase intent) that can be used for targeting. * **Segment Import**: The process of bringing third-party audience segments into Permutive for use in audience building, targeting, and activation. * **Eyeota**: A global data marketplace provider offering 60,000+ audience segments across 180 countries, with data from 30,000+ research companies, data owners, and publishers. * **LiveRamp**: A data marketplace provider offering premium third-party data segments with activation capabilities across hundreds of destinations including media, tech, and commerce platforms. * **Segment Activation**: The process of using imported third-party segments in campaigns by sending them to advertising destinations (DSPs, SSPs, etc.). ## Workflows ### Accessing Marketplace Data Providers Marketplace provides access to third-party data from providers like Eyeota and LiveRamp. To browse available segments and visualize pricing, follow these steps: * Navigate to Custom Cohorts * Click "Add Cohort" and fill in Name and Description * Select Third Party from the "Behavior" drop down. * You'll then see a list of third-party segments with segment name, CPM, Data provider You can combine several of those segments into a single cohort by selecting "And" and "Or" operators in the cohort builder. You can also combine them with your first-party audience data to create enriched audience definitions. This allows you to leverage both your proprietary data and third-party insights for more comprehensive targeting. Marketplace Segments ### Activating Marketplace Segments Cohorts built with third-party segments can be activated to your preferred advertising destinations just like first-party audiences. Marketplace segments are compatible with Permutive's activation infrastructure, enabling seamless delivery to DSPs, SSPs, and other advertising platforms. ## Troubleshooting If activation is failing for imported Marketplace segments: 1. The destination may not support third-party data segments 2. There may be data provider restrictions on activation 3. Segment format may not be compatible with the destination **Solution**: 1. Verify the destination supports third-party data activation 2. Check data provider terms and restrictions for the specific segments 3. Review segment format requirements for your activation destination 4. Contact support for destination-specific compatibility information If you're experiencing low match rates when activating Marketplace segments: 1. Identity resolution may be limited between your first-party data and third-party segments 2. The segments may use identifiers not present in your Identity Graph 3. Geographic or demographic targeting may be too narrow **Solution**: 1. Review your Identity Graph configuration to ensure compatible identifiers are collected 2. Check segment metadata to understand which identifiers the segments use 3. Consider broader segments or segments that align with your identity collection strategy 4. Use Identity Insights to analyze overlap between your data and third-party segments ## Environment Compatibility #### Core Product | Functionality | Web | iOS | Android | CTV | API Direct | | :------------------- | :----------- | :---------- | :---------- | :---------- | :---------- | | Marketplace Access | | | | | | | Eyeota Integration | | | | | | | LiveRamp Integration | | | | | | We are working on revamping existing integrations and build additional ones to provide Marketplace in other environments besides web only. The table above will evolve in the coming quarters. #### Activation Marketplace segments can be activated to all supported Permutive activation destinations, including DSPs, SSPs, and other advertising platforms. Activation compatibility depends on the specific destination's support for third-party data segments. ## Dependencies | Dependency | Required | Description | | :------------------ | :------- | :---------------------------------------------------------------------------------------------------------------------------- | | Permutive Workspace | ✓ | You must have access to a Permutive workspace with Marketplace enabled to access third-party data providers. | | Identity Graph | \~ | Recommended for optimal segment matching and activation. Identity Graph helps resolve third-party segments to your user base. | | Cohort Builder | ✓ | Required to combine third-party segments with first-party data and create enriched audience definitions. | ## FAQ Marketplace currently provides access to leading third-party data providers including Eyeota and LiveRamp. Eyeota offers 60,000+ global audience segments across 180 countries, while LiveRamp provides premium third-party data segments with activation capabilities. Additional providers may be added over time. Yes, all third-party data segments available through Marketplace are privacy-compliant and independently audited. Data providers like Eyeota maintain certifications including IAB Data Label and Neutronian to ensure data quality and privacy compliance. Yes, imported third-party segments can be combined with your first-party audience data in Permutive's Cohort Builder. This allows you to create enriched audience definitions that leverage both proprietary and third-party insights. Marketplace segments can be activated to all supported Permutive activation destinations, including DSPs, SSPs, and other advertising platforms. Activation compatibility depends on the specific destination's support for third-party data segments. Segment update frequency depends on the data provider. Eyeota and LiveRamp typically update their segments regularly, but specific refresh schedules may vary by segment type. Imported segments in Permutive are updated according to the provider's schedule. Marketplace provides access to a wide variety of audience segments including demographics, interests, intent, past purchase behavior, industry verticals, and seasonal events. Specific segment categories vary by data provider. Some data providers may require separate account setup or agreements. Contact Permutive support to understand the specific requirements for accessing Eyeota or LiveRamp segments through Marketplace. Marketplace segments work best when you have Identity Graph configured with compatible identifiers. Identity Graph helps resolve third-party segments to your user base, improving match rates and activation effectiveness. Use Identity Insights to analyze overlap between your data and third-party segments. Yes, Marketplace provides access to global audience segments. Eyeota, for example, offers segments across 180 countries, enabling international campaign targeting and audience expansion. # Audience Insights Source: https://docs.permutive.com/products/workflows-insights/insights/audience Understand your audience composition, behavior, and content preferences ## Overview **Audience Insights** provides a holistic overview of the users visiting your site and their on-site behaviors. By analyzing cohort composition, engagement patterns, and content preferences, publishers can build compelling data narratives for advertisers, optimize targeting strategies, and demonstrate the value of their first-party audiences. Audience Insights enables you to filter by cohorts or tags and explore five key dimensions: Engagement, Technographics, Index & Overlap, Third-Party data, and Content Read. Each dimension provides unique insights into who your audiences are and how they interact with your content. ## Why Use Audience Insights? **Understand audience behavior at scale** — Engagement metrics reveal how users in your cohorts interact with your site, from session length to articles viewed, helping you quantify audience value for advertisers. **Create compelling RFP responses** — Transform raw data into rich narratives that demonstrate audience quality, engagement depth, and content affinity to win more direct-sold campaigns. **Discover audience relationships** — Index and Overlap analysis shows how your cohorts compare to each other, revealing hidden connections and opportunities for audience extension. ## Concepts ### Definitions * **Base Cohort**: The baseline audience used for comparison in Index calculations. Typically set to "Everyone" to compare a specific cohort against all site users. Only one base cohort can be selected at a time. * **Comparison Cohorts**: The cohorts you want to measure against the base cohort in Index & Overlap reports. You can select multiple comparison cohorts or tag groups. * **Index**: A score indicating how much more or less likely users in a cohort are to exhibit a behavior compared to the base cohort. An index of 100 is the baseline (equal likelihood), above 100 indicates higher likelihood (e.g., 125 = 25% more likely or 1.25x), and below 100 indicates lower likelihood. * **Overlap**: The percentage of users in your selected cohort who also belong to a comparison cohort. For example, a 53% overlap with "Pet Owners" means 53% of users in your cohort are also in the Pet Owners cohort. * **Uniques**: The number of unique users who have been active on-site during the filtered date range and are members of the selected cohort. * **Sessions**: The number of sessions completed by users in the selected cohorts during the filtered timeframe. A session ends after 30 minutes of inactivity. * **Pageviews**: The number of pageview events fired by users in the selected cohorts during the filtered timeframe. * **Minimum Uniques**: A filter threshold that excludes comparison cohorts below a specified size from Index & Overlap reports, ensuring statistical relevance. ## Workflows ### Filtering Audience Insights Publishers configure Audience Insights by selecting cohorts or tags to analyze, then applying secondary filters such as date range, domain, device type, country, and platform. These filters allow precise segmentation of audience data for specific analysis needs. ### Analyzing Engagement Metrics The Engagement tab displays key metrics about audience activity including unique users, sessions, pageviews, average time spent, average session length, and articles viewed per session. This data supports RFP responses by quantifying how engaged a cohort is with your content. ### Comparing Cohorts with Index & Overlap Index & Overlap analysis reveals relationships between cohorts by showing both how likely users are to belong to multiple cohorts (index) and what percentage actually overlap. This helps identify audience extension opportunities and validate targeting assumptions. ### Exploring Content Preferences Content Read insights surface the custom properties from your Pageview events that resonate most with specific cohorts—from most-read authors to highest-indexed article categories. This enables content-based targeting recommendations for advertisers. ## Guides ### Configuring Audience Insights Filters All Audience Insights reports share a common filtering system that allows you to focus on specific audiences and contexts. Click *Edit* next to *Audience* to choose which cohorts to analyze. You can select: * A single cohort * Multiple cohorts * A tag group (all cohorts with a specific tag) Click *Edit* next to *Filter* to apply additional parameters: * **Date Range**: Last day, last 7 days, last 30 days, or custom range (up to 18 months lookback) * **Domain**: Filter by specific domains if Permutive is deployed across multiple sites * **Device Type**: Desktop, Mobile, or Tablet * **Countries**: Filter by geographic location * **Platform**: Web, AMP, FIA, iOS, or Android Click *Apply* to update the reports with your selected filters. If you encounter timeout errors, reduce the complexity of your report by removing some filters or narrowing the date range. Complex queries with multiple cohorts and filters may take longer to process. ### Viewing Engagement Insights Engagement insights provide data about the level of on-site activity for your selected cohorts. In Audience Insights, select the *Engagement* tab. Analyze the following data points: * **Uniques**: Number of unique users active in the cohort during the date range * **Sessions**: Total sessions completed by cohort members * **Pageviews**: Total pageview events from cohort members * **Average Time Spent (Mins)**: Average browsing time per user * **Average Session Length**: Average duration of each session * **Articles Viewed Per Session**: Average pageviews per session Click the download icon in the upper right corner of any chart to export as CSV or PNG. Some platforms have data collection limitations that affect Engagement metrics. See the [Environment Compatibility](#environment-compatibility) section for details on which metrics are available per platform. ### Using Index & Overlap Index & Overlap helps you understand how your audiences relate to each other. In Audience Insights, select the *Index & Overlap* tab. Select a base cohort from the dropdown. This is the baseline for index calculations. It is common practice to select *Everyone* to see how likely cohort members are to belong to other cohorts compared to all site users. However, if you have a lot of "passerby" traffic (single pageview visitors) it can also be useful to set this to a group of users that have done at least 2 or 3 pageviews across all of your properties to view a more normalized set of indices. Choose the cohorts or tag groups you want to compare against. You can also click *Select All Cohorts* to compare against your entire cohort library. Selecting too many / all cohorts for comparison can cause slow loading times or timeout issues due to the significant processing required for these reports. Try creating a tag for cohorts that are valuable for insights reporting in order to quickly filter this view to relevant comparison cohorts. Apply a minimum uniques threshold to filter out small cohorts that may not provide statistically meaningful results. For each comparison cohort, review: * **Overlap %**: What percentage of your selected cohort also belongs to the comparison cohort * **Index**: How much more or less likely users are to be in the comparison cohort vs. the base Index scores use 100 as the baseline: | Index Score | Interpretation | Example | | :---------- | :---------------------- | :------------------------------------------ | | 100 | Equal likelihood | Users are just as likely as the base cohort | | 125 | 25% more likely (1.25x) | Users are somewhat more likely | | 200 | 100% more likely (2x) | Users are twice as likely | | 75 | 25% less likely (0.75x) | Users are somewhat less likely | **Example**: If your "Foodies" cohort (primary cohort) shows an index of 145 against "New York Residents" (comparison cohort) with the base set to "Everyone," users in the Foodies cohort are 45% (or 1.45x) more likely to also be in the New York Residents cohort than the average site user. ### Viewing Technographics Technographics provide insights into the devices, platforms, browsers, and locations of your audience. In Audience Insights, select the *Technographics* tab. Analyze audience composition across: * **Device Type**: Desktop, Mobile, Tablet distribution * **Platform**: Web, AMP, FIA, iOS, Android distribution * **Browser**: Chrome, Safari, Firefox, Edge, etc. * **Country**: Geographic distribution * **Province/City**: Regional breakdowns (where available) Download pie charts as CSV data or PNG images for presentations. ### Viewing Third-Party Insights (Beta) Third-Party Insights is currently in **Beta**. Data is provided by Eyeota and shows how your cohorts index against third-party audience segments. In Audience Insights, select the *Third-Party* tab. Choose from available categories: * **Demographics**: Age, gender, household composition * **B2B**: Industry, company size, job function * **Consumer Intent**: Purchase intent signals * **Automobile Intent**: Vehicle purchase interests * **Travel Intent**: Travel and destination interests Select the base cohort for index comparison (typically "Everyone"). Review how your cohort indexes against Eyeota's third-party segments. For example, an index of 104 for "Female" means users in your cohort are 4% more likely to be female than the average user. ### Viewing Content Read Insights Content Read surfaces the custom properties from your Pageview events that are most associated with your selected cohorts. In Audience Insights, select the *Content Read* tab. The dashboard displays your custom Pageview properties (e.g., article.author, article.category, article.keywords) ranked by how strongly they index for your selected cohort. Use the data to understand what content types, authors, sections, or topics resonate most with specific audiences. Content Read uses **quantile limits** to keep only the most popular items and filter out the long tail. This optimization improves performance and reduces storage costs. For example, with a 0.5 (50%) quantile limit on article titles, only the top 50% most-viewed titles are retained. Less popular content may appear as null values. If you're seeing unexpected null values, contact your Customer Success Manager to review your quantile limit configuration. ### Viewing Hourly/Daily Activity **Deprecation Notice**: The Hourly/Daily Activity feature will be removed from the platform effective **January 19, 2026** and may already be ineffective in some workspaces. Plan to transition any workflows that depend on this feature. Hourly/Daily Activity shows when your audiences are most likely to be online, enabling optimized ad scheduling. In Audience Insights, select the *Hourly/Daily Activity* tab (if available). Note that the date range filter does not apply to this tab. Data is always aggregated from the last week. Hover over the information icon to see the exact date range. Select a base cohort to compare activity patterns against. Select how to segment the data: * **All**: Entire cohort across all platforms and devices * **Device**: Breakdown by Desktop, Mobile, Tablet * **Platform**: Breakdown by AMP, Android, FIA, iOS, Web Review the two graphs: * **Activity: Time of Day**: 24-hour activity pattern showing when users are most likely to be online * **Activity: Day of Week**: 7-day pattern showing which days have highest engagement All times in Hourly/Daily Activity reports are displayed in **UTC**. ### Adding Domains to Filters If you have Permutive deployed across multiple domains, you can add them to enable domain-specific filtering. Go to *Settings* in the Permutive Dashboard. Click on the *Project* tab. Under *Domains*, click *Edit*. Enter the domain in its cleanest form (e.g., `support.permutive.com` rather than `https://support.permutive.com`) and click *+ Add*. Click *Save* to apply the changes. Permutive will only start collecting data for a domain from when it was added to the Domains list. Historical data is not retroactively associated with newly added domains. ### Exporting Audience Insights Reports Export your insights as images, spreadsheets, or PDF reports for presentations and stakeholder sharing. Set all desired filters before exporting, as they cannot be changed during the export process. Click the green *Export Report* button in the top right corner of the Insights page. A loading indicator shows progress. Depending on filter complexity, this may take some time. If some charts fail to load, a *Retry* button will appear to reload them. Review the export preview, then choose an export format: * **Export chart images**: Downloads all charts as individual images in a ZIP file * **Export to Excel**: Downloads raw data as an Excel spreadsheet with each chart as a separate sheet * **Print Report**: Opens print dialog where you can save as PDF ### Introduction to Enterprise Workspaces Enterprise Workspaces organize publishers and alliances into a hierarchy, with each business unit having its own Permutive workspace. Understanding how data flows through this hierarchy is essential for interpreting Audience Insights correctly. Enterprise Workspaces allow large organizations to structure their Permutive deployment by business unit, region, or publication. A typical structure includes: * **Organization Workspace**: The top-level workspace representing the global organization * **Parent Workspaces**: Business units or regional divisions * **Child Workspaces**: Individual publications or properties This hierarchy enables centralized management while allowing each unit to maintain its own cohorts, activations, and insights. Data in Enterprise Workspaces flows **upward** through the hierarchy: * **Organization Workspace**: Has access to data from all workspaces * **Parent Workspace**: Can access data from its child workspaces * **Child Workspace**: Only has access to its own data When viewing Audience Insights, you see data aggregated from your current workspace and all its descendants. Cohorts flow **downward** through the hierarchy: * Cohorts created in a parent workspace are deployed to all child workspaces * Cohorts created in a child workspace are **not** visible in parent workspaces **Example**: A cohort created in the Organization workspace will be available in all Business Unit and Publication workspaces below it. The Audience filter in Insights only shows cohorts available to your current workspace. This includes: * Cohorts created in your current workspace * Cohorts inherited from parent workspaces This ensures no data leakage between organizational units. Remember that Insights data reflects your workspace's position in the hierarchy: * **At Organization level**: Data aggregated from all workspaces * **At Business Unit level**: Data from that unit and its publications * **At Publication level**: Data from that publication only Cohort sizes and metrics will vary based on which workspace you're viewing. **Predicted Audience Size** (the historical estimate shown in the Cohort Builder) and **Live Audience Size** (the real-time count of users currently qualifying) always reflect the workspace you're currently viewing, even for cohorts inherited from parent workspaces. For more about these metrics, see [Custom Cohorts: Audience Size Definitions](/products/signals/cohorts/custom#whats-the-difference-between-predicted-audience-size-and-live-audience-size). ## Troubleshooting **Cause**: The query is too complex due to multiple filters, large date ranges, or many cohorts selected. **Solution**: * Reduce the number of cohorts or filters selected * Narrow the date range * Remove less critical secondary filters * If the issue persists, contact support **Cause**: The domain hasn't been added to your project settings. **Solution**: * Navigate to *Settings > Project > Domains* * Add the domain and save * Note: Data collection begins from when the domain is added; historical data won't appear **Cause**: Quantile limits filter out less popular content to optimize performance. **Solution**: * This is expected behavior for long-tail content * Contact your Customer Success Manager if you need to adjust quantile thresholds for specific properties **Cause**: Some platforms have SDK limitations that prevent certain metrics from being collected. **Solution**: * Review the [Environment Compatibility](#environment-compatibility) table to understand which metrics are available per platform * This is a platform limitation, not a configuration issue **Cause**: The minimum uniques threshold is filtering out all comparison cohorts. **Solution**: * Lower or remove the minimum uniques filter * Ensure the selected comparison cohorts have sufficient user activity in the date range **Cause**: Some charts failed to load due to query complexity or timeout. **Solution**: * Click the *Retry* button to attempt reloading failed charts * If retries fail, simplify your filters and try again * Charts that don't load won't appear in the export **Cause**: The cohort was created in a child workspace and you're viewing a parent workspace. **Solution**: * Cohorts only propagate downward in the hierarchy * To use a cohort across multiple workspaces, create it in a parent workspace * Navigate to the workspace where the cohort was created to access it ## Environment Compatibility #### Core Product Audience Insights data availability varies by platform due to SDK limitations: | Metric | Web | AMP | FIA | iOS | Android | | :-------------------------- | :----------- | :----------- | :----------- | :----------- | :----------- | | Uniques | | | | | | | Sessions | | | | | | | Pageviews | | | | | | | Articles Viewed Per Session | | | | | | | Average Session Length | | | | | | #### Platform-Specific Notes | Platform | Support | Notes | | :------- | :-----: | :------------------------------------------------------------------------------------------------ | | Web | ✓ | Full support for all Audience Insights features | | AMP | \~ | Limited support; Sessions, Pageviews, and Session Length not available | | FIA | ✓ | Full support for all features | | iOS | \~ | Uniques, Sessions, and Articles Per Session available; Pageviews and Session Length not available | | Android | \~ | Uniques, Sessions, and Articles Per Session available; Pageviews and Session Length not available | ## Dependencies | Dependency | Required | Description | | :------------------- | :------- | :--------------------------------------------------------------------------------- | | Permutive SDK | ✓ | Required for collecting user behavior data that powers all Audience Insights | | Cohorts | ✓ | At least one cohort must exist to filter Audience Insights reports | | Eyeota Integration | \~ | Required only for Third-Party Insights (Beta). Contact your CSM to enable. | | Domain Configuration | \~ | Required only if filtering by domain. Domains must be added in Settings > Project. | ## Limits #### Data Retention | Feature | Description | Limit | | :-------------------- | :--------------------------------------------- | :------------------ | | Lookback Window | Maximum historical data available for analysis | 18 months | | Hourly/Daily Activity | Data aggregation period | Last 7 days (fixed) | #### Performance Limits | Metric | Description | Limit | | :--------------- | :------------------------------------------ | :-------------------------- | | Query Timeout | Maximum time for report generation | Varies by complexity | | Export Load Time | Time for charts to render in export preview | Varies by filter complexity | #### Feature Limits | Feature | Description | Limit | | :-------------------- | :------------------------------------ | :------------------- | | Base Cohort Selection | Number of base cohorts per report | 1 | | Comparison Cohorts | Number of cohorts for Index & Overlap | Varies by complexity | ## FAQ **Audience Insights** analyze users based on cohort membership and on-site behavior, helping you understand who your audiences are and how they engage with content. **Campaign Insights** analyze users who have interacted with specific advertising campaigns (via SlotClicked and SlotViewable events), helping you measure and optimize campaign performance. Index scores use 100 as the baseline: * **100** = Equal likelihood to the base cohort * **Above 100** = More likely (e.g., 150 = 50% more likely, or 1.5x) * **Below 100** = Less likely (e.g., 75 = 25% less likely, or 0.75x) For example, an index of 125 means users are either 1.25x more likely or 25% more likely than the base cohort. AMP and mobile SDKs have platform limitations that prevent certain metrics from being collected. For example, AMP doesn't support session tracking, so Sessions, Pageviews, and Session Length metrics are unavailable. This is a platform constraint, not a configuration issue. Quantile limits filter out less popular content to optimize performance. For example, a 0.5 (50%) limit on article titles keeps only the top 50% most-viewed titles. Content below the threshold appears as null values. This reduces storage costs and improves query speed while retaining the most meaningful data. Audience Insights supports an 18-month lookback window, starting from when you first deployed Permutive on your site. This extended history is possible because Permutive uses first-party cookies, and it enables seasonal campaign optimization and long-term trend analysis. Yes. Use the *Export Report* button to download: * **Chart images** (ZIP file of individual PNGs) * **Excel spreadsheet** (raw data with each chart as a sheet) * **PDF** (via Print dialog, save as PDF) Set all your filters before exporting, as they cannot be modified during the export process. Hourly/Daily Activity is being deprecated and will be removed from the platform on **January 19, 2026**. If you rely on this feature, plan to transition your workflows before that date. Third-Party Insights (currently in Beta) show how your cohorts index against third-party audience segments provided by Eyeota. Categories include Demographics, B2B, Consumer Intent, Automobile Intent, and Travel Intent. This helps enrich your first-party data with additional audience characteristics. The base cohort establishes the baseline for index calculations. Setting it to "Everyone" compares your selected cohort against all site users, answering "how much more likely are these users to exhibit X behavior than the average user?" You can also set a specific cohort as the base for more targeted comparisons. In Enterprise Workspaces, Insights data rolls up through the hierarchy: * **Organization level** sees aggregated data from all workspaces * **Parent workspaces** see data from their child workspaces * **Child workspaces** see only their own data The Audience filter shows only cohorts available to your workspace (those created locally or inherited from parent workspaces). Cohort sizes reflect data from your current workspace and its descendants. ## Changelog ### 2026 **January 2026** * Hourly/Daily Activity feature will be removed from the platform ### 2023 **February 2023** * Added Export Reports functionality for Audience, Campaign, and Revenue Insights * Support for PDF, Excel, and image exports ### 2021 **July 2021** * Updated Hourly/Daily Activity with improved filtering and breakdown options ### 2020 **June 2020** * Added Third-Party Insights (Beta) with Eyeota integration * Categories: Demographics, B2B, Consumer Intent, Automobile Intent, Travel Intent **May 2020** * Launched Audience Insights with Engagement, Technographics, Index & Overlap, and Content Read tabs For detailed changelog information, visit our [Changelog](https://changelog.permutive.com/). # Campaign Insights Source: https://docs.permutive.com/products/workflows-insights/insights/campaign Analyze campaign performance, delivery metrics, and audience engagement for your advertising campaigns ## Overview **Campaign Insights** analyzes the users who have interacted with your advertising campaigns, providing performance metrics, audience composition data, and engagement analytics. By capturing events on users who saw or clicked on an ad, Campaign Insights enables publishers to optimize campaigns mid-flight and create data-rich post-campaign reports that demonstrate delivery value to advertisers. Campaign Insights filters by Campaign ID and explores five key dimensions: Engagement, Technographics, Index & Overlap, Third-Party data, and Content Read. Each dimension helps you understand not just how your campaign performed, but who engaged with it and what content resonated. Campaign Insights is available for **display campaigns only**. Video and third-party creatives are not currently supported. ## Why Use Campaign Insights? **Optimize campaigns mid-flight** — Real-time engagement metrics and audience composition data allow you to identify underperforming segments and make targeting adjustments before campaign end, maximizing ROI for advertisers. **Create compelling post-campaign reports** — Transform delivery data into rich audience narratives that demonstrate not just impressions served, but who engaged with the campaign and how they behaved on-site. **Understand campaign audiences** — Index & Overlap analysis reveals which of your cohorts over-index for campaign engagement, enabling smarter targeting recommendations for future campaigns. **Validate targeting effectiveness** — See whether the intended audience segments actually engaged with the campaign, and identify unexpected high-performing audiences for optimization opportunities. ## Concepts ### Definitions * **Campaign ID**: The unique identifier for your campaign in the ad server. In Google Ad Manager, this is the Order ID. In Xandr, this is the Insertion Order ID. This is the primary filter for all Campaign Insights reports. * **SlotClicked**: An ad event fired when a user clicks on an ad creative. Used to measure click engagement in Campaign Insights. * **SlotViewable**: An ad event fired when an ad creative meets viewability standards (typically 50% of pixels in view for at least 1 second). Used to measure impression engagement. * **Uniques**: The number of unique users who have interacted with the campaign (via SlotClicked or SlotViewable events) during the filtered date range. * **Sessions**: The number of sessions during which users interacted with the campaign. * **Pageviews**: The number of pageview events from users who interacted with the campaign. * **Index**: A score indicating how much more or less likely users who engaged with the campaign are to belong to a specific cohort compared to a base cohort. An index of 100 is baseline, above 100 indicates higher likelihood. * **Overlap**: The percentage of campaign-engaged users who also belong to a specific cohort. ## Workflows ### Connecting Your Ad Server Before using Campaign Insights, you must connect your ad server (Google Ad Manager, Xandr or Equativ) to enable campaign event tracking. This one-time setup allows Permutive to receive SlotClicked and SlotViewable events from your campaigns. ### Filtering by Campaign ID Publishers start by entering their Campaign ID (Order ID for GAM, Insertion Order ID for Xandr, Insertion-ID for Equativ) in the search field. Unlike Audience Insights which filters by cohort, Campaign Insights uses the campaign as the primary filter, then allows secondary filters for date range, domain, device, and platform. ### Analyzing Campaign Engagement The Engagement tab displays performance metrics including unique users reached, sessions, pageviews, time spent, and articles viewed. This data helps quantify the depth of engagement beyond raw impression counts. ### Understanding Campaign Audience Composition Index & Overlap analysis reveals which of your existing cohorts over-index among users who engaged with the campaign. This helps validate whether targeting reached the intended audience and identifies unexpected high-performing segments. ## Guides ### Connecting Google Ad Manager To use Campaign Insights with GAM, you must first establish the integration. In the Permutive Dashboard, go to *Integrations > Advertising > Google Ad Manager*. Follow the integration setup instructions to connect your GAM account and enable event tracking. Once connected, SlotClicked and SlotViewable events will begin appearing in Campaign Insights within 24-48 hours. For detailed GAM integration instructions, see the [Google Ad Manager Integration](/integrations/advertising/ad-servers/google-ad-manager) documentation. ### Connecting Xandr To use Campaign Insights with Xandr, configure the Xandr integration. In the Permutive Dashboard, go to *Integrations > Advertising > Xandr*. Follow the integration setup instructions to connect your Xandr account. Once connected, campaign events will begin appearing in Campaign Insights. For detailed Xandr integration instructions, see the [Xandr Integration](/integrations/advertising/ad-servers/xandr) documentation. ### Connecting Equativ To use Campaign Insights with Equativ, configure the Equativ integration. In the Permutive Dashboard, go to *Integrations > Advertising > Equativ*. Follow the integration setup instructions to connect your Equativ account. Once connected, campaign events will begin appearing in Campaign Insights. For detailed Equativ integration instructions, see the [Equativ Integration](/integrations/advertising/ad-servers/equativ) documentation. ### Configuring Campaign Insights Filters All Campaign Insights reports start with selecting a campaign, then applying secondary filters. In the Campaign ID search field, enter: * **For GAM**: The Order ID from Google Ad Manager * **For Xandr**: The Insertion Order ID from Xandr * **For Equativ**: The Insertion-ID Click *Edit* next to *Filter* to apply additional parameters: * **Date Range**: Last day, last 7 days, last 30 days, or custom range * **Domain**: Filter by specific domains if deployed across multiple sites * **Device Type**: Desktop, Mobile, or Tablet * **Countries**: Filter by geographic location * **Platform**: Web or AMP only (FIA, iOS, and Android are not supported) Click *Apply* to update all reports with your selected filters. Campaign Insights does **not** support FIA (Facebook Instant Articles), iOS, or Android platforms. Only Web and AMP are available for campaign analysis. ### Viewing Campaign Engagement Engagement insights show how deeply users engaged with your campaign beyond simple impression metrics. In Campaign Insights, select the *Engagement* tab. Analyze the following data points: * **Uniques**: Number of unique users who engaged with the campaign * **Sessions**: Total sessions during which campaign engagement occurred * **Pageviews**: Total pageviews from campaign-engaged users * **Average Time Spent (Mins)**: Average browsing time for engaged users * **Average Session Length**: Average duration of sessions with campaign engagement * **Articles Viewed Per Session**: Average pageviews per session Click the download icon to export metrics as CSV or PNG. ### Using Campaign Index & Overlap Index & Overlap helps you understand which cohorts over-index among users who engaged with your campaign. In Campaign Insights, select the *Index & Overlap* tab. Select a base cohort from the dropdown. This is the baseline for index calculations. **Tip**: Set to *Everyone* to see how likely campaign-engaged users are to belong to specific cohorts compared to all site users. Choose the cohorts or tag groups you want to compare against. You can also click *Select All Cohorts* to compare against your entire cohort library. Apply a minimum uniques threshold to filter out small cohorts that may not provide statistically meaningful results. For each comparison cohort, review: * **Overlap %**: What percentage of campaign-engaged users also belong to the comparison cohort * **Index**: How much more or less likely campaign-engaged users are to be in the comparison cohort vs. the base Index scores use 100 as the baseline: | Index Score | Interpretation | Campaign Example | | :---------- | :---------------------- | :------------------------------------------------------------------- | | 100 | Equal likelihood | Campaign-engaged users are as likely as average to be in this cohort | | 150 | 50% more likely (1.5x) | Campaign is over-indexing with this audience | | 200 | 100% more likely (2x) | Strong affinity between campaign and cohort | | 75 | 25% less likely (0.75x) | Campaign is under-indexing with this audience | **Example**: If your "Luxury Travel" campaign shows an index of 175 against "High Income Households" with the base set to "Everyone," users who engaged with the campaign are 75% (or 1.75x) more likely to be in the High Income Households cohort than the average site user. ### Viewing Campaign Technographics Technographics reveal the device, platform, browser, and geographic distribution of users who engaged with your campaign. In Campaign Insights, select the *Technographics* tab. Analyze campaign-engaged user composition across: * **Device Type**: Desktop, Mobile, Tablet distribution * **Platform**: Web, AMP distribution * **Browser**: Chrome, Safari, Firefox, Edge, etc. * **Country**: Geographic distribution * **Province/City**: Regional breakdowns (where available) Download pie charts as CSV data or PNG images for post-campaign reports. ### Viewing Third-Party Insights (Beta) Third-Party Insights is currently in **Beta**. Data is provided by Eyeota and shows how campaign-engaged users index against third-party audience segments. In Campaign Insights, select the *Third-Party* tab. Choose from available categories: * **Demographics**: Age, gender, household composition * **B2B**: Industry, company size, job function * **Consumer Intent**: Purchase intent signals * **Automobile Intent**: Vehicle purchase interests * **Travel Intent**: Travel and destination interests Select the base cohort for index comparison (typically "Everyone"). Review how campaign-engaged users index against Eyeota's third-party segments. For example, an index of 130 for "High Income" means campaign-engaged users are 30% more likely to be high income than the average user. ### Viewing Campaign Content Read Content Read surfaces what content resonated with users who engaged with your campaign. In Campaign Insights, select the *Content Read* tab. The dashboard displays your custom Pageview properties (e.g., article.author, article.category, article.keywords) ranked by how strongly they index for campaign-engaged users. Use the data to understand what content types, authors, sections, or topics campaign-engaged users consumed before, during, and after engagement. Content Read uses **quantile limits** to keep only the most popular items and filter out the long tail. This optimization improves performance and reduces storage costs. For example, with a 0.5 (50%) quantile limit on article titles, only the top 50% most-viewed titles are retained. Less popular content may appear as null values. If you're seeing unexpected null values, contact your Customer Success Manager to review your quantile limit configuration. ### Viewing Hourly/Daily Activity **Deprecation Notice**: The Hourly/Daily Activity feature will be removed from the platform effective **January 19, 2026**. Plan to transition any workflows that depend on this feature. Hourly/Daily Activity shows when users who engaged with your campaign are most active on your site. In Campaign Insights, select the *Hourly/Daily Activity* tab (if available). Note that the date range filter does not apply to this tab. Data is always aggregated from the last week. Hover over the information icon to see the exact date range. Select a base cohort to compare activity patterns against. Select how to segment the data: * **All**: All campaign-engaged users across platforms * **Device**: Breakdown by Desktop, Mobile, Tablet * **Platform**: Breakdown by Web, AMP Review the two graphs: * **Activity: Time of Day**: 24-hour activity pattern showing when campaign-engaged users are online * **Activity: Day of Week**: 7-day pattern showing peak engagement days All times in Hourly/Daily Activity reports are displayed in **UTC**. ### Exporting Campaign Insights Reports Export your campaign analysis for post-campaign reports and client presentations. Set all desired filters before exporting, as they cannot be changed during the export process. Click the green *Export Report* button in the top right corner of the Campaign Insights page. A loading indicator shows progress. Depending on filter complexity, this may take some time. If some charts fail to load, a *Retry* button will appear to reload them. Review the export preview, then choose an export format: * **Export chart images**: Downloads all charts as individual images in a ZIP file * **Export to Excel**: Downloads raw data as an Excel spreadsheet with each chart as a separate sheet * **Print Report**: Opens print dialog where you can save as PDF ### Using Targeted Impressions The Targeted Impressions dashboard shows the volume of impressions targeted using Permutive cohorts compared to total targetable impressions. This helps demonstrate the value Permutive provides for campaign activation. The Targeted Impressions dashboard is only visible to users with **Administrator** or **Editor** roles. In Campaign Insights, select the *Targeted Impressions* tab from the navigation bar. Click *Edit* next to *Cohorts* to filter by: * All cohorts * Multiple specific cohorts * A single cohort **Note**: If a Cohort ID appears instead of a name, the cohort may have been deleted. Historical data remains available for analysis. Click *Edit* next to *Filter* to specify the date range for analysis. The Overview section displays: * **% Targeted**: Targeted impressions divided by total targetable impressions * **Change in % Targeted**: Comparison to the previous period * **Targeted Impressions**: Total impressions targeted with cohorts * **Total Impressions**: Total targetable impressions available Scroll to the Cohort Breakdown table to see per-cohort performance: * Cohort Name and ID * Cohort Type (first or third-party) * Targeted Impressions per cohort * % Targeted per cohort Use the Top 100/Bottom 100 filter to focus on highest or lowest performers. The Opportunities section reveals: * **Change in Targeted Impressions**: Growth and seasonality trends * **Top Performing Cohorts**: Which cohorts generate the most TIs * **TIs by Advertiser**: Top advertisers purchasing the most TIs * **TIs by Order**: Top campaigns flighting the most TIs Click the download icon on any chart to export as CSV. Pie charts and graphs can also be exported as PNG files. ## Troubleshooting **Cause**: The Targeted Impressions dashboard requires Administrator or Editor role access. **Solution**: * Verify your user role in Settings > Team * Contact your workspace administrator to request elevated permissions * Note that Viewer roles cannot access this dashboard **Cause**: The Campaign ID may be incorrect, the campaign hasn't started yet, or the ad server integration isn't configured properly. **Solution**: * Verify the Campaign ID matches exactly (Order ID for GAM, Insertion Order ID for Xandr) * Ensure the campaign has active delivery and isn't scheduled for a future date * Check that SlotClicked and SlotViewable events are configured in your ad server integration * Allow 24-48 hours after integration setup for data to begin flowing **Cause**: Campaign Insights does not support these platforms. **Solution**: * Campaign Insights only supports Web and AMP platforms * This is a product limitation, not a configuration issue * Use Audience Insights for analyzing users on FIA, iOS, and Android **Cause**: The query is too complex due to large date ranges or many filters selected. **Solution**: * Narrow the date range * Remove less critical secondary filters * If the issue persists, contact support **Cause**: The campaign audience size may be too small for statistically meaningful index calculations. **Solution**: * Extend the date range to capture more campaign engagement data * Review the minimum uniques threshold setting * Small sample sizes can produce volatile index scores **Cause**: Quantile limits filter out less popular content to optimize performance. **Solution**: * This is expected behavior for long-tail content * Contact your Customer Success Manager if you need to adjust quantile thresholds **Cause**: The Xandr integration may not be configured correctly, or the Insertion Order ID format may be incorrect. **Solution**: * Verify your Xandr integration is active in Settings > Integrations * Ensure you're using the Insertion Order ID (not Line Item ID or other identifiers) * Check that creative tags are properly implemented **Cause**: Some charts failed to load due to query complexity or timeout. **Solution**: * Click the *Retry* button to attempt reloading failed charts * If retries fail, simplify your filters and try again * Charts that don't load won't appear in the export ## Environment Compatibility #### Core Product Campaign Insights data availability is more limited than Audience Insights due to platform restrictions: | Metric | Web | AMP | FIA | iOS | Android | | :-------------------------- | :----------- | :----------- | :---------- | :---------- | :---------- | | Uniques | | | | | | | Sessions | | | | | | | Pageviews | | | | | | | Articles Viewed Per Session | | | | | | | Average Session Length | | | | | | #### Ad Server Support | Ad Server | Support | Campaign ID Field | | :---------------------- | :-----: | :----------------- | | Google Ad Manager (GAM) | ✓ | Order ID | | Xandr | ✓ | Insertion Order ID | | Equativ | ✓ | Insertion ID | #### Platform-Specific Notes | Platform | Support | Notes | | :------- | :-----: | :---------------------------------------------- | | Web | ✓ | Full support for all Campaign Insights features | | AMP | \~ | Limited support; only Uniques available | | FIA | ✗ | Not supported for Campaign Insights | | iOS | ✗ | Not supported for Campaign Insights | | Android | ✗ | Not supported for Campaign Insights | ## Dependencies | Dependency | Required | Description | | :-------------------- | :------- | :------------------------------------------------------------------------------------- | | Permutive SDK | ✓ | Required for collecting user behavior data that powers engagement metrics | | Ad Server Integration | ✓ | Either GAM, Xandr or Equativ integration must be configured to receive campaign events | | SlotClicked Events | ✓ | Ad click events must be configured in your ad server for click engagement data | | SlotViewable Events | ✓ | Viewability events must be configured for impression engagement data | | Cohorts | \~ | Required for Index & Overlap analysis; optional for other tabs | | Eyeota Integration | \~ | Required only for Third-Party Insights (Beta). Contact your CSM to enable. | ## Limits #### Campaign Restrictions | Restriction | Description | Limit | | :-------------------- | :--------------------------- | :----------------------- | | Deal Types | Supported campaign types | Direct and PG Deals only | | Creative Types | Supported creative formats | Display only (no video) | | Third-Party Creatives | Third-party served creatives | Not supported | #### Data Retention | Feature | Description | Limit | | :-------------------- | :-------------------------------- | :------------------ | | Lookback Window | Maximum historical data available | 18 months | | Hourly/Daily Activity | Data aggregation period | Last 7 days (fixed) | #### Performance Limits | Metric | Description | Limit | | :--------------- | :---------------------------------- | :-------------------------- | | Query Timeout | Maximum time for report generation | Varies by complexity | | Export Load Time | Time for charts to render in export | Varies by filter complexity | ## FAQ **Audience Insights** analyze users based on cohort membership and on-site behavior, filtering primarily by cohort. **Campaign Insights** analyze users who interacted with specific advertising campaigns (via SlotClicked and SlotViewable events), filtering primarily by Campaign ID. Key differences: * Campaign Insights uses Campaign ID as the primary filter; Audience Insights uses cohorts * Campaign Insights only supports Web and AMP; Audience Insights supports all platforms including FIA, iOS, and Android * Campaign Insights is limited to PG deals; Audience Insights has no such restriction Campaign Insights does not support FIA (Facebook Instant Articles), iOS, or Android platforms. This is a product limitation due to how ad events are tracked on these platforms. If you need to analyze audiences on these platforms, use Audience Insights instead. For Google Ad Manager, use the **Order ID**, not the Line Item ID. The Order ID can be found in GAM under Orders > \[Your Order] > Order details. For Xandr, use the **Insertion Order ID**. This can be found in the Xandr console under your Insertion Order details. Campaign Insights requires deterministic matching between ad events and user profiles. Programmatic Guaranteed (PG) deals provide the direct relationship needed for accurate tracking. Video creatives and third-party served ads have additional technical constraints that prevent reliable event attribution. Index scores show how much more or less likely campaign-engaged users are to belong to specific cohorts: * **100** = Equal likelihood to the base cohort * **Above 100** = More likely (e.g., 150 = 50% more likely) * **Below 100** = Less likely (e.g., 75 = 25% less likely) High index scores indicate strong affinity between your campaign and that cohort, validating targeting effectiveness or revealing new optimization opportunities. No, Campaign Insights filters by a single Campaign ID at a time. To compare campaigns, run separate reports and export the data for comparison in Excel or your preferred analysis tool. Hourly/Daily Activity is being deprecated and will be removed from the platform on **January 19, 2026**. If you rely on this feature, plan to transition your workflows before that date. Common causes include: * Campaign hasn't started delivery yet * Campaign ID is incorrect (verify Order ID for GAM, Insertion Order ID for Xandr) * Ad server integration isn't configured properly * SlotClicked/SlotViewable events aren't implemented * Data takes 24-48 hours to appear after integration setup Yes. Use the *Export Report* button to download: * **Chart images** (ZIP file of individual PNGs) * **Excel spreadsheet** (raw data with each chart as a sheet) * **PDF** (via Print dialog, save as PDF) These exports are designed for post-campaign reports and client presentations. The Targeted Impressions dashboard shows how many ad impressions were targeted using Permutive cohorts compared to total targetable impressions. Key metrics include: * **% Targeted**: The percentage of impressions targeted with cohorts * **Cohort Breakdown**: Per-cohort targeting performance * **Opportunities**: TI trends by cohort, advertiser, and order This dashboard helps demonstrate ROI by showing what proportion of inventory is being activated through Permutive. # Optimization Source: https://docs.permutive.com/products/workflows-insights/optimization Optimize campaign performance and audience targeting with AI-driven recommendations ## Overview **Optimization** enables publishers using Google Ad Manager (GAM) to understand cohort performance and identify opportunities to improve in-flight campaign delivery and CTR performance. The product provides insight into how individual cohorts are driving impressions and clicks for campaigns, along with AI-driven recommendations for cohorts that could improve campaign performance. Publishers can view their GAM campaigns, analyze how different cohorts are performing, and receive intelligent recommendations for new targeting opportunities—all without the time-consuming manual process of extracting and analyzing cohort-level data from GAM. ## Why Use Optimization? **Automate cohort performance analysis** — Optimization automates the ingestion and analysis of cohort performance data from GAM, eliminating the need for time-consuming manual data extraction and analysis. Get instant visibility into how your cohorts are driving campaign delivery and performance. **Receive AI-driven targeting recommendations** — See recommendations for cohorts you're not currently targeting that have a high propensity to click on your campaign or offer incremental user reach to drive increased delivery. The AI ensures recommendations align with your campaign's targeting intent. **Make data-driven optimization decisions** — Understand which cohorts are contributing to campaign success and troubleshoot any delivery issues with cohort-level performance data including impressions, CTR, and viewability metrics. ## Concepts ### Definitions * **Google Ad Manager (GAM)**: The ad server currently supported for the Optimization product. Publishers use GAM to set up and manage digital advertising campaigns. GAM is the primary source of truth for campaign impression, click, and viewability data. * **Order/Campaign**: An Order in GAM (sometimes referred to as a campaign) represents a configuration for advertising delivery on publisher sites. For direct campaigns, an order includes all targeting criteria (ad units, sites, audiences) and usually hosts the campaign creative. * **Line Item**: A sub-division of an Order in GAM that enables publishers to define more granular targeting strategies and delivery schedules within a campaign. Multiple Line Items are often used to test different audience targeting strategies or manage delivery of different creatives. * **Targeted Cohorts**: Permutive cohorts that are explicitly set as targeting criteria for a campaign's line items. * **Non-targeted Cohorts**: Cohorts that are present when an impression is delivered but are not explicitly targeted for the campaign. These cohorts represent potential optimization opportunities. * **AI-based Recommendations**: Intelligent suggestions for cohorts that could improve campaign delivery or CTR, generated using generative AI to ensure recommendations align with the campaign's existing targeting intent. ## Workflows ### Viewing Campaign Performance Publishers access the Optimization tool to see an overview of all GAM campaigns with key delivery and performance metrics. The Campaigns List provides aggregate metrics showing how Permutive cohorts are driving campaign delivery over the last 30 days. Campaigns list showing aggregate metrics and campaign overview ### Analyzing Cohort Performance After selecting a campaign, publishers can drill into cohort-level performance data. The Cohort Performance view shows which targeted cohorts are driving impressions and clicks, along with daily performance trends for the top-performing cohorts. Cohort performance view with targeted cohorts table and daily performance chart ### Using AI Recommendations The AI-based recommendations feature suggests cohorts that are not currently targeted but show potential for improving campaign delivery or CTR. Publishers can filter recommendations by relevance and metric type, then view detailed information about each recommended cohort. AI recommendations panel with recommendation cards ## Key Features ### Campaigns List The campaign list page provides aggregated metrics across GAM orders along with a searchable list of your orders with key delivery and performance metrics. #### Aggregate Campaign Metrics These tiles provide an overview of Permutive usage for campaign targeting over the last 30 days with comparisons to the prior 30-day period: | Metric | Description | Comparison | | :--------------- | :------------------------------------------------------------------------- | :--------------------------------- | | Active Campaigns | The number of campaigns that were live in the last 30 days | Compared with the previous 30 days | | Targeted Cohorts | The number of Permutive cohorts being targeted across all active campaigns | Compared with the previous 30 days | | Permutive TI | The number of impressions targeted using Permutive data | Compared with the previous 30 days | #### Campaign List Table The campaign list is searchable by advertiser name, order ID, and order name. You can filter by campaign status and sort columns. | Column | Description | | :------------ | :------------------------------------------------------------------ | | Campaign Name | Includes the advertiser (if available), order name, and order ID | | Start Date | The start date of the order | | End Date | The end date of the order | | Imps. | The total number of impressions recorded for the order to date | | CTR | The average click-through rate recorded for the order to date | | Status | Whether the campaign is active or inactive based on the order dates | The campaign list and aggregated metrics do not include data from line items set at price priority or lower. Orders without any end date are also currently excluded. ### Cohort Performance Once you click on a campaign, the Cohort Performance view provides detailed information for optimizing campaign performance. #### Campaign Header View key information including order name, date range, and campaign status. #### Line Items Filtering When running campaigns with multiple targeting strategies, use the line item filter to focus your analysis on specific line items within the order. #### Campaign KPIs View overall performance metrics across the order or selected line items: * **Impressions**: Total impressions delivered since the order start date * **CTR**: Average click-through rate achieved * **Viewability**: Average percentage of viewable impressions When you apply a line item filter, KPIs update to reflect only the selected line items, with comparison metrics showing how they relate to the total order. #### Targeted Cohorts Performance Table See delivery and performance metrics for each cohort targeted across your order or selected line items: | Column | Description | | :-------------- | :------------------------------------------------------------ | | Cohort Name | The name of the targeted cohort | | Type | The cohort type | | CTR | The CTR achieved by the cohort based on GAM measurement | | Viewability | The percentage of viewable impressions for the cohort | | Impressions (%) | The percentage of total impressions contributed by the cohort | | Impressions | The number of impressions contributed by the cohort | #### Daily Performance Chart Track performance trends over time with a line chart showing impressions, CTR, and viewability for the top 5 cohorts. This helps identify the impact of optimizations and troubleshoot sudden changes in delivery or performance. ### AI-based Recommendations The recommendations feature suggests cohorts that could improve campaign delivery or CTR performance. #### Recommendation Filters * **High Relevance**: Enabled by default, uses generative AI to prioritize cohorts similar to those already targeted for the campaign. Disable to see a broader range of cohorts. * **Metric Toggle**: Filter cohorts based on the metric you're optimizing (delivery, CTR, or all). #### Recommendation Cards Each recommendation card shows: * **Cohort Name**: The name of the recommended cohort * **Campaign Opportunity**: Description of the potential reach or CTR uplift opportunity * **Recommendation Details**: Link to view more metrics and historical campaign information * **Unique Users**: Number of unique users in the cohort (last 30 days) * **Cohort ID**: The cohort identifier #### Recommendation Details Click on a recommendation to see additional metrics and historical campaign data: **Cohort Metrics:** | Metric | Definition | Comparison Metric | Definition | | :-------------- | :--------------------------------------------------------------------- | :------------------- | :---------------------------------------------------------- | | UUs | Unique users for the cohort in the last 30 days | % of total audience | Cohort UUs as a percentage of total audience | | Campaign CTR | CTR based on cohort delivery data for the campaign | vs. campaign average | CTR index comparing cohort to campaign average (e.g., 1.2x) | | Overlap | Percentage of users in both the target audience AND recommended cohort | vs. total users | Target audience index comparing overlap vs. all users | | Incremental UUs | Users in the recommended cohort NOT in the target audience | % reach uplift | Percentage uplift vs. target audience (e.g., +74%) | **Recent Campaigns:** | Column | Description | | :---------- | :------------------------------------------ | | Campaign | Campaign name with order ID | | Imps. | Targeted impressions the cohort delivered | | CTR | Targeted CTR of the cohort for the campaign | | Viewability | Viewability achieved by the cohort | | Status | Whether the campaign is currently running | ## Guides ### Enabling Optimization for Your Organization To access the Optimization product, you must meet the following eligibility criteria: 1. Have at least one GAM network integrated in your Permutive dashboard 2. Have enabled the necessary permissions for API reporting (see [GAM API Usage Guide](/guides/platform-guides/gam-api-usage)) 3. Have opted in to Permutive's AI product development principles 4. Have at least 100 cohorts activated to GAM (recommended for consistent recommendations) If you meet these criteria and would like to enable Optimization, please reach out to your Customer Success Manager (CSM). ### Viewing Campaign Performance In the Permutive Dashboard, go to *Optimization* to access the Campaigns List. View the aggregate campaign metrics tiles showing active campaigns, targeted cohorts, and Permutive targeted impressions over the last 30 days. Use the search box to find specific campaigns by advertiser name, order ID, or order name. Filter by campaign status if needed. Click on any campaign to view detailed cohort performance data. ### Filtering by Line Items Navigate to the Cohort Performance view for your selected campaign. Click the Line Items filter button to open the filter modal. Choose the specific line items you want to analyze. You can select multiple line items. Apply the filter to update all data on the Cohort Performance page to reflect only your selected line items. ### Understanding AI Recommendations On the Cohort Performance page, scroll to the AI Recommendations section to see suggested cohorts. Toggle *High Relevance* on/off based on whether you want recommendations similar to existing targeting. Use the metric toggle to focus on delivery, CTR, or both. Scroll through the recommendation cards to see potential cohorts. Each card shows the cohort name, opportunity description, unique users, and cohort ID. Click on a recommendation card to open the details modal with additional metrics, AI relevance explanation, and recent campaign performance data. Use the cohort ID and performance data to add promising cohorts to your campaign targeting in GAM. Not all cohorts are eligible for campaign targeting (test cohorts, cohorts using client data, etc.). You can configure which cohorts are available to be shown within recommendations through the use of tags. To configure cohort restrictions: 1. Create one / multiple tags associated to cohorts that you want to have included in your Optimization instance 2. Contact your Customer Success Manager (CSM) to provide the tag(s) you have defined 3. Technical Services will configure the restrictions for your organization Once configured, only cohorts associated with the tag will be included in your recommendations. Any new cohorts that you apply the tag to will automatically be included moving forward. ## Data Collection and Analysis The Optimization product leverages several data sources and analytical models to provide accurate reporting and intelligent recommendations. ### GAM API Data Collection Optimization ingests data from the GAM reporting API to provide accurate representation of campaign delivery and performance. When onboarding, Permutive backfills at least 3 months of campaign data so you can immediately use the product for live campaigns. Key points about GAM data: * Data is collected daily, so impression counts may not exactly match real-time GAM UI data * For the most up-to-date metrics, refer to GAM directly * Data is retained over time, enabling access to historical campaign information For detailed information about GAM API permissions and data collection, see the [GAM API Usage Guide](/guides/platform-guides/gam-api-usage). ### Cohort User Metrics For metrics such as unique user calculations, overlap, and indices, Optimization leverages SDK event data via the insights architecture. This data is calculated over the last 30 days, making it most relevant for analyzing in-flight campaigns. ## Troubleshooting If a campaign doesn't appear in the Optimization tool, check the following: **Campaign Priority Level**: Optimization only displays campaigns at standard priority or sponsorship priority levels. Campaigns at price priority or below are automatically excluded. **Order End Date**: We currently do not display campaigns with no order end date. **Solution**: Verify the campaign's priority level and order end date in GAM. If it's at price priority or lower, it will not appear in Optimization. The Optimization tool requires specific GAM API permissions to access campaign data. Missing or incomplete permissions can prevent campaigns from appearing. **Solution**: Verify that the Permutive service account ([dfp@permutive.com](mailto:dfp@permutive.com)) has the required permissions: * Access the interface: Ad Exchange, Delivery, Overview * Define and deliver ads: Ad units, placements, and key-values (all Permutive-related key-values need to be reportable) * Orders and line items (with appropriate team assignments if Teams feature is used) * Reports: Ad exchange reports, Ad manager reports * User network settings: Change history See the [GAM API Usage Guide](/guides/platform-guides/gam-api-usage) for complete permission requirements. The Optimization tool may not work properly with video campaigns or preroll campaigns. **Solution**: Video campaigns are not currently supported in Optimization. For video campaign analysis, use the Campaign Insights feature instead. There can be problems with the mapping between GAM network codes and organization IDs in the system, causing campaigns to have data but not be properly associated with your workspace. **Solution**: If you have multiple GAM network codes, ensure each is properly configured and mapped. Contact Permutive support if campaigns appear for one network but not another. Optimization data may not exactly match what you see in GAM. **Solution**: Optimization collects data from GAM on a daily basis, so there may be up to a 24-hour delay. For real-time impression counts, refer to GAM directly. Optimization data is most accurate for historical analysis rather than real-time monitoring. The AI recommendations section may be empty if there are insufficient cohorts to analyze or if your campaign has not yet delivered enough impressions in order to return statistically significant results. The minimum number of impressions needed varies but typically you will start to see good results once a campaign has delivered 200k+ impressions. **Solution**: Ensure you have at least 100 cohorts activated to GAM. The recommendation engine needs a sufficient number of cohorts and impressions to identify optimization opportunities. Also verify that cohort restrictions aren't excluding all potential recommendations. ## Environment Compatibility #### Core Product | Functionality | Web | iOS | Android | CTV | API Direct | | :----------------- | :----------- | :---------- | :---------- | :---------- | :---------- | | Campaign analysis | | | | | | | Cohort performance | | | | | | | AI recommendations | | | | | | Optimization currently only supports web display campaigns served through Google Ad Manager. Video campaigns and other ad servers are not supported. #### Ad Server Support | Ad Server | Supported | | :---------------- | :----------- | | Google Ad Manager | | | FreeWheel | | | Xandr | | | Other | | ## Dependencies | Dependency | Required | Description | | :------------------ | :------- | :--------------------------------------------------------------------------------------------------------------------------------------- | | GAM Integration | ✓ | At least one GAM network must be integrated in your Permutive dashboard. | | GAM API Permissions | ✓ | Required permissions must be granted to the Permutive service account ([dfp@permutive.com](mailto:dfp@permutive.com)) for API reporting. | | Permutive SDK | ✓ | The Permutive SDK must be deployed to track events and enable cohort user metrics calculations. | | AI Opt-in | ✓ | You must opt in to Permutive's AI product development principles to access AI-based recommendations. | | Cohort Activation | \~ | At least 100 cohorts activated to GAM is recommended for consistent AI recommendations. | ## Limits #### Feature Limits | Feature | Description | Limit | | :---------------- | :------------------------------------------------------------- | :-------------------------------- | | Campaign priority | Only standard and sponsorship priority campaigns are supported | Price priority and below excluded | | Video campaigns | Video and preroll campaigns are not supported | Not available | | Ad server | Only Google Ad Manager is supported | GAM only | #### Performance Limits | Metric | Description | Limit | | :-------------------- | :---------------------------------------- | :----------- | | Data refresh | Frequency of GAM data ingestion | Daily | | Data delay | Maximum delay for campaign data to appear | 24 hours | | Cohort metrics window | Time window for unique user calculations | Last 30 days | #### Usage Limits | SKU | Description | Limit | | :------------------ | :------------------------------------------ | :----------------------- | | Optimization access | Number of users who can access Optimization | \[Contact support] | | AI recommendations | Number of recommendation requests | \[Included with product] | ## FAQ Optimization currently only supports Google Ad Manager (GAM). Support for other ad servers is being evaluated based on demand and data availability. Optimization only displays campaigns where the associated line items are set up as sponsorship or standard priority. Campaigns at price priority or below are not included. Additionally, video campaigns are not currently supported. Optimization also requires campaigns to have an order end date. If your campaign doesn't have an end date associated with it, it won't appear in the Optimization tool. Data is collected from GAM on a daily basis. As a result, impression counts, clicks, and other metrics may not reflect the exact data you see in your GAM UI. For the most up-to-date metrics on an order or line item, refer to GAM directly. No, video campaigns are not currently supported in the Optimization product. The tool is designed for web display campaigns served through Google Ad Manager. Permutive requires specific permissions for the service account ([dfp@permutive.com](mailto:dfp@permutive.com)) including access to Ad Exchange, Delivery, Overview, Ad units/placements/key-values, Orders and line items, and Reports. See the [GAM API Usage Guide](/guides/platform-guides/gam-api-usage) for complete details. We recommend having at least 100 cohorts activated to GAM to get consistent AI recommendations. The recommendation engine analyzes cohorts that are not currently targeted to identify optimization opportunities. The AI uses generative AI to analyze the cohorts currently targeted for your campaign and prioritizes recommendations for cohorts that are semantically similar. This helps ensure recommendations align with the original targeting intent of the campaign. Yes, you can configure cohort restrictions to exclude certain cohorts (such as test cohorts or cohorts using client data) from appearing in recommendations. Contact your Customer Success Manager (CSM) to set up restrictions. # Planning Source: https://docs.permutive.com/products/workflows-insights/planning Create plans to respond to campaign briefs and build targeted audiences with instant insights ## Overview **Planning** enables publishers to respond efficiently to campaign briefs by creating plans to store campaign opportunity information and build targeted audiences with instant insights. The product provides a collaborative workspace where teams can combine existing cohorts using boolean logic, analyze audience reach and composition, and generate targeting expressions for ad servers. Publishers can create plans for each campaign opportunity, build audiences from multiple cohorts, and access detailed insights about audience reach, overlap, devices, and domains—all within a single workflow designed to accelerate brief response times. ## Why Use Planning? **Centralize campaign opportunity management** — Planning provides a single source of truth for campaign opportunities, enabling teams to store advertiser information, RFP deadlines, budget details, and audience configurations in one place that can be easily shared with colleagues. **Find the most relevant cohorts for your campaign with AI** — Find the right cohorts using AI-powered recommendations and search (if enabled), or search manually across all of your cohorts to identify cohorts that overlap with your target audience. **Build audiences with flexible boolean logic** — Combine cohorts using ANY (OR), ALL (AND), and NONE (NAND) logic across multiple groups to create precisely targeted audiences that meet campaign requirements. This allows you to re-use existing cohorts to respond to custom requirements, making it easier to quickly create insights and run inventory forecasting. **Get instant insights for brief responses** — Access detailed Plan Insights including reach metrics, overlap analysis, device breakdowns, and domain distribution to quickly respond to briefs with accurate audience information. ## Concepts ### Definitions * **Plan**: A container for campaign opportunity information including advertiser details, RFP deadline, budget, and one or more audiences. Plans serve as the central workspace for responding to campaign briefs. * **Audience**: A combination of cohorts connected using boolean logic to define a target audience within a plan. Each audience generates real-time metrics and can include up to 15 cohorts across 3 groups. * **Plan Insights**: A set of analytics tabs (Overview, Overlap, Devices, Domains, Categories, Activity) that provide detailed metrics and visualizations for audiences within a plan. * **Audience Expression**: The targeting logic generated from an audience's cohort combination, formatted for use in ad servers. GAM360 users can copy expressions directly to line items. * **Boolean Logic**: The operators used to combine cohorts within and between groups—ANY (OR), ALL (AND), and NONE (NAND) within groups; OR and AND between groups. * **Unique Users (UU)**: The number of distinct Permutive user IDs that meet the audience criteria within the last 30 days. * **Pageviews (PV)**: The number of pageview events registered by users in the audience within the last 30 days. * **Overlap**: The percentage of users in an audience who are also members of a comparison cohort. * **Index**: A measure comparing the overlap of an audience with a cohort versus the overlap of all users with that cohort. An index greater than 1 indicates the audience over-indexes for that cohort. ## Workflows ### Creating a Plan Publishers create plans to capture campaign opportunity information. Each plan includes required fields (Plan Name, Advertiser, RFP Deadline) and optional fields (Agency, Budget, Countries, Domains) that help organize and filter campaign data. ### Building an Audience After creating a plan, publishers build audiences by combining cohorts using boolean logic. The audience builder provides real-time metrics showing unique users and pageviews as cohorts are added and logic is adjusted. ### Viewing Plan Insights Once an audience is saved, publishers can access Plan Insights to analyze reach, overlap, device distribution, and domain breakdown. These insights help you respond to brief requirements with accurate audience data. ### Activating Audiences in Ad Servers To activate audiences, publishers copy the individual cohort IDs or targeting expressions from the audience and apply them to line items in their ad server. GAM 360 users can copy GAM-specific expressions directly. ## Key Features ### Plans Plans store campaign opportunity information and serve as containers for audiences. #### Plan Fields | Field | Required | Description | | :----------- | :------- | :----------------------------------------------------------- | | Plan Name | Yes | A descriptive name for the campaign opportunity | | Advertiser | Yes | The advertiser associated with the campaign | | RFP Deadline | Yes | The deadline for responding to the brief | | Agency | No | The agency managing the campaign | | Budget | No | The campaign budget | | Countries | No | Country filters that apply to all metrics and visualizations | | Domains | No | Domain filters that apply to all metrics and visualizations | Country and Domain filters set at the plan level apply across all metrics, visualizations, and Plan Insights for audiences within that plan. #### Plan Management * **Sorting**: Sort plans by Name, Advertiser, RFP Deadline, or Created Date * **Sharing**: Share plans with colleagues via unique URLs * **Auto-save**: Audiences are automatically saved when created ### Audiences Audiences combine cohorts using boolean logic to define users for campaign targeting. #### Boolean Logic Structure Audiences support up to **3 groups** with up to **5 cohorts per group** (maximum 15 cohorts total). | Logic | Within Groups | Between Groups | Description | | :---- | :------------ | :------------- | :------------------------------------------------------ | | ANY | Yes | - | Users must be in at least one of the cohorts (OR logic) | | ALL | Yes | - | Users must be in all of the cohorts (AND logic) | | NONE | Yes\* | - | Users must not be in any of the cohorts (NAND logic) | | OR | - | Yes | Users meeting criteria of either group | | AND | - | Yes | Users meeting criteria of both groups | NONE logic is not available for the first cohort group—it can only be applied to subsequent groups. #### Real-time Metrics As you build audiences, the following metrics update in real-time: * **Est. Unique Users**: Estimated distinct users meeting the audience criteria (last 30 days) * **Est. Pageviews**: Estimated pageviews from audience users (last 30 days) Metrics are shown for each group and for the total audience. #### Cohort Discovery Methods | Method | AI Required | Description | | :-------------- | :---------- | :------------------------------------------------------------------------------------------------------------------ | | Recommendations | Yes | AI-powered suggestions based on advertiser and plan information, including trending cohorts with recent user growth | | AI Search | Yes | Natural language search to find relevant cohorts based on audience descriptions | | Cohort Library | No | Full list of cohorts with search and filter functionality by name, tags, and type | | Affinities | No | Find cohorts that overlap or index highly with a seed cohort to expand reach | Use drag-and-drop to reorganize cohorts between groups after adding them to the audience. You can restrict which cohorts are available to be added to audiences through the use of tags. Create one or multiple tags that are associated with cohorts that you would like to be INCLUDED in the Planning product for audience building. Open a support request listing the specific tags for cohorts to be included. \*\*When implemented, only cohorts associated with the defined tags will be included for audience building. \*\* ### Audience Builder - Analyze Tab The Analyze tab provides visualizations to understand audience composition and optimize reach. | Visualization | Description | | :---------------- | :----------------------------------------------------------------------------------------------------------- | | Browsers | Percentage breakdown of audience unique users across browsers compared to total user base | | Domains | Percentage breakdown of audience unique users across domains compared to total user base | | Cohort Uniqueness | Shows incremental reach per cohort—users unique to each cohort (pink) vs. users also in other cohorts (grey) | When using ALL (AND) logic, the Cohort Uniqueness chart will not show incremental users since users must be in all cohorts to qualify for the audience. ### Audience Targeting Logic After building an audience, access the targeting logic panel to see: * **Cohorts List**: Full cohort names with their cohort IDs * **Boolean Expression**: Standard representation of the audience logic * **GAM Expression**: Copy-ready expression for Google Ad Manager line items (GAM 360 only) ### Plan Insights Plan Insights provide detailed analytics across six tabs. Access insights after saving an audience within a plan. #### Overview Tab | Metric | Type | Description | | :------------------------ | :--------- | :--------------------------------------------------- | | Unique Users | Reach | Estimated unique users in the last 30 days | | Avg. Weekly UU | Reach | Average unique users in a rolling 7-day window | | Avg. Daily UU | Reach | Average daily unique users | | Avg. Active Days Per User | Reach | Average days users visited properties (last 30 days) | | Pageviews | Engagement | Estimated pageviews in the last 30 days | | PV/UU | Engagement | Average pageviews per unique user | The Overview tab also includes a 30-day trend chart showing daily unique users or pageviews. #### Overlap Tab Analyze how your audience overlaps with cohorts organized by tags. Select comparison tags to generate tables showing: | Column | Description | | :---------------- | :--------------------------------------------------------- | | Comparison Cohort | The cohort being compared | | Total Uniques | Total unique users in the comparison cohort | | Overlap Uniques | Users in both the audience and comparison cohort | | Overlap | Percentage of audience users also in the comparison cohort | | Index | Audience overlap vs. total user overlap with the cohort | The index metric shows you how much more likely a user in your audience is to be in the listed cohort vs. the average user. For example, if you have created an audience of Travel Lovers and it shows an index of 1.5x for a Cycling Enthusiasts cohort, this means that Travel Lovers are 1.5x more likely to be Cycling Enthusiasts than your average user. Export overlap data to CSV for further analysis or sharing with stakeholders. #### Devices Tab View audience distribution across devices, platforms, and browsers. The tab includes: * **Distribution Charts**: Percentage breakdown by unique users or pageviews * **Engagement Charts**: Pageviews per unique user for each category #### Domains Tab Understand how your audience is distributed across properties—useful for demonstrating the value of cross-property targeting. | Metric | Description | | :---------------- | :------------------------------------ | | Unique Users (UU) | Percentage of unique users per domain | | Pageviews (PV) | Percentage of pageviews per domain | | PV/UU | Engagement rate per domain | #### Categories Tab Requires Watson to be enabled for your workspace. View content category insights for your audience based on Watson categorization. Here you can see how your Audience engages with primary content categories (IAB tier 1) compared with your total audience. * Audience - colorful bars in the foreground * Total Audience - grey bars in the background #### Activity Tab Requires Watson to be enabled for your workspace. View engagement by time of day for your audience with the ability to select individual Watson categories for more detailed engagement insights. The heatmap shows daily % of unique users or time spent broken down by device type. ## Guides Learn how to create and edit plans for campaign opportunities Combine cohorts with boolean logic to build targeted audiences Analyze audience reach, overlap, and composition Share plans with colleagues via unique URLs ## Troubleshooting Loading times may increase when many comparison tags are selected in the Overlap tab. **Solution**: Reduce the number of comparison tags selected, or wait for the data to load. Large tag selections require more data processing. The Categories and Activity tabs in Plan Insights require Watson to be enabled. **Solution**: Contact your Customer Success Manager (CSM) to enable Watson for your workspace. Once enabled, these tabs will become available in Plan Insights. These features require opt-in to Permutive's generative AI products. **Solution**: Contact your Customer Success Manager (CSM) to enable AI features for your workspace. You can still use the Cohort Library and Affinities tab to discover cohorts. Audience metrics may appear lower than expected when using ALL (AND) logic, as users must be in all cohorts. **Solution**: Review your boolean logic configuration. Using ANY (OR) logic will typically produce larger audiences. Check the Cohort Uniqueness visualization in the Analyze tab to understand how cohorts contribute to reach. ## Environment Compatibility #### Core Product | Functionality | Web | iOS | Android | CTV | API Direct | | :--------------------------- | :----------- | :----------- | :----------- | :---------- | :---------- | | Plan management | | | | | | | Audience building | | | | | | | Plan Insights | | | | | | | Cohort data (for activation) | | | | | | Planning is a web-based dashboard product. Cohort data used in audiences is available for iOS and Android app targeting through the Permutive SDK. ## Dependencies | Dependency | Required | Description | | :--------------------- | :---------- | :----------------------------------------------------------------------------------------------------------------- | | Permutive SDK | Yes | The Permutive SDK must be deployed to track events and enable audience metrics calculations. | | Cohort Taxonomy | Recommended | A well-defined cohort taxonomy with tags makes it easier to discover and organize cohorts when building audiences. | | Generative AI Products | Optional | Required for AI-powered Recommendations and AI Search features. | | Watson | Optional | Required for Categories and Activity tabs in Plan Insights. | | GAM 360 | Optional | Required for copying GAM-specific targeting expressions directly to line items. | ## Limits #### Feature Limits | Feature | Limit | Description | | :------------------- | :-------- | :------------------------------------------------ | | Cohorts per audience | 15 max | Up to 3 groups with 5 cohorts each | | Groups per audience | 3 max | Groups are combined using OR or AND operators | | Cohorts per group | 5 max | Cohorts within groups use ANY, ALL, or NONE logic | | Plans | Unlimited | No limit on the number of plans you can create | #### Data Limits | Metric | Window | Description | | :-------------- | :------ | :------------------------------------------ | | Unique Users | 30 days | Rolling window for user metric calculations | | Pageviews | 30 days | Rolling window for pageview calculations | | Overlap metrics | 30 days | Calculated over the last 30 days | ## FAQ No, Audiences created within Planning are specific to the Planning product and cannot be used in the separate Audience Insights product. They serve different purposes—Planning is for brief response, while Audience Insights provides broader cohort analysis. Yes, Audience Insights remains available as a separate product. Planning and Audience Insights serve different use cases and can be used together. All user metrics (Unique Users, Pageviews, Overlap) are calculated over the last 30 days. Yes, as you add cohorts and adjust boolean logic in the audience builder, the estimated Unique Users and Pageviews update in real-time. No, plans cannot currently be deleted. This is a platform limitation. Plans persist indefinitely once created. No, there is no limit to the number of plans you can create. Audiences can contain a maximum of 15 cohorts, organized as up to 3 groups with up to 5 cohorts per group. Yes, Planning works at all workspace levels in Enterprise Workspace configurations. Performance is fastest at the child workspace level. Parent workspaces can see parent-level cohorts and aggregate metrics across all child workspaces. Child workspaces can see both parent and child-level cohorts, with metrics calculated only for domains within that child workspace. Audiences are activated by applying the individual cohorts to line items in your ad server. Copy the cohort IDs from the targeting logic panel and configure them as key-values in your ad server. GAM 360 users can copy the GAM-specific expression directly. Yes, Plan Insights data can be exported to CSV format. Look for the "Export Data" button on charts and tables within the Overlap, Devices, and Domains tabs. # Direct API Deployment Source: https://docs.permutive.com/sdks/api/overview Deploy Permutive without an SDK using direct API calls for server-side, CTV, and non-O&O environments A Direct API deployment lets you integrate Permutive by making API calls directly, rather than deploying one of our [SDKs](/sdks/web/javascript-sdk/overview). This approach is suited to environments where deploying an SDK is not feasible — for example, certain CTV or hybrid TV setups with technical constraints, non-owned-and-operated (non-O\&O) properties where you don't control the application environment, or custom server-side architectures. If your environment supports one of our [SDKs](/sdks/web/javascript-sdk/overview), that is the recommended approach — the SDK handles identity, event collection, caching, and activation delivery automatically. Use a Direct API deployment only when an SDK is not feasible. ## Deployment Flow A Direct API deployment involves two Permutive APIs, each serving a different purpose: | API | Purpose | Requires consent | Requires user identity | | ------------------------------------------------------------- | --------------------------------------------------------- | ---------------- | ---------------------- | | [Custom Cohort Segmentation (CCS) API](/api/ccs/introduction) | Collect events and retrieve **behavioral** cohort signals | Yes | Yes | | [Contextual API](/api/contextual/introduction) | Retrieve **contextual** cohort signals based on content | No | No | Your application orchestrates these APIs, merges their results, and passes the combined signals to your ad server for activation. ```mermaid theme={"dark"} flowchart TD A{User consented?} -->|Yes| B["CCS API
(events + identity)"] A -->|No| C["Contextual API
(content URL)"] B --> D[Behavioral cohort signals] C --> E[Contextual cohort signals] B -.->|Also call| C D --> F[Merge signals] E --> F F --> G[Ad server / SSP] ``` The sections below walk through each step in order. ## Step 1: Manage Consent Your application is responsible for enforcing consent before collecting any user data. * **CCS API** — only call this for users who have given appropriate consent under your applicable privacy framework (GDPR, CCPA, etc.). The API processes all events it receives; there is no server-side consent gate. * **Contextual API** — this can be called for **all users**, regardless of consent status, because it evaluates content properties rather than user behavior. Do not send events to the CCS API for users who have not consented. Unlike the SDK, which can be configured with `consentRequired` to gate data collection, the API has no built-in consent check. This means every user session follows one of two paths: | User consent status | APIs to call | Signals available | | ------------------- | ------------------------ | ----------------------- | | Consented | CCS API + Contextual API | Behavioral + Contextual | | Not consented | Contextual API only | Contextual only | ## Step 2: Manage Identity For consented users, you need a stable identifier to send with CCS API requests. Your application owns the identity lifecycle — generating, persisting, and passing identifiers consistently. ### Choosing a User Identifier Permutive supports three types of user identifier. They differ in **scope** — that is, how broadly the identifier can link a user across different contexts. We recommend using the broadest-scope identifier available in your environment. | # | User identifier | Scope | Links a user across... | | - | -------------------------------------------------------------------------- | ----------------- | -------------------------------------------- | | 1 | Authenticated first-party ID (e.g., hashed email, internal login ID) | Broadest | Multiple devices and environments | | 2 | Device ID (e.g., IDFA, AAID, CTV device identifiers, Alexa Advertising ID) | Device-level | Multiple applications on the same device | | 3 | Generated Permutive ID (a Version 4 UUID your application generates) | Application-level | Sessions within the current application only | A Direct API deployment does not require third-party identifiers such as cookies, universal IDs, or authenticated logins. A device ID or a self-generated Permutive ID is sufficient for real-time, cross-session behavioral segmentation. This means your deployment is not constrained by identity availability — even in environments where traditional identifiers are limited (e.g., iOS, CTV). If more than one identifier is available, you can pass them all in a single request — Permutive will use the highest-priority identifier and retain the others for identity resolution. ### Sending the User Identifier #### Authenticated First-Party ID Pass as an `alias`, with the tag matching a user identifier configured in your Permutive workspace (e.g., `email_sha256`, `internal_id`): ```json theme={"dark"} { "alias": { "tag": "email_sha256", "id": "a1b2c3d4e5f6..." }, "events": [...] } ``` #### Device ID Pass as an `alias`, with the tag matching the device identifier type (e.g., `idfa`, `aaid`, `tifa`, `alexa_advertising_id`): ```json theme={"dark"} { "alias": { "tag": "idfa", "id": "6D92078A-8246-4BA4-AE5B-76104861E7DC" }, "events": [...] } ``` #### Generated Permutive ID For users without an authenticated ID or accessible device ID, your application can generate a Version 4 UUID on-device, persist it in local storage, and reuse it across sessions. Pass it directly as `user_id`: ```json theme={"dark"} { "user_id": "2008c38f-dece-4570-976d-87593ed001c3", "events": [...] } ``` #### Multiple Identifiers If you have more than one identifier available, pass them in `aliases` with priorities (lower number = higher priority). Ordering by scope maximizes the chance of linking the user across sessions, devices, and environments: ```json theme={"dark"} { "aliases": [ { "tag": "email_sha256", "id": "a1b2c3d4e5f6...", "priority": 0 }, { "tag": "idfa", "id": "6D92078A-8246-4BA4-AE5B-76104861E7DC", "priority": 1 } ], "events": [...] } ``` ### Key Considerations * **Consistency** — always use the same identifier for the same user. Inconsistent identifiers create duplicate profiles and fragment cohort history. * **Uniqueness** - make sure the identifiers are unique for each individual user. If multiple users share the same identity then all behavior from those users will be shared between them. * **Persistence** — a generated Permutive ID must be stored in durable storage (e.g., device local storage) so it survives across sessions. * **Workspace configuration** — user identifiers must be configured in your Permutive workspace before use. You can do this in the Permutive dashboard — see [Configuring identifiers](/products/signals/identity/identity-graph#configuring-identifiers-in-the-dashboard). * **Identity resolution** — when multiple identifiers map to different Permutive user IDs, the highest-priority identifier (lowest number) wins and all future activity resolves to that user ID. Historical state under other user IDs is not retroactively merged into the winning profile — so consistent priority ordering across all platforms is important from the start. See [Identity concepts](/concepts/identity) and the [Identify API reference](/api/identity/identify) for the full resolution model. ## Step 3: Collect Data via the CCS API For consented users, send events to the [CCS API](/api/ccs/introduction) to collect behavioral data and trigger real-time cohort evaluation. Each request contains a user identity and one or more events. ### Event Structure Each event requires a `name`, `time`, and `properties` object that matches the schema defined in your Permutive workspace. | Field | Type | Required | Description | | ------------ | ------ | -------- | -------------------------------------------------------------------------------------------------------- | | `name` | string | Yes | The event name as defined in your workspace (e.g., `"Videoview"`, `"AudioPlay"`) | | `time` | string | Yes | ISO 8601 timestamp of when the event occurred | | `view_id` | UUID | No | Groups events within a single content engagement (e.g., watching a TV program or listening to a podcast) | | `session_id` | UUID | No | Groups events within a single session, across multiple content engagements | | `properties` | object | Yes | Event properties matching your workspace schema | Each request can include up to **10 events**. If you need to send more, split them across multiple requests. Events should be sent in chronological order. If a property is not applicable or not available for a given environment (e.g., `bluetooth_audio_active` on a web client), **omit it entirely** rather than sending it as `null` or an empty value. ### Session and View IDs * **`session_id`** — generate a new UUID at the start of each user session and reuse it for all events within that session, which may span multiple content engagements. Define what a "session" means for your application (e.g., a 30-minute inactivity timeout). * **`view_id`** — generate a new UUID for each content engagement (e.g., a video watched or a podcast listened to), and include it on every event that occurs within that engagement. These IDs are required for creating cohorts which target using 'this session' and 'the current page' time windows. ### Play and Completion Events For any given content engagement, we recommend tracking **two events**: * A **play event** when playback starts (e.g., `Videoview`, `AudioPlay`) — captures content metadata * A **completion event** when playback ends or the user stops (e.g., `VideoCompletion`, `AudioCompletion`) — captures engagement metrics such as time spent and percentage completed Both events share the same `view_id` to link them together. The exact event names and property schemas are configured in your Permutive workspace during onboarding. **1. Play event** — sent when the video starts playing: ```json theme={"dark"} { "name": "Videoview", "time": "2024-01-15T14:00:00Z", "view_id": "a2b3c4d5-e6f7-8901-2345-6789abcdef01", "session_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "properties": { "video_id": "vid_456", "title": "Highlights: Premier League Week 20", "categories": ["sports", "football"], "duration_seconds": 600 } } ``` **2. Completion event** — sent when the video ends or the user stops watching: ```json theme={"dark"} { "name": "VideoCompletion", "time": "2024-01-15T14:07:30Z", "view_id": "a2b3c4d5-e6f7-8901-2345-6789abcdef01", "session_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "properties": { "video_id": "vid_456", "title": "Highlights: Premier League Week 20", "categories": ["sports", "football"], "duration_seconds": 600, "time_spent_seconds": 450, "percentage_watched": 0.75 } } ``` **1. Play event** — sent when the audio starts playing: ```json theme={"dark"} { "name": "AudioPlay", "time": "2024-01-15T08:15:00Z", "view_id": "b3c4d5e6-f789-0123-4567-89abcdef0123", "session_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "properties": { "podcast_id": "pod_789", "title": "Morning News Briefing", "categories": ["news", "current-affairs"], "duration_seconds": 1800 } } ``` **2. Completion event** — sent when the audio ends or the user stops listening: ```json theme={"dark"} { "name": "AudioCompletion", "time": "2024-01-15T08:37:30Z", "view_id": "b3c4d5e6-f789-0123-4567-89abcdef0123", "session_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "properties": { "podcast_id": "pod_789", "title": "Morning News Briefing", "categories": ["news", "current-affairs"], "duration_seconds": 1800, "time_spent_seconds": 1350, "percentage_listened": 0.75 } } ``` Cohort rules often depend on engagement data from the completion event. If you skip it, cohorts that use conditions like "listened for more than 30 seconds" or "watched at least 50%" will not evaluate correctly. ### Engagement Metrics Your application is responsible for measuring engagement and including it on the completion event. Two metrics are typically captured: | Metric | Description | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Time spent | Total seconds the user spent engaging with the content | | Completion | Proportion of the content consumed (0–1). For video and audio, this is the percentage watched or listened to. For article web pages, it is the scroll depth. | ## Step 4: Retrieve Behavioral Cohort Signals The CCS API response contains the user's updated cohort membership and, if requested, activations grouped by ad platform. ### Response Format ```json theme={"dark"} { "user_id": "2008c38f-dece-4570-976d-87593ed001c3", "cohorts": ["12345", "67890", "11111"], "activations": { "freewheel": ["12345", "67890"], "adswizz_keyvalue": ["12345"], "dfp": ["12345", "67890", "11111"] } } ``` * **`user_id`** — the Permutive user ID assigned to this user. * **`cohorts`** — all cohort IDs the user currently belongs to. * **`activations`** — cohorts grouped by the ad platform they are activated for. Only included if `activations=true` is set as a query parameter. The keys (e.g., `freewheel`, `adswizz_keyvalue`, `dfp`) correspond to activation platforms configured in your Permutive dashboard. Always request activations by adding `?activations=true` to your API call if you intend to use cohorts for ad targeting. When attaching cohorts to an ad request, use the per-platform `activations` object — not the top-level `cohorts` array. The `cohorts` array includes every cohort the user belongs to, many of which may not be relevant to (or activated for) the specific ad platform you are calling. Using the per-platform `activations` keeps your ad request focused on cohorts that have been explicitly activated for that platform. ### Caching Call the CCS API on every content view to keep cohort membership up to date. However, cache the most recent response as a fallback: 1. **Cache each response** — store the latest cohorts and activations in a location accessible to your ad-serving logic (server-side session, in-memory cache, or device storage). 2. **Use cached values as fallback** — if the API call fails or is too slow, use cached cohorts from the previous response. 3. **Set a TTL** — a long TTL (e.g., 30 days or longer) is reasonable since the cached data is a short list of cohort IDs. Do not skip API calls and rely solely on cached data. Cohort membership updates in real time based on user behavior, and stale data reduces targeting accuracy. ## Step 5: Retrieve Contextual Cohort Signals The [Contextual API](/api/contextual/introduction) returns cohort signals based on the content being viewed, rather than the user's behavior. This means it: * Does **not** require user identity * Does **not** require consent * Should be called for **every content view**, regardless of consent status The Contextual API takes a content URL and the same content properties you send in your CCS API events — **minus any user-related data** — and returns contextual cohort membership. ### Example Request ```bash theme={"dark"} curl -X POST "https://api.permutive.com/ctx/v1/segment?k=YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "", "page_properties": { "client": { // Client properties }, "context": { // Context properties }, "audio": { // Audio properties (or video, article, etc. — matching your workspace schema) } } }' ``` Mirror the `properties` object from your CCS API event, omitting any user-related fields. This ensures the Contextual API classifies the same content the user is engaging with. See the [Contextual API reference](/api/contextual/introduction) for full request and response details. ### The `url` Field The Contextual API requires a `url` field. Any URL scheme is accepted, so in audio, video, or other non-web environments, construct a URL that uniquely and deterministically identifies the active content. **Recommendations:** * **Use the public web URL where one exists** (e.g., `https://example.com/shows/morning-news/`). If the content has a web presence that drives traffic, Permutive may already have URL-based affinity scores associated with it, improving contextual classification. * **Use an app-schema URL for in-app content** (e.g., `myapp://podcast/1234`) when no public web URL applies. * **Construct a deterministic URL for environments without a natural URL** (e.g., smart speakers). Build one from a stable content identifier, such as `smartspeaker://station/abc123`. The same piece of content should always resolve to the same URL — consistency is what allows Permutive to build and match contextual signals reliably. Contextual cohorts are configured separately in the Permutive dashboard. If your workspace uses contextual cohorts, this API call is essential for maximizing signal coverage — especially for unconsented users where it is the only source of cohort signals. ## Step 6: Merge Signals and Activate Before passing cohort signals to your ad server, merge the behavioral activations (from the CCS API) with the contextual activations (from the Contextual API) into a single set of key-values. ``` Behavioral activations (CCS API): {"freewheel": ["12345", "67890"]} Contextual activations (Contextual API): {"freewheel": ["99999", "88888"]} Merged activations: {"freewheel": ["12345", "67890", "99999", "88888"]} ``` For unconsented users, you will only have contextual activations — pass those directly. In some cases it may make sense to send behavioral and contextual cohorts as **separate** key-values (e.g., `permutive` and `prmtvctx`) rather than merging them into one. This can enable separate reporting and targeting on each signal type in your ad platform. Discuss with your Customer Success Manager to decide on the right approach for your ad stack. ### Attaching Signals to Ad Requests Once merged (or split, depending on your approach), append the signals to your ad request as key-values. The exact format depends on your ad platform — we recommend working with [Technical Services](mailto:technical-services@permutive.com) during setup to confirm the correct key-value structure for each platform you are activating against. ## End-to-End Example The following sequence diagram shows a complete content engagement — from playback start through to completion — illustrating how the CCS API, Contextual API, and ad server interact in practice. ```mermaid theme={"dark"} sequenceDiagram autonumber participant App as Client App
(CTV / server-side) participant CCS as CCS API participant CTX as Contextual API participant Ads as Ad Server Note over App: Preconditions
consent granted, session_id and view_id generated,
tifa available from device rect rgba(100, 160, 255, 0.15) Note over App,Ads: ▶ Playback starts par Behavioral (identity + consent required) App->>+CCS: POST /ccs/v1/segmentation?activations=true
identifier: tifa = a1b2c3d4-...
event: Videoview CCS-->>-App: user_id, cohorts,
activations per platform and Contextual (no identity, no consent) App->>+CTX: POST /ctx/v1/segment
url + content properties CTX-->>-App: contextual activations end Note over App: Cache responses (TTL ~24h)
Merge activations per platform
freewheel → behavioral + contextual App->>+Ads: Ad request with merged key-values Ads-->>-App: Ad creative end Note over App,Ads: ...video/TV content continues playing... rect rgba(255, 160, 100, 0.15) Note over App,Ads: ⏹ Playback ends — 450s of 600s watched (75%) App->>+CCS: POST /ccs/v1/segmentation?activations=true
SAME identifier, view_id, session_id
event: VideoCompletion + engagement metrics CCS-->>-App: Updated cohorts
(feeds the NEXT ad opportunity) end ``` ### Request and Response Payloads Expand each step below to see the corresponding request or response body. Step numbers match the sequence diagram above. The play event sent when playback starts, including the user identifier and content metadata: ```json theme={"dark"} { "aliases": [ { "tag": "tifa", "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "priority": 0 } ], "events": [{ "name": "Videoview", "time": "2024-01-15T14:00:00Z", "view_id": "a2b3c4d5-e6f7-8901-2345-6789abcdef01", "session_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "properties": { "video_id": "vid_456", "title": "Highlights: Premier League Week 20", "categories": ["sports", "football"], "duration_seconds": 600 } }] } ``` The response includes the user's cohort membership and activations grouped by ad platform: ```json theme={"dark"} { "user_id": "2008c38f-dece-4570-976d-87593ed001c3", "cohorts": ["12345", "67890", "11111"], "activations": { "freewheel": ["12345", "67890"], "dfp": ["12345", "67890", "11111"] } } ``` Mirrors the content properties from the CCS API event, with a URL and no user-related data: ```json theme={"dark"} { "url": "https://example.com/shows/pl-highlights/", "page_properties": { "video": { "video_id": "vid_456", "title": "Highlights: Premier League Week 20", "categories": ["sports", "football"], "duration_seconds": 600 } } } ``` Contextual cohort activations based on the content, independent of user identity: ```json theme={"dark"} { "activations": { "freewheel": ["99999"], "dfp": ["88888"] } } ``` The completion event sent when playback ends. Note the **same** user identifier, `view_id`, and `session_id` as the play event, with added engagement metrics: ```json theme={"dark"} { "aliases": [ { "tag": "tifa", "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "priority": 0 } ], "events": [{ "name": "VideoCompletion", "time": "2024-01-15T14:07:30Z", "view_id": "a2b3c4d5-e6f7-8901-2345-6789abcdef01", "session_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "properties": { "video_id": "vid_456", "title": "Highlights: Premier League Week 20", "categories": ["sports", "football"], "duration_seconds": 600, "time_spent_seconds": 450, "percentage_watched": 0.75 } }] } ``` ## Feature Comparison: Direct API vs SDK | Capability | SDK | Direct API | | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | Event tracking | Simple — helper functions in our Web and CTV SDKs make event tracking straightforward | Manual — events must be sent explicitly via API | | Engagement tracking (time spent, completion)\* | Automatic — the SDK measures engagement and manages state | Manual — your application must measure and track engagement | | Identity management | Partially managed — the SDK generates a Permutive ID and collects device IDs where configured | Manual — your application must generate, persist, and send user identifiers | | Classification Modeled Cohorts | Supported | Unsupported | | Cohort evaluation lookback | Supported — the SDK stores event history on-device, so newly created cohorts can be evaluated against the user's historical events | Unsupported | | Cohort caching | Automatic — cohorts are cached in local/device storage | Manual — your application must cache API responses | | Activation to ad platforms | Fully automatic on Web for supported platforms; on Mobile and CTV, cohorts are exposed via SDK APIs for your application to attach to ad calls | Manual — extract activations from the response and attach them to ad calls | | Latency | Real-time — on-device segmentation with minimal network dependency | Near real-time — dependent on the API round-trip | **\* Engagement Tracking Note:** With SDKs using MediaTracker, video engagement is measured and user state is refreshed automatically. With the Direct API, your app must measure engagement and send it in the `VideoCompletion` event. A user’s state is refreshed when the `VideoCompletion` event is received (rather than periodically during playback). ## Verification and Troubleshooting ### Verifying Your Integration After implementing a Direct API deployment, verify each step: 1. **Identity** — send a request and confirm the response returns a stable `user_id`. Send a second request with the same user identifier and confirm the same `user_id` is returned. 2. **Event collection** — create a test cohort in the Permutive dashboard with simple rules (e.g., "any AudioPlay event") and confirm the user enters it after sending a matching event. 3. **Activations** — with `activations=true`, check that the response includes the expected platforms and cohort IDs. 4. **Contextual signals** — call the Contextual API with a known URL and confirm contextual cohorts are returned. 5. **Signal merging** — verify that both behavioral and contextual signals appear in your ad requests. 6. **Consent gating** — confirm that no CCS API calls are made for unconsented users, and that contextual signals still flow. ### Common Issues **Problem:** Events are being sent but the user is not entering expected cohorts. **Cause:** The event name or property structure does not match the schema defined in your Permutive workspace. **Solutions:** * Verify the event `name` matches exactly (case-sensitive) what is configured in your workspace * Check that `properties` match the expected schema — missing required fields or incorrect types will cause the event to be ignored * Use the Permutive dashboard to inspect the event schema and compare it with what your application is sending * Ensure nested properties follow the correct structure (e.g., `audio.categories` should be an array, not a string) * For debugging, add `synchronous-validation=true` as a query parameter to your API call — this forces the CCS API to validate the event schema synchronously and return any schema mismatch errors in the response (by default, validation is asynchronous and schema errors return a `200` response). **Use this only for testing** — synchronous validation has performance implications and should not be enabled in production. **Problem:** The same real user appears as multiple users in Permutive, or cohort history is fragmented across sessions. **Cause:** Inconsistent identifiers are being sent across requests, or the identifier is not being persisted between sessions. **Solutions:** * Ensure you are using the same user identifier for the same user across all requests * If passing a tagged identifier, ensure both the tag and value are identical across all touchpoints * If you are generating a Permutive ID on-device, check that it is being read from durable storage on subsequent sessions and not regenerated * Verify that the user identifier is configured in your Permutive workspace **Problem:** API requests return `401 Unauthorized` or `403 Forbidden`. **Solutions:** * Find your API key in the [Permutive Dashboard](https://dash.permutive.com) under Settings * Provide the key as a query parameter (`?k=YOUR_API_KEY`) or header (`X-Api-Key: YOUR_API_KEY`) * Ensure you are using the key for the correct workspace * Check that the key has not been rotated or revoked **Problem:** The API returns successfully but `cohorts` is always an empty array. **Solutions:** * Check that cohorts are configured and active in your Permutive dashboard * Verify that the events you are sending match the rules defined in your cohorts * Ensure the event properties contain the values that your cohort rules are looking for * Allow a few requests to build up user history — some cohorts require multiple events or sessions to qualify **Problem:** The response includes `cohorts` but no `activations`. **Solutions:** * Ensure you are passing `activations=true` as a query parameter * Check that cohorts are activated for at least one platform in the Permutive dashboard * A cohort must be explicitly activated for a platform to appear in the activations map **Problem:** API requests are slow or timing out, delaying ad requests. **Solutions:** * Implement a timeout on your API call (e.g., 200-500ms) and fall back to cached cohorts * Send API requests as early as possible in the page/screen lifecycle * Contact [Support](mailto:support@permutive.com) if latency persists ## Next Steps Full request and response schemas for behavioral cohort segmentation Request and response schemas for contextual cohort signals Learn more about Permutive's identity resolution Consider an SDK if your environment supports it # Android TV Source: https://docs.permutive.com/sdks/ctv/android-tv Integrate Permutive on Android TV and Google TV using the Android SDK ## Overview Android TV and Google TV applications use the **Permutive Android SDK**. The same SDK that powers mobile Android applications fully supports Android TV, providing identical APIs for tracking, identity management, and ad targeting. **Same SDK, Same APIs:** The Permutive Android SDK works seamlessly on Android TV. All features documented in the [Android SDK](/sdks/mobile/android/overview) are available on Android TV. ## Requirements | Requirement | Version | | ----------- | -------------------------- | | Android API | 21+ (Android 5.0 Lollipop) | | Compile SDK | 34+ | | Java | 8+ (JVM target 1.8) | | Kotlin | 1.6+ (if using Kotlin) | ## Installation Add the Permutive SDK to your `build.gradle`: ```kotlin theme={"dark"} dependencies { // Core SDK (required) implementation("com.permutive.android:core:1.12.0") // Google Ads integration (optional) implementation("com.permutive.android:google-ads:2.2.0") } ``` ## Initialization Initialize the SDK in your Application class: ```kotlin theme={"dark"} import android.app.Application import com.permutive.android.Permutive class MyTVApplication : Application() { lateinit var permutive: Permutive private set override fun onCreate() { super.onCreate() permutive = Permutive.Builder( applicationContext = this, apiKey = "", workspaceId = "" ).build() } } ``` For detailed initialization options, see [Android Initialization](/sdks/mobile/android/getting-started/initialization). *** ## Video Tracking Video tracking on Android TV uses the `MediaTracker` API. This is the primary use case for CTV applications. ### Creating a MediaTracker ```kotlin theme={"dark"} import com.permutive.android.MediaTracker class VideoActivity : AppCompatActivity() { private val permutive by lazy { (application as MyTVApplication).permutive } private lateinit var videoTracker: MediaTracker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) videoTracker = permutive.trackVideoView( durationMilliseconds = 3600000, // 1 hour videoProperties = MediaTracker.VideoProperties( title = "Show Title - S1E1", genre = listOf("Drama", "Thriller"), contentType = listOf("Series", "Episode"), seasonNumber = 1, episodeNumber = 1, runtime = 3600 ), pageProperties = MediaTracker.PageProperties( title = "Now Playing", url = Uri.parse("https://example.com/shows/title/s1e1") ) ) } override fun onDestroy() { super.onDestroy() videoTracker.stop() // Important: Always stop to send VideoCompletion } } ``` ### Integrating with ExoPlayer Android TV applications typically use ExoPlayer (now part of Media3) for video playback: ```kotlin theme={"dark"} import com.permutive.android.MediaTracker import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.analytics.AnalyticsListener import androidx.media3.common.Player class VideoPlayerActivity : AppCompatActivity() { private val permutive by lazy { (application as MyTVApplication).permutive } private lateinit var videoTracker: MediaTracker private lateinit var exoPlayer: ExoPlayer override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_video_player) setupVideoTracker() setupExoPlayer() } private fun setupVideoTracker() { videoTracker = permutive.trackVideoView( durationMilliseconds = videoDurationMs, videoProperties = MediaTracker.VideoProperties( title = "Video Title", genre = listOf("Documentary"), contentType = listOf("Feature") ) ) } private fun setupExoPlayer() { exoPlayer = ExoPlayer.Builder(this).build() exoPlayer.addListener(object : Player.Listener { override fun onPlaybackStateChanged(state: Int) { when (state) { Player.STATE_READY -> { if (exoPlayer.isPlaying) { videoTracker.play() } } Player.STATE_BUFFERING -> { videoTracker.pause() } } } override fun onIsPlayingChanged(isPlaying: Boolean) { if (isPlaying) { videoTracker.play() } else { videoTracker.pause() } } }) // Track seeking exoPlayer.addAnalyticsListener(object : AnalyticsListener { override fun onPositionDiscontinuity( eventTime: AnalyticsListener.EventTime, reason: Int ) { if (reason == Player.DISCONTINUITY_REASON_SEEK) { videoTracker.play(exoPlayer.currentPosition) } } }) } override fun onPause() { super.onPause() videoTracker.pause() exoPlayer.pause() } override fun onResume() { super.onResume() if (exoPlayer.isPlaying) { videoTracker.play() } } override fun onDestroy() { super.onDestroy() videoTracker.stop() exoPlayer.release() } } ``` ### MediaTracker Lifecycle | Method | Description | Events Tracked | | ------------------ | ------------------------------------ | ------------------------ | | `play()` | Start/resume tracking | `Videoview` (first call) | | `play(positionMs)` | Start at specific position (seeking) | - | | `pause()` | Pause tracking (buffering, paused) | - | | `stop()` | End tracking permanently | `VideoCompletion` | **Always call `stop()`:** Failing to call `stop()` when the video ends or the user exits will prevent `VideoCompletion` events from being tracked. For complete MediaTracker documentation, see [Android Video Tracking](/sdks/mobile/android/features/video-tracking). *** ## Video Properties The SDK provides standard video properties for consistent tracking: ```kotlin theme={"dark"} MediaTracker.VideoProperties( title = "Video Title", genre = listOf("Action", "Thriller"), contentType = listOf("Movie", "Feature"), ageRating = "PG-13", runtime = 7200, // Runtime in seconds country = "US", originalLanguage = "en", audioLanguage = "en", areSubtitlesEnabled = true, subtitlesLanguage = "en", seasonNumber = 1, episodeNumber = 5, consecutiveEpisodes = 3, iabCategories = listOf("IAB1", "IAB2") ) ``` *** ## Video Ad Tracking Track video advertisements within your content: ```kotlin theme={"dark"} // When ad starts val adTracker = videoTracker.trackAdView( durationMs = 30000, // 30 second ad adProperties = AdTracker.AdProperties( title = "Product Ad", durationInSeconds = 30, campaignId = "campaign_123", creativeId = "creative_456" ) ) adTracker.play() // When ad completes adTracker.completion() ``` See [Android Video Ad Tracking](/sdks/mobile/android/features/video-ad-tracking) for details. *** ## Identity Management Set user identities for cross-device tracking: ```kotlin theme={"dark"} // userId: Your application's user identifier // hashedEmail: SHA256 hash of the user's email address permutive.setIdentity( listOf( Alias.create("user_id", userId, priority = 0), Alias.create("email_sha256", hashedEmail, priority = 1) ) ) ``` See [Android Identity Management](/sdks/mobile/android/core-concepts/identity-management) for details. *** ## Cohorts and Targeting ### Accessing Cohorts ```kotlin theme={"dark"} // Get all cohorts val cohorts = permutive.cohorts // Get activations for a specific ad platform val gamActivations = permutive.activations("gam") ``` ### Google Ad Manager Integration ```kotlin theme={"dark"} import com.permutive.android.googleads.addPermutiveTargeting val adRequest = AdManagerAdRequest.Builder() .addPermutiveTargeting(permutive) .build() adView.loadAd(adRequest) ``` See [Android Google Ad Manager](/sdks/mobile/android/integrations/google-ad-manager) for complete integration details. *** ## Android TV-Specific Considerations Android TV uses the Leanback library for its 10-foot UI. Ensure your tracking implementation doesn't interfere with Leanback components: ```kotlin theme={"dark"} class VideoFragment : VideoSupportFragment() { private lateinit var videoTracker: MediaTracker override fun onStart() { super.onStart() // VideoSupportFragment handles player lifecycle // Just sync tracking with player state } } ``` Android TV uses D-pad navigation. Tracking works independently of navigation, but ensure you handle the back button appropriately: ```kotlin theme={"dark"} override fun onBackPressed() { // Stop tracking before navigating away videoTracker.stop() super.onBackPressed() } ``` If your app supports PiP mode, continue tracking during PiP: ```kotlin theme={"dark"} override fun onPictureInPictureModeChanged( isInPictureInPictureMode: Boolean, newConfig: Configuration ) { super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) // Continue tracking - no changes needed // MediaTracker tracks independently of UI mode } ``` Android TV supports AAID. Use the AAID provider for automatic advertising ID tracking: ```kotlin theme={"dark"} dependencies { implementation("com.permutive.android:aaid-provider:1.12.0") } ``` ```kotlin theme={"dark"} val permutive = Permutive.Builder(this, apiKey, workspaceId) .enableAaidProvider() .build() ``` See [AAID Provider](/sdks/mobile/android/integrations/aaid-provider) for details. Android TV apps may continue video playback when the home button is pressed. Handle this appropriately: ```kotlin theme={"dark"} override fun onStop() { super.onStop() if (!isChangingConfigurations) { // User left the app if (exoPlayer.isPlaying) { // Playback continues in background // Keep tracking active } else { videoTracker.pause() } } } ``` *** ## Best Practices * Initialize SDK early in Application.onCreate() * Create MediaTracker when video is ready to play * Call `stop()` when video ends or user exits * Sync `play()`/`pause()` with actual player state * Handle buffering states with `pause()` * Include video metadata for richer cohorts * Use Leanback-compatible patterns * Don't create multiple MediaTrackers simultaneously * Don't forget to call `stop()` when done * Don't skip the duration parameter * Don't block the main thread with SDK calls * Don't send PII in event properties * Don't interfere with Leanback focus handling *** ## Troubleshooting **Problem:** Permutive instance is null or initialization fails. **Solutions:** * Verify API key and workspace ID are correct * Ensure initialization happens in Application.onCreate() * Check for initialization errors in Logcat * Enable debug mode: `permutive.setDeveloperMode(true)` **Problem:** Video events don't show in Permutive dashboard. **Solutions:** * Verify MediaTracker was created successfully * Ensure `play()` is called when video starts * Ensure `stop()` is called when video ends * Wait 5-10 minutes for events to process * Check Logcat for "Accepted: 1 / 1" messages **Problem:** Engagement metrics don't match expected values. **Solutions:** * Sync `play()`/`pause()` calls with actual player state * Call `pause()` during buffering * Provide accurate duration when creating MediaTracker **Problem:** VideoCompletion events not appearing. **Solutions:** * Ensure `stop()` is called in `onDestroy()` * Handle back button to call `stop()` before navigation * Verify app isn't killed before event sends *** ## Related Documentation Complete Android SDK documentation Detailed MediaTracker documentation Video advertisement tracking Cross-platform video tracking guide # Connected TV Overview Source: https://docs.permutive.com/sdks/ctv/overview Integrate Permutive across Connected TV platforms for audience targeting and video engagement tracking ## Overview Connected TV (CTV) encompasses a variety of devices and platforms that deliver streaming content to televisions. Permutive provides comprehensive support across the CTV ecosystem, enabling you to track user engagement, build audience segments, and target ads effectively. **Permutive's CTV Philosophy:** Rather than building separate SDKs for each CTV platform, Permutive extends existing SDKs to work seamlessly in CTV environments. This means you get the same reliable APIs and features you're already familiar with from web and mobile. ## Platform Selection Guide Choose your integration approach based on your CTV platform: | Platform | Technology | SDK to Use | Documentation | | ---------------------- | ----------------------- | -------------------------- | ---------------------------------- | | Samsung Tizen | Web/JavaScript | JavaScript SDK + CTV addon | [Web CTV](/sdks/ctv/web-ctv) | | LG WebOS | Web/JavaScript | JavaScript SDK + CTV addon | [Web CTV](/sdks/ctv/web-ctv) | | HbbTV | Web/JavaScript | JavaScript SDK + CTV addon | [Web CTV](/sdks/ctv/web-ctv) | | tvOS | Native iOS/Swift | iOS SDK | [tvOS](/sdks/ctv/tvos) | | Android TV / Google TV | Native Android/Kotlin | Android SDK | [Android TV](/sdks/ctv/android-tv) | | Roku | BrightScript/SceneGraph | Permutive Roku Client | [Roku](/sdks/ctv/roku) | ## CTV Environment Types Understanding your CTV environment helps determine the right integration approach: Many CTV platforms run web-based applications using embedded browsers. These include: * **Samsung Tizen** - Uses a Chromium-based web engine * **LG WebOS** - Uses a WebKit-based web engine * **HbbTV** - Standard for hybrid broadcast/broadband TV For these platforms, deploy the [JavaScript SDK](/sdks/web/javascript-sdk) with the **CTV addon** enabled. The CTV addon provides specialized video tracking APIs designed for streaming content. **Key Consideration:** Web-based CTV environments have stricter JavaScript engine limitations than desktop browsers. Permutive provides platform-specific SDK bundles optimized for each platform version. Some CTV platforms use native application frameworks: * **tvOS** - Native Swift/Objective-C apps using UIKit/SwiftUI * **Android TV / Google TV** - Native Kotlin/Java apps For these platforms, use the corresponding mobile SDK: * tvOS apps use the [iOS SDK](/sdks/mobile/ios/overview) (supports tvOS 12.0+) * Android TV apps use the [Android SDK](/sdks/mobile/android/overview) **Key Consideration:** Native CTV apps benefit from full SDK capabilities including MediaTracker for video engagement tracking. **Roku** operates on its own platform (BrightScript/SceneGraph) and requires a dedicated client: * **Roku** - Uses the Permutive Roku Client component The [Roku Client](/sdks/ctv/roku) is a SceneGraph Task component that provides event tracking, identity management, and Google Ad Manager targeting. ## Video Tracking Across Platforms All CTV platforms support Permutive's video event schema for consistent audience analytics: | Event | Description | Automatic | | --------------------- | ----------------------------- | --------- | | **Videoview** | User initiated video playback | Yes | | **VideoCompletion** | User finished or exited video | Yes | | **VideoAdView** | Video ad started playing | Manual | | **VideoAdCompletion** | Video ad finished | Manual | | **VideoAdClicked** | User clicked on video ad | Manual | See [CTV Video Tracking](/sdks/ctv/video-tracking) for implementation details across all platforms. ## Prerequisites Before integrating CTV, ensure you have: An active Permutive workspace. Contact [support@permutive.com](mailto:support@permutive.com) if you need one. Your workspace API key and ID from the [Dashboard](https://dash.permutive.com/settings/keys/). For web-based CTV platforms, enable the CTV addon in your [Dashboard integrations](https://dash.permutive.com/settings/integrations/). Ensure your video events conform to your workspace schema. Work with [Technical Services](mailto:technical-services@permutive.com) to configure this. ## Features by Platform | Feature | Web CTV | tvOS | Android TV | Roku | | -------------------- | ------------ | ----------------- | --------------- | ---------------- | | Video event tracking | CTV addon | MediaTracker | MediaTracker | Manual | | Engagement time | Automatic | Automatic | Automatic | Manual | | User identity | `identify()` | `setIdentities()` | `setIdentity()` | `identity` field | | Cohort retrieval | `segments()` | `cohorts` | `cohorts` | `cohorts` | | GAM targeting | Automatic | Manual | Manual | `gamKeyValues` | | Contextual cohorts | URL-based | URL-based | URL-based | Not available | ## Getting Started Samsung Tizen, LG WebOS, HbbTV integration with JavaScript SDK Native tvOS integration using the iOS SDK Native Android TV integration using the Android SDK Roku channel integration with the Permutive Roku Client ## Additional Resources Unified video event implementation guide Full JavaScript SDK documentation Full iOS/tvOS SDK documentation Full Android SDK documentation ## Getting Help * **Customer Success Manager** - Your primary contact for strategy and planning * **[Technical Services](mailto:technical-services@permutive.com)** - Technical integration support * **[Support](mailto:support@permutive.com)** - Troubleshooting and help # Roku Source: https://docs.permutive.com/sdks/ctv/roku Integrate Permutive on Roku using the Permutive Roku Client ## Overview The Permutive Roku Client enables audience tracking and targeting on Roku devices. Built as a SceneGraph Task component, it integrates seamlessly with your Roku channel to collect events, manage identities, and target ads through Google Ad Manager. **BrightScript/SceneGraph:** The Roku Client is implemented as a SceneGraph Task component (`PermutiveTask.xml`) that runs asynchronously alongside your channel. ## Installation ### 1. Add the Component Copy `PermutiveTask.xml` from the Permutive repository into your Roku channel's `components` directory: ``` your-channel/ ├── components/ │ ├── PermutiveTask.xml ← Add here │ └── ... ├── source/ └── manifest ``` ### 2. Instantiate the Client Create and configure the Permutive Task node in your root SceneGraph component: ```brightscript theme={"dark"} sub init() permutive = CreateObject("roSGNode", "PermutiveTask") permutive.apiKey = "your-permutive-api-key-here" permutive.workspaceId = "your-permutive-workspace-id-here" permutive.control = "run" end sub ``` ### 3. Store Globally Store the Permutive Task node globally for access from other components: ```brightscript theme={"dark"} m.global.addFields({ permutive: permutive }) ``` *** ## Configuration | Field | Type | Required | Description | | ------------- | ------- | -------- | ---------------------------------- | | `apiKey` | String | Yes | Your Permutive workspace API key | | `workspaceId` | String | Yes | Your Permutive workspace ID | | `debugMode` | Boolean | No | Enable schema validation logging | | `control` | String | Yes | Set to `"run"` to start the client | ```brightscript theme={"dark"} permutive = CreateObject("roSGNode", "PermutiveTask") permutive.apiKey = "your-api-key" permutive.workspaceId = "your-workspace-id" permutive.debugMode = true ' Enable for development permutive.control = "run" ``` *** ## Identity Management Set user identities to enable cross-platform audience matching and enrichment. ### Default Identity Set a simple string identity: ```brightscript theme={"dark"} m.global.permutive.identity = "some_default_identity" ``` ### Tagged Identities Set identities with tags and optional priority: ```brightscript theme={"dark"} ' Set an email hash identity m.global.permutive.identity = { tag: "email_sha256" id: "a1b2c3d4e5f6..." priority: 1 } ' Set a user account ID m.global.permutive.identity = { tag: "user_id" id: "user_12345" priority: 2 } ' Set Roku advertising ID m.global.permutive.identity = { tag: "rida" id: CreateObject("roDeviceInfo").GetRIDA() } ``` ### Identity Fields | Field | Type | Required | Description | | ---------- | ------- | -------- | ---------------------------------------------------------- | | `tag` | String | Yes | Identifier type (must be configured in Dashboard) | | `id` | String | Yes | The identifier value | | `priority` | Integer | No | Priority for identity resolution (lower = higher priority) | Configure identity tags in your [Dashboard identifiers settings](https://dash.permutive.com/settings/identifiers) before using them. *** ## Event Tracking Track events to capture user behavior and build audience segments. ### View ID Generate a new view ID for each piece of content the user engages with: ```brightscript theme={"dark"} m.global.permutive.view_id = CreateObject("roDeviceInfo").GetRandomUUID() ``` **Always set a new view\_id** when the user starts watching new content. This links all events for that viewing session. ### Tracking Events Track events with a name and properties: ```brightscript theme={"dark"} m.global.permutive.event = { name: "Videoview" properties: { video: { title: "Show Title" genre: ["Drama"] season_number: 1 episode_number: 5 } } } ``` ### Video Events Track standard video events for consistent analytics: ```brightscript theme={"dark"} ' When video starts m.global.permutive.view_id = CreateObject("roDeviceInfo").GetRandomUUID() m.global.permutive.event = { name: "Videoview" properties: { video: { title: m.video.title genre: m.video.genres runtime: m.video.duration } } } ' When video completes m.global.permutive.event = { name: "VideoCompletion" properties: { video: { title: m.video.title } aggregations: { VideoEngagement: { completion: 0.95 ' 95% watched engaged_time: 3420 ' seconds } } } } ' When ad starts m.global.permutive.event = { name: "VideoAdView" properties: { ad: { title: "Acme Cereal" duration: 30 muted: false campaign_id: "campaign_123" creative_id: "creative_456" } } } ' When ad completes m.global.permutive.event = { name: "VideoAdCompletion" properties: { ad: { title: "Acme Cereal" campaign_id: "campaign_123" creative_id: "creative_456" } } } ``` *** ## Special Properties The Roku Client supports special property constants that are replaced with device-derived data: ### Geo Information Add geographic data based on IP: ```brightscript theme={"dark"} m.global.permutive.event = { name: "Videoview" properties: { video: { title: "Video Title" } geo_info: "$ip_geo_info" } } ``` ### ISP Information Add ISP data based on IP: ```brightscript theme={"dark"} m.global.permutive.event = { name: "Videoview" properties: { video: { title: "Video Title" } isp_info: "$ip_isp_info" } } ``` | Constant | Description | | -------------- | ----------------------------------------------------- | | `$ip_geo_info` | Geographic information derived from IP address | | `$ip_isp_info` | Internet Service Provider information derived from IP | *** ## Debug Mode Enable debug mode during development to validate event schemas: ```brightscript theme={"dark"} permutive.debugMode = true permutive.control = "run" ``` Debug mode logs schema validation errors: ``` Failed to upload some events (HTTP 400). Not retrying. One or more events failed validation. Errors: ``` **Disable in production:** Debug mode adds latency due to schema validation. Only use during development. *** ## Retrieving Data ### Get Cohorts Retrieve the user's matched cohort IDs: ```brightscript theme={"dark"} cohorts = m.global.permutive.cohorts ' cohorts is an array of strings: ["cohort_1", "cohort_2", ...] ``` ### Get User ID Retrieve the Permutive user ID: ```brightscript theme={"dark"} userId = m.global.permutive.userId ' userId is a string ``` *** ## Google Ad Manager Targeting Attach Permutive targeting data to GAM ad requests: ### Get Key-Values ```brightscript theme={"dark"} gamKeyValues = m.global.permutive.gamKeyValues ' gamKeyValues is an associative array: { "permutive": "cohort1,cohort2", "puid": "user_id" } ``` ### Serialize for GAM Convert key-values to the format GAM expects: ```brightscript theme={"dark"} function SerializeGamCustParams(custParams as Object) as String paramsAsStrings = [] for each kv in custParams.items() paramsAsStrings.Push(kv.key + "=" + kv.value) end for return paramsAsStrings.Join("&") end function ``` ### Attach to Ad Request ```brightscript theme={"dark"} gamKeyValues = m.global.permutive.gamKeyValues ' Create GAM stream request gamRequest = gamSdk.CreateStreamRequest() ' Serialize and encode custom parameters custParamsString = SerializeGamCustParams(gamKeyValues) urlEncodedCustParams = CreateObject("roUrlTransfer").Encode(custParamsString) ' Attach to request gamRequest.adTagParameters = "cust_params=" + urlEncodedCustParams ``` See [Google's IMA documentation](https://support.google.com/admanager/answer/7320899) for more on custom parameters. *** ## Reset User State Clear all Permutive data from disk and memory: ```brightscript theme={"dark"} m.global.permutive.reset = true ``` Use this when: * User logs out * User requests data deletion * Testing fresh state *** ## Complete Example ```brightscript theme={"dark"} ' main.brs - Channel initialization sub Main(args as Dynamic) screen = CreateObject("roSGScreen") m.port = CreateObject("roMessagePort") screen.setMessagePort(m.port) scene = screen.CreateScene("MainScene") screen.show() while(true) msg = wait(0, m.port) msgType = type(msg) if msgType = "roSGScreenEvent" if msg.isScreenClosed() then return end if end while end sub ' MainScene.brs - Scene initialization sub init() ' Initialize Permutive permutive = CreateObject("roSGNode", "PermutiveTask") permutive.apiKey = "your-api-key" permutive.workspaceId = "your-workspace-id" permutive.control = "run" ' Store globally m.global.addFields({ permutive: permutive }) ' Set user identity if logged in if m.user <> invalid m.global.permutive.identity = { tag: "user_id" id: m.user.id } end if end sub ' VideoPlayer.brs - Video playback component sub onVideoStart() ' Generate new view ID for this content m.global.permutive.view_id = CreateObject("roDeviceInfo").GetRandomUUID() ' Track video view m.global.permutive.event = { name: "Videoview" properties: { video: { title: m.currentVideo.title genre: m.currentVideo.genres runtime: m.currentVideo.duration season_number: m.currentVideo.season episode_number: m.currentVideo.episode } geo_info: "$ip_geo_info" } } end sub sub onVideoComplete() m.global.permutive.event = { name: "VideoCompletion" properties: { video: { title: m.currentVideo.title } aggregations: { VideoEngagement: { completion: m.player.position / m.player.duration engaged_time: m.engagedSeconds } } } } end sub sub onAdStart(ad as Object) m.global.permutive.event = { name: "VideoAdView" properties: { ad: { title: ad.title duration: ad.duration muted: ad.muted campaign_id: ad.campaignId creative_id: ad.creativeId } } } end sub sub loadAds() gamKeyValues = m.global.permutive.gamKeyValues custParams = SerializeGamCustParams(gamKeyValues) adRequest = m.gamSdk.CreateStreamRequest() adRequest.adTagParameters = "cust_params=" + CreateObject("roUrlTransfer").Encode(custParams) m.gamSdk.requestStream(adRequest) end sub ``` *** ## Troubleshooting **Problem:** Events don't appear in the Permutive dashboard. **Solutions:** * Verify `control = "run"` is set after configuration * Check API key and workspace ID are correct * Enable `debugMode` to see validation errors * Ensure network connectivity on the device * Wait 5-10 minutes for events to process **Problem:** Debug mode shows schema validation failures. **Solutions:** * Verify event names match your workspace schema * Check property types match expected schema types * Ensure required properties are included * Contact [Technical Services](mailto:technical-services@permutive.com) to update schemas **Problem:** Ads don't show Permutive targeting. **Solutions:** * Verify `gamKeyValues` is populated before ad request * Check URL encoding of custom parameters * Ensure cohorts are synced to GAM in Dashboard settings * Verify GAM line items target Permutive key-values **Problem:** `cohorts` array is empty. **Solutions:** * Ensure events are being tracked successfully * Wait for cohort processing (can take minutes) * Verify user qualifies for configured cohorts * Check cohorts are enabled for Roku in Dashboard *** ## API Reference ### Task Fields | Field | Type | Access | Description | | -------------- | ------------- | ------ | ------------------------ | | `apiKey` | String | Write | Workspace API key | | `workspaceId` | String | Write | Workspace ID | | `debugMode` | Boolean | Write | Enable schema validation | | `control` | String | Write | Set to "run" to start | | `identity` | String/Object | Write | Set user identity | | `view_id` | String | Write | Current view session ID | | `event` | Object | Write | Track an event | | `reset` | Boolean | Write | Clear all state | | `cohorts` | String\[] | Read | User's cohort IDs | | `userId` | String | Read | Permutive user ID | | `gamKeyValues` | Object | Read | GAM targeting key-values | *** ## Related Documentation Platform selection guide Video event best practices GAM integration guide Cross-platform identity # tvOS Source: https://docs.permutive.com/sdks/ctv/tvos Integrate Permutive on tvOS using the iOS SDK ## Overview tvOS applications use the **Permutive iOS SDK**. The same SDK that powers iOS applications fully supports tvOS 12.0 and later, providing identical APIs for tracking, identity management, and ad targeting. **Same SDK, Same APIs:** The Permutive iOS SDK supports both iOS and tvOS with a unified codebase. All features documented in the [iOS SDK](/sdks/mobile/ios/overview) are available on tvOS unless otherwise noted. ## Requirements | Requirement | Version | | ----------- | ------- | | tvOS | 12.0+ | | Xcode | 13.0+ | | Swift | 5.0+ | ## Installation Add the package URL in Xcode: ``` https://github.com/permutive-engineering/permutive-ios-spm ``` Select `Permutive_iOS` as the package product. ```ruby theme={"dark"} target 'YourTVApp' do platform :tvos, '12.0' pod 'Permutive_iOS', '~> 2.2.0' end ``` ## Initialization Initialize the SDK in your app delegate or app entry point: ```swift theme={"dark"} import Permutive_iOS @main class AppDelegate: UIResponder, UIApplicationDelegate { func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let options = Options( apiKey: "", organisationId: "" ) do { try Permutive.configure(options) } catch { print("Failed to configure Permutive: \(error)") } return true } } ``` For detailed initialization options, see [iOS Initialization](/sdks/mobile/ios/getting-started/initialization). *** ## Video Tracking Video tracking on tvOS uses the same `MediaTracker` API as iOS. This is the primary use case for CTV applications. ### Creating a Video Tracker The iOS SDK does not auto-nest publisher properties under `video` — hand-build the nested `EventProperties` dictionary so the emitted event matches the canonical schema. See [CTV Video Tracking](/sdks/ctv/video-tracking#video-event-schema). ```swift theme={"dark"} import Permutive_iOS import AVKit class VideoPlayerViewController: UIViewController { private var mediaTracker: (NSObject & MediaTrackerProtocol)? private var player: AVPlayer! override func viewDidLoad() { super.viewDidLoad() let videoProps = try? EventProperties([ "video": [ "title": "Show Title - S1E1", "genre": ["Drama", "Thriller"], "content_type": ["Series", "Episode"] ] ]) let context = Context( title: "Now Playing", url: URL(string: "https://example.com/shows/title/s1e1") ) // `duration` is a TimeInterval (seconds), not milliseconds. mediaTracker = try? Permutive.shared.createVideoTracker( duration: video.duration, properties: videoProps, context: context ) } } ``` ### Integrating with AVPlayer tvOS applications typically use `AVPlayerViewController` for video playback: ```swift theme={"dark"} import Permutive_iOS import AVKit class TVVideoPlayerViewController: UIViewController { private var mediaTracker: (NSObject & MediaTrackerProtocol)? private var player: AVPlayer! private var playerObserver: Any? override func viewDidLoad() { super.viewDidLoad() setupPlayer() setupMediaTracker() observePlayer() } private func setupPlayer() { let url = URL(string: "https://example.com/video.m3u8")! player = AVPlayer(url: url) let playerViewController = AVPlayerViewController() playerViewController.player = player addChild(playerViewController) view.addSubview(playerViewController.view) playerViewController.view.frame = view.bounds } private func setupMediaTracker() { player.currentItem?.asset.loadValuesAsynchronously(forKeys: ["duration"]) { [weak self] in guard let self = self, let duration = self.player.currentItem?.duration, !duration.isIndefinite else { return } let durationSeconds = CMTimeGetSeconds(duration) DispatchQueue.main.async { let videoProps = try? EventProperties([ "video": ["title": "Video Title"] ]) self.mediaTracker = try? Permutive.shared.createVideoTracker( duration: durationSeconds, properties: videoProps ) } } } private func observePlayer() { playerObserver = player.observe(\.timeControlStatus) { [weak self] player, _ in switch player.timeControlStatus { case .playing: self?.mediaTracker?.play() case .paused: self?.mediaTracker?.pause() case .waitingToPlayAtSpecifiedRate: break // Buffering @unknown default: break } } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) mediaTracker?.stop() } } ``` ### MediaTracker Lifecycle | Method | Effect | Events Generated | | --------- | -------------------------------- | ------------------------------------------------------- | | `play()` | Starts/resumes playback tracking | `Videoview` (first call) | | `pause()` | Pauses engagement tracking | None — pause stops the timer but does not emit an event | | `stop()` | Ends tracking permanently | `VideoCompletion` | **Always call `stop()`:** Failing to call `stop()` when the video ends or the user exits will prevent `VideoCompletion` events from being tracked. For complete MediaTracker documentation, see [iOS Video Tracking](/sdks/mobile/ios/features/video-tracking). *** ## Video Ad Tracking **iOS/tvOS does not have a native AdTracker.** Emit standard `VideoAdView`, `VideoAdCompletion`, and `VideoAdClicked` events using `Permutive.shared.track(event:properties:)` with properties nested under an `ad` parent per the [canonical schema](/sdks/ctv/video-tracking#ad-event-properties). ```swift theme={"dark"} // When ad starts try? Permutive.shared.track( event: "VideoAdView", properties: try? EventProperties([ "ad": [ "title": "Advertisement", "duration": 30, "muted": false, "campaign_id": "campaign_123", "creative_id": "creative_456" ] ]) ) // When ad completes try? Permutive.shared.track( event: "VideoAdCompletion", properties: try? EventProperties([ "ad": [ "title": "Advertisement", "campaign_id": "campaign_123", "creative_id": "creative_456" ] ]) ) ``` See [iOS Video Ad Tracking](/sdks/mobile/ios/features/video-ad-tracking) for details. *** ## Identity Management Set user identities for cross-device tracking: ```swift theme={"dark"} let aliases = [ Alias(tag: "user_id", identity: userId), Alias(tag: "email_sha256", identity: hashedEmail) ] try? Permutive.shared.setIdentities(aliases: aliases) ``` See [iOS Identity Management](/sdks/mobile/ios/core-concepts/identity-management) for details. *** ## Cohorts and Targeting ### Accessing Cohorts ```swift theme={"dark"} // Get all cohorts let cohorts = Permutive.shared.cohorts // Get activations for a specific ad platform let gamActivations = Permutive.shared.activations(forPlatform: "gam") ``` ### Google Ad Manager Integration ```swift theme={"dark"} import GoogleMobileAds // bannerView is a GAMBannerView instance in your view controller let adRequest = GAMRequest() adRequest.customTargeting = Permutive.shared.googleCustomTargeting( adTargetable: mediaTracker ) bannerView.load(adRequest) ``` See [iOS Google Ad Manager](/sdks/mobile/ios/integrations/google-ad-manager) for complete integration details. *** ## tvOS-Specific Considerations tvOS uses focus-based navigation with the Siri Remote. Ensure your tracking implementation doesn't interfere with focus handling. ```swift theme={"dark"} override func didUpdateFocus( in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator ) { // Handle focus changes // Tracking works independently of focus } ``` Unlike iOS, tvOS does not support IDFA or App Tracking Transparency. Use alternative identifiers: * User account IDs (hashed) * `identifierForVendor` for anonymous tracking * Custom identifiers from your authentication system ```swift theme={"dark"} // Use vendor identifier if let vendorId = UIDevice.current.identifierForVendor?.uuidString { try? Permutive.shared.setIdentities(aliases: [ Alias(tag: "vendor_id", identity: vendorId) ]) } ``` tvOS apps may continue video playback in the background. Ensure your MediaTracker handles this: ```swift theme={"dark"} NotificationCenter.default.addObserver( self, selector: #selector(appDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil ) @objc func appDidEnterBackground() { // Continue tracking if video plays in background // Or pause if playback stops if !player.isPlaying { mediaTracker?.pause() } } ``` If your app has a Top Shelf extension, note that extensions run in a separate process and cannot share the Permutive SDK state with your main app. *** ## Best Practices * Initialize SDK early in app launch * Create MediaTracker when video is ready to play * Call `stop()` when video ends or user exits * Sync `play()`/`pause()` with actual player state * Use `identifierForVendor` or account IDs for identity * Include video metadata for richer cohorts * Don't create multiple MediaTrackers simultaneously * Don't forget to call `stop()` when done * Don't expect IDFA to work on tvOS * Don't block the main thread with SDK calls * Don't send PII in event properties *** ## Troubleshooting **Problem:** Permutive.shared is nil or initialization fails. **Solutions:** * Verify API key and organization ID are correct * Ensure `configure()` is called before accessing `shared` * Check for initialization errors in the catch block * Enable debug logging: `options.logModes = LogMode.all` **Problem:** Video events don't show in Permutive dashboard. **Solutions:** * Verify MediaTracker was created successfully * Ensure `play()` is called when video starts * Ensure `stop()` is called when video ends * Wait 5-10 minutes for events to process * Check network connectivity on the device **Problem:** Engagement metrics don't match expected values. **Solutions:** * Sync `play()`/`pause()` calls with actual player state * Handle buffering states with `pause()` * Provide accurate duration when creating MediaTracker *** ## Related Documentation Complete iOS/tvOS SDK documentation Detailed MediaTracker documentation Video advertisement tracking Cross-platform video tracking guide # CTV Video Tracking Source: https://docs.permutive.com/sdks/ctv/video-tracking Best practices for tracking video events across all CTV platforms ## Overview Consistent video event tracking across CTV platforms enables unified audience analytics and cross-platform cohort building. This guide covers the standard video event schema and implementation patterns for all supported platforms. ## Video Event Schema Permutive uses a consistent video event schema across all CTV platforms: ### Core Events | Event | Description | Trigger | | --------------------- | -------------------------------------- | --------------------------------------------- | | **Videoview** | User initiated video playback | When video starts or user selects content | | **VideoEngagement** | Internal SDK event — see callout below | Periodically during playback (on-device only) | | **VideoCompletion** | User finished watching | When video ends or user exits | | **VideoAdView** | Video ad started playing | When ad playback begins | | **VideoAdCompletion** | Video ad finished | When ad playback ends | | **VideoAdClicked** | User clicked video ad | When user interacts with ad | ### Standard Video Properties Use these properties consistently across events for unified analytics: **Property Naming:** The schema uses `snake_case` property names, nested under a top-level `video` parent. Each platform's SDK or addon handles input differently — see [Property Mapping](#property-mapping) below for the per-platform details. | Property | Type | Description | | :--------------------------- | :-------- | :-------------------------------------------- | | `video.title` | string | Video title | | `video.genre` | string\[] | List of genres (e.g., \["Drama", "Thriller"]) | | `video.content_type` | string\[] | Content types (e.g., \["Series", "Episode"]) | | `video.age_rating` | string | Age rating (e.g., "PG-13", "TV-MA") | | `video.runtime` | number | Runtime in seconds | | `video.country` | string | Origin country | | `video.original_language` | string | Original language code | | `video.audio_language` | string | Audio language being watched | | `video.subtitles.enabled` | boolean | Whether subtitles are enabled | | `video.subtitles.language` | string | Subtitle language | | `video.season_number` | number | Season number (for series) | | `video.episode_number` | number | Episode number (for series) | | `video.consecutive_episodes` | number | Consecutive episodes watched | | `video.iab_categories` | string\[] | IAB content taxonomy categories | ### Completion Event Properties Additional properties on `VideoCompletion` (aggregated from `VideoEngagement` data accumulated during playback): | Property | Type | Description | | :------------------------------------------ | :----- | :----------------------------- | | `aggregations.VideoEngagement.completion` | number | Percentage watched (0.0 - 1.0) | | `aggregations.VideoEngagement.engaged_time` | number | Time spent watching in seconds | ### Ad Event Properties Properties for video ad events (`VideoAdView`, `VideoAdCompletion`, `VideoAdClicked`): | Property | Type | Description | | :--------------- | :------ | :----------------------- | | `ad.title` | string | Ad title | | `ad.duration` | number | Ad duration in seconds | | `ad.muted` | boolean | Whether the ad was muted | | `ad.campaign_id` | string | Campaign identifier | | `ad.creative_id` | string | Creative identifier | ### Ad Completion Event Properties Additional properties on `VideoAdCompletion`: | Property | Type | Description | | :-------------------------------------------- | :----- | :------------------------------------ | | `aggregations.VideoAdEngagement.completion` | number | Percentage of ad watched (0.0 - 1.0) | | `aggregations.VideoAdEngagement.engaged_time` | number | Time spent watching the ad in seconds | *** ## Platform Implementation **Platforms:** Samsung Tizen, LG WebOS, HbbTV Use the CTV addon for automatic event tracking: ```javascript theme={"dark"} // Initialize with video properties permutive.addon("ctv", { duration: 3600000, // milliseconds videoProperties: { title: "Show Title - S1E1", genre: ["Drama", "Thriller"], season_number: 1, episode_number: 1, runtime: 3600 } }) // Sync with player events videoElement.addEventListener("play", () => { permutive.addons.ctv.play(videoElement.currentTime * 1000) }) videoElement.addEventListener("pause", () => { permutive.addons.ctv.pause(videoElement.currentTime * 1000) }) // Track completion const onVideoEnd = () => { permutive.addons.ctv.stop(videoElement.currentTime * 1000) } // Track ads manually const onAdStart = (ad) => { permutive.addons.ctv.track("VideoAdView", { ad: { title: ad.title, duration: ad.duration, muted: ad.muted, campaign_id: ad.campaignId, creative_id: ad.creativeId } }) } ``` **Automatic Events:** * `Videoview` - tracked when addon is initialized * `VideoCompletion` - tracked when `.stop()` is called See [Web CTV documentation](/sdks/ctv/web-ctv#ctv-addon) for full details. **Platform:** tvOS Create a video tracker via `createVideoTracker(duration:properties:context:)`: ```swift theme={"dark"} import Permutive_iOS // The iOS SDK does not auto-nest publisher properties under `video` like // Android does — hand-build the nested EventProperties dictionary so the // emitted event matches the schema (`video.title`, `video.content_type`, ...). let videoProps = try? EventProperties([ "video": [ "title": "Show Title - S1E1", "genre": ["Drama", "Thriller"], "content_type": ["Series", "Episode"] ] ]) let context = Context( title: "Now Playing", url: URL(string: "https://example.com/show/s1e1") ) // `duration` is a TimeInterval (seconds), not milliseconds. mediaTracker = try? Permutive.shared.createVideoTracker( duration: videoDurationSeconds, properties: videoProps, context: context ) // Sync with player player.observe(\.timeControlStatus) { player, _ in switch player.timeControlStatus { case .playing: mediaTracker?.play() case .paused: mediaTracker?.pause() default: break } } // Track completion override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) mediaTracker?.stop() } ``` **Automatic Events:** * `Videoview` - tracked on first `play()` call * `VideoCompletion` - tracked on `stop()` call Pause stops the engagement timer but does not emit an event. See [tvOS documentation](/sdks/ctv/tvos#video-tracking) for full details. **Platforms:** Android TV, Google TV Use MediaTracker for video tracking: ```kotlin theme={"dark"} import com.permutive.android.MediaTracker // Create MediaTracker when video starts videoTracker = permutive.trackVideoView( durationMilliseconds = videoDurationMs, videoProperties = MediaTracker.VideoProperties( title = "Show Title - S1E1", genre = listOf("Drama", "Thriller"), contentType = listOf("Series", "Episode"), seasonNumber = 1, episodeNumber = 1 ), pageProperties = MediaTracker.PageProperties( title = "Now Playing", url = Uri.parse("https://example.com/show/s1e1") ) ) // Sync with ExoPlayer exoPlayer.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { if (isPlaying) { videoTracker.play() } else { videoTracker.pause() } } }) // Track completion override fun onDestroy() { super.onDestroy() videoTracker.stop() } ``` **Automatic Events:** * `Videoview` - tracked when MediaTracker is created * `VideoCompletion` - tracked on `stop()` call See [Android TV documentation](/sdks/ctv/android-tv#video-tracking) for full details. **Platform:** Roku Track events manually through the Permutive Task: ```brightscript theme={"dark"} ' Generate view ID for this content session m.global.permutive.view_id = CreateObject("roDeviceInfo").GetRandomUUID() ' Track video start m.global.permutive.event = { name: "Videoview" properties: { video: { title: m.video.title genre: m.video.genres season_number: m.video.season episode_number: m.video.episode runtime: m.video.duration } } } ' Track video completion sub onVideoComplete() m.global.permutive.event = { name: "VideoCompletion" properties: { video: { title: m.video.title } aggregations: { VideoEngagement: { completion: m.player.position / m.player.duration engaged_time: m.engagedSeconds } } } } end sub ' Track ad events sub onAdStart(ad as Object) m.global.permutive.event = { name: "VideoAdView" properties: { ad: { title: ad.title duration: ad.duration muted: ad.muted campaign_id: ad.campaignId creative_id: ad.creativeId } } } end sub ``` **Manual Events:** All events must be tracked explicitly on Roku. See [Roku documentation](/sdks/ctv/roku#event-tracking) for full details. *** ## Engagement Tracking ### How Engagement Works Engagement time measures how long a user actively watches content: 1. **Start** - When `play()` is called, engagement timer starts 2. **Pause** - When `pause()` is called, timer pauses 3. **Resume** - When `play()` is called again, timer resumes 4. **Complete** - When `stop()` is called, total engagement is recorded ### Buffering Considerations Buffering should pause engagement tracking: ```javascript theme={"dark"} videoElement.addEventListener("waiting", () => { permutive.addons.ctv.pause() }) videoElement.addEventListener("playing", () => { permutive.addons.ctv.play() }) ``` ```kotlin theme={"dark"} // Android example exoPlayer.addListener(object : Player.Listener { override fun onPlaybackStateChanged(state: Int) { when (state) { Player.STATE_BUFFERING -> videoTracker.pause() Player.STATE_READY -> { if (exoPlayer.isPlaying) videoTracker.play() } } } }) ``` ### Seeking/Scrubbing When users seek to a new position: ```javascript theme={"dark"} // Web CTV videoElement.addEventListener("seeked", () => { permutive.addons.ctv.play(videoElement.currentTime * 1000) }) ``` ```kotlin theme={"dark"} // Android exoPlayer.addAnalyticsListener(object : AnalyticsListener { override fun onPositionDiscontinuity( eventTime: AnalyticsListener.EventTime, reason: Int ) { if (reason == Player.DISCONTINUITY_REASON_SEEK) { videoTracker.play(exoPlayer.currentPosition) } } }) ``` *** ## Ad Break Handling ### Pre-roll Ads Track ads before main content: ```javascript theme={"dark"} // Web CTV example const onPrerollStart = (ad) => { permutive.addons.ctv.track("VideoAdView", { ad: { title: ad.title, duration: ad.duration, muted: ad.muted, campaign_id: ad.campaignId, creative_id: ad.creativeId } }) } const onPrerollEnd = (ad) => { permutive.addons.ctv.track("VideoAdCompletion", { ad: { title: ad.title, campaign_id: ad.campaignId, creative_id: ad.creativeId } }) // Now start main content permutive.addon("ctv", { duration: videoDuration, videoProperties: { title: "Show Title" } }) permutive.addons.ctv.play() } ``` ### Mid-roll Ads Pause main content tracking during ads: ```javascript theme={"dark"} const onMidrollStart = (ad) => { // Pause main content tracking permutive.addons.ctv.pause() // Track ad permutive.addons.ctv.track("VideoAdView", { ad: { title: ad.title, duration: ad.duration, muted: ad.muted, campaign_id: ad.campaignId, creative_id: ad.creativeId } }) } const onMidrollEnd = (ad) => { permutive.addons.ctv.track("VideoAdCompletion", { ad: { title: ad.title, campaign_id: ad.campaignId, creative_id: ad.creativeId } }) // Resume main content tracking permutive.addons.ctv.play() } ``` ### Post-roll Ads Track ads after main content completes: ```javascript theme={"dark"} const onContentEnd = () => { // Complete main content tracking first permutive.addons.ctv.stop() } const onPostrollStart = (ad) => { permutive.addons.ctv.track("VideoAdView", { ad: { title: ad.title, duration: ad.duration, muted: ad.muted, campaign_id: ad.campaignId, creative_id: ad.creativeId } }) } ``` *** ## Best Practices * **Set duration accurately** - Provide video duration in milliseconds when initializing * **Sync with player state** - Call `play()`/`pause()` when the player state changes * **Always call stop()** - Ensure `stop()` is called when video ends or user exits * **Handle buffering** - Pause tracking during buffering states * **Track all ad events** - Capture ad views and completions for ad analytics * **Use consistent properties** - Follow the standard schema across all platforms * **Include video metadata** - Richer metadata enables more precise cohort building * **Generate unique view IDs** - Create new view IDs per content session (Roku) * Don't forget to call `stop()` when done * Don't create multiple trackers simultaneously * Don't skip duration - completion percentage requires it * Don't track engagement during ads (pause main tracker) * Don't use inconsistent property names across platforms * Don't send PII in video properties * Don't guess at video duration - wait for it to be available *** ## Cross-Platform Consistency For unified analytics across platforms, ensure: ### Event Name Consistency | Platform | Videoview Event | Completion Event | Ad View Event | | ---------- | ----------------------------------- | ----------------------------------------- | --------------------------------------- | | Web CTV | `Videoview` (auto via addon) | `VideoCompletion` (auto via addon) | `VideoAdView` (manual via `.track()`) | | tvOS | `Videoview` (auto via MediaTracker) | `VideoCompletion` (auto via MediaTracker) | `VideoAdView` (manual via `track()`) | | Android TV | `Videoview` (auto via MediaTracker) | `VideoCompletion` (auto via MediaTracker) | `VideoAdView` (auto via VideoAdTracker) | | Roku | `Videoview` (manual) | `VideoCompletion` (manual) | `VideoAdView` (manual) | ### Property Mapping Each platform exposes a different publisher input shape, but all platforms ultimately produce events that conform to the schema above (properties nested under a top-level `video` parent). | Platform | Publisher input shape | Mapping behavior | | --------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | Web CTV (addon) | Flat `videoProperties: { title, content_type, ... }` in `snake_case` | Addon auto-nests publisher properties under `video` | | Android SDK | Typed `MediaTracker.VideoProperties(title, contentType, ...)` in `camelCase` | SDK maps `camelCase` → `snake_case` and nests under `video` | | iOS SDK | Untyped `EventProperties` dictionary; publisher must hand-build `["video": ["title": ..., "content_type": ...]]` | No auto-mapping or auto-nesting — publishers write `snake_case` directly under a `video` key | | Roku | Manually constructed event dict with `properties: { video: { ... } }` | None — publisher controls the full event shape | Work with [Technical Services](mailto:technical-services@permutive.com) to configure a unified event schema across all platforms. *** ## Troubleshooting **Problem:** Engaged time doesn't match expected values. **Solutions:** * Ensure `play()` is called only when video is actually playing * Call `pause()` during buffering and paused states * Verify `stop()` is called when video ends * Check that ad breaks pause the main content tracker **Problem:** VideoCompletion event shows 0% or null completion. **Solutions:** * Provide duration when creating the tracker/addon * Ensure duration is in the correct unit (milliseconds for most SDKs) * Wait for video metadata to load before creating tracker **Problem:** Events don't show in Permutive analytics. **Solutions:** * Verify SDK is initialized with correct credentials * Check browser/device console for errors * Ensure events match your workspace schema * Wait 5-10 minutes for events to process **Problem:** Ad events aren't associated with video content. **Solutions:** * Track ads using the same addon/tracker instance * On Roku, use the same `view_id` for content and ads * Ensure ad events fire during the video session *** ## Related Documentation Platform selection guide Tizen, WebOS, HbbTV integration tvOS integration Android TV integration Roku integration Detailed iOS MediaTracker docs # Web-Based CTV Source: https://docs.permutive.com/sdks/ctv/web-ctv Integrate Permutive on Samsung Tizen, LG WebOS, and HbbTV platforms using the JavaScript SDK ## Overview Web-based CTV platforms run JavaScript applications in embedded browser engines. Permutive's JavaScript SDK integrates with these platforms through the **CTV addon**, which provides specialized video tracking capabilities for streaming content. **Supported Platforms:** * [Samsung Tizen](https://docs.tizen.org/platform/what-is-tizen/profiles/tv/) * [LG WebOS](https://webostv.developer.lge.com/) * [HbbTV](https://developer.hbbtv.org/) ## Prerequisites An active Permutive workspace. If you're not yet a customer, [get in touch](mailto:support@permutive.com). Your API key from the [Dashboard](https://dash.permutive.com/settings/keys/). Enable the CTV addon in your [Dashboard integrations](https://dash.permutive.com/settings/integrations/). *** ## Setup ### The JavaScript Tag Deploy the Permutive JavaScript tag in your CTV web application. The tag initializes the SDK and downloads your workspace-specific bundle. ```html theme={"dark"} ``` Replace the following placeholders: * `` - Your workspace API key * `` - Your workspace ID * `` - Your organization ID * `` - The platform identifier (e.g., `hbbtv`, `lg-webos-24`) ### Deployment Considerations **CTV Browser Limitations:** CTV platforms use embedded browser engines with different JavaScript capabilities than desktop browsers. Always use the platform-specific SDK bundle that matches your target platform version. * **Use the correct platform bundle** - Each platform version has a specific SDK bundle optimized for its JavaScript engine * **Initialize before video playback** - Ensure the SDK is loaded before initializing the CTV addon * **Test on device** - Emulators may not reflect actual device behavior; test on physical CTV hardware * **Configure network access** - Add required domains to your app's external access policy (especially on Tizen) *** ## Samsung Tizen ### Platform Version SDKs Tizen requires platform-specific SDK bundles. Select the bundle matching your target [Tizen platform version](https://developer.samsung.com/smarttv/develop/specifications/web-engine-specifications.html): | Platform Version | Script Source | | :--------------- | :---------------------------- | | Tizen 8.0 | `-tizen-8-0.js` | | Tizen 7.0 | `-tizen-7-0.js` | | Tizen 6.5 | `-tizen-6-5.js` | | Tizen 6.0 | `-tizen-6-0.js` | | Tizen 5.5 | `-tizen-5-5.js` | | Tizen 5.0 | `-tizen-5-0.js` | | Tizen 4.0 | `-tizen-4-0.js` | | Tizen 3.0 | `-tizen-3-0.js` | | Tizen 2.4 | `-tizen-2-4.js` | | Tizen 2.3 | `-tizen-2-3.js` | **Tizen Deployment Note:** The Tizen script path includes a `/tizensdk` prefix: ```html theme={"dark"} ``` ### Privileges Configure the required [Tizen privileges](https://docs.tizen.org/application/web/tutorials/sec-privileges/) in your application: | Privilege | Description | | :------------------------------------ | :-------------------------- | | `http://tizen.org/privilege/internet` | Required for network access | ### External Access Policy Add Permutive's CDN and APIs to your [external access policy](https://docs.tizen.org/application/web/tutorials/process/setting-properties/#defining-external-access-policies-in-the-policy-tab) in `config.xml`: ```xml theme={"dark"} ``` ### TIFA (Tizen ID for Advertising) Samsung provides [TIFA](https://www.samsungtifa.com/) as a unique advertising identifier. To use TIFA with Permutive: 1. Add `tifa` as an identifier in your [Dashboard identifiers settings](https://dash.permutive.com/settings/identifiers) 2. Identify users using the TIFA ID: ```javascript theme={"dark"} permutive.identify([{ id: "", tag: "tifa" }]) ``` See the [Tizen TIFA documentation](https://developer.samsung.com/smarttv/develop/guides/unique-identifiers-for-smarttv/tizen-id-for-advertising.html) for retrieving the TIFA value. *** ## LG WebOS ### Platform Version SDKs LG WebOS requires platform-specific SDK bundles. Select the bundle matching your target [WebOS platform version](https://webostv.developer.lge.com/develop/specifications/web-api-and-web-engine#web-engine): | Platform Version | Script Source | | :--------------- | :------------------------------ | | webOS TV 24 | `-lg-webos-24.js` | | webOS TV 23 | `-lg-webos-23.js` | | webOS TV 22 | `-lg-webos-22.js` | | webOS TV 6.x | `-lg-webos-6.js` | | webOS TV 5.x | `-lg-webos-5.js` | | webOS TV 4.x | `-lg-webos-4.js` | | webOS TV 3.x | `-lg-webos-3.js` | | webOS TV 2.x | `-lg-webos-2.js` | | webOS TV 1.x | `-lg-webos-1.js` | ### Script Example ```html theme={"dark"} ``` *** ## HbbTV HbbTV (Hybrid Broadcast Broadband TV) applications use the standard CTV script path: ```html theme={"dark"} ``` *** ## CTV Addon The CTV addon provides video-specific tracking APIs. Initialize it when video content starts: ### Initialization ```javascript theme={"dark"} const props = { videoProperties: { title: "Episode Title", genre: ["Drama", "Thriller"], audio_language: "en" }, duration: 3600000 // Duration in milliseconds (1 hour) } // Tracks Videoview event permutive.addon("ctv", props) ``` The addon becomes available at `permutive.addons.ctv`. ### Addon Methods | Method | Description | Events Tracked | | :---------------------------------- | :------------------------------------------------------------------------------------------------- | :---------------- | | `.play(position?)` | Call when content plays or position changes. Starts engagement tracking. Position in milliseconds. | - | | `.pause(position?)` | Call when content pauses. Stops engagement tracking. Position in milliseconds. | - | | `.stop(position?)` | Call when user stops/exits content. Sends completion event. Position in milliseconds. | `VideoCompletion` | | `.setDuration(duration)` | Update content duration in milliseconds. Set before stopping for accurate completion. | - | | `.track(name, properties, options)` | Track custom events (e.g., ad events). | Specified event | ### Video Properties The following properties are available for video events. All are optional but tracking more enables richer cohort building: | Property | Type | Description | | :--------------------- | :-------- | :------------------------------ | | `title` | string | Video title | | `genre` | string\[] | List of genres | | `content_type` | string\[] | Content types | | `age_rating` | string | Age rating (e.g., "PG-13") | | `runtime` | number | Runtime in seconds | | `country` | string | Origin country | | `original_language` | string | Original language code | | `audio_language` | string | Audio language being watched | | `subtitles.enabled` | boolean | Whether subtitles are enabled | | `subtitles.language` | string | Subtitle language | | `season_number` | number | Season number | | `episode_number` | number | Episode number | | `consecutive_episodes` | number | Consecutive episodes watched | | `iab_categories` | string\[] | IAB content taxonomy categories | **Output shape:** The addon nests these properties under a `video` parent in the resulting `Videoview` event. The same nested shape (`video.*`) is what you'll see in the dashboard and use for cohort building. See [CTV Video Tracking](/sdks/ctv/video-tracking#video-event-schema) for the canonical schema. Properties are sanitized to match expected types. Invalid types are removed rather than causing errors. ### Ad Properties The CTV addon does not have a typed ad-tracking API — publishers track ad events manually using `.track()` (see [Manual Events](#manual-events) below). To produce events that match the schema, wrap these properties under an `ad` parent in the `.track()` `properties` argument. | Property | Type | Description | | :------------ | :------ | :----------------------- | | `title` | string | Ad title | | `duration` | number | Ad duration in seconds | | `muted` | boolean | Whether the ad was muted | | `campaign_id` | string | Campaign identifier | | `creative_id` | string | Creative identifier | **Output shape:** When passed under an `ad` key in `.track()`, these become `ad.*` properties in the resulting event (e.g., `ad.title`, `ad.campaign_id`). ### Complete Example ```javascript theme={"dark"} // Initialize CTV addon with video metadata permutive.addon("ctv", { duration: 3600000, // 1 hour in milliseconds videoProperties: { title: "Breaking Bad", genre: ["Drama", "Crime"], season_number: 1, episode_number: 1, audio_language: "en", runtime: 3600 } }) // Get video element reference const videoElement = document.querySelector("video") // Track play events videoElement.addEventListener("play", function() { permutive.addons.ctv.play(videoElement.currentTime * 1000) }) // Track pause events videoElement.addEventListener("pause", function() { permutive.addons.ctv.pause(videoElement.currentTime * 1000) }) // Track ad breaks const onAdStart = () => { videoElement.pause() permutive.addons.ctv.track("VideoAdView", { ad: { title: "Acme Cereal", duration: 30, muted: false, campaign_id: "campaign_123", creative_id: "creative_456" } }) } const onAdComplete = () => { permutive.addons.ctv.track("VideoAdCompletion", { ad: { title: "Acme Cereal", campaign_id: "campaign_123", creative_id: "creative_456" } }) } // Track video completion const onVideoEnd = () => { permutive.addons.ctv.stop(videoElement.currentTime * 1000) } ``` *** ## Event Tracking ### Automatic Events The CTV addon automatically tracks: | Event | Description | | :------------------ | :------------------------------------------------------------------------------------------------------------------------------------ | | **Videoview** | Tracked when addon is initialized. Indicates user intent to watch content. | | **VideoCompletion** | Tracked when `.stop()` is called. Includes `aggregations.VideoEngagement.completion` and `aggregations.VideoEngagement.engaged_time`. | ### Manual Events Track additional video events using `.track()`: | Event | Description | | :-------------------- | :----------------------- | | **VideoAdView** | Video ad started playing | | **VideoAdCompletion** | Video ad finished | | **VideoAdClicked** | User clicked on video ad | ```javascript theme={"dark"} // Track ad view permutive.addons.ctv.track("VideoAdView", { ad: { title: "Acme Cereal", duration: 30, muted: false, campaign_id: "campaign_123", creative_id: "creative_456" } }) // Track ad completion permutive.addons.ctv.track("VideoAdCompletion", { ad: { title: "Acme Cereal", campaign_id: "campaign_123", creative_id: "creative_456" } }) ``` *** ## JavaScript SDK Reference For advanced use cases, the full [JavaScript SDK](/sdks/web/javascript-sdk) is available. Key APIs include: * `permutive.identify([...])` - Set user identities * `permutive.track(eventName, properties)` - Track custom events * `permutive.segments()` - Get current cohort memberships * `permutive.ready(callback)` - Execute code when SDK is ready *** ## Troubleshooting **Problem:** Permutive SDK fails to load on CTV device. **Solutions:** * Verify external access policy includes Permutive domains * Check network connectivity on the device * Ensure correct platform-specific script URL * Verify API key and workspace ID are correct **Problem:** Events don't appear in the dashboard. **Solutions:** * Verify CTV addon is enabled in dashboard settings * Check browser console for JavaScript errors * Ensure events match your workspace schema * Wait 5-10 minutes for events to process **Problem:** Engagement metrics don't match expected values. **Solutions:** * Ensure `.play()` is called when video plays * Ensure `.pause()` is called when video pauses or buffers * Call `.stop()` when video ends or user exits * Set correct duration before calling `.stop()` **Problem:** Google Ad Manager targeting values not applied. **Solutions:** * Ensure GPT library loads before Permutive * Verify `_pdfps` exists in localStorage * Check that ad requests fire after Permutive initializes *** ## Related Documentation Platform selection guide Video event best practices Full JavaScript SDK reference GAM integration guide # Cohorts and Activations Source: https://docs.permutive.com/sdks/mobile/android/core-concepts/cohorts-and-activations Understanding user segmentation and platform-specific targeting Understanding cohorts and activations is fundamental to using the Permutive SDK effectively. ## What are Cohorts? ### Cohort Types ## What are Activations? ### Available Activation Types ## Cohorts vs. Activations In the SDK, access cohorts via `currentCohorts` and activations via `currentActivations`. **Example:** ``` User's Cohorts: ["sports_enthusiast", "premium_subscriber", "mobile_user", "frequent_visitor"] Activations: - dfp: ["sports_enthusiast", "premium_subscriber"] // Only 2 activated for GAM - freewheel: ["sports_enthusiast"] // Only 1 activated for Freewheel ``` ## Accessing Cohorts and Activations Get all cohorts the user currently belongs to: ```kotlin Kotlin theme={"dark"} val permutive = (application as MyApplication).permutive val currentCohorts: List = permutive.currentCohorts currentCohorts.forEach { cohortId -> Log.d("Permutive", "User is in cohort: $cohortId") } ``` ```java Java theme={"dark"} Permutive permutive = ((MyApplication) getApplication()).getPermutive(); List currentCohorts = permutive.getCurrentCohorts(); for (String cohortId : currentCohorts) { Log.d("Permutive", "User is in cohort: " + cohortId); } ``` Get cohorts activated for specific platforms: ```kotlin Kotlin theme={"dark"} val currentActivations: Map> = permutive.currentActivations // Get Google Ad Manager activations val gamActivations = currentActivations["dfp"].orEmpty() // Get AppNexus activations val appNexusActivations = currentActivations["appnexus_adserver"].orEmpty() // Get contextual activations val gamContextual = currentActivations["dfp_contextual"].orEmpty() ``` ```java Java theme={"dark"} Map> currentActivations = permutive.getCurrentActivations(); // Get Google Ad Manager activations List gamActivations = currentActivations.getOrDefault("dfp", Collections.emptyList()); // Get AppNexus activations List appNexusActivations = currentActivations.getOrDefault("appnexus_adserver", Collections.emptyList()); ``` For real-time cohort changes, use `TriggersProvider`: ```kotlin theme={"dark"} val triggersProvider = permutive.triggersProvider() // Get notified when user enters/exits a cohort val trigger = triggersProvider.triggerAction("premium_subscriber") { isInCohort -> if (isInCohort) { showPremiumFeatures() } else { hidePremiumFeatures() } } // Don't forget to close when done override fun onDestroy() { super.onDestroy() trigger.close() } ``` Complete documentation for reactive updates `currentCohorts` and `currentActivations` return **snapshot values** at the time they're called. For real-time updates, use TriggersProvider. ## Use Cases ```kotlin theme={"dark"} class PersonalizedHomeActivity : AppCompatActivity() { private val permutive by lazy { (application as MyApplication).permutive } private fun personalizeContent() { val cohorts = permutive.currentCohorts when { "premium_subscriber" in cohorts -> showPremiumContent() "sports_enthusiast" in cohorts -> showSportsRecommendations() "tech_reader" in cohorts -> showTechRecommendations() else -> showGeneralContent() } } } ``` ```kotlin theme={"dark"} class ExperimentalFeatureActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val cohorts = permutive.currentCohorts // Use cohort membership as feature flag val enableNewCheckout = "checkout_experiment_v2" in cohorts if (enableNewCheckout) { setContentView(R.layout.activity_checkout_v2) } else { setContentView(R.layout.activity_checkout_v1) } } } ``` Activations are automatically used by our add-on libraries: ```kotlin theme={"dark"} // Google Ad Manager (automatic with add-on) val adRequest = AdManagerAdRequest.Builder() .addPermutiveTargeting(permutive) // Adds activations automatically .build() // AppNexus (automatic with add-on) adView.addPermutiveTargeting(permutive) // Adds activations automatically ``` ```kotlin theme={"dark"} class AnalyticsHelper(private val permutive: Permutive) { fun logUserSegments() { val cohorts = permutive.currentCohorts // Log to your analytics platform Analytics.setUserProperty("permutive_cohorts", cohorts.joinToString(",")) Analytics.logEvent("cohort_count", mapOf("count" to cohorts.size)) } fun getActivationInfo(): Map { val activations = permutive.currentActivations return mapOf( "gam_cohort_count" to (activations["dfp"]?.size ?: 0), "appnexus_cohort_count" to (activations["appnexus_adserver"]?.size ?: 0), "has_contextual" to (activations["dfp_contextual"]?.isNotEmpty() ?: false) ) } } ``` ## Contextual Cohorts in Detail Contextual cohorts require SDK version 1.10.0+, feature enabled by your CSM, and page/video tracking with URLs. ```kotlin theme={"dark"} // Track a page with URL val pageTracker = permutive.trackPage( title = "Electric Vehicle Buying Guide", url = Uri.parse("https://example.com/articles/ev-buying-guide"), eventProperties = EventProperties.from( "category" to "automotive", "subcategory" to "electric_vehicles" ) ) // Contextual cohorts appear in activations val contextualCohorts = permutive.currentActivations["dfp_contextual"].orEmpty() // ["automotive_ev", "automotive_buying_guides", "sustainability"] // Used automatically in ad requests val adRequest = AdManagerAdRequest.Builder() .addPermutiveTargeting(permutive) // Includes contextual cohorts .build() ``` Complete contextual cohorts documentation ## Deprecated APIs The SDK previously used integer-based segment IDs. These are now deprecated: ```kotlin theme={"dark"} // Old (Deprecated) val segments: List = permutive.currentSegments val reactions: Map> = permutive.currentReactions // New val cohorts: List = permutive.currentCohorts val activations: Map> = permutive.currentActivations ``` Classification Model cohorts and contextual cohorts use string identifiers. The new APIs support all cohort types uniformly. ## Troubleshooting **Problem:** `currentCohorts` returns an empty list. **Solutions:** * Wait a few seconds after initialization * Track some events and check again * Use TriggersProvider for reactive updates * Enable debug logging to see sync status **Problem:** User is in a cohort but it doesn't appear in activations. **Cause:** Not all cohorts are activated for all platforms. This is configured in your Permutive dashboard. **Solution:** Check your dashboard configuration or contact your Customer Success Manager. **Problem:** `dfp_contextual` activations are empty. **Solutions:** * Verify feature is enabled with your CSM * Update to SDK 1.10.0+ * Ensure you're using PageTracker with valid URLs * Check debug logs for classification errors ## Best Practices * Use `currentCohorts` for one-time checks * Use TriggersProvider for reactive updates * Check activation keys exist before accessing * Handle empty lists gracefully * Log cohorts for debugging (in development only) * Poll `currentCohorts` repeatedly (use TriggersProvider instead) * Assume cohorts will be available immediately after initialization * Hard-code cohort IDs without checking dashboard * Share PII in cohort names or IDs * Cache cohorts for long periods (they can change) ## Related Documentation Reactive cohort updates Content-based segmentation GAM integration AppNexus integration # Contextual Data Source: https://docs.permutive.com/sdks/mobile/android/core-concepts/contextual-data Real-time content-based segmentation for privacy-friendly targeting Contextual data enables real-time content-based segmentation without relying on historical user behavior. The SDK analyzes content users are viewing and generates contextual cohorts instantly. ## Overview In the Android SDK, contextual cohorts are generated in real-time when you track pages or videos with URLs. The SDK automatically includes these cohorts in ad requests alongside behavioral cohorts. ## Requirements * **Core SDK:** 1.10.0 or higher * **Google Ads Add-on:** 2.2.0 or higher (for GAM integration) * **AppNexus Add-on:** 1.7.0 or higher (for Xandr integration) * **Minimum Android API:** 21 (Android 5.0) * **Feature Enablement:** Contact your Customer Success Manager **Feature Activation Required:** Contextual content classification must be enabled by Permutive for your workspace. Please contact your Customer Success Manager if you'd like to use this feature. ## How It Works Use `PageTracker` or `MediaTracker` with content URLs Permutive analyzes the content at the URL (page title, main text, keywords, IAB categories, sentiment) Content-based segments are created in real-time Contextual cohorts automatically included in ad requests *** ## Implementation ### Page Tracking with Contextual Data Simply include URLs when tracking pages - contextual analysis happens automatically: ```kotlin Kotlin theme={"dark"} import android.net.Uri import com.permutive.android.EventProperties class ArticleActivity : AppCompatActivity() { private val permutive by lazy { (application as MyApplication).permutive } private lateinit var pageTracker: PageTracker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_article) // Track page with URL - contextual analysis automatic pageTracker = permutive.trackPage( title = "Complete Guide to Electric Vehicles", url = Uri.parse("https://example.com/articles/electric-vehicle-guide"), referrer = Uri.parse("https://example.com/automotive"), eventProperties = EventProperties.from( "category" to "automotive", "subcategory" to "electric_vehicles", "author" to "Jane Smith" ) ) } override fun onDestroy() { super.onDestroy() pageTracker.close() } } ``` ```java Java theme={"dark"} import android.net.Uri; import com.permutive.android.EventProperties; import com.permutive.android.PageTracker; public class ArticleActivity extends AppCompatActivity { private PageTracker pageTracker; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_article); Permutive permutive = ((MyApplication) getApplication()).getPermutive(); // Track page with URL - contextual analysis automatic EventProperties properties = new EventProperties.Builder() .with("category", "automotive") .with("subcategory", "electric_vehicles") .with("author", "Jane Smith") .build(); pageTracker = permutive.trackPage( properties, "Complete Guide to Electric Vehicles", Uri.parse("https://example.com/articles/electric-vehicle-guide"), Uri.parse("https://example.com/automotive") ); } @Override protected void onDestroy() { super.onDestroy(); pageTracker.close(); } } ``` ### Video Tracking with Contextual Data Contextual analysis also works with video content: ```kotlin theme={"dark"} val videoTracker = permutive.trackVideoView( durationMilliseconds = videoDurationMs, videoProperties = MediaTracker.VideoProperties( title = "Electric Car Review: Tesla Model 3", genre = listOf("Automotive", "Reviews"), contentType = listOf("Video", "Review") ), pageProperties = MediaTracker.PageProperties( title = "Video: Tesla Model 3 Review", url = Uri.parse("https://example.com/videos/tesla-model-3-review"), referrer = Uri.parse("https://example.com/automotive") ) ) ``` *** ## Accessing Contextual Cohorts Contextual cohorts are automatically included in activations with special keys: ### In Current Activations ```kotlin theme={"dark"} val activations = permutive.currentActivations // Google Ad Manager contextual cohorts val gamContextual = activations["dfp_contextual"].orEmpty() // Example: ["automotive_ev", "automotive_reviews", "tech_innovation"] // Xandr/AppNexus contextual cohorts val xandrContextual = activations["appnexus_adserver_contextual"].orEmpty() // Log contextual cohorts Log.d("Permutive", "GAM contextual cohorts: ${gamContextual.joinToString()}") ``` ### In Ad Requests Our add-on libraries automatically include contextual cohorts: #### Google Ad Manager ```kotlin theme={"dark"} import com.google.android.gms.ads.admanager.AdManagerAdRequest import com.permutive.android.ads.addPermutiveTargeting // Contextual cohorts automatically added with key "prmtvctx" val adRequest = AdManagerAdRequest.Builder() .addPermutiveTargeting(permutive) // Includes both behavioral and contextual .build() // The resulting ad request includes: // - permutive: ["123", "456"] // Behavioral cohorts // - prmtvctx: ["cabf", "cabc", "bzwu"] // Contextual cohorts // - puid: "user_id" // - ptime: "timestamp" ``` #### Xandr/AppNexus ```kotlin theme={"dark"} import com.permutive.android.appnexus.addPermutiveTargeting // Contextual cohorts automatically added adView.addPermutiveTargeting(permutive) adView.loadAd() // Custom keywords include: // - permutive: behavioral cohorts // - prmtvctx: contextual cohorts // - puid: user ID // - ptime: timestamp ``` *** ## Configuration Contextual features are controlled by server-side configuration automatically applied to your SDK: ### Configuration Flags The SDK receives configuration that controls: * **Segmentation enabled** - Whether content classification is active * **Event enrichment** - Whether contextual data is added to events * **Platform activations** - Which ad platforms receive contextual cohorts ### Checking Configuration Status ```kotlin theme={"dark"} // Configuration is applied automatically // No manual configuration needed in the SDK ``` > 💡 Automatic Configuration > > Contextual features are enabled/disabled by your workspace configuration. Contact your Customer Success Manager to modify settings. *** ## Performance Considerations ### Content Analysis Timing * **First analysis:** May take 1-2 seconds for initial content fetch and analysis * **Cached results:** Subsequent views of the same URL use cached analysis * **Background processing:** Analysis happens asynchronously, doesn't block UI * **Network required:** Content classification requires network connectivity ### Impact on App Performance Contextual analysis is designed to be lightweight: * ✅ **Async processing** - Doesn't block main thread * ✅ **Cached results** - Same URLs reuse previous analysis * ✅ **Efficient networking** - Minimal data transfer * ✅ **Graceful degradation** - Falls back to behavioral targeting if unavailable ### Best Practices for Performance ```kotlin theme={"dark"} // ✅ Good: Track page early, analysis starts immediately override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_article) // Start tracking (and analysis) as early as possible pageTracker = permutive.trackPage(...) // Load your content loadArticleContent() } // ❌ Avoid: Delaying page tracking override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_article) loadArticleContent() // Tracking delayed - contextual cohorts may not be ready for early ads Handler().postDelayed({ pageTracker = permutive.trackPage(...) }, 5000) } ``` *** ## Privacy and Compliance ### Privacy-Friendly Targeting ### Data Sent for Analysis When tracking a page with a URL: * **URL** - The page URL for content fetching * **Title** - Page title (if provided) * **No PII** - No personal information sent for classification The URL should be **publicly accessible** for content analysis to work. ### Opt-Out Handling If users opt out of targeting: ```kotlin theme={"dark"} // If user opts out, you can still track pages but don't use contextual cohorts val pageTracker = permutive.trackPage(...) // Don't include contextual activations in ads // Behavioral targeting already respects consent at SDK level ``` *** ## Error Handling ### Classification Failures Contextual classification may fail if: * URL is not publicly accessible * Content is behind a paywall or login * Network connectivity issues * Server-side analysis errors **The SDK handles failures gracefully:** ```kotlin theme={"dark"} // If contextual classification fails: // 1. Behavioral cohorts still work // 2. Events still tracked // 3. No error surfaced to app // 4. Contextual activations will be empty val contextualCohorts = permutive.currentActivations["dfp_contextual"].orEmpty() if (contextualCohorts.isEmpty()) { // This is OK - may be: // - Feature not enabled // - Classification in progress // - Classification failed // - No URL tracked yet // Behavioral targeting still works val behavioralCohorts = permutive.currentActivations["dfp"].orEmpty() } ``` ### Debugging Classification Enable debug logging to see classification status: ```kotlin theme={"dark"} permutive.setDeveloperMode(true) // Look for logs like: // D/Permutive: Contextual classification requested for URL: https://... // D/Permutive: Contextual cohorts received: [cohort1, cohort2, ...] // D/Permutive: Contextual classification failed: ``` *** ## Use Cases ### Content-Aligned Advertising ```kotlin theme={"dark"} class ArticleActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_article) // Track article page pageTracker = permutive.trackPage( title = article.title, url = Uri.parse(article.url) ) // Load ad - contextual cohorts automatically included loadAd() } private fun loadAd() { // Contextual cohorts ensure ad is relevant to article content val adRequest = AdManagerAdRequest.Builder() .addPermutiveTargeting(permutive) .build() adView.loadAd(adRequest) } } ``` ### Video Ad Targeting ```kotlin theme={"dark"} class VideoPlayerActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Track video with URL videoTracker = permutive.trackVideoView( durationMilliseconds = video.duration, videoProperties = MediaTracker.VideoProperties( title = video.title, genre = video.genres ), pageProperties = MediaTracker.PageProperties( url = Uri.parse(video.url) ) ) // Load pre-roll ad with contextual targeting loadPreRollAd() } private fun loadPreRollAd() { // Ad is contextually targeted to video content val adRequest = AdManagerAdRequest.Builder() .addPermutiveTargeting(permutive) .build() imaAdsLoader.requestAds(adRequest) } } ``` ### Contextual + Behavioral Targeting Combine contextual and behavioral cohorts for powerful targeting: ```kotlin theme={"dark"} val activations = permutive.currentActivations // Behavioral: User interests over time val behavioral = activations["dfp"].orEmpty() // Example: ["sports_enthusiast", "premium_subscriber"] // Contextual: Current content val contextual = activations["dfp_contextual"].orEmpty() // Example: ["sports_soccer", "sports_worldcup"] // Both automatically included in ad requests val adRequest = AdManagerAdRequest.Builder() .addPermutiveTargeting(permutive) .build() // Ad platform receives: // - permutive: ["sports_enthusiast", "premium_subscriber"] // - prmtvctx: ["sports_soccer", "sports_worldcup"] // // Result: Highly targeted ad for soccer content to a sports enthusiast ``` *** ## Contextual vs. Behavioral Cohorts In the SDK, contextual cohorts use activation keys `dfp_contextual` and `appnexus_adserver_contextual`, while behavioral cohorts use `dfp` and `appnexus_adserver`. ### When to Use Each **Use Behavioral Cohorts When:** * Building long-term audience segments * Retargeting campaigns * Personalization based on user history * Lookalike modeling **Use Contextual Cohorts When:** * Real-time content alignment needed * Privacy regulations are strict * New users without history * Brand safety is critical * Cookie-less environment **Use Both When:** * Maximum targeting precision needed * Combining user intent (contextual) with user interests (behavioral) * Premium inventory requires both signals *** ## Troubleshooting ### No Contextual Cohorts Appearing **Problem:** `dfp_contextual` is empty or not present. **Possible Causes & Solutions:** 1. **Feature not enabled** * Contact Customer Success Manager to enable 2. **SDK version too old** * Update to Core 1.10.0+, Google Ads 2.2.0+, or AppNexus 1.7.0+ 3. **No URL tracked** * Ensure you're using `PageTracker` or `MediaTracker` with URLs 4. **Classification in progress** * First analysis may take 1-2 seconds * Check again after a moment 5. **URL not accessible** * Ensure URL is publicly accessible * Remove authentication requirements for analysis 6. **Network issues** * Check device connectivity * Look for network errors in logs ### Classification Taking Too Long **Problem:** Contextual cohorts not appearing before ads load. **Solutions:** * Start page tracking earlier in activity lifecycle * Consider delaying ad request slightly if contextual is critical * Use behavioral cohorts as fallback ### Wrong Contextual Cohorts **Problem:** Cohorts don't match content. **Possible Causes:** * URL points to different content than displayed * Content changed since classification * Cache from previous analysis **Solutions:** * Ensure URL matches actual content * Use unique URLs for different content * Wait for cache expiry or contact support to invalidate *** ## Best Practices ### ✅ Do * Track pages/videos with URLs as early as possible * Use publicly accessible URLs * Ensure URLs are stable and won't change * Test with debug logging enabled * Use unique URLs for different content * Combine with behavioral targeting for best results ### ❌ Don't * Use authentication-protected URLs * Track pages without URLs (contextual won't work) * Expect instant results (allow 1-2 seconds) * Rely solely on contextual for returning users * Use duplicate URLs for different content *** ## Related Documentation * [Cohorts and Activations](/sdks/mobile/android/core-concepts/cohorts-and-activations) * [Page Tracking](/sdks/mobile/android/features/page-tracking) * [Video Tracking](/sdks/mobile/android/features/video-tracking) * [Google Ad Manager Integration](/sdks/mobile/android/integrations/google-ad-manager) * [Xandr Integration](/sdks/mobile/android/integrations/appnexus-xandr) *** ## API Reference Contextual cohorts are accessed through standard SDK APIs: * `Permutive.currentActivations["dfp_contextual"]` - GAM contextual cohorts * `Permutive.currentActivations["appnexus_adserver_contextual"]` - Xandr contextual cohorts * `PageTracker` - Track pages with URLs for analysis * `MediaTracker` - Track videos with URLs for analysis For complete API documentation, see the [Javadocs](https://sdk-docs.permutive.com/index.html). # Event Properties Source: https://docs.permutive.com/sdks/mobile/android/core-concepts/event-properties Add structured, typed data to your events Event properties allow you to add structured, typed data to your events. Properties must match your dashboard schema exactly, or the event will be rejected. ## Supported Types EventProperties can contain the following types: | Type | Kotlin/Java Type | Example | | ----------------- | ----------------- | -------------------------- | | **Boolean** | `Boolean` | `true`, `false` | | **Integer** | `Int`, `Long` | `42`, `1000L` | | **String** | `String` | `"Technology"` | | **Float** | `Float`, `Double` | `99.99`, `3.14f` | | **DateTime** | `java.util.Date` | `Date()` | | **Nested Object** | `EventProperties` | Nested properties | | **List** | `List` | List of any supported type | ### Nullable Values The API accepts nullable types. **Properties with `null` values will be automatically filtered** and not sent with the event. ```kotlin theme={"dark"} EventProperties.from( "optional_field" to null // This will be filtered out automatically ) ``` *** ## Creating Event Properties ```kotlin Kotlin theme={"dark"} import com.permutive.android.EventProperties val properties = EventProperties.from( "article_id" to 12345, "title" to "Breaking News", "category" to "Technology", "price" to 9.99, "is_premium" to true, "published_at" to Date(), "tags" to listOf("tech", "mobile", "android") ) ``` ```java Java theme={"dark"} import com.permutive.android.EventProperties; EventProperties properties = new EventProperties.Builder() .with("article_id", 12345) .with("title", "Breaking News") .with("category", "Technology") .with("price", 9.99) .with("is_premium", true) .with("published_at", new Date()) .withStrings("tags", Arrays.asList("tech", "mobile", "android")) .build(); ``` *** ## Nested Properties Use nested EventProperties to represent hierarchical data structures: ```kotlin Kotlin theme={"dark"} val properties = EventProperties.from( "article_id" to 12345, "user" to EventProperties.from( "type" to "premium", // user.type "premium" to true, // user.premium "subscription_tier" to "gold" // user.subscription_tier ), "metadata" to EventProperties.from( "source" to "mobile_app", "version" to "2.1.0" ) ) ``` ```java Java theme={"dark"} EventProperties userProps = new EventProperties.Builder() .with("type", "premium") .with("premium", true) .with("subscription_tier", "gold") .build(); EventProperties metadataProps = new EventProperties.Builder() .with("source", "mobile_app") .with("version", "2.1.0") .build(); EventProperties properties = new EventProperties.Builder() .with("article_id", 12345) .with("user", userProps) .with("metadata", metadataProps) .build(); ``` *** ## Complete Example Here's a complete example matching a typical Pageview event schema: ### Schema Definition | Name | Type | | ------------- | -------------- | | id | Long | | description | String | | tags | List of String | | published\_at | DateTime | | user.type | String | | user.premium | Boolean | ```kotlin Kotlin theme={"dark"} private fun pageProperties( id: Long, description: String, tags: List, published: Date, userType: String, userIsPremium: Boolean ): EventProperties = EventProperties.from( "isp_info" to EventProperties.ISP_INFO, "geo_info" to EventProperties.GEO_INFO, "id" to id, "description" to description, "tags" to tags, "published_at" to published, "user" to EventProperties.from( "type" to userType, // user.type "premium" to userIsPremium // user.premium ) ) // Usage val properties = pageProperties( id = 12345L, description = "Article about Android development", tags = listOf("android", "mobile", "development"), published = Date(), userType = "subscriber", userIsPremium = true ) pageTracker = permutive.trackPage( title = "Article Title", url = Uri.parse("https://example.com/article"), eventProperties = properties ) ``` ```java Java theme={"dark"} private EventProperties pageProperties( Long id, String description, List tags, Date published, String userType, Boolean userIsPremium ) { return new EventProperties.Builder() .with("isp_info", EventProperties.getISP_INFO()) .with("geo_info", EventProperties.getGEO_INFO()) .with("id", id) .with("description", description) .withStrings("tags", tags) .with("published_at", published) .with("user", new EventProperties.Builder() .with("type", userType) // user.type .with("premium", userIsPremium) // user.premium .build() ) .build(); } // Usage EventProperties properties = pageProperties( 12345L, "Article about Android development", Arrays.asList("android", "mobile", "development"), new Date(), "subscriber", true ); PageTracker pageTracker = permutive.trackPage( properties, "Article Title", Uri.parse("https://example.com/article"), null ); ``` *** ## Event Enrichment Events can be enriched with additional information automatically by using special constants. ### Location and ISP Enrichment Add location and ISP data to events using these constants: ```kotlin theme={"dark"} EventProperties.from( "isp_info" to EventProperties.ISP_INFO, // ISP information "geo_info" to EventProperties.GEO_INFO, // Geographic location "ip_address" to EventProperties.IP_ADDRESS_HASH // Hashed IP address ) ``` This enriches events with: * **ISP\_INFO**: Internet Service Provider details * **GEO\_INFO**: Geographic location (country, region, city) * **IP\_ADDRESS\_HASH**: Hashed IP address for privacy Contact [Technical Services](mailto:technical-services@permutive.com) for configuration details. ### Watson NLP Enrichment Add IBM Watson Natural Language Understanding data: ```kotlin theme={"dark"} EventProperties.from( "watson_concepts" to EventProperties.ALCHEMY_CONCEPTS, "watson_entities" to EventProperties.ALCHEMY_ENTITIES, "watson_keywords" to EventProperties.ALCHEMY_KEYWORDS, "watson_taxonomy" to EventProperties.ALCHEMY_TAXONOMY, "watson_emotion" to EventProperties.ALCHEMY_DOCUMENT_EMOTION, "watson_sentiment" to EventProperties.ALCHEMY_DOCUMENT_SENTIMENT, "watson_taxonomy_labels" to EventProperties.ALCHEMY_TAXONOMY_LABELS, "watson_entity_names" to EventProperties.ALCHEMY_ENTITY_NAMES ) ``` See [IBM Watson Integration](/integrations/contextual/ibm-watson) for details. ### Complete Enrichment Example ```kotlin Kotlin theme={"dark"} pageTracker = permutive.trackPage( eventProperties = EventProperties.from( "isp_info" to EventProperties.ISP_INFO, "geo_info" to EventProperties.GEO_INFO, "ip_address" to EventProperties.IP_ADDRESS_HASH, "article_id" to 12345, "category" to "technology" ), title = "Tech Article", url = Uri.parse("https://www.example.com/article"), referrer = Uri.parse("https://www.example.com/home") ) ``` ```java Java theme={"dark"} pageTracker = permutive.trackPage( new EventProperties.Builder() .with("isp_info", EventProperties.getISP_INFO()) .with("geo_info", EventProperties.getGEO_INFO()) .with("ip_address", EventProperties.getIP_ADDRESS_HASH()) .with("article_id", 12345) .with("category", "technology") .build(), "Tech Article", Uri.parse("https://www.example.com/article"), Uri.parse("https://www.example.com/home") ); ``` *** ## Type-Specific Methods (Java) When building properties in Java, use type-specific methods for lists: ```java theme={"dark"} EventProperties properties = new EventProperties.Builder() .withStrings("tags", Arrays.asList("tech", "mobile")) // List .withIntegers("scores", Arrays.asList(1, 2, 3)) // List .withLongs("ids", Arrays.asList(100L, 200L)) // List .withBooleans("flags", Arrays.asList(true, false)) // List .withDoubles("prices", Arrays.asList(9.99, 19.99)) // List .build(); ``` *** ## Common Patterns ```kotlin theme={"dark"} val purchaseProperties = EventProperties.from( "order_id" to "ORD-12345", "total_amount" to 149.99, "currency" to "USD", "item_count" to 3, "items" to listOf( EventProperties.from( "product_id" to "PROD-001", "name" to "Widget", "price" to 49.99, "quantity" to 1 ), EventProperties.from( "product_id" to "PROD-002", "name" to "Gadget", "price" to 99.99, "quantity" to 2 ) ), "payment_method" to "credit_card", "shipping_method" to "express" ) ``` ```kotlin theme={"dark"} val contentProperties = EventProperties.from( "article_id" to 12345, "title" to "Article Title", "author" to "John Doe", "category" to "Technology", "subcategories" to listOf("Mobile", "Android"), "word_count" to 1500, "published_at" to Date(), "is_premium" to true, "tags" to listOf("android", "sdk", "mobile"), "isp_info" to EventProperties.ISP_INFO, "geo_info" to EventProperties.GEO_INFO ) ``` ```kotlin theme={"dark"} val userContextProperties = EventProperties.from( "user" to EventProperties.from( "id" to "user_12345", "type" to "premium", "subscription_tier" to "gold", "is_logged_in" to true, "account_age_days" to 365 ), "session" to EventProperties.from( "id" to "session_abc123", "duration_seconds" to 120, "page_views" to 5 ) ) ``` *** ## Validation and Errors ### Schema Validation Properties are validated against your event schema in the Permutive dashboard: **✅ Valid:** * Property names match exactly (case-sensitive) * Property types match schema * All required properties included **❌ Invalid:** * Misspelled property names * Wrong property types * Missing required properties ### Common Validation Errors **Problem:** Event rejected with "Schema validation failed" **Causes:** 1. Property name doesn't match dashboard (case-sensitive) 2. Property type doesn't match schema 3. Required property missing **Solutions:** 1. Verify property names in dashboard 2. Check property types match 3. Include all required properties **Example of incorrect types:** ```kotlin theme={"dark"} // ❌ WRONG - Sending string where number expected EventProperties.from( "article_id" to "12345" // Schema expects Long, got String ) // ✅ CORRECT EventProperties.from( "article_id" to 12345L // Matches schema ) ``` *** ## Best Practices * Match property names and types exactly with your schema * Use enrichment constants when available * Create reusable property builder functions * Use nested properties for related data * Include relevant context properties * Validate against your schema before deploying * Include PII (Personally Identifiable Information) without hashing * Use arbitrary property names not in your schema * Send null for required properties * Mix up property types * Create overly complex nested structures **Never send unhashed PII:** ```kotlin theme={"dark"} // ❌ WRONG - Sending raw email EventProperties.from("email" to "user@example.com") // ✅ CORRECT - Hash before sending EventProperties.from("email_sha256" to hashSHA256("user@example.com")) ``` ## Troubleshooting **Solutions:** 1. Check property names match schema exactly 2. Verify property types are correct 3. Ensure event is being accepted (check logs) 4. Confirm properties are defined in dashboard schema **Cause:** Incompatible type passed to builder. **Solution:** Ensure types match: ```kotlin theme={"dark"} // ❌ WRONG EventProperties.from("count" to "10") // String instead of number // ✅ CORRECT EventProperties.from("count" to 10) // Number ``` **Solution:** Ensure nested properties are defined in your dashboard schema: ``` user.type user.premium user.subscription_tier ``` ## Related Documentation Using properties with PageTracker Using properties with EventTracker Hashing PII Solutions to common issues *** ## API Reference For complete API documentation, see the [Javadocs](https://sdk-docs.permutive.com/index.html). ### EventProperties Class **Kotlin:** * `EventProperties.from(vararg pairs: Pair)` - Create from pairs **Java:** * `EventProperties.Builder()` - Create builder * `builder.with(key: String, value: Any)` - Add property * `builder.withStrings(key: String, values: List)` - Add string list * `builder.withIntegers(key: String, values: List)` - Add integer list * `builder.withLongs(key: String, values: List)` - Add long list * `builder.withBooleans(key: String, values: List)` - Add boolean list * `builder.withDoubles(key: String, values: List)` - Add double list * `builder.build()` - Build EventProperties ### Enrichment Constants * `EventProperties.ISP_INFO` - ISP enrichment * `EventProperties.GEO_INFO` - Geographic enrichment * `EventProperties.IP_ADDRESS_HASH` - IP address hash * `EventProperties.ALCHEMY_*` - Watson NLP enrichment constants # Identity Management Source: https://docs.permutive.com/sdks/mobile/android/core-concepts/identity-management Track users across sessions, devices, and platforms with aliases Identity management allows you to track users across different sessions, devices, and platforms by associating multiple identifiers (aliases) with a single user profile. ## Overview In the Android SDK, you can set aliases using the `setIdentity()` method or configure automatic identity providers like AAID at initialization. ## Key Concepts ## Setting Identity For apps with only one type of user identifier: ```kotlin Kotlin theme={"dark"} // Set identity when user logs in fun onUserLoggedIn(userId: String) { permutive.setIdentity(userId) } ``` ```java Java theme={"dark"} // Set identity when user logs in public void onUserLoggedIn(String userId) { permutive.setIdentity(userId); } ``` When using the single-parameter `setIdentity()`, the alias tag is automatically set to `"default"`. ### With Priority and Expiry ```kotlin Kotlin theme={"dark"} fun onUserLoggedIn(userId: String) { val expiryDate = Calendar.getInstance().apply { add(Calendar.YEAR, 1) // Expire in 1 year }.time permutive.setIdentity( identity = userId, priority = 0, // Highest priority expiry = expiryDate ) } ``` ```java Java theme={"dark"} public void onUserLoggedIn(String userId) { Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.YEAR, 1); Date expiryDate = calendar.getTime(); permutive.setIdentity(userId, 0, expiryDate); } ``` For apps tracking multiple types of identifiers (email, internal ID, ad ID, etc.): ```kotlin Kotlin theme={"dark"} import com.permutive.android.Alias fun setupUserIdentities(userId: String, emailHash: String, adId: String?) { val aliases = buildList { // Highest priority: hashed email (never expires) add(Alias.create( tag = "email_sha256", identity = emailHash, priority = 0 )) // Medium priority: internal user ID (expires in 1 year) val oneYearLater = Calendar.getInstance().apply { add(Calendar.YEAR, 1) }.time add(Alias.create( tag = "internal_id", identity = userId, priority = 1, expiry = oneYearLater )) // Lower priority: advertising ID (expires in 30 days) adId?.let { id -> val thirtyDaysLater = Calendar.getInstance().apply { add(Calendar.DAY_OF_YEAR, 30) }.time add(Alias.create( tag = "ad_id", identity = id, priority = 2, expiry = thirtyDaysLater )) } } permutive.setIdentity(aliases) } ``` ```java Java theme={"dark"} import com.permutive.android.Alias; public void setupUserIdentities(String userId, String emailHash, String adId) { List aliases = new ArrayList<>(); // Highest priority: hashed email aliases.add(Alias.create("email_sha256", emailHash, 0)); // Medium priority: internal user ID Calendar oneYearLater = Calendar.getInstance(); oneYearLater.add(Calendar.YEAR, 1); aliases.add(Alias.create("internal_id", userId, 1, oneYearLater.getTime())); // Lower priority: advertising ID if (adId != null) { Calendar thirtyDaysLater = Calendar.getInstance(); thirtyDaysLater.add(Calendar.DAY_OF_YEAR, 30); aliases.add(Alias.create("ad_id", adId, 2, thirtyDaysLater.getTime())); } permutive.setIdentity(aliases); } ``` You can provide aliases when creating the Permutive instance: ```kotlin Kotlin theme={"dark"} val customAliases = listOf( Alias.create( tag = "email_sha256", identity = "f660ab912ec121d1b1e928a0bb4bc61b15f5ad44d5efdc4e1c92a25e", priority = 0 ), Alias.create( tag = "internal_id", identity = "user_12345", priority = 1 ) ) val permutive = Permutive( context = this, workspaceId = YOUR_WORKSPACE_ID, apiKey = YOUR_API_KEY, customAliases = customAliases ) ``` ```java Java theme={"dark"} Permutive permutive = new Permutive.Builder() .context(this) .workspaceId(YOUR_WORKSPACE_ID) .apiKey(YOUR_API_KEY) .customAlias(Alias.create("email_sha256", "f660ab912ec121d...", 0)) .customAlias(Alias.create("internal_id", "user_12345", 1)) .build(); ``` ## Security Best Practices **Always hash** personally identifiable information like email addresses before sending to Permutive. ```kotlin theme={"dark"} import java.security.MessageDigest fun hashEmail(email: String): String { val normalized = email.trim().lowercase() val bytes = MessageDigest.getInstance("SHA-256").digest(normalized.toByteArray()) return bytes.joinToString("") { "%02x".format(it) } } // Usage val emailHash = hashEmail("user@example.com") permutive.setIdentity( listOf(Alias.create("email_sha256", emailHash, priority = 0)) ) ``` ```kotlin theme={"dark"} // DON'T DO THIS - sending PII permutive.setIdentity( listOf(Alias.create("email", "user@example.com", priority = 0)) // WRONG! ) ``` ### Standard Tag Names | Identifier Type | Recommended Tag | Notes | | -------------------- | -------------------------- | --------------------- | | SHA-256 hashed email | `email_sha256` | Most common | | Internal user ID | `internal_id` or `user_id` | Your system's ID | | Advertising ID | `aaid` (Android) | Use AaidAliasProvider | | Customer ID | `customer_id` | E-commerce systems | | Subscriber ID | `subscriber_id` | Subscription services | ## Automatic Identity Providers ### AAID (Android Advertising ID) From v1.12.0, the AAID and IP address can be **collected automatically** when enabled in your workspace config - no provider code required. See the [v1.12 Migration Guide](/sdks/mobile/android/guides/migration/v1-12). If you opt into automatic collection, you generally don't need the manual `AaidAliasProvider` setup below. Use the Google Ads add-on for automatic AAID identification: ```kotlin theme={"dark"} dependencies { implementation("com.permutive.android:core:1.12.0") implementation("com.permutive.android:google-ads:2.2.0") } ``` In your `AndroidManifest.xml`: ```xml theme={"dark"} ``` From Android 13 (API 33+), the AD\_ID permission is required to access the advertising ID. ```kotlin Kotlin theme={"dark"} import com.permutive.android.aaid.AaidAliasProvider val permutive = Permutive( context = this, workspaceId = YOUR_WORKSPACE_ID, apiKey = YOUR_API_KEY, aliasProviders = listOf(AaidAliasProvider(this)) ) ``` ```java Java theme={"dark"} import com.permutive.android.aaid.AaidAliasProvider; Permutive permutive = new Permutive.Builder() .context(this) .workspaceId(YOUR_WORKSPACE_ID) .apiKey(YOUR_API_KEY) .aliasProvider(new AaidAliasProvider(this)) .build(); ``` The AAID will be automatically retrieved and set with the tag `"aaid"`. ## Common Patterns ```kotlin theme={"dark"} class AuthActivity : AppCompatActivity() { private val permutive by lazy { (application as MyApplication).permutive } fun onLoginSuccess(userId: String, email: String) { val emailHash = hashEmail(email) val aliases = listOf( Alias.create("email_sha256", emailHash, priority = 0), Alias.create("internal_id", userId, priority = 1) ) permutive.setIdentity(aliases) navigateToHome() } private fun hashEmail(email: String): String { val normalized = email.trim().lowercase() val bytes = MessageDigest.getInstance("SHA-256") .digest(normalized.toByteArray()) return bytes.joinToString("") { "%02x".format(it) } } } ``` ```kotlin theme={"dark"} fun onLogout() { // Clear all persistent data lifecycleScope.launch(Dispatchers.IO) { permutive.clearPersistentData() .onSuccess { withContext(Dispatchers.Main) { navigateToLogin() } } .onFailure { error -> Log.e("Permutive", "Failed to clear data", error) } } } ``` ```kotlin theme={"dark"} class UserManager(private val permutive: Permutive) { // User starts as guest (anonymous) fun onAppLaunch() { // SDK automatically creates anonymous ID } // User logs in or signs up fun onUserAuthenticated(userId: String, email: String) { val emailHash = hashEmail(email) // Permutive merges guest data with authenticated profile permutive.setIdentity( listOf( Alias.create("email_sha256", emailHash, priority = 0), Alias.create("internal_id", userId, priority = 1) ) ) } } ``` ## Expiry Aliases can expire automatically, useful for GDPR compliance, ad IDs, and session identifiers. ```kotlin theme={"dark"} // Never expire (default) Alias.create(tag = "email_sha256", identity = emailHash, expiry = Alias.NEVER_EXPIRE) // Expire in 30 days val expiry30Days = Calendar.getInstance().apply { add(Calendar.DAY_OF_YEAR, 30) }.time Alias.create(tag = "session_id", identity = sessionId, expiry = expiry30Days) ``` ## Troubleshooting **Problem:** Setting identity doesn't seem to merge user data. **Solutions:** * Check network connectivity * Verify alias has been used in other sessions/devices * Enable debug logging: `permutive.setDeveloperMode(true)` * Look for "Identified user with aliases" in logs **Problem:** Same user appears as two different users. **Solution:** Use consistent alias tags and values. When user logs in on multiple devices, use the same alias (e.g., hashed email). **Problem:** Worried about sending personally identifiable information. **Solution:** * Always hash emails and other PII with SHA-256 * Normalize data before hashing (lowercase, trim whitespace) * Never send raw email addresses, phone numbers, or names ## Best Practices * Hash PII (especially email addresses) before setting as identity * Use meaningful tag names that describe the identifier type * Set priorities based on reliability and persistence * Use expiry dates for temporary or less reliable identifiers * Set identity as early as possible in the user journey * Test identity resolution with debug logging enabled * Send unhashed email addresses or phone numbers * Use generic tag names like "id1", "id2" * Set identity multiple times unnecessarily * Forget to handle logout scenarios * Hard-code identities (use dynamic values) * Skip priority assignment when using multiple aliases ## Related Documentation Automatic advertising ID tracking Understanding user segments React to cohort changes Solutions to common issues # Event Tracking Source: https://docs.permutive.com/sdks/mobile/android/features/event-tracking Track standalone events not associated with pages **Use PageTracker Instead for Most Cases** For most tracking scenarios, **[PageTracker](/sdks/mobile/android/features/page-tracking) is the recommended approach** as it integrates better with Permutive's standard events and provides richer insights. Use EventTracker only for specific use cases where PageTracker doesn't fit. ## When to Use Which? EventTracker should be used **only** for: * Standalone events not associated with a page or screen * Button clicks or interactions not tied to page views * Background events (app lifecycle, notifications) * Form submissions without page context * Lightweight events without engagement tracking needs Use **[PageTracker](/sdks/mobile/android/features/page-tracking)** for: * Article or content views * Screen views in your app * Any page the user navigates to * When you need engagement metrics * When you want contextual cohorts **Default to PageTracker** for most tracking needs. Most customer implementations should primarily use PageTracker with EventTracker reserved for specific cases. *** ## Basic Usage ### Creating an EventTracker ```kotlin theme={"dark"} val eventTracker = permutive.eventTracker() ``` EventTracker is lightweight and can be created anytime you need to track a standalone event. ### Tracking an Event ```kotlin Kotlin theme={"dark"} import com.permutive.android.EventProperties class MainActivity : AppCompatActivity() { private val permutive by lazy { (application as MyApplication).permutive } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val eventTracker = permutive.eventTracker() // Track a simple event settingsButton.setOnClickListener { eventTracker.track("SettingsButtonClicked") } // Track an event with properties shareButton.setOnClickListener { eventTracker.track( "ContentShared", EventProperties.from( "share_method" to "email", "content_type" to "article" ) ) } } } ``` ```java Java theme={"dark"} import com.permutive.android.EventProperties; import com.permutive.android.EventTracker; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Permutive permutive = ((MyApplication) getApplication()).getPermutive(); EventTracker eventTracker = permutive.eventTracker(); // Track a simple event settingsButton.setOnClickListener(v -> { eventTracker.track("SettingsButtonClicked"); }); // Track an event with properties shareButton.setOnClickListener(v -> { EventProperties properties = new EventProperties.Builder() .with("share_method", "email") .with("content_type", "article") .build(); eventTracker.track("ContentShared", properties); }); } } ``` *** ## Valid Use Cases ```kotlin theme={"dark"} class MyApplication : Application() { override fun onCreate() { super.onCreate() val eventTracker = permutive.eventTracker() eventTracker.track( "AppLaunched", EventProperties.from( "app_version" to BuildConfig.VERSION_NAME, "is_first_launch" to isFirstLaunch() ) ) } } ``` ```kotlin theme={"dark"} class SyncWorker : Worker(context, params) { override fun doWork(): Result { val eventTracker = permutive.eventTracker() eventTracker.track( "DataSynced", EventProperties.from( "items_synced" to 42, "sync_duration_ms" to 1250 ) ) return Result.success() } } ``` ```kotlin theme={"dark"} // In a dialog or overlay that's not a full page class RatingDialog : DialogFragment() { fun onRatingSubmitted(rating: Int) { val eventTracker = permutive.eventTracker() eventTracker.track( "AppRated", EventProperties.from( "rating" to rating, "prompt_location" to "after_checkout" ) ) } } ``` *** ## Invalid Use Cases (Use PageTracker Instead) ### ❌ Don't: Track Screen Views with EventTracker ```kotlin theme={"dark"} // ❌ WRONG - This should use PageTracker! class ArticleActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val eventTracker = permutive.eventTracker() eventTracker.track("ArticleViewed") // Use PageTracker instead! } } ``` ### ✅ Do: Use PageTracker for Screen Views ```kotlin theme={"dark"} // ✅ CORRECT - Use PageTracker for pages/screens class ArticleActivity : AppCompatActivity() { private lateinit var pageTracker: PageTracker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) pageTracker = permutive.trackPage( title = "Article Title", url = Uri.parse("app://article/123") ) } override fun onDestroy() { super.onDestroy() pageTracker.close() } } ``` *** ## Event Properties Events can include structured properties. See [Event Properties Guide](/sdks/mobile/android/core-concepts/event-properties) for complete documentation. ### Example ```kotlin theme={"dark"} eventTracker.track( "PurchaseCompleted", EventProperties.from( "order_id" to "ORD-12345", "total_amount" to 99.99, "currency" to "USD", "item_count" to 3, "payment_method" to "credit_card", "shipping_method" to "express" ) ) ``` *** ## Troubleshooting ### Events Not Accepted **Problem:** Events showing as rejected in logs. **Cause:** Event schema doesn't match dashboard configuration. **Solution:** 1. Verify event name matches dashboard (case-sensitive) 2. Check property names and types match schema 3. See [Common Errors](../troubleshooting/common-errors.mdx#schema-validation-failed) ### Should I Use EventTracker or PageTracker? **Ask yourself:** * Is this a page or screen the user is viewing? → **Use PageTracker** * Do I need to track engagement time or scroll depth? → **Use PageTracker** * Do I want contextual cohorts for this content? → **Use PageTracker** * Is this a standalone action/event? → **Use EventTracker** **When in doubt, use PageTracker.** *** ## Comparison: EventTracker vs PageTracker | Feature | EventTracker | PageTracker | | ------------------------ | ------------------- | ------------------ | | **Use Case** | Standalone events | Page/screen views | | **Engagement Tracking** | ❌ No | ✅ Yes (automatic) | | **Scroll Depth** | ❌ No | ✅ Yes | | **Contextual Cohorts** | ❌ No | ✅ Yes (with URLs) | | **Platform Integration** | Basic | ✅ Full integration | | **Lifecycle Management** | None | pause/resume/close | | **Insights Quality** | Basic | ✅ Rich | | **Recommendation** | Specific cases only | **Primary method** | *** ## Best Practices * Use EventTracker for truly standalone events * Use EventTracker for background/system events * Prefer PageTracker for user-facing content * Include relevant event properties * Follow your event schema exactly * Use EventTracker for page/screen views * Use EventTracker when PageTracker is more appropriate * Create events without checking schema * Track PII in event properties *** ## Related Documentation Recommended primary method Add structured data to events Get started with the SDK Track video content Solve common issues *** ## API Reference For complete API documentation, see the [Javadocs](https://sdk-docs.permutive.com/index.html). ### EventTracker Interface * `track(eventName: String)` - Track event without properties * `track(eventName: String, properties: EventProperties?)` - Track event with properties ### Creating EventTracker * `Permutive.eventTracker()` - Create an EventTracker instance # Page Tracking Source: https://docs.permutive.com/sdks/mobile/android/features/page-tracking Track pages and screens with automatic engagement metrics **PageTracker is the recommended approach** for tracking user interactions in the Permutive Android SDK. It provides automatic Pageview event tracking, engagement metrics, and seamless integration with Permutive's insights platform. ## Why Use PageTracker? PageTracker is the primary tracking method because it: * **Integrates with standard events** - Works seamlessly with Permutive's event schema * **Tracks engagement automatically** - Measures time spent and scroll depth * **Enables contextual cohorts** - Supports content-based targeting when URLs are provided * **Provides better insights** - Delivers richer data to the Permutive platform * **Associates related events** - Groups events with page context Default to PageTracker for tracking screens and content views in your app. Use EventTracker only for standalone events not associated with a page or screen. *** ## Basic Usage ### Creating a PageTracker A PageTracker object should be created when the user starts viewing a page/screen, and closed when viewing finishes. ```kotlin Kotlin theme={"dark"} import androidx.appcompat.app.AppCompatActivity import android.net.Uri import android.os.Bundle import com.permutive.android.EventProperties import com.permutive.android.PageTracker class ArticleActivity : AppCompatActivity() { private val permutive by lazy { (application as MyApplication).permutive } private lateinit var pageTracker: PageTracker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_article) // Create the PageTracker with properties pageTracker = permutive.trackPage( title = "Article Title", url = Uri.parse("https://www.example.com/article"), referrer = Uri.parse("https://www.example.com/home"), eventProperties = EventProperties.from( "article_id" to 12345, "category" to "technology", "author" to "John Doe" ) ) } override fun onPause() { super.onPause() pageTracker.pause() } override fun onResume() { super.onResume() pageTracker.resume() } override fun onDestroy() { super.onDestroy() pageTracker.close() } } ``` ```java Java theme={"dark"} import androidx.appcompat.app.AppCompatActivity; import android.net.Uri; import android.os.Bundle; import com.permutive.android.EventProperties; import com.permutive.android.PageTracker; public class ArticleActivity extends AppCompatActivity { private PageTracker pageTracker; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_article); Permutive permutive = ((MyApplication) getApplication()).getPermutive(); // Create the PageTracker with properties EventProperties properties = new EventProperties.Builder() .with("article_id", 12345) .with("category", "technology") .with("author", "John Doe") .build(); pageTracker = permutive.trackPage( properties, "Article Title", Uri.parse("https://www.example.com/article"), Uri.parse("https://www.example.com/home") ); } @Override protected void onPause() { super.onPause(); pageTracker.pause(); } @Override protected void onResume() { super.onResume(); pageTracker.resume(); } @Override protected void onDestroy() { super.onDestroy(); pageTracker.close(); } } ``` *** ## Lifecycle Management ### When to Create Create the PageTracker in `onCreate()`: ```kotlin theme={"dark"} override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) pageTracker = permutive.trackPage(...) // Create early loadContent() // Then load your content } ``` ### When to Pause/Resume Call `pause()` and `resume()` to track engagement time accurately: ```kotlin theme={"dark"} override fun onPause() { super.onPause() // User can't see the page - stop tracking time pageTracker.pause() } override fun onResume() { super.onResume() // User can see the page again - resume tracking time pageTracker.resume() } ``` ### When to Close Always close the PageTracker when done: ```kotlin theme={"dark"} override fun onDestroy() { super.onDestroy() // Page viewing finished - send completion event pageTracker.close() } ``` > ⚠️ Important: Always Close > > Failing to close PageTracker can lead to memory leaks and inaccurate engagement metrics. *** ## Tracking Engagement ### Tracking Time Spent PageTracker automatically tracks time spent by using `pause()` and `resume()`: ```kotlin theme={"dark"} class ArticleActivity : AppCompatActivity() { private lateinit var pageTracker: PageTracker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) pageTracker = permutive.trackPage(...) // Time tracking starts automatically } override fun onPause() { super.onPause() pageTracker.pause() // Pause time tracking } override fun onResume() { super.onResume() pageTracker.resume() // Resume time tracking } override fun onDestroy() { super.onDestroy() pageTracker.close() // Final engaged time sent } } ``` The engaged time is sent in the PageviewComplete event when you call `close()`. ### Tracking Scroll Depth Use `updatePercentageViewed()` to track how much content the user has read: ```kotlin Kotlin theme={"dark"} class ArticleActivity : AppCompatActivity() { private lateinit var pageTracker: PageTracker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_article) pageTracker = permutive.trackPage(...) // Add scroll listener scrollView.viewTreeObserver.addOnScrollChangedListener { val contentHeight = scrollView.getChildAt(0).height val visibleHeight = scrollView.scrollY.toFloat() + scrollView.height val percentageViewed = visibleHeight / contentHeight pageTracker.updatePercentageViewed(percentageViewed) } } } ``` ```java Java theme={"dark"} public class ArticleActivity extends AppCompatActivity { private PageTracker pageTracker; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_article); pageTracker = permutive.trackPage(...); ScrollView scrollView = findViewById(R.id.scrollView); // Add scroll listener scrollView.getViewTreeObserver().addOnScrollChangedListener( new ViewTreeObserver.OnScrollChangedListener() { @Override public void onScrollChanged() { float contentHeight = scrollView.getChildAt(0).getHeight(); float visibleHeight = scrollView.getScrollY() + scrollView.getHeight(); float percentageViewed = visibleHeight / contentHeight; pageTracker.updatePercentageViewed(percentageViewed); } } ); } } ``` **RecyclerView Example:** ```kotlin theme={"dark"} recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { val layoutManager = recyclerView.layoutManager as LinearLayoutManager val totalItems = layoutManager.itemCount val lastVisibleItem = layoutManager.findLastVisibleItemPosition() val percentageViewed = (lastVisibleItem + 1).toFloat() / totalItems pageTracker.updatePercentageViewed(percentageViewed) } }) ``` *** ## Tracking Additional Events Track other events associated with the page using PageTracker's `track()` methods: ```kotlin Kotlin theme={"dark"} class ArticleActivity : AppCompatActivity() { private lateinit var pageTracker: PageTracker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) pageTracker = permutive.trackPage(...) // Track page-related events shareButton.setOnClickListener { pageTracker.track( "ArticleShared", EventProperties.from( "share_method" to "twitter" ) ) } commentButton.setOnClickListener { pageTracker.track("CommentAdded") } } } ``` ```java Java theme={"dark"} public class ArticleActivity extends AppCompatActivity { private PageTracker pageTracker; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); pageTracker = permutive.trackPage(...); // Track page-related events shareButton.setOnClickListener(v -> { EventProperties props = new EventProperties.Builder() .with("share_method", "twitter") .build(); pageTracker.track("ArticleShared", props); }); commentButton.setOnClickListener(v -> { pageTracker.track("CommentAdded"); }); } } ``` *** ## URL Schemes ### Web Content For web content, use full URLs: ```kotlin theme={"dark"} pageTracker = permutive.trackPage( title = "Article Title", url = Uri.parse("https://www.example.com/articles/tech-news"), referrer = Uri.parse("https://www.example.com/home") ) ``` ### App Screens For native app screens, use custom URI schemes: ```kotlin theme={"dark"} // Option 1: app:// scheme pageTracker = permutive.trackPage( title = "Home Screen", url = Uri.parse("app://home") ) // Option 2: your-app:// scheme pageTracker = permutive.trackPage( title = "Profile Screen", url = Uri.parse("myapp://profile/user/12345") ) // Option 3: https with app path pageTracker = permutive.trackPage( title = "Settings", url = Uri.parse("https://app.example.com/settings") ) ``` > 💡 URLs Enable Contextual Cohorts > > When you provide URLs, Permutive can generate contextual cohorts based on content analysis. This works for publicly accessible web URLs. *** ## Events Generated PageTracker automatically generates these events: ### Pageview Event Tracked when PageTracker is created: ``` Event: Pageview Properties: - title: "Article Title" - url: "https://www.example.com/article" - referrer: "https://www.example.com/home" - [your custom properties] ``` ### PageviewComplete Event Tracked when PageTracker is closed: ``` Event: PageviewComplete Properties: - engaged_time: 45.2 (seconds) - completion: 0.75 (75% viewed) - [your custom properties] ``` ### PageviewEngagement Events Periodic engagement events (if configured): ``` Event: PageviewEngagement Properties: - engaged_time: 15.0 (seconds) - [your custom properties] ``` *** ## Use Cases ```kotlin theme={"dark"} class ArticleActivity : AppCompatActivity() { private lateinit var pageTracker: PageTracker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val article = intent.getParcelableExtra
("article") pageTracker = permutive.trackPage( title = article.title, url = Uri.parse(article.url), referrer = Uri.parse(intent.getStringExtra("referrer")), eventProperties = EventProperties.from( "article_id" to article.id, "category" to article.category, "author" to article.author, "published_at" to article.publishedDate, "word_count" to article.wordCount ) ) setupScrollTracking() } } ``` ```kotlin theme={"dark"} class ProductActivity : AppCompatActivity() { private lateinit var pageTracker: PageTracker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val product = viewModel.product.value pageTracker = permutive.trackPage( title = product.name, url = Uri.parse("app://product/${product.id}"), eventProperties = EventProperties.from( "product_id" to product.id, "product_name" to product.name, "category" to product.category, "price" to product.price, "in_stock" to product.inStock ) ) addToCartButton.setOnClickListener { pageTracker.track( "ProductAddedToCart", EventProperties.from( "product_id" to product.id, "quantity" to 1 ) ) } } } ``` For video content itself, use [MediaTracker](/sdks/mobile/android/features/video-tracking). For video details/description pages: ```kotlin theme={"dark"} class VideoDetailsActivity : AppCompatActivity() { private lateinit var pageTracker: PageTracker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) pageTracker = permutive.trackPage( title = video.title, url = Uri.parse("app://video/${video.id}/details"), eventProperties = EventProperties.from( "video_id" to video.id, "video_title" to video.title, "genre" to video.genre, "duration_seconds" to video.durationSeconds ) ) playButton.setOnClickListener { pageTracker.track("VideoPlayButtonClicked") startVideoPlayer() } } } ``` *** ## When to Use PageTracker vs EventTracker ### Use PageTracker When: * ✅ Tracking article/content views * ✅ Tracking screen views in your app * ✅ Measuring time spent on a page/screen * ✅ Tracking scroll depth * ✅ You want contextual cohorts * ✅ Events are associated with a page context ### Use EventTracker Only When: * ⚠️ Tracking standalone events (button clicks not tied to pages) * ⚠️ Tracking background events * ⚠️ Events have no page context > 💡 Default to PageTracker > > Most customer implementations should primarily use PageTracker. EventTracker is for specific use cases. See [Event Tracking](/sdks/mobile/android/features/event-tracking) for EventTracker documentation. *** ## Troubleshooting **Problem:** Engaged time is always 0. **Cause:** Not calling `pause()` and `resume()`. **Solution:** Implement lifecycle callbacks: ```kotlin theme={"dark"} override fun onPause() { super.onPause() pageTracker.pause() // IMPORTANT } override fun onResume() { super.onResume() pageTracker.resume() // IMPORTANT } ``` **Problem:** App memory usage grows over time. **Cause:** Not closing PageTracker. **Solution:** Always close in `onDestroy()`: ```kotlin theme={"dark"} override fun onDestroy() { super.onDestroy() pageTracker.close() // IMPORTANT } ``` **Problem:** No contextual cohorts for pages. **Causes:** 1. Feature not enabled 2. Not providing URLs 3. URLs not publicly accessible **Solutions:** 1. Contact Customer Success Manager to enable 2. Always provide URLs in `trackPage()` 3. Ensure URLs are publicly accessible for web content See [Contextual Data](/sdks/mobile/android/core-concepts/contextual-data) for details. **Problem:** Pageview events not tracked. **Solutions:** 1. Enable debug logging: `permutive.setDeveloperMode(true)` 2. Check logs for "Published events with names (Pageview)" 3. Verify event properties match your schema 4. See [Verification Guide](/sdks/mobile/android/getting-started/verification) *** ## Best Practices * Create PageTracker in `onCreate()` * Call `pause()` in `onPause()` * Call `resume()` in `onResume()` * Call `close()` in `onDestroy()` * Provide URLs when possible for contextual cohorts * Track scroll depth for content pages * Use PageTracker for all page/screen views * Forget to close PageTracker * Create multiple PageTrackers for the same page * Skip pause/resume calls * Use EventTracker when PageTracker is more appropriate *** ## Related Documentation Get started with the SDK Add structured data to events Content-based segmentation For standalone events For video content Verify your integration *** ## API Reference For complete API documentation, see the [Javadocs](https://sdk-docs.permutive.com/index.html). ### PageTracker Interface * `pause()` - Pause engagement tracking * `resume()` - Resume engagement tracking * `updatePercentageViewed(percentage: Float)` - Update scroll depth * `track(eventName: String)` - Track event with this page context * `track(eventName: String, properties: EventProperties?)` - Track event with properties * `close()` - Complete page tracking and send PageviewComplete event # Triggers Provider Source: https://docs.permutive.com/sdks/mobile/android/features/triggers-provider React to real-time cohort membership changes The `TriggersProvider` enables real-time reactive programming patterns by allowing you to observe cohort membership changes and trigger actions when users enter or exit specific cohorts. This is powerful for personalizing user experiences, A/B testing, and dynamic content delivery. ## Overview The Triggers Provider allows you to: * **React to cohort changes in real-time** - Execute code when users enter or exit cohorts * **Observe activations** - Monitor when users qualify for specific ad platform activations * **Build personalized experiences** - Show/hide content based on cohort membership * **Implement A/B tests** - Dynamically segment users and track their behavior ## Creating a TriggersProvider Create a `TriggersProvider` instance from your Permutive SDK instance: ```kotlin Kotlin theme={"dark"} import com.permutive.android.TriggersProvider class MyActivity : AppCompatActivity() { private val permutive by lazy { (application as MyApplication).permutive } private val triggersProvider: TriggersProvider by lazy { permutive.triggersProvider() } // Use the triggers provider... } ``` ```java Java theme={"dark"} import com.permutive.android.TriggersProvider; public class MyActivity extends AppCompatActivity { private TriggersProvider triggersProvider; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Permutive permutive = ((MyApplication) getApplication()).getPermutive(); triggersProvider = permutive.triggersProvider(); } } ``` > 💡 Lifecycle Management > > You can create any number of `TriggersProvider` instances. They're lightweight and don't maintain heavy state. *** ## Use Cases ### 1. Observing Cohort Membership Monitor when a user is in a specific cohort: ```kotlin Kotlin theme={"dark"} import com.permutive.android.TriggerAction class PersonalizedActivity : AppCompatActivity() { private var premiumContentTrigger: TriggerAction? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_personalized) val triggersProvider = permutive.triggersProvider() // Monitor if user is in the "premium_readers" cohort premiumContentTrigger = triggersProvider.triggerAction("premium_readers") { isInCohort -> if (isInCohort) { showPremiumContent() } else { showStandardContent() } } } override fun onDestroy() { super.onDestroy() // Clean up the trigger when the activity is destroyed premiumContentTrigger?.close() } private fun showPremiumContent() { // Update UI to show premium features binding.premiumBanner.visibility = View.VISIBLE binding.subscriptionPrompt.visibility = View.GONE } private fun showStandardContent() { // Update UI for standard users binding.premiumBanner.visibility = View.GONE binding.subscriptionPrompt.visibility = View.VISIBLE } } ``` ```java Java theme={"dark"} import com.permutive.android.TriggerAction; import com.permutive.android.internal.Method; public class PersonalizedActivity extends AppCompatActivity { private TriggerAction premiumContentTrigger; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_personalized); TriggersProvider triggersProvider = permutive.triggersProvider(); // Monitor if user is in the "premium_readers" cohort premiumContentTrigger = triggersProvider.triggerAction( "premium_readers", new Method() { @Override public void invoke(Boolean isInCohort) { if (isInCohort) { showPremiumContent(); } else { showStandardContent(); } } } ); } @Override protected void onDestroy() { super.onDestroy(); if (premiumContentTrigger != null) { premiumContentTrigger.close(); } } private void showPremiumContent() { // Update UI to show premium features } private void showStandardContent() { // Update UI for standard users } } ``` *** ### 2. Observing Multiple Cohorts React to multiple cohort memberships: #### Kotlin ```kotlin theme={"dark"} class ContentRecommendationActivity : AppCompatActivity() { private val triggers = mutableListOf() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val triggersProvider = permutive.triggersProvider() // Sports enthusiasts triggers.add( triggersProvider.triggerAction("sports_enthusiast") { isInCohort -> if (isInCohort) addRecommendation("Sports") } ) // Tech readers triggers.add( triggersProvider.triggerAction("tech_reader") { isInCohort -> if (isInCohort) addRecommendation("Technology") } ) // Business professionals triggers.add( triggersProvider.triggerAction("business_professional") { isInCohort -> if (isInCohort) addRecommendation("Business") } ) } override fun onDestroy() { super.onDestroy() triggers.forEach { it.close() } triggers.clear() } private fun addRecommendation(category: String) { // Update UI with personalized recommendations } } ``` *** ### 3. Observing Activations for Ad Platforms Monitor cohorts activated for specific ad platforms: #### Kotlin ```kotlin theme={"dark"} class AdTargetingActivity : AppCompatActivity() { private var gamActivationTrigger: TriggerAction? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val triggersProvider = permutive.triggersProvider() // Monitor Google Ad Manager activations gamActivationTrigger = triggersProvider.cohortActivations("dfp") { cohorts -> Log.d("Permutive", "User has ${cohorts.size} active GAM cohorts: $cohorts") updateAdTargeting(cohorts) } } override fun onDestroy() { super.onDestroy() gamActivationTrigger?.close() } private fun updateAdTargeting(cohorts: List) { // Use cohorts for ad targeting } } ``` #### Available Activation Types | Activation Type | Description | | -------------------------------- | ------------------------------------------------------------------- | | `"dfp"` | Google Ad Manager (formerly DoubleClick for Publishers) activations | | `"appnexus_adserver"` | Xandr/AppNexus ad server activations | | `"freewheel"` | Freewheel activations | | `"dfp_contextual"` | Google Ad Manager contextual cohorts | | `"appnexus_adserver_contextual"` | Xandr/AppNexus contextual cohorts | *** ### 4. A/B Testing Based on Cohorts Implement feature flags or A/B tests using cohort membership: #### Kotlin ```kotlin theme={"dark"} class ExperimentActivity : AppCompatActivity() { private var experimentTrigger: TriggerAction? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val triggersProvider = permutive.triggersProvider() // A/B test: Show new checkout flow to test cohort experimentTrigger = triggersProvider.triggerAction("checkout_experiment_v2") { isInTestGroup -> if (isInTestGroup) { showNewCheckoutFlow() } else { showStandardCheckoutFlow() } } } override fun onDestroy() { super.onDestroy() experimentTrigger?.close() } } ``` *** ## TriggerAction Lifecycle The `TriggerAction` interface represents an active trigger subscription. It's crucial to manage its lifecycle properly to prevent memory leaks. ### Key Points 1. **Create** triggers when you need to start observing 2. **Close** triggers when you're done (typically in `onDestroy()`) 3. **Store** references if you need to close them later 4. **Don't recreate** unnecessarily - triggers survive configuration changes if managed properly ### Best Practices #### ✅ Good: Proper Lifecycle Management ```kotlin theme={"dark"} class MyActivity : AppCompatActivity() { private var trigger: TriggerAction? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) trigger = triggersProvider.triggerAction("cohort_id") { /* ... */ } } override fun onDestroy() { super.onDestroy() trigger?.close() } } ``` #### ✅ Good: Using ViewModel for Configuration Changes ```kotlin theme={"dark"} class MyViewModel : ViewModel() { private var trigger: TriggerAction? = null fun observeCohort(triggersProvider: TriggersProvider) { if (trigger == null) { trigger = triggersProvider.triggerAction("cohort_id") { /* ... */ } } } override fun onCleared() { super.onCleared() trigger?.close() } } ``` #### ❌ Bad: Not Closing Triggers ```kotlin theme={"dark"} class MyActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Creates a new trigger every time, never closes the old ones triggersProvider.triggerAction("cohort_id") { /* ... */ } } // Memory leak! Triggers never closed } ``` *** ## Thread Safety The `TriggersProvider` and `TriggerAction` are thread-safe. Callbacks are delivered on a background thread, so if you need to update UI, use appropriate threading mechanisms: ```kotlin Kotlin theme={"dark"} private var trigger: TriggerAction? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) trigger = triggersProvider.triggerAction("cohort_id") { isInCohort -> // Callback is on background thread lifecycleScope.launch(Dispatchers.Main) { // Update UI on main thread if (isInCohort) { textView.text = "User is in cohort" } } } } ``` ```java Java theme={"dark"} private final Handler mainHandler = new Handler(Looper.getMainLooper()); private TriggerAction trigger; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); trigger = triggersProvider.triggerAction("cohort_id", new Method() { @Override public void invoke(Boolean isInCohort) { // Callback is on background thread mainHandler.post(() -> { // Update UI on main thread if (isInCohort) { textView.setText("User is in cohort"); } }); } }); } ``` *** ## Advanced Patterns ### Combining Multiple Cohorts Trigger actions only when user is in multiple cohorts: ```kotlin theme={"dark"} class AdvancedTargetingActivity : AppCompatActivity() { private var isPremium = false private var isSportsReader = false private val triggers = mutableListOf() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val triggersProvider = permutive.triggersProvider() triggers.add( triggersProvider.triggerAction("premium_subscriber") { inCohort -> isPremium = inCohort updateContent() } ) triggers.add( triggersProvider.triggerAction("sports_enthusiast") { inCohort -> isSportsReader = inCohort updateContent() } ) } private fun updateContent() { when { isPremium && isSportsReader -> showPremiumSportsContent() isPremium -> showPremiumGeneralContent() isSportsReader -> showFreeSportsContent() else -> showGeneralContent() } } override fun onDestroy() { super.onDestroy() triggers.forEach { it.close() } triggers.clear() } } ``` *** ## Deprecated APIs ### Legacy Segment-Based Triggers The following methods are deprecated as they only support integer-based segment IDs: ```kotlin theme={"dark"} @Deprecated("Use triggerAction(cohortId: String, callback: Method)") fun triggerAction(queryId: Int, callback: Method): TriggerAction @Deprecated("Use cohortActivations") fun querySegments(callback: Method>): TriggerAction @Deprecated("Use cohortActivations") fun queryReactions(reaction: String, callback: Method>): TriggerAction ``` **Migration Guide:** Old (Deprecated): ```kotlin theme={"dark"} triggersProvider.triggerAction(12345) { result: Boolean -> // Handle result } ``` New: ```kotlin theme={"dark"} triggersProvider.triggerAction("cohort_id_string") { isInCohort -> // Handle result } ``` > 📘 Why the Change? > > The SDK now supports Classification Model cohorts which use string IDs. The new APIs support both traditional integer-based cohorts and newer string-based cohorts seamlessly. *** ## Troubleshooting **Problem:** Your trigger callback is never invoked. **Possible Causes:** 1. SDK hasn't finished initializing 2. Cohort ID is incorrect 3. User hasn't generated enough events to be segmented **Solutions:** * Enable debug logging: `permutive.setDeveloperMode(true)` * Check logs for cohort updates * Verify cohort ID matches your dashboard configuration * Ensure events are being tracked (see [Verification Guide](/sdks/mobile/android/getting-started/verification)) **Problem:** App memory usage grows over time. **Cause:** `TriggerAction` instances not being closed. **Solution:** Always call `close()` on triggers in appropriate lifecycle methods (usually `onDestroy()` or `onCleared()`). **Problem:** Crash when trying to update UI from callback. **Cause:** Callbacks execute on background threads. **Solution:** Use `Handler`, coroutines, or `runOnUiThread()` to update UI (see Thread Safety section above). *** ## Best Practices * Close all triggers when done to prevent memory leaks * Handle threading appropriately when updating UI * Use descriptive cohort IDs that match your dashboard * Store trigger references if you need to close them later * Test with debug logging enabled * Create triggers and never close them * Update UI directly from callbacks without switching threads * Create multiple triggers for the same cohort unnecessarily * Assume callbacks will fire immediately (SDK needs time to initialize) *** ## Example: Complete Implementation Here's a complete example showing proper implementation: ```kotlin theme={"dark"} import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import com.permutive.android.TriggerAction import com.permutive.android.TriggersProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch class PersonalizedHomeActivity : AppCompatActivity() { private val permutive by lazy { (application as MyApplication).permutive } private val triggersProvider by lazy { permutive.triggersProvider() } private val activeTriggers = mutableListOf() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_personalized_home) setupCohortTriggers() } private fun setupCohortTriggers() { // Premium content trigger activeTriggers.add( triggersProvider.triggerAction("premium_subscriber") { isSubscriber -> lifecycleScope.launch(Dispatchers.Main) { updatePremiumFeatures(isSubscriber) } } ) // Sports content trigger activeTriggers.add( triggersProvider.triggerAction("sports_enthusiast") { isSportsReader -> lifecycleScope.launch(Dispatchers.Main) { updateSportsRecommendations(isSportsReader) } } ) // Ad activations trigger activeTriggers.add( triggersProvider.cohortActivations("dfp") { cohorts -> lifecycleScope.launch(Dispatchers.Main) { updateAdTargeting(cohorts) } } ) } private fun updatePremiumFeatures(isPremium: Boolean) { binding.premiumBadge.visibility = if (isPremium) View.VISIBLE else View.GONE binding.upgradePrompt.visibility = if (isPremium) View.GONE else View.VISIBLE } private fun updateSportsRecommendations(showSports: Boolean) { if (showSports) { binding.sportsSection.visibility = View.VISIBLE loadSportsContent() } else { binding.sportsSection.visibility = View.GONE } } private fun updateAdTargeting(cohorts: List) { Log.d("Permutive", "Updated ad targeting with ${cohorts.size} cohorts") // Ad targeting logic here } private fun loadSportsContent() { // Load sports-specific content } override fun onDestroy() { super.onDestroy() // Clean up all triggers activeTriggers.forEach { it.close() } activeTriggers.clear() } } ``` *** ## Related Documentation Understanding how cohorts work How events feed into segmentation Tracking users across sessions Content-based segmentation *** ## API Reference For complete API documentation, see the [Javadocs](https://sdk-docs.permutive.com/index.html). ### Key Interfaces * `TriggersProvider` - Factory for creating trigger subscriptions * `TriggerAction` - Represents an active trigger subscription * `Method` - Callback interface for Java compatibility # Video Ad Tracking Source: https://docs.permutive.com/sdks/mobile/android/features/video-ad-tracking Track video advertisements, clicks, and completions The `AdTracker` API allows you to track video advertisement events. Use this to measure ad views, engagement, clicks, and completions within video content. **Android is the only Permutive SDK with a native ad tracker.** iOS / tvOS and Web CTV publishers emit ad events manually via `track(...)`. The typed `AdTracker` lifecycle below is unique to Android — use it instead of generic `track()` calls so the emitted events match the canonical schema automatically. ## Overview `AdTracker` provides comprehensive video ad tracking: * **Automatic lifecycle tracking** - Tracks VideoAdView and VideoAdCompletion events * **Engagement metrics** - Measures time spent watching ads * **Click tracking** - Tracks ad interactions * **Custom events** - Track custom ad-related events * **Shared context** - Can share View ID with parent MediaTracker *** ## Creating an AdTracker AdTracker can be created in two ways: ### 1. From MediaTracker (Recommended) Use this when the ad is related to video content currently being viewed. The AdTracker will share the same View ID with the MediaTracker: ```kotlin theme={"dark"} val videoTracker = permutive.trackVideoView(...) val adTracker = videoTracker.trackAdView( durationMs = 15000, // 15 second ad adProperties = AdTracker.AdProperties( title = "Product Advertisement", durationInSeconds = 15, isMuted = false, campaignId = "camp_123", creativeId = "creative_456" ) ) ``` ### 2. Standalone AdTracker Use this for standalone video ads not associated with specific video content: ```kotlin theme={"dark"} val adTracker = permutive.trackVideoAdView( durationMs = 30000, // 30 second ad adProperties = AdTracker.AdProperties( title = "Standalone Ad", durationInSeconds = 30, isMuted = false, campaignId = "camp_789", creativeId = "creative_012" ) ) ``` *** ## Ad Events AdTracker automatically generates these events: ### 1. VideoAdView Event Tracked when AdTracker is created: ``` Event: VideoAdView Properties: - [ad properties] - [custom properties] ``` ### 2. VideoAdCompletion Event Tracked when `completion()` is called: ``` Event: VideoAdCompletion Properties: - engaged_time: Time spent watching ad (seconds) - completion: Percentage of ad viewed (0.0 - 1.0) - [ad properties] - [custom properties] ``` ### 3. VideoAdClicked Event Tracked when `clicked()` is called: ``` Event: VideoAdClicked Properties: - [ad properties] - [custom properties] ``` *** ## AdTracker Methods ### play() Call when the ad starts playing. AdTracker initializes in a paused state, so call this immediately if the ad auto-plays: ```kotlin theme={"dark"} adTracker.play() ``` ### play(positionMs) Call when playback position changes (e.g., seeking): ```kotlin theme={"dark"} adTracker.play(positionMs = 5000) // Resume at 5 seconds ``` ### pause() Call when the ad is paused or buffering: ```kotlin theme={"dark"} adTracker.pause() ``` ### clicked() Call when the user clicks on the ad. This tracks a **VideoAdClicked** event: ```kotlin theme={"dark"} adTracker.clicked() ``` ### completion() Call when the ad is no longer being shown. This tracks a **VideoAdCompletion** event with engagement properties: ```kotlin theme={"dark"} adTracker.completion() ``` > ⚠️ Important > > Once `completion()` is called, the AdTracker is closed and subsequent method calls will have no effect. ### Custom Events Track custom ad-related events: ```kotlin theme={"dark"} // Track without additional properties adTracker.trackWithAdProperties("AdMuted") // Track with additional properties adTracker.trackWithAdProperties( "AdQualityChanged", EventProperties.from( "from_quality" to "480p", "to_quality" to "720p" ) ) ``` *** ## Ad Properties ### Standard Ad Properties ```kotlin theme={"dark"} AdTracker.AdProperties( title = "Ad Title", durationInSeconds = 30, isMuted = false, campaignId = "campaign_abc123", creativeId = "creative_xyz789" ) ``` ### Property Reference | Property | Type | Description | | --------------------- | ------- | ------------------------ | | **title** | String | Ad title or name | | **durationInSeconds** | Int | Ad duration in seconds | | **isMuted** | Boolean | Whether ad started muted | | **campaignId** | String | Ad campaign identifier | | **creativeId** | String | Ad creative identifier | All properties are passed to every event tracked through the AdTracker. *** ## Complete Example ### Kotlin - Pre-Roll Ad ```kotlin theme={"dark"} import com.permutive.android.MediaTracker import com.permutive.android.AdTracker import com.permutive.android.EventProperties class VideoFragment : Fragment() { private val permutive by lazy { (requireActivity().application as MyApplication).permutive } private lateinit var videoTracker: MediaTracker private lateinit var adTracker: AdTracker private lateinit var exoPlayer: ExoPlayer override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Create video tracker videoTracker = permutive.trackVideoView( durationMilliseconds = 180000, // 3 minute video videoProperties = MediaTracker.VideoProperties( title = "Main Video Content" ) ) // Create ad tracker for pre-roll ad adTracker = videoTracker.trackAdView( durationMs = 15000, // 15 second ad adProperties = AdTracker.AdProperties( title = "Pre-Roll Advertisement", durationInSeconds = 15, isMuted = false, campaignId = "preroll_camp_123", creativeId = "creative_456" ) ) setupAdPlayer() return inflater.inflate(R.layout.fragment_video, container, false) } private fun setupAdPlayer() { exoPlayer = ExoPlayer.Builder(requireContext()).build() // Load ad content val adUri = Uri.parse("https://example.com/ad.mp4") val adMediaItem = MediaItem.fromUri(adUri) exoPlayer.setMediaItem(adMediaItem) exoPlayer.prepare() // Track ad playback exoPlayer.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { if (isPlaying) { adTracker.play() } else { adTracker.pause() } } override fun onPlaybackStateChanged(state: Int) { when (state) { Player.STATE_ENDED -> { // Ad finished adTracker.completion() // Now load main video content loadMainVideoContent() } Player.STATE_BUFFERING -> { adTracker.pause() } } } }) // Track ad clicks playerView.setOnClickListener { adTracker.clicked() openAdLandingPage() } // Start ad playback exoPlayer.play() } private fun loadMainVideoContent() { // Load main video val videoUri = Uri.parse("https://example.com/video.mp4") val videoMediaItem = MediaItem.fromUri(videoUri) exoPlayer.setMediaItem(videoMediaItem) exoPlayer.prepare() exoPlayer.play() // Now track main video with videoTracker videoTracker.play() } private fun openAdLandingPage() { val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://advertiser.com/landing")) startActivity(intent) } override fun onDestroy() { super.onDestroy() videoTracker.stop() exoPlayer.release() } } ``` ### Java - Pre-Roll Ad ```java theme={"dark"} import com.permutive.android.MediaTracker; import com.permutive.android.AdTracker; public class VideoFragment extends Fragment { private MediaTracker videoTracker; private AdTracker adTracker; private ExoPlayer exoPlayer; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Permutive permutive = ((MyApplication) requireActivity().getApplication()).getPermutive(); // Create video tracker MediaTracker.VideoProperties videoProps = new MediaTracker.VideoProperties.Builder() .title("Main Video Content") .build(); videoTracker = permutive.trackVideoView( 180000, // 3 minutes videoProps, null, null ); // Create ad tracker AdTracker.AdProperties adProps = new AdTracker.AdProperties( "Pre-Roll Advertisement", 15, // 15 seconds false, // not muted "preroll_camp_123", "creative_456" ); adTracker = videoTracker.trackAdView(15000, adProps); setupAdPlayer(); return inflater.inflate(R.layout.fragment_video, container, null); } private void setupAdPlayer() { exoPlayer = new ExoPlayer.Builder(requireContext()).build(); // Load ad Uri adUri = Uri.parse("https://example.com/ad.mp4"); MediaItem adMediaItem = MediaItem.fromUri(adUri); exoPlayer.setMediaItem(adMediaItem); exoPlayer.prepare(); // Track ad playback exoPlayer.addListener(new Player.Listener() { @Override public void onIsPlayingChanged(boolean isPlaying) { if (isPlaying) { adTracker.play(); } else { adTracker.pause(); } } @Override public void onPlaybackStateChanged(int state) { if (state == Player.STATE_ENDED) { adTracker.completion(); loadMainVideoContent(); } else if (state == Player.STATE_BUFFERING) { adTracker.pause(); } } }); // Track clicks playerView.setOnClickListener(v -> { adTracker.clicked(); openAdLandingPage(); }); exoPlayer.play(); } private void loadMainVideoContent() { Uri videoUri = Uri.parse("https://example.com/video.mp4"); MediaItem videoMediaItem = MediaItem.fromUri(videoUri); exoPlayer.setMediaItem(videoMediaItem); exoPlayer.prepare(); exoPlayer.play(); videoTracker.play(); } private void openAdLandingPage() { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://advertiser.com/landing")); startActivity(intent); } @Override public void onDestroy() { super.onDestroy(); videoTracker.stop(); exoPlayer.release(); } } ``` *** ## Use Cases Ad shown **before** video content starts: ```kotlin theme={"dark"} // Create video tracker first val videoTracker = permutive.trackVideoView(...) // Create ad tracker from video tracker val preRollAd = videoTracker.trackAdView( durationMs = 15000, adProperties = AdTracker.AdProperties( title = "Pre-Roll Ad", durationInSeconds = 15, campaignId = "preroll_123" ) ) // Play ad preRollAd.play() // ... ad plays ... preRollAd.completion() // Then play main video videoTracker.play() ``` Ad shown **during** video content: ```kotlin theme={"dark"} // Main video playing videoTracker.pause() // Create mid-roll ad val midRollAd = videoTracker.trackAdView( durationMs = 30000, adProperties = AdTracker.AdProperties( title = "Mid-Roll Ad", durationInSeconds = 30, campaignId = "midroll_456" ) ) midRollAd.play() // ... ad plays ... midRollAd.completion() // Resume main video videoTracker.play() ``` Ad **not associated** with video content: ```kotlin theme={"dark"} // Ad not associated with video content val standaloneAd = permutive.trackVideoAdView( durationMs = 30000, adProperties = AdTracker.AdProperties( title = "Standalone Video Ad", durationInSeconds = 30, campaignId = "standalone_789" ) ) standaloneAd.play() // ... ad plays ... standaloneAd.completion() ``` **Multiple ads** played in sequence: ```kotlin theme={"dark"} val ads = listOf( AdInfo("Ad 1", 15000, "camp_1", "creative_1"), AdInfo("Ad 2", 15000, "camp_2", "creative_2"), AdInfo("Ad 3", 30000, "camp_3", "creative_3") ) ads.forEach { adInfo -> val adTracker = videoTracker.trackAdView( durationMs = adInfo.duration, adProperties = AdTracker.AdProperties( title = adInfo.title, durationInSeconds = adInfo.duration / 1000, campaignId = adInfo.campaignId, creativeId = adInfo.creativeId ) ) // Play ad adTracker.play() // ... wait for completion ... adTracker.completion() } ``` *** ## Tracking Ad Interactions ### Click Tracking ```kotlin theme={"dark"} // User clicks ad adClickArea.setOnClickListener { adTracker.clicked() // Tracks VideoAdClicked event openAdvertiserUrl() } ``` ### Skip Tracking The canonical schema defines `VideoAdView`, `VideoAdEngagement`, `VideoAdCompletion`, and `VideoAdClicked` — there is no standard "skipped" event. If you want to capture skips, emit a custom event via `trackWithAdProperties(...)` and add the event name to your workspace schema: ```kotlin theme={"dark"} // User skips ad (custom event — register the name in your workspace schema) skipButton.setOnClickListener { adTracker.trackWithAdProperties( "VideoAdSkipped", EventProperties.from( "time_watched_seconds" to getWatchedTime() ) ) adTracker.completion() loadMainContent() } ``` ### Error Tracking Errors are likewise not in the standard schema — emit a custom event if you need to capture ad-load failures: ```kotlin theme={"dark"} // Ad fails to load (custom event — register the name in your workspace schema) adPlayer.setErrorListener { error -> adTracker.trackWithAdProperties( "VideoAdError", EventProperties.from( "error_code" to error.code, "error_message" to error.message ) ) adTracker.completion() } ``` *** ## Troubleshooting **Problem:** VideoAdCompletion events not appearing. **Cause:** `completion()` not called. **Solution:** Always call `completion()` when ad finishes: ```kotlin theme={"dark"} // When ad ends adTracker.completion() ``` **Problem:** Ad events have same View ID as video. **Cause:** This is expected when creating AdTracker from MediaTracker. **This is correct behavior** - it links ad to video content. For independent tracking, use standalone AdTracker: ```kotlin theme={"dark"} // Standalone (separate View ID) val adTracker = permutive.trackVideoAdView(...) ``` **Problem:** Engaged time is wrong. **Causes:** 1. Not calling `play()` when ad starts 2. Not calling `pause()` for buffering **Solution:** Track all state changes: ```kotlin theme={"dark"} player.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { if (isPlaying) adTracker.play() else adTracker.pause() } }) ``` **Problem:** Multiple VideoAdCompletion events tracked. **Cause:** Calling `completion()` multiple times. **Solution:** Call `completion()` only once. After calling it, AdTracker is closed. See [Common Errors](/sdks/mobile/android/troubleshooting/common-errors) for more troubleshooting. *** ## Best Practices * Always call `completion()` when ad finishes * Track play/pause accurately * Use `clicked()` for click tracking * Create from MediaTracker for in-content ads * Provide campaign and creative IDs * Call `play()` immediately if ad auto-plays * Forget to call `completion()` * Call methods after `completion()` * Skip play/pause lifecycle events * Use standalone tracker for in-content ads *** ## Related Documentation Track video content Custom properties Track pages Solve common issues *** ## API Reference For complete API documentation, see the [Javadocs](https://sdk-docs.permutive.com/index.html). ### AdTracker Interface * `play()` - Start/resume ad playback * `play(positionMs: Long)` - Start at specific position * `pause()` - Pause ad playback * `clicked()` - Track ad click (sends VideoAdClicked event) * `completion()` - Complete ad tracking (sends VideoAdCompletion event) * `trackWithAdProperties(eventName: String)` - Track custom event with ad properties * `trackWithAdProperties(eventName: String, customProperties: EventProperties)` - Track custom event with ad and custom properties ### Creating AdTracker * `MediaTracker.trackAdView(durationMs: Long, adProperties: AdTracker.AdProperties?)` - Create from MediaTracker (shares View ID) * `Permutive.trackVideoAdView(durationMs: Long, adProperties: AdTracker.AdProperties?, pageProperties: MediaTracker.PageProperties?, customEventProperties: EventProperties? = null)` - Create standalone AdTracker ### AdProperties * `AdTracker.AdProperties(title, durationInSeconds, isMuted, campaignId, creativeId)` - Ad property container # Video Tracking Source: https://docs.permutive.com/sdks/mobile/android/features/video-tracking Track video playback, engagement, and completion metrics The `MediaTracker` API allows you to track video content events with Permutive. Use this for tracking video playback, engagement, and completion metrics. **Connected TV Integration** Video event tracking described below is available once the connected TV integration has been enabled. Please contact your Customer Success Manager (CSM) to enable this feature. ## Overview `MediaTracker` provides comprehensive video tracking capabilities: * **Automatic lifecycle tracking** - Tracks Videoview and VideoCompletion events * **Engagement metrics** - Measures time spent watching and completion percentage * **Custom events** - Track custom video-related events * **Rich metadata** - Support for extensive video properties *** ## Video Events MediaTracker automatically tracks two event types: ### 1. Videoview Event Tracked when MediaTracker is created: ``` Event: Videoview Properties: - [your custom properties] - [standard video properties] ``` ### 2. VideoCompletion Event Tracked when video is stopped: ``` Event: VideoCompletion Properties: - aggregations.VideoEngagement.engaged_time: Total time spent watching (seconds) - aggregations.VideoEngagement.completion: Percentage of video viewed (0.0 - 1.0) - [your custom properties] - [standard video properties] ``` > 📘 Note > > The `aggregations.VideoEngagement.completion` property requires a known duration to be set. Pass `durationMilliseconds` when creating the MediaTracker. *** ## Basic Usage ### Creating a MediaTracker Create a MediaTracker when video playback begins: ```kotlin Kotlin theme={"dark"} import com.permutive.android.MediaTracker import android.net.Uri class VideoActivity : AppCompatActivity() { private val permutive by lazy { (application as MyApplication).permutive } private lateinit var videoTracker: MediaTracker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) videoTracker = permutive.trackVideoView( durationMilliseconds = 120000, // 2 minutes videoProperties = MediaTracker.VideoProperties( title = "Sample Video", genre = listOf("Documentary"), contentType = listOf("Education"), runtime = 120 ) ) } override fun onDestroy() { super.onDestroy() videoTracker.stop() // Important: Always stop to send VideoCompletion } } ``` ```java Java theme={"dark"} import com.permutive.android.MediaTracker; import android.net.Uri; public class VideoActivity extends AppCompatActivity { private MediaTracker videoTracker; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Permutive permutive = ((MyApplication) getApplication()).getPermutive(); MediaTracker.VideoProperties videoProps = new MediaTracker.VideoProperties.Builder() .title("Sample Video") .genre(Arrays.asList("Documentary")) .contentType(Arrays.asList("Education")) .runtime(120) .build(); videoTracker = permutive.trackVideoView( 120000, // 2 minutes in milliseconds videoProps, null, // pageProperties (optional) null // customEventProperties (optional) ); } @Override protected void onDestroy() { super.onDestroy(); videoTracker.stop(); // Important: Always stop to send VideoCompletion } } ``` *** ## MediaTracker Lifecycle ### Expected Usage Flow 1. **Create** MediaTracker instance with properties 2. **Call** `play()` when playback begins 3. **Track buffering** with `pause()` / `play()` 4. **Track scrubbing** with `play(position)` 5. **Call** `stop()` when playback completes > ⚠️ Single Instance Limitation > > Only a single instance of `PageTracker` or `MediaTracker` is available at any time. Close existing trackers before creating new ones. ### Playback Control Methods #### play() Call when video starts playing: ```kotlin theme={"dark"} videoTracker.play() ``` #### play(positionMs) Call when playback position changes (e.g., seeking): ```kotlin theme={"dark"} videoTracker.play(positionMs = 30000) // Seek to 30 seconds ``` #### pause() Call when video is paused or buffering: ```kotlin theme={"dark"} videoTracker.pause() ``` #### stop() Call when video playback completes or user exits: ```kotlin theme={"dark"} videoTracker.stop() // Sends VideoCompletion event ``` > ⚠️ Important: Always Call stop() > > Failing to call `stop()` will prevent VideoCompletion events from being tracked. *** ## Video Properties ### Standard Video Properties The SDK provides standard video properties for consistent tracking: ```kotlin theme={"dark"} MediaTracker.VideoProperties( title = "Video Title", genre = listOf("Action", "Thriller"), contentType = listOf("Movie", "Feature"), ageRating = "PG-13", runtime = 7200, // Runtime in seconds country = "US", originalLanguage = "en", audioLanguage = "en", areSubtitlesEnabled = true, subtitlesLanguage = "en", seasonNumber = 1, episodeNumber = 5, consecutiveEpisodes = 3, iabCategories = listOf("IAB1", "IAB2") ) ``` ### Property Reference These are the Kotlin SDK parameter names. The SDK maps them to the `snake_case` event schema and nests them under the `video` parent automatically. | Property | Type | Schema Name | Description | | ----------------------- | ------------- | ---------------------------- | --------------------------------------- | | **title** | String | `video.title` | Video title | | **genre** | List\ | `video.genre` | Video genres (e.g., "Drama", "Comedy") | | **contentType** | List\ | `video.content_type` | Content types (e.g., "Movie", "Series") | | **ageRating** | String | `video.age_rating` | Age rating (e.g., "PG-13", "TV-MA") | | **runtime** | Int | `video.runtime` | Runtime in **seconds** | | **country** | String | `video.country` | Origin country | | **originalLanguage** | String | `video.original_language` | Original language code | | **audioLanguage** | String | `video.audio_language` | Audio language code | | **areSubtitlesEnabled** | Boolean | `video.subtitles.enabled` | Whether subtitles are enabled | | **subtitlesLanguage** | String | `video.subtitles.language` | Subtitle language code | | **seasonNumber** | Int | `video.season_number` | Season number (for series) | | **episodeNumber** | Int | `video.episode_number` | Episode number (for series) | | **consecutiveEpisodes** | Int | `video.consecutive_episodes` | Consecutive episodes watched | | **iabCategories** | List\ | `video.iab_categories` | IAB content taxonomy categories | > 💡 Best Practice > > Track as many properties as possible to enable richer cohort creation and insights. *** ## Page Properties (Optional) If the video is displayed within a page context (e.g., embedded in article), include page properties: ```kotlin theme={"dark"} videoTracker = permutive.trackVideoView( durationMilliseconds = 120000, videoProperties = MediaTracker.VideoProperties( title = "Tutorial Video" ), pageProperties = MediaTracker.PageProperties( title = "How to Use Android SDK", url = Uri.parse("https://example.com/tutorial"), referrer = Uri.parse("https://example.com/docs") ) ) ``` ### PageProperties Reference | Property | Type | Description | | ------------ | ------ | ------------------ | | **title** | String | Page title | | **url** | Uri | Page URL | | **referrer** | Uri | Referring page URL | *** ## Custom Properties Add custom properties specific to your event schema: ```kotlin theme={"dark"} import com.permutive.android.EventProperties videoTracker = permutive.trackVideoView( durationMilliseconds = 120000, videoProperties = videoProps, pageProperties = null, customEventProperties = EventProperties.from( "video_id" to "vid_12345", "playlist_id" to "playlist_abc", "is_premium_content" to true, "content_partner" to "PartnerXYZ" ) ) ``` *** ## Complete Example ```kotlin Kotlin theme={"dark"} import com.permutive.android.MediaTracker import android.net.Uri import androidx.appcompat.app.AppCompatActivity class VideoPlayerFragment : Fragment() { private val permutive by lazy { (requireActivity().application as MyApplication).permutive } private lateinit var videoTracker: MediaTracker private lateinit var exoPlayer: ExoPlayer override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Initialize video tracker videoTracker = permutive.trackVideoView( durationMilliseconds = 180000, // 3 minutes videoProperties = MediaTracker.VideoProperties( title = "Introduction to Android", genre = listOf("Educational", "Technology"), contentType = listOf("Tutorial"), ageRating = "G", runtime = 180, country = "US", originalLanguage = "en", audioLanguage = "en", areSubtitlesEnabled = false, iabCategories = listOf("IAB19") ), pageProperties = MediaTracker.PageProperties( title = "Android Tutorial Page", url = Uri.parse("https://example.com/tutorials/android"), referrer = Uri.parse("https://example.com/home") ), customEventProperties = EventProperties.from( "video_id" to "vid_tutorial_001", "is_premium" to false ) ) // Set up ExoPlayer with tracking setupVideoPlayer() return inflater.inflate(R.layout.fragment_video, container, false) } private fun setupVideoPlayer() { exoPlayer = ExoPlayer.Builder(requireContext()).build() exoPlayer.addListener(object : Player.Listener { override fun onPlaybackStateChanged(state: Int) { when (state) { Player.STATE_READY -> { // Video ready to play if (exoPlayer.isPlaying) { videoTracker.play() } } Player.STATE_BUFFERING -> { videoTracker.pause() } } } override fun onIsPlayingChanged(isPlaying: Boolean) { if (isPlaying) { videoTracker.play() } else { videoTracker.pause() } } }) // Track position updates exoPlayer.addAnalyticsListener(object : AnalyticsListener { override fun onPositionDiscontinuity( eventTime: AnalyticsListener.EventTime, reason: Int ) { if (reason == Player.DISCONTINUITY_REASON_SEEK) { videoTracker.play(exoPlayer.currentPosition) } } }) } override fun onPause() { super.onPause() videoTracker.pause() exoPlayer.pause() } override fun onResume() { super.onResume() if (exoPlayer.isPlaying) { videoTracker.play() } } override fun onDestroy() { super.onDestroy() videoTracker.stop() // Send VideoCompletion event exoPlayer.release() } } ``` ```java Java theme={"dark"} import com.permutive.android.MediaTracker; import android.net.Uri; public class VideoPlayerFragment extends Fragment { private MediaTracker videoTracker; private ExoPlayer exoPlayer; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { Permutive permutive = ((MyApplication) requireActivity().getApplication()).getPermutive(); // Build video properties MediaTracker.VideoProperties videoProps = new MediaTracker.VideoProperties.Builder() .title("Introduction to Android") .genre(Arrays.asList("Educational", "Technology")) .contentType(Arrays.asList("Tutorial")) .ageRating("G") .runtime(180) .country("US") .originalLanguage("en") .audioLanguage("en") .areSubtitlesEnabled(false) .iabCategories(Arrays.asList("IAB19")) .build(); // Build page properties MediaTracker.PageProperties pageProps = new MediaTracker.PageProperties( "Android Tutorial Page", Uri.parse("https://example.com/tutorials/android"), Uri.parse("https://example.com/home") ); // Build custom properties EventProperties customProps = new EventProperties.Builder() .with("video_id", "vid_tutorial_001") .with("is_premium", false) .build(); // Create video tracker videoTracker = permutive.trackVideoView( 180000, // 3 minutes videoProps, pageProps, customProps ); setupVideoPlayer(); return inflater.inflate(R.layout.fragment_video, container, null); } private void setupVideoPlayer() { exoPlayer = new ExoPlayer.Builder(requireContext()).build(); exoPlayer.addListener(new Player.Listener() { @Override public void onPlaybackStateChanged(int state) { if (state == Player.STATE_READY && exoPlayer.isPlaying()) { videoTracker.play(); } else if (state == Player.STATE_BUFFERING) { videoTracker.pause(); } } @Override public void onIsPlayingChanged(boolean isPlaying) { if (isPlaying) { videoTracker.play(); } else { videoTracker.pause(); } } }); } @Override public void onPause() { super.onPause(); videoTracker.pause(); exoPlayer.pause(); } @Override public void onResume() { super.onResume(); if (exoPlayer.isPlaying()) { videoTracker.play(); } } @Override public void onDestroy() { super.onDestroy(); videoTracker.stop(); exoPlayer.release(); } } ``` *** ## Tracking Custom Events Track custom video-related events using the `track()` method: ```kotlin theme={"dark"} // User shares video videoTracker.track( "VideoShared", EventProperties.from( "share_method" to "twitter", "video_position_seconds" to 45 ) ) // User adds to favorites videoTracker.track("VideoAddedToFavorites") // Quality changed videoTracker.track( "VideoQualityChanged", EventProperties.from( "from_quality" to "720p", "to_quality" to "1080p" ) ) ``` *** ## Video Ad Tracking If you need to track video ads within video content, use the `AdTracker` API: ```kotlin theme={"dark"} // Create ad tracker from video tracker val adTracker = videoTracker.trackAdView( durationMs = 15000, // 15 second ad adProperties = AdTracker.AdProperties( title = "Product Ad", durationInSeconds = 15, isMuted = false, campaignId = "campaign_123", creativeId = "creative_456" ) ) // Track ad playback adTracker.play() // ... ad plays ... adTracker.completion() ``` See [Video Ad Tracking](/sdks/mobile/android/features/video-ad-tracking) for complete documentation. *** ## Use Cases ```kotlin theme={"dark"} videoTracker = permutive.trackVideoView( durationMilliseconds = movieDurationMs, videoProperties = MediaTracker.VideoProperties( title = movie.title, genre = movie.genres, contentType = listOf("Movie", "Feature"), ageRating = movie.rating, runtime = movie.runtimeSeconds, country = movie.country, originalLanguage = movie.language, iabCategories = movie.iabCategories ) ) ``` ```kotlin theme={"dark"} videoTracker = permutive.trackVideoView( durationMilliseconds = tutorialDurationMs, videoProperties = MediaTracker.VideoProperties( title = tutorial.title, genre = listOf("Educational"), contentType = listOf("Tutorial"), runtime = tutorial.runtimeSeconds ), customEventProperties = EventProperties.from( "course_id" to tutorial.courseId, "module_id" to tutorial.moduleId, "difficulty_level" to tutorial.difficulty ) ) ``` ```kotlin theme={"dark"} videoTracker = permutive.trackVideoView( durationMilliseconds = episodeDurationMs, videoProperties = MediaTracker.VideoProperties( title = episode.title, genre = series.genres, contentType = listOf("Series", "Episode"), seasonNumber = episode.season, episodeNumber = episode.number, consecutiveEpisodes = userWatchData.consecutiveCount, runtime = episode.runtimeSeconds ) ) ``` *** ## Troubleshooting **Problem:** VideoCompletion events not appearing. **Cause:** `stop()` not called. **Solution:** Always call `stop()` in `onDestroy()`: ```kotlin theme={"dark"} override fun onDestroy() { super.onDestroy() videoTracker.stop() // REQUIRED } ``` **Problem:** Engaged time is inaccurate. **Causes:** 1. Not calling `play()` when video plays 2. Not calling `pause()` when video pauses/buffers **Solution:** Track all play/pause events: ```kotlin theme={"dark"} exoPlayer.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { if (isPlaying) { videoTracker.play() } else { videoTracker.pause() } } }) ``` **Problem:** `completion` property is missing or null. **Cause:** Duration not provided when creating MediaTracker. **Solution:** Always provide `durationMilliseconds`: ```kotlin theme={"dark"} videoTracker = permutive.trackVideoView( durationMilliseconds = videoDurationMs // REQUIRED for completion ) ``` **Problem:** Error about multiple trackers. **Cause:** Creating new PageTracker or MediaTracker without closing existing one. **Solution:** Close existing tracker first: ```kotlin theme={"dark"} // Close old tracker videoTracker.stop() // Now create new tracker videoTracker = permutive.trackVideoView(...) ``` See [Common Errors](/sdks/mobile/android/troubleshooting/common-errors) for more troubleshooting. *** ## Best Practices * Always call `stop()` when video finishes * Provide duration for completion tracking * Track play/pause events accurately * Include as many video properties as possible * Close tracker before creating a new one * Track buffering with pause/play * Forget to call `stop()` * Create multiple MediaTrackers simultaneously * Skip play/pause lifecycle events * Omit video duration *** ## Related Documentation Track video advertisements Track non-video content Custom properties Solve common issues *** ## API Reference For complete API documentation, see the [Javadocs](https://sdk-docs.permutive.com/index.html). ### MediaTracker Interface * `play()` - Start/resume video playback * `play(positionMs: Long)` - Start at specific position * `pause()` - Pause video playback * `stop()` - Complete video tracking (sends VideoCompletion) * `setDuration(durationMs: Long)` - Update video duration * `track(eventName: String)` - Track custom event * `track(eventName: String, properties: EventProperties)` - Track custom event with properties * `trackAdView(...)` - Create AdTracker for video ad ### Creating MediaTracker * `Permutive.trackVideoView(durationMilliseconds, videoProperties, pageProperties, customEventProperties)` - Create MediaTracker # SDK Initialization Source: https://docs.permutive.com/sdks/mobile/android/getting-started/initialization Initialize the Permutive SDK in your Android application The Permutive SDK must be initialized before you can track events or use any SDK features. This guide shows you how to properly initialize the SDK. **Prerequisites:** * SDK dependencies added to your project ([Installation Guide](/sdks/mobile/android/getting-started/installation)) * **Workspace ID** and **API Key** from your Permutive dashboard * Your project enabled for Android by Permutive The Permutive object should be created **only once** as a singleton. Initialize in your Application class: ```kotlin Kotlin theme={"dark"} import android.app.Application import com.permutive.android.Permutive import java.util.UUID class MyApplication : Application() { // Create a single instance using lazy initialization val permutive by lazy { Permutive( context = this, workspaceId = UUID.fromString("YOUR_WORKSPACE_ID"), apiKey = UUID.fromString("YOUR_API_KEY") ) } override fun onCreate() { super.onCreate() // SDK will initialize when first accessed } } ``` ```java Java theme={"dark"} import android.app.Application; import com.permutive.android.Permutive; import java.util.UUID; public class MyApplication extends Application { private Permutive permutive; // Create a single instance using double-checked locking public Permutive getPermutive() { if (permutive == null) { synchronized (this) { if (permutive == null) { permutive = new Permutive.Builder() .context(this) .workspaceId(UUID.fromString("YOUR_WORKSPACE_ID")) .apiKey(UUID.fromString("YOUR_API_KEY")) .build(); } } } return permutive; } } ``` Set user identity during initialization: ```kotlin Kotlin theme={"dark"} import com.permutive.android.Alias // Single identity with default tag val permutive = Permutive( context = this, workspaceId = YOUR_WORKSPACE_ID, apiKey = YOUR_API_KEY, identity = "user_12345" ) // Or with multiple aliases: val customAliases = listOf( Alias.create( tag = "email_sha256", identity = "f660ab912ec121d1b1e928a0bb4bc61b15f5ad44d5efdc4e1c92a25e", priority = 0 ), Alias.create( tag = "internal_id", identity = "user_12345", priority = 1 ) ) val permutive = Permutive( context = this, workspaceId = YOUR_WORKSPACE_ID, apiKey = YOUR_API_KEY, customAliases = customAliases ) ``` ```java Java theme={"dark"} import com.permutive.android.Alias; // Single identity Permutive permutive = new Permutive.Builder() .context(this) .workspaceId(YOUR_WORKSPACE_ID) .apiKey(YOUR_API_KEY) .identity("user_12345") .build(); // Multiple aliases Permutive permutive = new Permutive.Builder() .context(this) .workspaceId(YOUR_WORKSPACE_ID) .apiKey(YOUR_API_KEY) .customAlias(Alias.create("email_sha256", "f660ab912ec121d...", 0)) .customAlias(Alias.create("internal_id", "user_12345", 1)) .build(); ``` See [Identity Management](/sdks/mobile/android/core-concepts/identity-management) for details. Use alias providers for automatic identity management: ```kotlin Kotlin theme={"dark"} import com.permutive.android.aaid.AaidAliasProvider val permutive = Permutive( context = this, workspaceId = YOUR_WORKSPACE_ID, apiKey = YOUR_API_KEY, aliasProviders = listOf(AaidAliasProvider(this)) ) ``` ```java Java theme={"dark"} import com.permutive.android.aaid.AaidAliasProvider; Permutive permutive = new Permutive.Builder() .context(this) .workspaceId(YOUR_WORKSPACE_ID) .apiKey(YOUR_API_KEY) .aliasProvider(new AaidAliasProvider(this)) .build(); ``` AAID requires the `google-ads` add-on and `AD_ID` permission. See [AAID Integration](/sdks/mobile/android/integrations/aaid-provider). ## Important Considerations Creating multiple Permutive instances will result in undefined behavior. Always use a singleton pattern. **Good:** ```kotlin theme={"dark"} class MyApplication : Application() { val permutive by lazy { Permutive(...) } // Single instance } ``` **Bad:** ```kotlin theme={"dark"} class MyActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { val permutive = Permutive(...) // NEW instance every time - DON'T! } } ``` * **Initialization is fast** - No significant impact on app startup * **No main thread blocking** - Network calls happen asynchronously * **Start tracking immediately** - No need to wait for initialization to complete We **strongly** recommend that you do **not** instantiate the Permutive object when receiving a push notification, but rather when the user interacts with the push notification. This prevents unnecessary SDK initialization from background processes. As a data controller, you may need to receive consent from the user before tracking data against them. The Permutive SDK assumes that at the time it is initialized, this consent has already been granted. **Coming Soon:** GDPR Compliance documentation is being developed. Contact your Customer Success Manager for immediate assistance. ## Troubleshooting **Symptoms:** ``` D/Permutive: Error fetching configuration - please check that your workspace id & API key is correct ``` **Solutions:** 1. Verify credentials are correct UUIDs 2. Ensure using `workspaceId` (not deprecated `projectId`) 3. Contact your Customer Success Manager to verify Android is enabled 4. Check device network connectivity **Causes:** 1. Invalid UUID format 2. Context is null 3. ProGuard/R8 stripping required classes **Solutions:** 1. Verify UUID format: `"550e8400-e29b-41d4-a716-446655440000"` 2. Use Application context (not Activity) 3. Check ProGuard rules are applied ## Next Steps Start tracking user interactions Confirm everything is working Track users across sessions Solutions to common issues ## API Reference For complete API documentation, see the [Javadocs](https://sdk-docs.permutive.com/index.html). **Key Classes:** * `Permutive` - Main SDK class * `Permutive.Builder` - Builder for Java * `Alias` - User identity alias * `AliasProvider` - Automatic alias provider interface # Installation Source: https://docs.permutive.com/sdks/mobile/android/getting-started/installation Install the Permutive Android SDK and optional add-ons The Android SDK is comprised of the core SDK, and optional add-on libraries integrating with other 3rd party SDKs. ## Requirements | Requirement | Version | | ----------- | -------------------------- | | Android API | 21+ (Android 5.0 Lollipop) | | Compile SDK | 34+ | | Java | 8+ (JVM target 1.8) | | Kotlin | 1.6+ (if using Kotlin) | You'll also need your **Workspace ID** and **API Key** from your Permutive dashboard. ## Latest Versions | Package | Version | | ---------- | ------- | | Core | 1.12.0 | | Google Ads | 2.2.0 | | AppNexus | 1.7.0 | For API documentation, see the [Javadocs](https://sdk-docs.permutive.com/index.html). ## Installation Ensure you have Maven Central in your project's `settings.gradle.kts`: ```kotlin theme={"dark"} dependencyResolutionManagement { repositories { google() mavenCentral() } } ``` Add the Permutive SDK to your app module's `build.gradle.kts` or `build.gradle`: ```kotlin build.gradle.kts theme={"dark"} dependencies { // Core SDK (required) implementation("com.permutive.android:core:1.12.0") // Optional add-ons implementation("com.permutive.android:google-ads:2.2.0") // Google Ad Manager implementation("com.permutive.android:appnexus:1.7.0") // Xandr / AppNexus } ``` ```groovy build.gradle theme={"dark"} dependencies { // Core SDK (required) implementation "com.permutive.android:core:1.12.0" // Optional add-ons implementation "com.permutive.android:google-ads:2.2.0" // Google Ad Manager implementation "com.permutive.android:appnexus:1.7.0" // Xandr / AppNexus } ``` Sync your project with Gradle files to download the dependencies. ## Optional Add-Ons The Google Ads add-on provides: * Automatic AAID (Android Advertising ID) identification * Helper methods for Google Ad Manager targeting * Automatic contextual cohort integration (v2.2.0+) ```kotlin theme={"dark"} implementation("com.permutive.android:google-ads:2.2.0") ``` **Requirements:** * Google Play Services Ads dependency (included transitively) * `AD_ID` permission for Android API 31+ Learn how to integrate with Google Ad Manager The AppNexus add-on provides: * Helper methods for Xandr/AppNexus ad targeting * Automatic impression tracking * Automatic contextual cohort integration (v1.7.0+) ```kotlin theme={"dark"} implementation("com.permutive.android:appnexus:1.7.0") ``` **Requirements:** * Xandr SDK dependency (must be added separately) Learn how to integrate with Xandr/AppNexus ## Version Compatibility ### Android API Levels | SDK Version | Minimum Android API | Target Android API | | --------------- | ------------------- | ------------------ | | 1.11.x | 21 (Android 5.0) | 34 (Android 14) | | 1.10.x | 21 (Android 5.0) | 34 (Android 14) | | 1.8.0 - 1.9.x | 21 (Android 5.0) | 34 (Android 14) | | 1.7.x and below | 16 (Android 4.1) | 33 (Android 13) | ### Add-On Compatibility | Add-On | Minimum Core Version | Notes | | ---------------- | -------------------- | ----------------------------------- | | google-ads 2.2.0 | 1.10.0 | Contextual cohorts support | | google-ads 2.1.0 | 1.8.0 | Minimum API 23 required | | appnexus 1.7.0 | 1.10.0 | Contextual cohorts support | | appnexus 1.6.x | 1.8.0+ | Compatible with older core versions | ## ProGuard / R8 If you're using code minification, the SDK includes consumer ProGuard rules automatically. No additional configuration is required. ## Troubleshooting **Cause:** Maven Central repository not configured. **Solution:** Add Maven Central to your `settings.gradle.kts`: ```kotlin theme={"dark"} dependencyResolutionManagement { repositories { google() mavenCentral() } } ``` **Cause:** Multiple SDK versions in dependencies. **Solution:** Force version resolution: ```kotlin theme={"dark"} configurations.all { resolutionStrategy { force("com.permutive.android:core:1.12.0") force("com.permutive.android:google-ads:2.2.0") } } ``` Check dependencies with: `./gradlew app:dependencies` **Cause:** Minification removing SDK classes. **Solution:** The SDK includes consumer ProGuard rules automatically. If issues persist, contact support. ## Next Steps Set up Permutive in your Application class Track your first page view Verify your integration is working Solutions to common issues # Quick Start Guide Source: https://docs.permutive.com/sdks/mobile/android/getting-started/quick-start Get up and running with the Permutive Android SDK in minutes Get up and running with the Permutive Android SDK. This guide will help you install the SDK, initialize it, and track your first page view. **Prerequisites:** * Minimum Android API level 21 (Android 5.0) * Your Permutive **Workspace ID** and **API Key** (available from your Permutive dashboard) * Your project enabled for Android by Permutive (contact your Customer Success Manager if unsure) Add the Permutive SDK to your app's `build.gradle.kts` or `build.gradle` file: ```kotlin build.gradle.kts theme={"dark"} dependencies { implementation("com.permutive.android:core:1.12.0") } ``` ```groovy build.gradle theme={"dark"} dependencies { implementation "com.permutive.android:core:1.12.0" } ``` Sync your project with Gradle files. Initialize Permutive in your `Application` class. The Permutive object should be created **once** as a singleton. ```kotlin Kotlin theme={"dark"} import android.app.Application import com.permutive.android.Permutive import java.util.UUID class MyApplication : Application() { // Create a single instance of Permutive using lazy initialization val permutive by lazy { Permutive( context = this, workspaceId = UUID.fromString("YOUR_WORKSPACE_ID"), apiKey = UUID.fromString("YOUR_API_KEY") ) } override fun onCreate() { super.onCreate() // The SDK will initialize when first accessed } } ``` ```java Java theme={"dark"} import android.app.Application; import com.permutive.android.Permutive; import java.util.UUID; public class MyApplication extends Application { private Permutive permutive; // Create a single instance of Permutive using double-checked locking public Permutive getPermutive() { if (permutive == null) { synchronized (this) { if (permutive == null) { permutive = new Permutive.Builder() .context(this) .workspaceId(UUID.fromString("YOUR_WORKSPACE_ID")) .apiKey(UUID.fromString("YOUR_API_KEY")) .build(); } } } return permutive; } } ``` Create only **one** instance of the Permutive object. Creating multiple instances will result in undefined behavior. Track a page view from an Activity using `PageTracker`. **PageTracker is the recommended approach** as it integrates with Permutive's standard events and enables richer insights. ```kotlin Kotlin theme={"dark"} import androidx.appcompat.app.AppCompatActivity import android.net.Uri import android.os.Bundle import com.permutive.android.EventProperties import com.permutive.android.PageTracker class MainActivity : AppCompatActivity() { private val permutive by lazy { (application as MyApplication).permutive } private lateinit var pageTracker: PageTracker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Track a page view with PageTracker trackMainScreen() } private fun trackMainScreen() { pageTracker = permutive.trackPage( title = "Home Screen", url = Uri.parse("app://main"), eventProperties = EventProperties.from( "screen_name" to "MainActivity", "session_start" to true ) ) } override fun onPause() { super.onPause() // Pause tracking when screen is not visible pageTracker.pause() } override fun onResume() { super.onResume() // Resume tracking when screen is visible pageTracker.resume() } override fun onDestroy() { super.onDestroy() // Always close the tracker when done pageTracker.close() } } ``` ```java Java theme={"dark"} import androidx.appcompat.app.AppCompatActivity; import android.net.Uri; import android.os.Bundle; import com.permutive.android.EventProperties; import com.permutive.android.PageTracker; import com.permutive.android.Permutive; public class MainActivity extends AppCompatActivity { private Permutive permutive; private PageTracker pageTracker; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); permutive = ((MyApplication) getApplication()).getPermutive(); // Track a page view with PageTracker trackMainScreen(); } private void trackMainScreen() { EventProperties properties = new EventProperties.Builder() .with("screen_name", "MainActivity") .with("session_start", true) .build(); pageTracker = permutive.trackPage( properties, "Home Screen", Uri.parse("app://main"), null // referrer ); } @Override protected void onPause() { super.onPause(); // Pause tracking when screen is not visible pageTracker.pause(); } @Override protected void onResume() { super.onResume(); // Resume tracking when screen is visible pageTracker.resume(); } @Override protected void onDestroy() { super.onDestroy(); // Always close the tracker when done pageTracker.close(); } } ``` **Why PageTracker?** PageTracker automatically tracks Pageview events with proper structure, measures engagement time and scroll depth, integrates seamlessly with Permutive's insights platform, enables contextual cohorts when URLs are provided, and associates related events with the page context. Enable debug logging to verify events are being tracked: ```kotlin theme={"dark"} override fun onCreate() { super.onCreate() permutive.setDeveloperMode(true) } ``` ```bash theme={"dark"} adb shell setprop log.tag.Permutive VERBOSE adb logcat -s Permutive ``` **What to look for in logs:** ``` D/Permutive: Starting Permutive v1.12.0 D/Permutive: Fetched configuration information D/Permutive: Page started (id: ): title: Home Screen url: app://main D/Permutive: Published events with names (Pageview) (Accepted: 1 / 1) ``` If you see "Accepted: 1 / 1" and the Pageview event, your integration is working! ## Next Steps Learn how to track user engagement with content Connect user behavior across sessions and devices Integrate with GAM for personalized advertising Integrate with Xandr ad platform For video streaming or CTV applications Comprehensive verification and troubleshooting ## Troubleshooting **Cause:** Invalid credentials or Android not enabled for your workspace. **Solution:** 1. Verify your Workspace ID and API Key are correct 2. Ensure you're using `workspaceId` (not the deprecated `projectId`) 3. Contact your Customer Success Manager to verify Android is enabled **Cause:** Event schema doesn't match what's configured in your Permutive dashboard. **Solution:** 1. Ensure the event name matches exactly (case-sensitive) 2. Verify property names and types match your schema 3. Check logs for detailed validation errors **Cause:** Debug logging not enabled. **Solution:** Enable developer mode or use ADB commands shown in Step 4. See the complete troubleshooting guide for solutions to more issues # Verifying Your Integration Source: https://docs.permutive.com/sdks/mobile/android/getting-started/verification Verify your Permutive Android SDK integration is working correctly This guide shows you how to verify your Permutive Android SDK integration is working correctly by checking debug logs. **Prerequisites:** * SDK installed and initialized * **adb** (Android Debug Bridge) installed on your computer * Device configured for developer mode ## Enable Debug Logging Debug logging is disabled by default. Enable it using one of these methods: Enable debug logging via command line: ```bash theme={"dark"} # Enable verbose logging adb shell setprop log.tag.Permutive VERBOSE # Start capturing logs adb logcat -s Permutive ``` To disable later: ```bash theme={"dark"} adb shell setprop log.tag.Permutive INFO ``` Enable developer mode programmatically: ```kotlin Kotlin theme={"dark"} // In debug builds only if (BuildConfig.DEBUG) { permutive.setDeveloperMode(true) } ``` ```java Java theme={"dark"} // In debug builds only if (BuildConfig.DEBUG) { permutive.setDeveloperMode(true); } ``` Use `BuildConfig.DEBUG` to ensure developer mode is only enabled in debug builds, never in production. ## What to Verify When the SDK starts successfully, you should see: ``` D/Permutive(15399): Starting Permutive v1.12.0 D/Permutive(15399): Fetched configuration information ``` This confirms SDK initialized successfully and credentials are correct. **If you see an error:** ``` D/Permutive(15124): Error fetching configuration - please check that your workspace id & API key is correct ``` Check your Workspace ID, API Key, and ensure Android is enabled for your workspace. When identity is set, you should see: ``` D/Permutive(15859): Identified user with aliases: aaid, default ``` Common aliases: * **aaid** - Android Advertising ID (if using AaidAliasProvider) * **default** - Default anonymous ID * **user\_id** - Custom user ID * **email\_sha256** - Hashed email When events are tracked and accepted: ``` D/Permutive(15859): Published events with names (Pageview, SegmentEntry) (Accepted: 4 / 4) ``` All events accepted = Your integration is working correctly! **If events are rejected**, you'll see schema validation errors with details about what needs to be fixed. When ads request targeting data: ``` D/Permutive: Permutive segments appended to Google Ad request: seg_123, seg_456, seg_789 ``` This confirms cohort IDs are being added to ad requests. ## PageTracker Lifecycle If you're using the PageTracker API, verify this sequence in logs: ``` Page started (id: abc123): title: Article Title url: https://example.com/article referrer: https://example.com/home properties: {article_id=12345, category=technology} ``` Logged when `pageTracker = permutive.trackPage(...)` is called. ``` Page paused (id: abc123) ``` Logged when `pageTracker.pause()` is called (typically in `onPause()`). ``` Page resumed (id: abc123) ``` Logged when `pageTracker.resume()` is called (typically in `onResume()`). ``` Page stopped (id: abc123) ``` Logged when `pageTracker.close()` is called (typically in `onDestroy()`). ``` Page percentage viewed updated (id: abc123) to 75% ``` Logged when `pageTracker.updatePercentageViewed()` is called. ## Schema Validation Errors If events are rejected due to schema violations: ``` D/Permutive(16644): Error publishing event with name "Pageview": D/Permutive(16644): Code: 1007 D/Permutive(16644): Status: BadRequest D/Permutive(16644): Cause: Schema validation failed with reason(s): [#: extraneous key [publishingDate] is not permitted] [#/article/categories: expected type: JSONArray, found: String] ``` **How to fix:** 1. **Unknown property** - Remove property or add it to your schema in the dashboard 2. **Type mismatch** - Fix the property type in your code: ```kotlin theme={"dark"} // Wrong - String instead of Array EventProperties.from("categories" to "Technology") // Correct - Array type EventProperties.from("categories" to listOf("Technology", "Mobile")) ``` Events with schema violations are **rejected by servers** and will not be processed. Always fix schema issues before releasing your app. ## Common Verification Issues **Solutions:** 1. Verify logging is enabled: `adb shell setprop log.tag.Permutive VERBOSE` 2. Check adb is connected: `adb devices` 3. Restart logcat: `adb logcat -c` then `adb logcat -s Permutive` 4. Verify SDK is initialized **Causes:** 1. Dashboard requires time to process events (5-10 minutes) 2. Event filtering in dashboard 3. Date/time range in dashboard **Solutions:** 1. Wait 10 minutes and refresh dashboard 2. Check dashboard filters 3. Verify date/time range includes now **Causes:** 1. Clearing app data between sessions 2. Not setting identity on initialization 3. Using test/emulator that resets **Solutions:** 1. Don't clear app data during testing 2. Set identity or use AAID provider at initialization 3. Test on physical device ## Verification Checklist Use this checklist to verify your integration: ### Basic Setup * [ ] SDK starts successfully ("Starting Permutive v1.12.0") * [ ] Configuration fetched without errors * [ ] Workspace ID and API Key are correct ### Identity * [ ] Identity log appears on first launch * [ ] Correct aliases are shown * [ ] Identity persists across app restarts ### Event Tracking * [ ] Events are being published * [ ] All events show "Accepted" (not rejected) * [ ] No schema validation errors * [ ] Correct event names (case-sensitive) ### PageTracker (if using) * [ ] "Page started" log appears * [ ] "Page paused" when activity pauses * [ ] "Page resumed" when activity resumes * [ ] "Page stopped" when activity destroyed ### Ad Targeting (if using) * [ ] Segments appended to ad requests * [ ] Targeting log appears when ads load ### Dashboard * [ ] Events appearing in dashboard (may take a few minutes) * [ ] User cohorts showing (after qualifying events) ## Next Steps Once verification is complete: 1. Remove debug logging from production builds 2. Test all user flows that include tracking 3. Verify dashboard data after 24 hours of live usage 4. Monitor for errors in production logs Learn about PageTracker lifecycle Property types and validation User identification Solutions to common issues # Migration Guide: v1.12.x Source: https://docs.permutive.com/sdks/mobile/android/guides/migration/v1-12 Upgrade to Permutive Android SDK v1.12.x and opt into config-driven automatic ID collection This guide covers upgrading to Permutive Android SDK v1.12.x, which introduces **config-driven automatic ID collection**. When enabled for your workspace, the SDK automatically collects the user's Android Advertising ID (AAID) and IP address as identity aliases — with no provider code required. **Migration Difficulty:** Easy | **Estimated Time:** 15 minutes | **Breaking Changes:** None ## What's New in v1.12.0 The SDK captures the AAID as an `aaid` alias when enabled in your workspace config — no `AaidAliasProvider` needed The SDK captures the user's IP as an `ip_address` alias from an internal endpoint — no app changes required **Good news:** This release is fully backward compatible with no breaking changes or deprecations. Automatic collection is **off by default** and is enabled remotely through your dashboard. ## How Automatic ID Collection Works Collection is driven by your workspace configuration, not by app code. Once you enable it for your workspace in the dashboard, the SDK picks it up on its next config refresh. * Stored as an alias with tag **`aaid`**, the priority defined in your config, and no expiry. * **Re-fetched on each new session** to detect Limit Ad Tracking changes, AAID resets, and priority changes from the server config. * If the user has **Limit Ad Tracking** enabled, no alias is stored and any existing `aaid` alias is cleared. * If an alias with the same tag, value, and priority already exists (from a previous auto-collection **or** a manual [`AaidAliasProvider`](/sdks/mobile/android/integrations/aaid-provider)), storage is skipped to avoid duplicates. * Stored as an alias with tag **`ip_address`**, the priority defined in your config, and no expiry. * Retrieved from Permutive's internal endpoint and updated when the IP changes. * **No app or manifest changes are required** for IP collection. ## Standard Upgrade Upgrading the SDK requires only a dependency bump. Update your `build.gradle.kts` or `build.gradle` to the v1.12.x core: ```kotlin theme={"dark"} dependencies { implementation("com.permutive.android:core:1.11.3") } ``` ```kotlin theme={"dark"} dependencies { implementation("com.permutive.android:core:1.12.0") } ``` Then sync and rebuild: ```bash theme={"dark"} ./gradlew clean build ``` Add-on libraries (`google-ads`, `appnexus`) are unaffected by this release. Automatic collection stays **off** until you enable it for your workspace in the dashboard, so no further changes are needed to complete the upgrade. ## Opting Into AAID Collection The steps below are **only required if you opt into automatic AAID collection**. They do not apply to a standard version upgrade, and they are not needed for automatic IP address collection (which requires no app or manifest changes). To collect the AAID, the client app must declare the `AD_ID` permission in its `AndroidManifest.xml`: ```xml theme={"dark"} ``` This permission is required on **Android 13 (API 33)** and above. Without it, the AAID is returned as all zeros (`00000000-0000-0000-0000-000000000000`), and the SDK treats this the same as no AAID — nothing is stored. Google Play Services must also be available on the device. If Play Services is missing or unavailable, the fetch fails silently and no alias is stored. Because the AAID is now collected and sent off-device, declare it in **Play Console → App content → Data safety**: * **Data type collected:** `Device or other IDs` → `Advertising ID` (under `Personal info`) * **Collection:** Yes, collected; sent off-device * **Optional vs. required:** Optional — the user can deny via Limit Ad Tracking or by resetting the AAID, and the SDK respects both * **Purposes:** whichever apply to your use of Permutive (typically Analytics, Advertising or marketing, Personalization) Keep your Data Safety declaration in sync with what the SDK actually collects. Misdeclaring collected identifiers can lead to Play Store policy violations. Automatic collection is enabled remotely via the dashboard. Contact your Customer Success Manager to help enabling or disabling the feature. ## Verifying Collection Enable developer logging and look for the identity being set: ```kotlin theme={"dark"} permutive.setDeveloperMode(true) ``` ``` D/Permutive: Identified user with aliases: aaid, ip_address ``` See the [Verification Guide](/sdks/mobile/android/getting-started/verification) for complete steps. ## Manual vs. Automatic AAID Collection If you already add [`AaidAliasProvider`](/sdks/mobile/android/integrations/aaid-provider) manually, we recommend **removing the manual provider** once automatic collection is enabled, unless you have a specific use case for it. Automatic, config-driven collection covers the same capture, lets you enable or disable it without shipping an app update, and is managed centrally from the dashboard. Running both is safe in the meantime — automatic collection deduplicates against any existing `aaid` alias with the same value and priority, so you won't get duplicate identities during the transition. | | Manual (`AaidAliasProvider`) | Automatic (config-driven) | | ----------------------------- | ---------------------------- | ------------------------- | | Enablement | App code at initialization | Workspace config (remote) | | App update needed to toggle | Yes | No | | `AD_ID` permission required | Yes (Android 13+) | Yes (Android 13+) | | Google Play Services required | Yes | Yes | | Alias tag | `aaid` | `aaid` | ## Rollback If you need to roll back to v1.11.x: ```kotlin theme={"dark"} dependencies { implementation("com.permutive.android:core:1.11.3") } ``` Then sync and rebuild. You can leave the `AD_ID` permission in your manifest — it is harmless when collection is disabled. ## Requirements v1.12.0 maintains the same minimum requirements as v1.11.x: | Requirement | Version | | ----------- | ---------------------- | | Android API | 21+ (Android 5.0) | | Compile SDK | 34+ | | Kotlin | 1.6+ (if using Kotlin) | | Java | 8+ (JVM target 1.8) | ## Getting Help Manual AAID capture and privacy considerations How aliases and identities work Verify identity capture Solutions to common issues # Migration Guide: v1.9.x to v1.10.x Source: https://docs.permutive.com/sdks/mobile/android/guides/migration/v1-9-to-v1-10 Upgrade from Permutive Android SDK v1.9.x to v1.10.x This guide covers migrating from Permutive Android SDK v1.9.x to v1.10.x, which introduces contextual cohorts, Dagger dependency injection, and several improvements. **Migration Difficulty:** Very Easy | **Estimated Time:** 15 minutes | **Breaking Changes:** None ## What's New in v1.10.0 Content-based segmentation for real-time targeting Internal dependency injection framework (v2.53.1) Automatic contextual cohort targeting for GAM Automatic contextual cohort targeting for Xandr **Good news:** This release is fully backward compatible with no breaking changes or deprecations. ## Migration Steps Update your `build.gradle.kts` or `build.gradle`: ```kotlin theme={"dark"} dependencies { implementation("com.permutive.android:core:1.9.8") implementation("com.permutive.android:google-ads:2.1.0") implementation("com.permutive.android:appnexus:1.6.1") } ``` ```kotlin theme={"dark"} dependencies { implementation("com.permutive.android:core:1.10.0") implementation("com.permutive.android:google-ads:2.2.0") // For GAM contextual implementation("com.permutive.android:appnexus:1.7.0") // For Xandr contextual } ``` ```bash theme={"dark"} ./gradlew clean build ``` Your existing code will continue to work without changes. Test to ensure: * Events are still being tracked * Cohorts are being received * Ad targeting is working ## Using New Features ### Contextual Cohorts Contextual cohorts are automatically enabled if your workspace has the feature activated. No code changes required! ```kotlin theme={"dark"} val activations = permutive.currentActivations // Get Google Ad Manager contextual cohorts val gamContextual = activations["dfp_contextual"].orEmpty() // Get Xandr contextual cohorts val xandrContextual = activations["appnexus_adserver_contextual"].orEmpty() ``` If you're using our add-on libraries, contextual cohorts are automatically included: **Google Ad Manager:** ```kotlin theme={"dark"} // Contextual cohorts automatically added with key "prmtvctx" val adRequest = AdManagerAdRequest.Builder() .addPermutiveTargeting(permutive) .build() ``` **Xandr/AppNexus:** ```kotlin theme={"dark"} // Contextual cohorts automatically added adView.addPermutiveTargeting(permutive) ``` See the [Contextual Data Guide](/sdks/mobile/android/core-concepts/contextual-data) for full documentation. ### Dagger Integration v1.10.0 introduces Dagger for internal dependency management. This is an internal implementation detail. **No action required.** Dagger is used internally by the SDK and doesn't require any changes to your code. If your app also uses Dagger and you experience conflicts: 1. Check your Dagger version matches or is compatible with v2.53.1 2. Don't exclude Dagger from the Permutive SDK dependencies: ```kotlin theme={"dark"} // Don't do this: implementation("com.permutive.android:core:1.10.0") { exclude(group = "com.google.dagger") // Will cause issues! } ``` ## Testing Your Migration Enable debug logging and verify events are still being tracked: ```kotlin theme={"dark"} permutive.setDeveloperMode(true) ``` Look for: ``` D/Permutive: Published events with names (Pageview) (Accepted: 1 / 1) ``` Verify behavioral cohorts are still working: ```kotlin theme={"dark"} val cohorts = permutive.currentCohorts Log.d("Migration Test", "Cohorts: ${cohorts.size}") ``` If contextual is enabled for your workspace: ```kotlin theme={"dark"} // Track a page with URL val pageTracker = permutive.trackPage( title = "Test Article", url = Uri.parse("https://example.com/test") ) // Wait a moment for classification Handler(Looper.getMainLooper()).postDelayed({ val contextual = permutive.currentActivations["dfp_contextual"] Log.d("Migration Test", "Contextual cohorts: $contextual") }, 2000) ``` Verify ads are still loading with targeting: ```kotlin theme={"dark"} val adRequest = AdManagerAdRequest.Builder() .addPermutiveTargeting(permutive) .build() adView.loadAd(adRequest) // Check logs for: // D/Permutive: Permutive segments appended to Google Ad request: ... ``` ## Rollback If you need to rollback to v1.9.x: ```kotlin theme={"dark"} dependencies { implementation("com.permutive.android:core:1.9.8") implementation("com.permutive.android:google-ads:2.1.0") implementation("com.permutive.android:appnexus:1.6.1") } ``` Then sync and rebuild. ## Requirements v1.10.0 maintains the same minimum requirements as v1.9.x: | Requirement | Version | | ----------- | ---------------------- | | Android API | 21+ (Android 5.0) | | Compile SDK | 34+ | | Kotlin | 1.6+ (if using Kotlin) | | Java | 8+ (JVM target 1.8) | ## Getting Help Solutions to common issues Full contextual documentation GAM integration guide Understanding segments # AAID Provider Source: https://docs.permutive.com/sdks/mobile/android/integrations/aaid-provider Automatically identify users by Android Advertising ID The AAID (Android Advertising ID) provider automatically identifies users by their Android Advertising ID. This is available through the Google Ads add-on library. ## Overview The `AaidAliasProvider` provides: * **Automatic AAID capture** - No manual ID retrieval needed * **Persistent identification** - User tracking across app sessions * **Privacy compliant** - Respects user opt-out preferences * **Zero maintenance** - Automatically updates when AAID changes ## Prerequisites 1. **Google Ads add-on** installed 2. **Google Play Services** on device 3. **AD\_ID permission** (Android API 33+) *** ## Installation The AAID provider is included in the Google Ads add-on: ```kotlin theme={"dark"} dependencies { implementation("com.permutive.android:core:1.12.0") implementation("com.permutive.android:google-ads:2.2.0") // Includes AaidAliasProvider } ``` See [Installation Guide](/sdks/mobile/android/getting-started/installation) for details. *** ## Basic Setup ### Add AaidAliasProvider at Initialization Add the `AaidAliasProvider` when creating your Permutive instance: ```kotlin Kotlin theme={"dark"} import android.app.Application import com.permutive.android.Permutive import com.permutive.android.aaid.AaidAliasProvider class MyApplication : Application() { val permutive by lazy { Permutive( context = this, workspaceId = YOUR_WORKSPACE_ID, apiKey = YOUR_API_KEY, aliasProviders = listOf(AaidAliasProvider(this)) // Add AAID provider ) } } ``` ```java Java theme={"dark"} import android.app.Application; import com.permutive.android.Permutive; import com.permutive.android.aaid.AaidAliasProvider; public class MyApplication extends Application { private Permutive permutive; public Permutive getPermutive() { if (permutive == null) { synchronized (this) { if (permutive == null) { permutive = new Permutive.Builder() .context(this) .workspaceId(YOUR_WORKSPACE_ID) .apiKey(YOUR_API_KEY) .aliasProvider(new AaidAliasProvider(this)) // Add AAID provider .build(); } } } return permutive; } } ``` That's it! The AAID will now be automatically captured and set as a user identity. *** ## Required Permissions (Android API 33+) ### Add AD\_ID Permission From Android 13 (API 33) and later, the `AD_ID` permission is required to access the AAID. Add this to your `AndroidManifest.xml`: ```xml theme={"dark"} ... ``` **Important for Android 13+** Without the `AD_ID` permission on Android API 33+, the AAID is returned as all zeros (`00000000-0000-0000-0000-000000000000`) and the `AaidAliasProvider` will not capture it. ### Checking Permission Status The permission is automatically granted - no runtime permission request needed. However, users can opt out of ad tracking in their device settings. *** ## How It Works ### Automatic ID Capture When `AaidAliasProvider` is added: 1. **On initialization** - AAID is retrieved from Google Play Services 2. **Async retrieval** - ID fetched in background (non-blocking) 3. **Auto-set identity** - AAID automatically set as user alias 4. **Tag created** - Stored with tag `"aaid"` 5. **Persisted** - AAID saved for future sessions ### Alias Details The AAID is stored as an alias with: * **Tag:** `"aaid"` * **Identity:** The advertising ID (e.g., `"38400000-8cf0-11bd-b23e-10b96e40000d"`) * **Priority:** 0 (default) * **Expiry:** Never (unless manually cleared) ### User Opt-Out Handling If a user opts out of ad personalization in device settings: * The AAID provider will receive a zeroed-out ID * This zeroed-out ID will not be set as an alias * Permutive will use the default anonymous ID instead *** ## Combining with Other Identities You can use AAID alongside other identity methods: ### AAID + Custom Identity ```kotlin theme={"dark"} val permutive = Permutive( context = this, workspaceId = YOUR_WORKSPACE_ID, apiKey = YOUR_API_KEY, identity = "user_12345", // Your internal user ID aliasProviders = listOf(AaidAliasProvider(this)) // + AAID ) ``` Now the user has two identities: 1. Custom identity: `"user_12345"` (tag: `"user_id"`) 2. AAID: `"38400000-8cf0-11bd-b23e-10b96e40000d"` (tag: `"aaid"`) ### AAID + Multiple Custom Aliases ```kotlin theme={"dark"} import com.permutive.android.Alias val permutive = Permutive( context = this, workspaceId = YOUR_WORKSPACE_ID, apiKey = YOUR_API_KEY, customAliases = listOf( Alias.create( tag = "email_sha256", identity = hashSHA256(userEmail), priority = 0 ), Alias.create( tag = "internal_id", identity = "user_12345", priority = 1 ) ), aliasProviders = listOf(AaidAliasProvider(this)) // + AAID ) ``` See [Identity Management](/sdks/mobile/android/core-concepts/identity-management) for more on multiple identities. *** ## Verification ### Verify AAID is Set Enable debug logging to verify AAID is captured: ```kotlin theme={"dark"} permutive.setDeveloperMode(true) ``` Look for logs like: ``` D/Permutive: Setting identity with tag: aaid, value: 38400000-8cf0-11bd-b23e-10b96e40000d ``` ### Check Current Identities You can programmatically check if AAID is set: ```kotlin theme={"dark"} // This is internal API - use debug logs instead // But for verification in tests: // Check that events include AAID in aliases ``` See [Verification Guide](/sdks/mobile/android/getting-started/verification) for complete verification steps. *** ## Use Cases ### Anonymous User Tracking For apps without user login, AAID provides persistent tracking: ```kotlin theme={"dark"} // No custom identity - just AAID val permutive = Permutive( context = this, workspaceId = YOUR_WORKSPACE_ID, apiKey = YOUR_API_KEY, aliasProviders = listOf(AaidAliasProvider(this)) ) ``` User is identified by AAID across app launches and sessions. ### Logged-In User + AAID For apps with user accounts, combine both: ```kotlin theme={"dark"} class MyApplication : Application() { val permutive by lazy { Permutive( context = this, workspaceId = YOUR_WORKSPACE_ID, apiKey = YOUR_API_KEY, aliasProviders = listOf(AaidAliasProvider(this)) ) } fun onUserLoggedIn(userId: String) { // Add user ID as additional identity permutive.setIdentity( tag = "internal_user_id", identity = userId ) } fun onUserLoggedOut() { // AAID remains, but remove user ID // (User is still tracked via AAID) } } ``` ### Cross-Device Tracking AAID is device-specific. For cross-device tracking, combine with email or other identifiers: ```kotlin theme={"dark"} fun onUserLoggedIn(email: String) { permutive.setIdentity( tag = "email_sha256", identity = hashSHA256(email) ) // Now user has both AAID (device) and email hash (cross-device) } ``` *** ## Privacy Considerations ### GDPR Compliance AAID is considered personal data under GDPR. Ensure: * ✅ User consent obtained before initialization * ✅ Privacy policy explains AAID usage * ✅ User can opt out via device settings * ✅ Data can be deleted on request ```kotlin theme={"dark"} class MyApplication : Application() { private var _permutive: Permutive? = null val permutive: Permutive? get() = _permutive fun onConsentGranted() { if (_permutive == null) { _permutive = Permutive( context = this, workspaceId = YOUR_WORKSPACE_ID, apiKey = YOUR_API_KEY, aliasProviders = listOf(AaidAliasProvider(this)) // Only after consent ) } } } ``` Contact your Customer Success Manager for details on GDPR compliance. ### User Opt-Out Users can opt out of ad personalization in: * **Settings → Google → Ads → Opt out of Ads Personalization** When opted out: * AAID is zeroed out (`00000000-0000-0000-0000-000000000000`) * `AaidAliasProvider` will not set this as an identity * Permutive uses anonymous ID instead ### Data Deletion To clear all user data including AAID: ```kotlin theme={"dark"} permutive.clearPersistentData() ``` This removes: * All identities (including AAID) * All event data * All cohort information See [Identity Management](../core-concepts/identity-management.mdx#removing-persistent-data). *** ## Troubleshooting **Problem:** AAID not appearing in events. **Causes:** 1. Google Ads add-on not installed 2. `AaidAliasProvider` not added to Permutive 3. Missing `AD_ID` permission (Android 13+) 4. Google Play Services not available 5. User opted out of ad personalization **Solutions:** 1. Verify `google-ads:2.2.0` is in dependencies 2. Add `AaidAliasProvider` to `aliasProviders` list 3. Add `AD_ID` permission to manifest (Android 13+) 4. Test on device with Google Play Services 5. Check user's ad personalization settings 6. Enable debug logging to see AAID capture attempts **Problem:** Error about AD\_ID permission. **Cause:** `AD_ID` permission not declared. **Solution:** Add to `AndroidManifest.xml`: ```xml theme={"dark"} ``` **Problem:** AAID cannot be retrieved. **Cause:** Device doesn't have Google Play Services (e.g., some devices in China). **Solutions:** 1. Implement fallback identification method 2. Use custom identity instead 3. Check Play Services availability: ```kotlin theme={"dark"} val availability = GoogleApiAvailability.getInstance() val resultCode = availability.isGooglePlayServicesAvailable(context) if (resultCode == ConnectionResult.SUCCESS) { // Play Services available - AAID will work } else { // Play Services not available - use fallback } ``` **Problem:** AAID changes unexpectedly. **Causes:** 1. User reset advertising ID in settings 2. Factory reset 3. App reinstalled **This is expected behavior.** AAID can change, and your implementation should handle this gracefully. See [Common Errors](/sdks/mobile/android/troubleshooting/common-errors) for more troubleshooting. *** ## Best Practices * Add `AaidAliasProvider` at SDK initialization * Declare `AD_ID` permission for Android 13+ * Obtain user consent before capturing AAID (GDPR) * Combine AAID with other identifiers for robustness * Handle Google Play Services unavailability * Respect user opt-out preferences * Use AAID without user consent (GDPR) * Forget `AD_ID` permission on Android 13+ * Rely solely on AAID (can change or be unavailable) * Assume AAID is always available * Ignore user opt-out preferences *** ## Related Documentation Use AAID with GAM Multiple identities SDK setup Verify AAID capture Solve common issues *** ## API Reference For complete API documentation, see the [Javadocs](https://sdk-docs.permutive.com/index.html). ### AaidAliasProvider * `AaidAliasProvider(context: Context)` - Creates AAID alias provider ### Usage in Permutive **Kotlin:** ```kotlin theme={"dark"} Permutive( context = ..., workspaceId = ..., apiKey = ..., aliasProviders = listOf(AaidAliasProvider(context)) ) ``` **Java:** ```java theme={"dark"} new Permutive.Builder() .context(context) .workspaceId(workspaceId) .apiKey(apiKey) .aliasProvider(new AaidAliasProvider(context)) .build() ``` *** ## Getting Help If you encounter issues with AAID: 1. Check [Troubleshooting Guide](/sdks/mobile/android/troubleshooting/common-errors) 2. Verify Google Ads add-on is installed 3. Confirm `AD_ID` permission is declared (Android 13+) 4. Enable debug logging 5. Test on device with Google Play Services 6. Contact your Customer Success Manager For Google Play Services issues, consult [Google's documentation](https://developers.google.com/android/guides/overview). # Integrations Source: https://docs.permutive.com/sdks/mobile/android/integrations/overview Connect the Android SDK with ad platforms and tracking providers The Permutive Android SDK integrates with major ad platforms to enable audience targeting. For detailed setup instructions, see the main integration documentation for each platform. ## Ad Server Integrations Set up GAM targeting for Android apps Configure Xandr ad platform targeting ## SDK Providers Automatic Android advertising ID tracking # Android SDK Source: https://docs.permutive.com/sdks/mobile/android/overview Integrate Permutive into your Android application for user segmentation, personalization, and ad targeting ## Overview The Permutive Android SDK enables user segmentation, personalization, and ad targeting in your Android application. Track user behavior, manage identities across devices, and deliver targeted advertising through integrations with major ad platforms. ## Getting Started New to the Permutive Android SDK? Start here. Get up and running with your first page view in minutes Detailed installation and dependency setup SDK initialization patterns and configuration Verify your integration is working correctly ## Core Concepts Understand the fundamental concepts of the Permutive SDK. Track users across devices and sessions Structure and validate event data Understanding user segmentation Content-based real-time targeting ## Features Detailed guides for specific SDK features. Track pageviews and user engagement (recommended approach) Track custom events Track video content viewing Track video advertisement engagement React to cohort changes in real-time ## Integrations Connect Permutive with ad platforms and other services. Google Ad Manager, Xandr, and more Automatic advertising ID tracking ## Common Tasks **PageTracker is the recommended approach** for tracking user interactions as it integrates with Permutive's standard events and provides richer insights. ```kotlin theme={"dark"} class ArticleActivity : AppCompatActivity() { private lateinit var pageTracker: PageTracker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) pageTracker = permutive.trackPage( title = "Article Title", url = Uri.parse("https://example.com/article"), eventProperties = EventProperties.from( "category" to "sports", "author" to "John Doe" ) ) } override fun onPause() { super.onPause() pageTracker.pause() } override fun onResume() { super.onResume() pageTracker.resume() } override fun onDestroy() { super.onDestroy() pageTracker.close() } } ``` ```kotlin theme={"dark"} val emailHash = hashEmail(userEmail) permutive.setIdentity( listOf( Alias.create("email_sha256", emailHash, priority = 0), Alias.create("internal_id", userId, priority = 1) ) ) ``` ```kotlin theme={"dark"} val trigger = triggersProvider.triggerAction("premium_user") { isInCohort -> if (isInCohort) { showPremiumFeatures() } } // Don't forget to close! override fun onDestroy() { super.onDestroy() trigger.close() } ``` ```kotlin theme={"dark"} val adRequest = AdManagerAdRequest.Builder() .addPermutiveTargeting(permutive) .build() adView.loadAd(adRequest) ``` ## Requirements | Requirement | Version | | ----------- | -------------------------- | | Android API | 21+ (Android 5.0 Lollipop) | | Compile SDK | 34+ | | Java | 8+ (JVM target 1.8) | | Kotlin | 1.6+ (if using Kotlin) | ```kotlin theme={"dark"} dependencies { // Core SDK (required) implementation("com.permutive.android:core:1.12.0") // Optional add-ons implementation("com.permutive.android:google-ads:2.2.0") implementation("com.permutive.android:appnexus:1.7.0") } ``` ## Key Concepts ## Additional Resources Best practices, patterns, and how-tos. * **[Migration: v1.9.x to v1.10.x](/sdks/mobile/android/guides/migration/v1-9-to-v1-10)** - Upgrade to the latest version **Coming Soon:** Architecture Patterns, Testing Strategies, GDPR Compliance, and Performance Optimization guides are being developed. API documentation and technical reference. * **[Javadocs](https://sdk-docs.permutive.com/)** - Complete API documentation * **[Sample Apps](https://github.com/permutive/android-sdk-samples)** - Example integrations **Coming Soon:** Changelog, Minimum Versions, and Event Schema reference documentation. ## FAQ **PageTracker** is the recommended approach for most use cases. It automatically tracks Pageview events, measures engagement time and scroll depth, and enables contextual cohorts when URLs are provided. **EventTracker** is for specialized cases where you need to track custom events that don't fit the page model. Enable developer mode in your code: ```kotlin theme={"dark"} permutive.setDeveloperMode(true) ``` Or via ADB: ```bash theme={"dark"} adb shell setprop log.tag.Permutive VERBOSE adb logcat -s Permutive ``` Use hashed emails (SHA-256), internal user IDs, or mobile advertising IDs. Never send plain text PII. See the [Identity Management](/sdks/mobile/android/core-concepts/identity-management) guide for details. Events typically appear within 5 minutes. If you see "Accepted: 1 / 1" in your debug logs, the event was successfully sent. ## Getting Help * **[Troubleshooting Guide](/sdks/mobile/android/troubleshooting/common-errors)** - Solutions to common issues * **Customer Success Manager (CSM)** - For enabling features and exploring use cases * **[Technical Services](mailto:technical-services@permutive.com)** - For technical setup and configuration * **[Support](mailto:support@permutive.com)** - For troubleshooting and help ## Privacy & Compliance Permutive is designed with privacy in mind: GDPR compliant, CCPA compliant, no PII storage, user consent respected, and transparent data usage. # Release Notes Source: https://docs.permutive.com/sdks/mobile/android/release-notes/index Android SDK updates and announcements * Config-driven automatic ID collection **(New)** * Header encoding for OkHttp 4.x compatibility **(Fix)** * Add 16KB page size support for Android 15+ devices **(Fix)** * Fix missing `logs()` method in Logger interface **(Fix)** * Integrate contextual cohorts with classification models **(Update)** * Integrate content-based contextual cohorts **(New)** * Introduce Dagger as dependency injection framework (v2.53.1) **(New)** * Automatic contextual cohort targeting integration for Google Ad Manager requests **(New)** * Automatic contextual cohort targeting integration for AppNexus/Xandr ad requests **(New)** * Fix issue causing cohort transition event aggregation to stop working in some situations. **(Fix)** * Remove aho-corasick string-searching from cohort processing to reduce memory usage. **(Fix)** * Integrate with new API for audience matching to improve matching based on external data **(New)** * Fix close button on debug overlay **(Fix)** * Fix logging when setting completion percentage **(Fix)** * Update Google Play Services Ads dependency to 24.0.0 **(Update)** * Changed Android minSdk version to 23 **(Update)** * Add aggregations of cohort transition events to PageviewComplete event properties to reduce our network load **(New)** * Fix missing class issues by removing dependency on ktor utils library from the query engine **(Fix)** * Fix issue that could occur when synchronising user state **(Fix)** * Update compile SDK version to 34 **(Update)** * Update Google Play Services Ads dependency to 23.2.0 **(Update)** * Remove `PermutiveAdManagerAdRequestBuilder.addNetworkExtras` function (removed from ads SDK) **(Breaking change)** * Remove `PermutiveAdManagerAdRequestBuilder.setLocation` function (removed from ads SDK) **(Breaking change)** * Deprecate `PermutiveAdManagerAdRequestBuilder.addCustomEventsExtrasBundle` function (deprecated in ads SDK) **(Deprecation)** * Add remotely configurable list of third and second party data providers that are excluded from usage reporting **(New)** * Replace RxJava2 with Kotlin Flow in state synchronisation **(Update)** * Add new feature flag to enable Classification Model cohorts for this version and above **(Update)** * Add video ad tracking functionality through the `AdTracker`. [See here for more information](/sdks/mobile/android/features/video-ad-tracking) **(New)** * Add new container classes for video properties, `MediaTracker.VideoProperties` and `MediaTracker.PageProperties`. **(New)** * Add new `trackVideoView` function which takes the new property objects and deprecate `createVideoTracker`. **(Update)** * Release beta version of the debug overlay UI tool. **(New)** * Add new `TriggersProvider` functions which support `boolean` results for cohorts. **(New)** * Remove Rhino Javascript engine library. **(Update)** * Remove intermediate objects created during event processing to improve memory usage. **(Update)** * Fix RxJava undeliverable exceptions escaping error handling. **(Fix)** * Fix issue resulting from Kotlin reflection library dependency conflicts. **(Fix)** * Update the native query runtime to the latest version with more efficient state updates. This will reduce the number of calls to the Permutive API made by the SDK. **(New)** * Fix crash occurring on devices running Android 25 and below caused by desugared java.time library not being included in AAR. **(Fix)** * Increase minimum Android SDK version from 16 to 21. For more information on why we're doing it and how we'll be minimising disruption [read here](/sdks/mobile/android/guides/migration/v1-9-to-v1-10). * Added support for Classification Model cohorts. **(New)** * Added new properties for retrieving cohorts and activations. `PermutiveSdk.currentCohorts: List` & `PermutiveSdk.currentActivations: Map>`. **(New)** To allow us to support more cohorts we have switched from using integers to strings as cohort IDs. The new properties are equivalent to the old `PermutiveSdk.currentSegments: List` & `PermutiveSdk.currentReactions: Map>` properties respectively. * Deprecate the `PermutiveSdk.currentSegments` & `PermutiveSdk.currentReactions` properties. **(Deprecation)** * Fix a query in the native query runtime. **(Fix)** * Change evaluation of some debug log strings to be lazy. **(Fix)** * Retrieve cohorts using the new `PermutiveSdk.currentActivations` function. **(Update)** * Retrieve cohorts using the new `PermutiveSdk.currentActivations` function. **(Update)** * Downgrade Kotlin requirement to v1.6+. **(Update)** * Enable the native query runtime by default. **(New)** * Fix native query runtime not interpreting some queries correctly. **(Fix)** * Fix issue with external state decoding. **(Fix)** * Fix crash occurring when retrieving Classification Model cohorts **(Fix)** * Fix missing common module classes **(Fix)** * Fix issue with debug event recorder caching large amount of events. **(Fix)** * Remove the "CTV" prefix from the names of our video events tracked through the `MediaTracker` API to better reflect their usecase for all video content. **(Update)** * Update to how we capture the `client.user_agent` event property to allow us to support better device distinction in the insights platform. **(Update)** * Additional fixes to query interpretation when using the native engine. **(Fix)** * Update the new `clearPersistentData` function to always run on coroutine `Dispatchers.IO` to prevent possibility of database access from the wrong thread. **(Fix)** * Added some additional proguard consumer rules to prevent native engine deserialization classes from being removed. **(Fix)** Please ensure you test you application with minification and Permutive debugging enabled (see [verification guide](/sdks/mobile/android/getting-started/verification)) * Fix issue causing certain queries not to be interpreted correctly. **(Fix)** * Add new instance functions for clearing persistent data and deprecate old function. **(New)** The previous `clearPersistentData` function was potentially causing issues with the Permutive SDK database instance resulting in crashes. The new functions are safer to use and also inform of success or failure. * Add migration path for users moving from event-sync to our new native state-sync. **(New)** * Add handling for undelivered exceptions. **(New)** * Fix exception when migrating from database version 4 to 5. **(Fix)** * Add new `setPermutiveAdListener` function which enables automatic impression tracking. For more information see [AppNexus/Xandr integration](/sdks/mobile/android/integrations/appnexus-xandr). **(New)** * Fix issue which caused the SDK to stop if the user received network errors when making an identity call. **(Fix)** * Add new native engine to replace the Rhino Javascript engine. This feature will be rolled out gradually. **(New)** Rhino Javascript will be removed in a subsequent release after rollout of the native engine is complete. * Update EventProperties API so that it allows nullable values. **(New)** * Fix exception which could occur when doing database migration. **(Fix)** * Add support for setting priority and expiry for aliases set through the identity APIs. **(New)** The identity APIs support new functionality for prioritising aliases and deleting them automatically on expiry. For more information see our documentation on [identity management](/sdks/mobile/android/core-concepts/identity-management). * Add support for tracking Fire and Fire TV as platforms. **(New)** Fire and Fire TV devices are now tracked as separate platforms types for clients. * Fix printing of nested EventProperties which previously showed as 'null'. **(Fix)** Nested EventProperties in the developer log were incorrectly showing as null, they now show as the correct nested structure. * Fix memory leak caused by some internal resources not closing correctly. **(Fix)** Parts of the SDK were found to still run even after `close()` was called on an SDK instance. These have now been fixed. * Fix issue that could sometimes cause events to be processed more than once. **(Fix)** Sometimes events could end up being tracked more than once causing slowdown and memory consumption. This has now been fixed. * Update Xandr/AppNexus dependency to 7.X. * Add support for Google Ads slots clicked events. **(New)** Additional information is included in the GAM request which allows for server side generation of slot events. * Fix in-IDE documentation for core APIs. **(New)** API documentation is now accessible in the IDE. * Adds in page engagement support. **(New)** Engagement segments can not be evaluated in the current page, rather than only once that page has closed/completed. * Add video tracking support. **(New)** Similar to pages we now support tracking video events and video engagement using the MediaTracker class. * Fix exception happening when events containing an empty list as a child of `EventProperties` are tracked. **(Fix)** This version has a known issue causing some events not to be tracked. Please use version 1.5.11 * Optimise segmentation engine memory usage. **(New)** An optimised way of passing events to the segmentation engine has been added which will reduce OutOfMemoryErrors. * Fix exception when using `EventProperties` containing a list of `EventProperties`. **(Fix)** Correct property handling has been added for `EventProperties` which contain a value which itself is a list of `EventProperties`. * Add more comprehensive logging to the SDK. **(New)** Improved logging will assist in verifying integrations and debugging any potential issues. See the [verification guide](/sdks/mobile/android/getting-started/verification) for details on what logs are available. * Remove internal logging of current segments and current reactions. **(New)** These can easily be logged externally so have been removed to reduce noise from the internal SDK logs. * Fix memory leaks when restarting the SDK **(Fix)** * Update library dependencies **(New)** Updates Google-Play-Services dependency from v18 to v20. * Update library dependencies **(New)** Updates third party dependencies - room, rxjava, androidx to the latest versions. * Update minimum SDK version to 16 **(New)** Updates minimum SDK version from 14 to 16. * Always identify on startup **(New)** In order to prevent potential identity collapse, the SDK always identifies upon startup, so any such issues can be resolved by the server. * Fix ConcurrentModificationException at startup **(Fix)** The deferred queue has been updated to fix an issue where ConcurrentModificationException can be thrown due to a deferred operation that itself modifies the deferred queue. * Fix SQLite issue when migrating to state sync **(Fix)** Rewrite SQLite query for migration to state sync so that it does not exceed the maximum variable limit for the Android SQLite implementation. * Fix conflicting cache issue with OkHttp **(Fix)** Cache issues can occur if both the host application and the Permutive SDK use OkHttp in the same cache directory. Permutive SDK now uses a subdirectory of the cache directory. * Update URLs to `permutive.app` **(New)** Moving from `permutive.com` - SDK traffic should use `permutive.app` name. * Fine tune metrics for the state-sync/event-sync variants **(Fix)** Metrics are now able to be fine tuned so that the variants do not interfere with the reported data. * Add state synchronisation support **(New)** We now merge off-device state using states rather than raw events. * Fix migration issue with 1.5.2 **(Fix)** Fixes a critical persistence issue when upgrading to 1.5.2 with persisted state from an older version. * Constrain event cache upon startup **(New)** Excess events are dropped upon startup if the events cache is lowered. * Handle OOM exception when evaluating script **(Fix)** We now disable the SDK & report an issue if an OOM condition occured when evaluating the segmentation script. * Update Kotlin to 1.4.20 **(New)** Updated to the latest stable developer release of Kotlin. Upgrading from an older versions of this SDK can result in a migration issue, we strongly recommend using a previous version. If you are using 1.5.2 please contact our support staff for further details. * Disable pre lollipop devices by default **(Fix)** Devices older than 5.0 are unable to communicate with our servers as we use the more secure TLS 1.2 protocol. * Handle OOM exceptions more gracefully **(Fix)** Disable the SDK when an OOM occurs during segmentation, freeing up memory for the host application. * Add CheckResult annotation for all public methods that return values **(New)** Warnings should occur in the IDE when using public functions that returns values. * Updated to Kotlin 1.4.0 **(New)** * Better developer logs & dynamic log support **(New)** It is now possible to dynamically turn on logging for a deployed app, which can help for verification of the deployment. * Fix halting when there is a connection issue **(Fix)** Under some circumstances the SDK would halt execution when a connection issue occurred. * Do not include rejected events in cache **(Fix)** Rejected events are not not included in the events cache. * Fix watson decode issue **(Fix)** Watson would not decode correctly if a client was using a later version of the moshi library. * Add Enterprise workspace support **(New)** Passing through a Workspace Id rather than a Project Id will use segments and events configured for that particular workspace, rather than for the whole organization. If migrating from an existing ProjectId to a new Workspace Id, local state is still retained during the migration. * Stop running the segmentation engine when a memory trim level has been sent by the OS **(New)** When the device starts running out of memory, the Android SDK will shutdown it's running systems until a call to the public APIs is made. * Only track engagement events if configured to **(Fix)** We only send PageviewComplete events if the project is configured for this. * Handle devices that are configured with a DNS sinkhole **(Fix)** Fix issue when a device is connected to a DNS sinkhole under certain conditions the SDK can make repeated requests for the same resource. * Add Watson enrichment support **(New)** EventProperties.ALCHEMY\_\* strings can be used to enrich an event with the watson information provided for the current/page URL. * Add PageviewComplete event **(New)** Adds page engagement support for the Android platform. The PageviewComplete event is automatically generated when calling close on a PageTracker object. * Always use application context on initialisation **(Fix)** Prevents Activity leaks when passing an Activity as the SDK's context, rather than the Application. * Turn off TPD usage reporting as a default **(Fix)** TPD usage reporting is not used by our customers at this time, so this has defaulted to off. * Report SDK version as a metric **(New)** Reporting SDK version will give us better information on current deployments. * Add hashed ip as an event enrichment type **(New)** The string "\$ip\_address\_hash" will now resolve to a hashed version of the client's ip address. * Fix instances of dependencies upon startup **(Fix)** Fixes a potential race condition that could allow double creations of SDK dependencies. * Fix error reporting so that it includes script date/time **(Fix)** To better diagnose issues we now report the query language creation time in error reports. * Handle illegal argument exception when unregistering broadcast receiver **(Fix)** Fixed an issue when unregistering a broadcast receiver that has not yet been registered in some cases. * Always ask server to resolve resources, regardless of cache-control max-age **(Fix)** We now do not honor the cache-control max-age header, as if this is very long we will not fetch the updates files from our servers. * do not generate segment\_exit events for deleted/missing segments **(Fix)** We now do not generate segment\_exit events for missing/deleted segments. * update library dependencies **(New)** We've updated our library dependencies to take advantage of the latest bugfixes & enhancements. * Remove deleted queries from persisted state **(New)** We now detect & delete queries & segments that have been deleted/disabled from the dashboard. * Add date support to EventProperties **(New)** In older SDKs a developer had to manually format a datetime object to a String (in ISO 9601), we've added date support for EventProperties so that this will happen automatically. * Add appnexus support to add permutive targeting **(New)** We've added preliminary support for ad targeting using the AppNexus SDK with our new appnexus addon library. * Add OS/Script/App info to error reports **(New)** When we report an error to our endpoints we now include additional information to help us diagnose issues: The operating system version, the segmentation queries version that is being executed as well as information on the Application (version & identifier). * Limit deferred functions upon startup **(New)** While the SDK is initialising, we defer execution of public function calls until this has completed. This was previously unlimited, we have put in a cap so that this does not become unbounded. * Add pagetracking API & deprecate context APIs **(New)** The PageTracker API gives support for Pageview and PageviewEngagementAggregate events. The page's context (url/title/referrer) are now tied to a Page, rather than a global Permutive setting. Using the PageTracker API will allow for better reporting & better visibility of engagement on the Android SDK. As a result, we would recommend using PageTracker rather than EventTracker for page related events. As a general rule: if an event has a url/title/referrer - it should probably be tracked by the PageTracker object. The global context methods setTitle/setUrl/setReferrer are now deprecated as a result, and we have stopped persisting these values. * Fix illegal state in db when reading **(Fix)** A small percentage (around 0.5%) of users have reported an IllegalStateException under certain circumstances. Previously we would halt segmentation & disable the SDK for these users, for 1.4.0 we have a fix so that this will no longer happen. * Fix regex matching when disabling **(Fix)** We have made our regular expression matching more straightforward in the event that we have to disable a deployment. * Fix consumer proguard typo **(Fix)** Some setups of proguard have stripped away functions that we are using internally. Although we had a @Keep clause on the function, it is still possible that it can be stripped away. We have updated our consumer-proguard so that this is fixed. * fix issue when migrating from older versions Upgrading from older versions of this SDK can result in a migration issue, we recommend using version 1.3.1 which has a fix for this issue. * Add ability to remotely disable by application, os, or sdk version **(New)** * Remove dependency on core library for google ads library **(New)** * Add third party data usage reporting support **(New)** * Make engine events session\_id & user\_id optional **(Fix)** * Fix stack size issue for Dalvik VMs **(Fix)** Segmentation may not occur for OSes using Dalvik VMs, we recommend using version 1.2.0 which has a fix for this issue. * Use Rhino as the js engine **(New)** * Non-beta release of the Android SDK * Correctly handle date time when the format is incorrect **(Fix)** * Reduce memory overhead for db so that it performs well on low end systems **(Fix)** * Only update event server time when posting events **(Fix)** # Troubleshooting Source: https://docs.permutive.com/sdks/mobile/android/troubleshooting/common-errors Solutions to common issues when integrating the Permutive Android SDK This guide covers common issues you may encounter when integrating the Permutive Android SDK and how to resolve them. **Quick Diagnostics:** Enable debug logging before troubleshooting: ```kotlin theme={"dark"} permutive.setDeveloperMode(true) ``` Or via ADB: ```bash theme={"dark"} adb shell setprop log.tag.Permutive VERBOSE adb logcat -s Permutive ``` ## Initialization Issues **Symptoms:** ``` D/Permutive: Error fetching configuration - please check that your workspace id & API key is correct, or contact customer support (Http error: 404) ``` **Causes:** 1. Incorrect Workspace ID or API Key 2. Using deprecated `projectId` instead of `workspaceId` 3. Android not enabled for your workspace **Solutions:** **1. Verify Credentials** ```kotlin theme={"dark"} // ❌ Wrong: Using deprecated projectId val permutive = Permutive.Builder() .projectId(UUID.fromString("...")) // DEPRECATED .build() // ✅ Correct: Use workspaceId val permutive = Permutive( context = this, workspaceId = UUID.fromString("YOUR_WORKSPACE_ID"), apiKey = UUID.fromString("YOUR_API_KEY") ) ``` **2. Check Credentials Format** Ensure your IDs are valid UUIDs (format: 8-4-4-4-12 hexadecimal digits): ```kotlin theme={"dark"} // Example: "550e8400-e29b-41d4-a716-446655440000" // ❌ Wrong: Not a UUID workspaceId = UUID.fromString("12345") // Throws exception // ✅ Correct: Valid UUID workspaceId = UUID.fromString("550e8400-e29b-41d4-a716-446655440000") ``` **3. Enable Android** Contact your Customer Success Manager to verify Android is enabled for your workspace. **Symptoms:** Events tracked multiple times, undefined behavior. **Cause:** Creating more than one `Permutive` instance. **Solution:** Use a singleton pattern: ```kotlin Kotlin theme={"dark"} class MyApplication : Application() { val permutive by lazy { Permutive( context = this, workspaceId = YOUR_WORKSPACE_ID, apiKey = YOUR_API_KEY ) } } // Access from activities class MyActivity : AppCompatActivity() { private val permutive by lazy { (application as MyApplication).permutive } } ``` ```java Java theme={"dark"} public class MyApplication extends Application { private volatile Permutive permutive; public Permutive getPermutive() { if (permutive == null) { synchronized (this) { if (permutive == null) { permutive = new Permutive.Builder() .context(this) .workspaceId(YOUR_WORKSPACE_ID) .apiKey(YOUR_API_KEY) .build(); } } } return permutive; } } ``` ## Event Tracking Issues **Symptoms:** ``` D/Permutive: Error publishing event with name "Pageview": D/Permutive: Code: 1007 D/Permutive: Status: BadRequest D/Permutive: Cause: Schema validation failed with reason(s): [#: extraneous key [publishDate] is not permitted;#/article/categories: expected type: JSONArray, found: String] ``` **Causes:** 1. Property name doesn't match dashboard schema 2. Property type doesn't match dashboard schema 3. Property not defined in dashboard **Solutions:** **1. Check Property Names** (case-sensitive): ```kotlin theme={"dark"} // ❌ Wrong: Typo in property name EventProperties.from("publishDate" to date) // Schema expects "published_at" // ✅ Correct: Exact match with schema EventProperties.from("published_at" to date) ``` **2. Check Property Types:** ```kotlin theme={"dark"} // ❌ Wrong: String instead of Array EventProperties.from("categories" to "sports") // Schema expects List // ✅ Correct: Array type EventProperties.from("categories" to listOf("sports", "football")) ``` **3. Verify Schema in Dashboard:** 1. Log into Permutive dashboard 2. Navigate to Events 3. Find your event type 4. Verify property names and types match your code **Symptoms:** Events show as "Accepted" in logs but don't appear in dashboard. **Causes:** 1. Dashboard filter settings 2. Time lag (events can take up to 5 minutes) 3. Wrong workspace/environment **Solutions:** 1. **Check Logs**: Verify events are accepted: ``` D/Permutive: Published events with names (Pageview) (Accepted: 1 / 1) ``` 2. **Wait**: Events may take up to 5 minutes to appear 3. **Check Dashboard Filters**: * Verify date range includes now * Check workspace selection * Clear any active filters 4. **Verify Environment**: Ensure you're using production workspace ID **Symptoms:** ``` IllegalArgumentException: Invalid event name "My Event": must contain only the characters [a-zA-Z0-9_] ``` **Cause:** Event name contains invalid characters. **Solution:** Event names must contain only: `a-z`, `A-Z`, `0-9`, `_` ```kotlin theme={"dark"} // ❌ Wrong: Contains spaces and special characters tracker.track("My Event!", properties) tracker.track("page-view", properties) // ✅ Correct: Only valid characters tracker.track("MyEvent", properties) tracker.track("page_view", properties) tracker.track("PageView123", properties) ``` ## Identity Issues **Symptoms:** Setting identity doesn't merge user data from other devices. **Causes:** 1. Network connectivity issues 2. Alias not previously used 3. Timing - resolution takes a moment **Solutions:** 1. **Check Logs**: ``` D/Permutive: Identified user with aliases: email_sha256, internal_id ``` 2. **Verify Network**: Ensure device has connectivity 3. **Wait**: Identity resolution isn't instant, allow a few seconds 4. **Debug**: ```kotlin theme={"dark"} permutive.setDeveloperMode(true) permutive.setIdentity( listOf(Alias.create("email_sha256", emailHash, 0)) ) // Check logs for "Identified user" message ``` **Problem:** Accidentally sending personally identifiable information. **Solution:** Always hash PII before setting as identity: ```kotlin theme={"dark"} import java.security.MessageDigest fun hashEmail(email: String): String { val normalized = email.trim().lowercase() val bytes = MessageDigest.getInstance("SHA-256") .digest(normalized.toByteArray()) return bytes.joinToString("") { "%02x".format(it) } } // ❌ Wrong: Plain text email permutive.setIdentity( listOf(Alias.create("email", "user@example.com", 0)) // DON'T! ) // ✅ Correct: Hashed email val emailHash = hashEmail("user@example.com") permutive.setIdentity( listOf(Alias.create("email_sha256", emailHash, 0)) ) ``` ## Cohort and Activation Issues **Symptoms:** `currentCohorts` returns empty list. **Causes:** 1. SDK just initialized - cohorts haven't synced yet 2. User hasn't triggered events to qualify for cohorts 3. Network issues **Solutions:** 1. **Wait for Initialization**: ```kotlin theme={"dark"} // Don't check cohorts immediately Handler(Looper.getMainLooper()).postDelayed({ val cohorts = permutive.currentCohorts Log.d("Debug", "Cohorts: ${cohorts.size}") }, 3000) ``` 2. **Use TriggersProvider**: For reactive updates: ```kotlin theme={"dark"} val trigger = triggersProvider.triggerAction("cohort_id") { isInCohort -> // Called when cohort status changes } ``` 3. **Track Events**: User needs to trigger events to qualify for cohorts **Symptoms:** `dfp_contextual` or `appnexus_adserver_contextual` are empty. **Causes:** 1. Feature not enabled 2. SDK version too old 3. Not tracking pages with URLs 4. Classification in progress **Solutions:** 1. **Check SDK Version**: * Core: 1.10.0+ * Google Ads: 2.2.0+ * AppNexus: 1.7.0+ 2. **Verify Feature Enabled**: Contact Customer Success Manager 3. **Track with URLs**: ```kotlin theme={"dark"} // ❌ Wrong: No URL val pageTracker = permutive.trackPage(title = "Article Title") // ✅ Correct: Include URL val pageTracker = permutive.trackPage( title = "Article Title", url = Uri.parse("https://example.com/article") ) ``` 4. **Wait for Classification**: First analysis takes 1-2 seconds 5. **Check URL Accessibility**: URL must be publicly accessible ## Ad Integration Issues **Symptoms:** Ads load but don't seem to be targeted. **Causes:** 1. Not using add-on library helper methods 2. Cohorts not ready when ad request made 3. Dashboard activation configuration **Solutions:** **1. Use Helper Methods** ```kotlin theme={"dark"} // ❌ Wrong: Manual ad request without Permutive val adRequest = AdManagerAdRequest.Builder().build() // ✅ Correct: Use Permutive helper val adRequest = AdManagerAdRequest.Builder() .addPermutiveTargeting(permutive) .build() ``` **2. Verify Logs** Look for: ``` D/Permutive: Permutive segments appended to Google Ad request: 123, 456, 789 ``` If this doesn't appear, cohorts aren't being added. **3. Check Activations** ```kotlin theme={"dark"} val activations = permutive.currentActivations val gamCohorts = activations["dfp"] if (gamCohorts.isNullOrEmpty()) { Log.w("Debug", "No GAM activations available") // Either cohorts not ready or not activated for GAM } ``` ## Performance Issues **Symptoms:** App performance degrades after SDK integration. **Causes:** 1. Creating multiple Permutive instances 2. Not closing PageTrackers/MediaTrackers 3. Memory leaks from unclosed TriggerActions **Solutions:** **1. Use Singleton** (see "Multiple Permutive instances detected" above) **2. Always Close Trackers** ```kotlin theme={"dark"} class MyActivity : AppCompatActivity() { private lateinit var pageTracker: PageTracker override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) pageTracker = permutive.trackPage(...) } override fun onDestroy() { super.onDestroy() pageTracker.close() // IMPORTANT! } } ``` **3. Close Triggers** ```kotlin theme={"dark"} class MyActivity : AppCompatActivity() { private val triggers = mutableListOf() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) triggers.add(triggersProvider.triggerAction("cohort") { /*...*/ }) } override fun onDestroy() { super.onDestroy() triggers.forEach { it.close() } // IMPORTANT! triggers.clear() } } ``` **Symptoms:** App memory usage grows over time. **Cause:** Not closing SDK resources. **Solution:** Use Android Studio's Profiler to identify leaks, then ensure all SDK resources are closed: * `PageTracker.close()` * `MediaTracker.stop()` * `AdTracker.completion()` * `TriggerAction.close()` ## Build Issues **Symptoms:** App crashes in release build with minification enabled. **Cause:** SDK classes being stripped by ProGuard/R8. **Solution:** The SDK includes consumer ProGuard rules automatically. If you're still experiencing issues: 1. **Verify consumer rules are applied**: ```groovy theme={"dark"} android { buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } ``` 2. **Test with debug logging**: ```kotlin theme={"dark"} if (BuildConfig.DEBUG) { permutive.setDeveloperMode(true) } ``` **Symptoms:** ``` Duplicate class com.permutive.android.X found in modules... ``` **Causes:** 1. Multiple SDK versions in dependencies 2. Transitive dependency conflicts **Solutions:** **1. Check Dependencies** ```bash theme={"dark"} ./gradlew app:dependencies ``` Look for multiple versions of Permutive libraries. **2. Force Version Resolution** ```kotlin theme={"dark"} configurations.all { resolutionStrategy { force("com.permutive.android:core:1.12.0") force("com.permutive.android:google-ads:2.2.0") } } ``` ## Debugging Tips ```kotlin theme={"dark"} // Enable Permutive debug logs permutive.setDeveloperMode(true) // Enable network logging (if using OkHttp) val loggingInterceptor = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } ``` ```kotlin theme={"dark"} // Log SDK version Log.d("Debug", "Permutive SDK Version: ${BuildConfig.VERSION_NAME}") ``` ```kotlin theme={"dark"} val metrics = permutive.currentMetrics Log.d("Debug", "SDK Metrics: $metrics") ``` ## Getting Help If you're still experiencing issues: 1. Enable debug logging and capture logs 2. Note the SDK version you're using 3. Document steps to reproduce the issue 4. Gather SDK version, device info, and error logs **Contact Support:** * [Support](mailto:support@permutive.com) Comprehensive integration verification Upgrade to the latest SDK version # Cohorts and Activations Source: https://docs.permutive.com/sdks/mobile/ios/core-concepts/cohorts-and-activations Understanding user segmentation and platform-specific targeting Understanding cohorts and activations is fundamental to using the Permutive SDK effectively. ## What are Cohorts? ### Cohort Types ## What are Activations? ### Available Activation Types ## Cohorts vs. Activations In the SDK, access cohorts via `cohorts` and activations via `activations`. **Example:** ``` User's Cohorts: ["sports_enthusiast", "premium_subscriber", "mobile_user", "frequent_visitor"] Activations: - dfp: ["sports_enthusiast", "premium_subscriber"] // Only 2 activated for GAM - freewheel: ["sports_enthusiast"] // Only 1 activated for Freewheel ``` ## Accessing Cohorts and Activations Get all cohorts the user currently belongs to: ```swift Swift theme={"dark"} let currentCohorts: Set = Permutive.shared.cohorts for cohortId in currentCohorts { print("User is in cohort: \(cohortId)") } ``` ```objectivec Objective-C theme={"dark"} NSSet *currentCohorts = [Permutive.shared cohorts]; for (NSString *cohortId in currentCohorts) { NSLog(@"User is in cohort: %@", cohortId); } ``` Get cohorts activated for specific platforms: ```swift Swift theme={"dark"} let activations: [String: [String]] = Permutive.shared.activations // Get Google Ad Manager activations let gamActivations = activations["dfp"] ?? [] // Get AppNexus activations let appNexusActivations = activations["appnexus_adserver"] ?? [] // Get Freewheel activations let freewheelActivations = activations["freewheel"] ?? [] // Get contextual activations let gamContextual = activations["dfp_contextual"] ?? [] ``` ```objectivec Objective-C theme={"dark"} NSDictionary *> *activations = [Permutive.shared activations]; // Get Google Ad Manager activations NSArray *gamActivations = activations[@"dfp"] ?: @[]; // Get AppNexus activations NSArray *appNexusActivations = activations[@"appnexus_adserver"] ?: @[]; ``` For real-time cohort changes, use `TriggerProvider`: ```swift Swift theme={"dark"} var trigger: TriggerAction? func watchCohort() { trigger = Permutive.shared.triggerProvider?.action( boolFor: ["premium_subscriber"], action: { query, isInCohort in if isInCohort { self.showPremiumFeatures() } else { self.hidePremiumFeatures() } } ) } // Release when done deinit { trigger = nil } ``` ```objectivec Objective-C theme={"dark"} @property (nonatomic, strong) PermutiveTriggerAction *trigger; - (void)watchCohort { NSSet *cohorts = [NSSet setWithObject:@"premium_subscriber"]; self.trigger = [Permutive.shared.triggerProvider actionWithBoolFor:cohorts action:^(NSString *cohort, BOOL isInCohort) { if (isInCohort) { [self showPremiumFeatures]; } else { [self hidePremiumFeatures]; } }]; } ``` Complete documentation for reactive updates `cohorts` and `activations` return **snapshot values** at the time they're called. For real-time updates, use TriggerProvider. ## Use Cases ```swift Swift theme={"dark"} class PersonalizedHomeViewController: UIViewController { func personalizeContent() { let cohorts = Permutive.shared.cohorts if cohorts.contains("premium_subscriber") { showPremiumContent() } else if cohorts.contains("sports_enthusiast") { showSportsRecommendations() } else if cohorts.contains("tech_reader") { showTechRecommendations() } else { showGeneralContent() } } } ``` ```objectivec Objective-C theme={"dark"} - (void)personalizeContent { NSSet *cohorts = [Permutive.shared cohorts]; if ([cohorts containsObject:@"premium_subscriber"]) { [self showPremiumContent]; } else if ([cohorts containsObject:@"sports_enthusiast"]) { [self showSportsRecommendations]; } else if ([cohorts containsObject:@"tech_reader"]) { [self showTechRecommendations]; } else { [self showGeneralContent]; } } ``` ```swift theme={"dark"} class ExperimentalFeatureViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let cohorts = Permutive.shared.cohorts // Use cohort membership as feature flag let enableNewCheckout = cohorts.contains("checkout_experiment_v2") if enableNewCheckout { setupNewCheckoutUI() } else { setupClassicCheckoutUI() } } } ``` Activations are used with ad targeting: ```swift Swift theme={"dark"} // Google Ad Manager let adRequest = GAMRequest() adRequest.customTargeting = Permutive.shared.googleCustomTargeting( adTargetable: pageTracker ) bannerView.load(adRequest) ``` ```objectivec Objective-C theme={"dark"} // Google Ad Manager GAMRequest *adRequest = [GAMRequest request]; adRequest.customTargeting = [Permutive.shared googleCustomTargetingWithAdTargetable:self.pageTracker]; [self.bannerView loadRequest:adRequest]; ``` ```swift theme={"dark"} class AnalyticsHelper { func logUserSegments() { let cohorts = Permutive.shared.cohorts // Log to your analytics platform Analytics.setUserProperty( "permutive_cohorts", value: cohorts.joined(separator: ",") ) Analytics.logEvent( "cohort_count", parameters: ["count": cohorts.count] ) } func getActivationInfo() -> [String: Any] { let activations = Permutive.shared.activations return [ "gam_cohort_count": activations["dfp"]?.count ?? 0, "appnexus_cohort_count": activations["appnexus_adserver"]?.count ?? 0, "has_contextual": (activations["dfp_contextual"]?.isEmpty == false) ] } } ``` ## Contextual Cohorts Contextual cohorts are generated in real-time based on content being viewed. They require SDK version 2.0.0+ and feature enablement by your CSM. ```swift Swift theme={"dark"} // Track a page with URL let properties = try? EventProperties([ "category": "automotive", "subcategory": "electric_vehicles" ]) let context = Context( title: "Electric Vehicle Buying Guide", url: URL(string: "https://example.com/articles/ev-buying-guide"), referrer: nil ) let pageTracker = try? Permutive.shared.createPageTracker( properties: properties, context: context ) try? pageTracker?.resume() // Contextual cohorts appear in activations let contextualCohorts = Permutive.shared.activations["dfp_contextual"] ?? [] // Example: ["automotive_ev", "automotive_buying_guides", "sustainability"] // Used automatically in ad requests let adRequest = GAMRequest() adRequest.customTargeting = Permutive.shared.googleCustomTargeting( adTargetable: pageTracker ) ``` ```objectivec Objective-C theme={"dark"} // Track a page with URL NSError *error = nil; PermutiveEventProperties *properties = [[PermutiveEventProperties alloc] init:@{@"category": @"automotive", @"subcategory": @"electric_vehicles"} error:&error]; PermutiveContext *context = [[PermutiveContext alloc] initWithTitle:@"Electric Vehicle Buying Guide" url:[NSURL URLWithString:@"https://example.com/articles/ev-buying-guide"] referrer:nil]; NSObject *pageTracker = [Permutive.shared createPageTrackerWithProperties:properties context:context error:&error]; [pageTracker resumeAndReturnError:nil]; // Contextual cohorts appear in activations NSArray *contextualCohorts = [Permutive.shared activations][@"dfp_contextual"] ?: @[]; ``` Complete contextual cohorts documentation ## tvOS Considerations **tvOS Note:** Cohorts and activations work identically on tvOS. The same APIs are available, and cohort data syncs across all Apple platforms when identity is set. ## Troubleshooting **Problem:** `cohorts` returns an empty set. **Solutions:** * Wait a few seconds after initialization * Track some events to generate data * Use TriggerProvider for reactive updates * Enable debug logging to see sync status * Verify cohorts are configured in your dashboard **Problem:** User is in a cohort but it doesn't appear in activations. **Cause:** Not all cohorts are activated for all platforms. This is configured in your Permutive dashboard. **Solution:** Check your dashboard configuration or contact your Customer Success Manager. **Problem:** `dfp_contextual` activations are empty. **Solutions:** * Verify feature is enabled with your CSM * Update to SDK 2.0.0+ * Ensure you're using PageTracker with valid URLs * Verify the URL is publicly accessible * Check debug logs for classification errors ## Best Practices * Use `cohorts` for one-time checks * Use TriggerProvider for reactive updates * Check activation keys exist before accessing * Handle empty sets gracefully * Log cohorts for debugging (in development only) * Cache cohort checks that are expensive to re-evaluate * Poll `cohorts` repeatedly (use TriggerProvider instead) * Assume cohorts will be available immediately after initialization * Hard-code cohort IDs without checking dashboard * Share PII in cohort names or IDs * Cache cohorts for long periods (they can change) ## Related Documentation Reactive cohort updates Content-based segmentation GAM integration AppNexus integration # Contextual Data Source: https://docs.permutive.com/sdks/mobile/ios/core-concepts/contextual-data Real-time content-based segmentation for privacy-friendly targeting Contextual data enables real-time content-based segmentation without relying on historical user behavior. The SDK analyzes content users are viewing and generates contextual cohorts instantly. ## Overview In the iOS SDK, contextual cohorts are generated in real-time when you track pages or videos with URLs. The SDK automatically includes these cohorts in ad requests alongside behavioral cohorts. ## Requirements * **SDK Version:** 2.0.0 or higher * **Platform:** iOS 12.0+ / tvOS 12.0+ * **Feature Enablement:** Contact your Customer Success Manager **Feature Activation Required:** Contextual content classification must be enabled by Permutive for your workspace. Please contact your Customer Success Manager if you'd like to use this feature. ## How It Works Use `PageTracker` with a content URL Permutive analyzes the content at the URL (page title, main text, keywords, IAB categories, sentiment) Content-based segments are created in real-time Contextual cohorts automatically included in ad requests *** ## Implementation ### Page Tracking with Contextual Data Simply include URLs when tracking pages - contextual analysis happens automatically: ```swift Swift theme={"dark"} import UIKit import Permutive_iOS class ArticleViewController: UIViewController { private var pageTracker: PageTrackerProtocol? override func viewDidLoad() { super.viewDidLoad() // Create event properties let properties = try? EventProperties([ "category": "automotive", "subcategory": "electric_vehicles", "author": "Jane Smith" ]) // Track page with URL - contextual analysis automatic let context = Context( title: "Complete Guide to Electric Vehicles", url: URL(string: "https://example.com/articles/electric-vehicle-guide"), referrer: URL(string: "https://example.com/automotive") ) pageTracker = try? Permutive.shared.createPageTracker( properties: properties, context: context ) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) try? pageTracker?.resume() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) pageTracker?.pause() } deinit { pageTracker?.stop() } } ``` ```objectivec Objective-C theme={"dark"} #import "ArticleViewController.h" @import Permutive_iOS; @interface ArticleViewController () @property (nonatomic, strong) NSObject *pageTracker; @end @implementation ArticleViewController - (void)viewDidLoad { [super viewDidLoad]; // Create event properties NSError *error = nil; PermutiveEventProperties *properties = [[PermutiveEventProperties alloc] init:@{ @"category": @"automotive", @"subcategory": @"electric_vehicles", @"author": @"Jane Smith" } error:&error]; // Track page with URL - contextual analysis automatic PermutiveContext *context = [[PermutiveContext alloc] initWithTitle:@"Complete Guide to Electric Vehicles" url:[NSURL URLWithString:@"https://example.com/articles/electric-vehicle-guide"] referrer:[NSURL URLWithString:@"https://example.com/automotive"]]; self.pageTracker = [Permutive.shared createPageTrackerWithProperties:properties context:context error:&error]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self.pageTracker resumeAndReturnError:nil]; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [self.pageTracker pause]; } - (void)dealloc { [self.pageTracker stop]; } @end ``` *** ## Accessing Contextual Cohorts Contextual cohorts are automatically included in activations with special keys: ### In Current Activations ```swift Swift theme={"dark"} let activations = Permutive.shared.activations // Google Ad Manager contextual cohorts let gamContextual = activations["dfp_contextual"] ?? [] // Example: ["automotive_ev", "automotive_reviews", "tech_innovation"] // Xandr/AppNexus contextual cohorts let xandrContextual = activations["appnexus_adserver_contextual"] ?? [] // Log contextual cohorts print("GAM contextual cohorts: \(gamContextual)") ``` ```objectivec Objective-C theme={"dark"} NSDictionary *activations = [Permutive.shared activations]; // Google Ad Manager contextual cohorts NSArray *gamContextual = activations[@"dfp_contextual"] ?: @[]; // Xandr/AppNexus contextual cohorts NSArray *xandrContextual = activations[@"appnexus_adserver_contextual"] ?: @[]; NSLog(@"GAM contextual cohorts: %@", gamContextual); ``` ### In Ad Requests Contextual cohorts are automatically included when using `googleCustomTargeting`: ```swift Swift theme={"dark"} // Contextual cohorts automatically added with key "prmtvctx" let adRequest = GAMRequest() adRequest.customTargeting = Permutive.shared.googleCustomTargeting( adTargetable: pageTracker // Include PageTracker for view ID ) bannerView.load(adRequest) // The resulting ad request includes: // - permutive: ["123", "456"] // Behavioral cohorts // - prmtvctx: ["cabf", "cabc", "bzwu"] // Contextual cohorts // - puid: "user_id" // - ptime: "timestamp" // - prmtvvid: "view_id" ``` ```objectivec Objective-C theme={"dark"} // Contextual cohorts automatically added GAMRequest *adRequest = [GAMRequest request]; adRequest.customTargeting = [Permutive.shared googleCustomTargetingWithAdTargetable:self.pageTracker]; [self.bannerView loadRequest:adRequest]; ``` *** ## Performance Considerations ### Content Analysis Timing * **First analysis:** May take 1-2 seconds for initial content fetch and analysis * **Cached results:** Subsequent views of the same URL use cached analysis * **Background processing:** Analysis happens asynchronously, doesn't block UI * **Network required:** Content classification requires network connectivity ### Best Practices for Performance ```swift Swift theme={"dark"} // ✅ Good: Create PageTracker early, analysis starts immediately override func viewDidLoad() { super.viewDidLoad() // Start tracking (and analysis) as early as possible pageTracker = try? Permutive.shared.createPageTracker( properties: properties, context: context ) // Load your content loadArticleContent() } ``` ```swift Swift theme={"dark"} // ❌ Avoid: Delaying page tracking override func viewDidLoad() { super.viewDidLoad() loadArticleContent() // Tracking delayed - contextual cohorts may not be ready for early ads DispatchQueue.main.asyncAfter(deadline: .now() + 5) { self.pageTracker = try? Permutive.shared.createPageTracker(...) } } ``` *** ## Privacy and Compliance ### Privacy-Friendly Targeting ### Data Sent for Analysis When tracking a page with a URL: * **URL** - The page URL for content fetching * **Title** - Page title (if provided) * **No PII** - No personal information sent for classification The URL should be **publicly accessible** for content analysis to work. *** ## Error Handling ### Classification Failures Contextual classification may fail if: * URL is not publicly accessible * Content is behind a paywall or login * Network connectivity issues * Server-side analysis errors **The SDK handles failures gracefully:** ```swift Swift theme={"dark"} // If contextual classification fails: // 1. Behavioral cohorts still work // 2. Events still tracked // 3. No error surfaced to app // 4. Contextual activations will be empty let contextualCohorts = Permutive.shared.activations["dfp_contextual"] ?? [] if contextualCohorts.isEmpty { // This is OK - may be: // - Feature not enabled // - Classification in progress // - Classification failed // - No URL tracked yet // Behavioral targeting still works let behavioralCohorts = Permutive.shared.activations["dfp"] ?? [] } ``` ```objectivec Objective-C theme={"dark"} NSArray *contextualCohorts = [Permutive.shared activations][@"dfp_contextual"] ?: @[]; if (contextualCohorts.count == 0) { // Behavioral targeting still works NSArray *behavioralCohorts = [Permutive.shared activations][@"dfp"] ?: @[]; } ``` ### Debugging Classification Enable debug logging to see classification status: ```swift theme={"dark"} options.logModes = LogMode.all // Look for logs like: // Permutive: [info] Contextual classification requested for URL: https://... // Permutive: [info] Contextual cohorts received: [cohort1, cohort2, ...] // Permutive: [error] Contextual classification failed: ``` *** ## Use Cases ### Content-Aligned Advertising ```swift Swift theme={"dark"} class ArticleViewController: UIViewController { private var pageTracker: PageTrackerProtocol? override func viewDidLoad() { super.viewDidLoad() // Track article page let context = Context( title: article.title, url: URL(string: article.url), referrer: nil ) pageTracker = try? Permutive.shared.createPageTracker( properties: nil, context: context ) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) try? pageTracker?.resume() // Load ad - contextual cohorts automatically included loadAd() } private func loadAd() { // Contextual cohorts ensure ad is relevant to article content let adRequest = GAMRequest() adRequest.customTargeting = Permutive.shared.googleCustomTargeting( adTargetable: pageTracker ) bannerView.load(adRequest) } } ``` ```objectivec Objective-C theme={"dark"} - (void)viewDidLoad { [super viewDidLoad]; PermutiveContext *context = [[PermutiveContext alloc] initWithTitle:self.article.title url:[NSURL URLWithString:self.article.url] referrer:nil]; self.pageTracker = [Permutive.shared createPageTrackerWithProperties:nil context:context error:nil]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self.pageTracker resumeAndReturnError:nil]; [self loadAd]; } - (void)loadAd { GAMRequest *adRequest = [GAMRequest request]; adRequest.customTargeting = [Permutive.shared googleCustomTargetingWithAdTargetable:self.pageTracker]; [self.bannerView loadRequest:adRequest]; } ``` ### Contextual + Behavioral Targeting Combine contextual and behavioral cohorts for powerful targeting: ```swift theme={"dark"} let activations = Permutive.shared.activations // Behavioral: User interests over time let behavioral = activations["dfp"] ?? [] // Example: ["sports_enthusiast", "premium_subscriber"] // Contextual: Current content let contextual = activations["dfp_contextual"] ?? [] // Example: ["sports_soccer", "sports_worldcup"] // Both automatically included in ad requests let adRequest = GAMRequest() adRequest.customTargeting = Permutive.shared.googleCustomTargeting( adTargetable: pageTracker ) // Ad platform receives: // - permutive: ["sports_enthusiast", "premium_subscriber"] // - prmtvctx: ["sports_soccer", "sports_worldcup"] // // Result: Highly targeted ad for soccer content to a sports enthusiast ``` *** ## Contextual vs. Behavioral Cohorts In the SDK, contextual cohorts use activation keys `dfp_contextual` and `appnexus_adserver_contextual`, while behavioral cohorts use `dfp` and `appnexus_adserver`. ### When to Use Each **Use Behavioral Cohorts When:** * Building long-term audience segments * Retargeting campaigns * Personalization based on user history * Lookalike modeling **Use Contextual Cohorts When:** * Real-time content alignment needed * Privacy regulations are strict * New users without history * Brand safety is critical * Cookie-less environment **Use Both When:** * Maximum targeting precision needed * Combining user intent (contextual) with user interests (behavioral) * Premium inventory requires both signals *** ## tvOS Considerations **tvOS Note:** Contextual data works identically on tvOS. Provide URLs in the Context when creating PageTrackers for content analysis. *** ## Troubleshooting **Problem:** `dfp_contextual` is empty or not present. **Possible Causes & Solutions:** 1. **Feature not enabled** * Contact Customer Success Manager to enable 2. **SDK version too old** * Update to SDK 2.0.0+ 3. **No URL tracked** * Ensure you're using `PageTracker` with a Context that has a valid URL 4. **Classification in progress** * First analysis may take 1-2 seconds * Check again after a moment 5. **URL not accessible** * Ensure URL is publicly accessible * Remove authentication requirements for analysis 6. **Network issues** * Check device connectivity * Look for network errors in logs **Problem:** Contextual cohorts not appearing before ads load. **Solutions:** * Start page tracking earlier in view lifecycle * Consider delaying ad request slightly if contextual is critical * Use behavioral cohorts as fallback **Problem:** Cohorts don't match content. **Possible Causes:** * URL points to different content than displayed * Content changed since classification * Cache from previous analysis **Solutions:** * Ensure URL matches actual content * Use unique URLs for different content * Wait for cache expiry or contact support to invalidate *** ## Best Practices ### Do * Track pages with URLs as early as possible * Use publicly accessible URLs * Ensure URLs are stable and won't change * Test with debug logging enabled * Use unique URLs for different content * Combine with behavioral targeting for best results ### Don't * Use authentication-protected URLs * Track pages without URLs (contextual won't work) * Expect instant results (allow 1-2 seconds) * Rely solely on contextual for returning users * Use duplicate URLs for different content *** ## Related Documentation Understanding user segmentation PageTracker guide GAM integration AppNexus integration # Event Properties Source: https://docs.permutive.com/sdks/mobile/ios/core-concepts/event-properties Structure and validate event metadata Event properties allow you to attach metadata to events for richer segmentation and targeting. The iOS SDK uses the `EventProperties` class to encapsulate this data with type safety. ## Creating Properties `EventProperties` wraps a dictionary with type validation. Invalid types throw an exception on construction. ```swift Swift theme={"dark"} do { let properties = try EventProperties([ "category": "sports", "author": "John Doe", "published": true, "word_count": 1500 ]) try Permutive.shared.track(event: "ArticleView", properties: properties) } catch { print("Failed to create properties: \(error)") } ``` ```objectivec Objective-C theme={"dark"} NSError *error = nil; PermutiveEventProperties *properties = [[PermutiveEventProperties alloc] init:@{ @"category": @"sports", @"author": @"John Doe", @"published": @YES, @"word_count": @1500 } error:&error]; if (error != nil) { NSLog(@"Failed to create properties: %@", error); return; } [Permutive.shared trackWithEvent:@"ArticleView" properties:properties error:&error]; ``` ## Supported Types `EventProperties` supports the following value types: | Type | Swift | Objective-C | Example | | ------------- | ----------------- | -------------------------- | ------------ | | String | `String` | `NSString` | `"sports"` | | Integer | `Int` | `NSNumber` | `42` | | Float | `Float`, `Double` | `NSNumber` | `3.14` | | Boolean | `Bool` | `NSNumber` | `true` | | Date | `Date` | `NSDate` | `Date()` | | Nested Object | `EventProperties` | `PermutiveEventProperties` | See below | | Array | `[T]` | `NSArray` | `["a", "b"]` | ### Nested Objects ```swift Swift theme={"dark"} let author = try EventProperties([ "name": "Jane Smith", "id": "author_123" ]) let properties = try EventProperties([ "title": "Article Title", "author": author // Nested EventProperties ]) ``` ```objectivec Objective-C theme={"dark"} PermutiveEventProperties *author = [[PermutiveEventProperties alloc] init:@{@"name": @"Jane Smith", @"id": @"author_123"} error:nil]; PermutiveEventProperties *properties = [[PermutiveEventProperties alloc] init:@{@"title": @"Article Title", @"author": author} error:nil]; ``` ### Arrays ```swift Swift theme={"dark"} let properties = try EventProperties([ "tags": ["technology", "mobile", "ios"], "category_ids": [1, 2, 3], "featured": true ]) ``` ```objectivec Objective-C theme={"dark"} PermutiveEventProperties *properties = [[PermutiveEventProperties alloc] init:@{ @"tags": @[@"technology", @"mobile", @"ios"], @"category_ids": @[@1, @2, @3], @"featured": @YES } error:nil]; ``` ## Event Enrichment Events can be enriched with server-side data using special property values: ### Available Enrichment Values | Value | Description | | ----------------------------------------------- | ------------------------- | | `EventProperties.geoInfoValue` | Geographic location data | | `EventProperties.ispInfoValue` | ISP information | | `EventProperties.ipHashInfoValue` | Hashed IP address | | `EventProperties.alchemyConceptsValue` | Watson NLU concepts | | `EventProperties.alchemyEntitiesValue` | Watson NLU entities | | `EventProperties.alchemyKeywordsValue` | Watson NLU keywords | | `EventProperties.alchemyTaxonomyValue` | Watson NLU taxonomy | | `EventProperties.alchemyDocumentEmotionValue` | Watson document emotion | | `EventProperties.alchemyDocumentSentimentValue` | Watson document sentiment | | `EventProperties.alchemyTaxonomyLabelsValue` | Watson taxonomy labels | | `EventProperties.alchemyEntityNamesValue` | Watson entity names | ### Location and ISP Enrichment ```swift Swift theme={"dark"} do { let properties = try EventProperties([ "geo_info": EventProperties.geoInfoValue, "isp_info": EventProperties.ispInfoValue, "form_id": "contact_form" ]) try Permutive.shared.track(event: "FormSubmission", properties: properties) } catch { print("Error: \(error)") } ``` ```objectivec Objective-C theme={"dark"} PermutiveEventProperties *properties = [PermutiveEventProperties new]; [properties setValue:PermutiveEventProperties.geoInfoValue forKey:@"geo_info"]; [properties setValue:PermutiveEventProperties.ispInfoValue forKey:@"isp_info"]; [properties setValue:@"contact_form" forKey:@"form_id"]; NSError *error = nil; [Permutive.shared trackWithEvent:@"FormSubmission" properties:properties error:&error]; ``` ### Watson Content Analysis (Standard Cohorts) For Standard Cohorts support, include Watson taxonomy labels: ```swift Swift theme={"dark"} // Create nested taxonomy structure let urlTaxonomy = try EventProperties([ "taxonomy_labels": EventProperties.alchemyTaxonomyLabelsValue ]) let properties = try EventProperties([ "classifications_watson": urlTaxonomy ]) // Create PageTracker with these properties let context = Context( title: "Article Title", url: URL(string: "https://example.com/article"), referrer: nil ) let pageTracker = try Permutive.shared.createPageTracker( properties: properties, context: context ) ``` ```objectivec Objective-C theme={"dark"} NSError *error = nil; PermutiveEventProperties *urlTaxonomy = [[PermutiveEventProperties alloc] init:@{@"taxonomy_labels": PermutiveEventProperties.alchemyTaxonomyLabelsValue} error:&error]; PermutiveEventProperties *properties = [[PermutiveEventProperties alloc] init:@{@"classifications_watson": urlTaxonomy} error:&error]; PermutiveContext *context = [[PermutiveContext alloc] initWithTitle:@"Article Title" url:[NSURL URLWithString:@"https://example.com/article"] referrer:nil]; NSObject *pageTracker = [Permutive.shared createPageTrackerWithProperties:properties context:context error:&error]; ``` **Standard Cohorts Requirement:** For Standard Cohorts to work, both a valid URL in the Context and the `classifications_watson.taxonomy_labels` property must be set. Contact your Customer Success Manager (CSM) to enable this feature. ## Schema Validation Event properties **must match** the schema defined in your Permutive dashboard. Mismatches result in event rejection. ### Common Schema Errors Enable debug logging to see schema validation errors: ```swift theme={"dark"} options.logModes = LogMode.all ``` Error messages look like: ``` Permutive: [error] Encountered 1 event rejection! Permutive: [error] Schema validation failed with reason(s): [#: extraneous key [invalid_key] is not permitted] ``` ### Property Name Rules Property names must follow these rules: * Only alphanumeric characters and underscores: `[a-zA-Z0-9_]` * Cannot start with a number * Case-sensitive ```swift theme={"dark"} "category" // ✅ "word_count" // ✅ "articleId" // ✅ "section_2" // ✅ ``` ```swift theme={"dark"} "word-count" // ❌ Hyphen not allowed "2_section" // ❌ Cannot start with number "my property" // ❌ Spaces not allowed ``` ## Using Properties with PageTracker Properties passed to `PageTracker` are included with automatically generated events: ```swift Swift theme={"dark"} class ArticleViewController: UIViewController { private var pageTracker: PageTrackerProtocol? override func viewDidLoad() { super.viewDidLoad() let properties = try? EventProperties([ "category": "technology", "subcategory": "mobile", "author_id": "author_456", "premium": true ]) let context = Context( title: "iOS Development Guide", url: URL(string: "https://example.com/ios-guide"), referrer: nil ) // Properties included with Pageview and PageViewComplete events pageTracker = try? Permutive.shared.createPageTracker( properties: properties, context: context ) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) try? pageTracker?.resume() } func onLinkClick(linkName: String) { // Additional properties for specific events let clickProperties = try? EventProperties([ "link_name": linkName, "position": "header" ]) try? pageTracker?.track(event: "LinkClick", properties: clickProperties) } } ``` ```objectivec Objective-C theme={"dark"} @interface ArticleViewController () @property (nonatomic, strong) NSObject *pageTracker; @end @implementation ArticleViewController - (void)viewDidLoad { [super viewDidLoad]; NSError *error = nil; PermutiveEventProperties *properties = [[PermutiveEventProperties alloc] init:@{ @"category": @"technology", @"subcategory": @"mobile", @"author_id": @"author_456", @"premium": @YES } error:&error]; PermutiveContext *context = [[PermutiveContext alloc] initWithTitle:@"iOS Development Guide" url:[NSURL URLWithString:@"https://example.com/ios-guide"] referrer:nil]; self.pageTracker = [Permutive.shared createPageTrackerWithProperties:properties context:context error:&error]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self.pageTracker resumeAndReturnError:nil]; } - (void)onLinkClick:(NSString *)linkName { NSError *error = nil; PermutiveEventProperties *clickProperties = [[PermutiveEventProperties alloc] init:@{@"link_name": linkName, @"position": @"header"} error:&error]; [self.pageTracker trackWithEvent:@"LinkClick" properties:clickProperties error:&error]; } @end ``` ## Updating Properties Dynamically For values that change during tracking, use the Objective-C style setter: ```swift theme={"dark"} let properties = EventProperties() properties.setValue("initial_value", forKey: "status") // Later... properties.setValue("updated_value", forKey: "status") ``` ## Best Practices * Match property names and types exactly to your schema * Use consistent property names across your app * Use meaningful, descriptive property names * Validate properties exist in your schema before deploying * Use nested properties for complex structured data * Test with debug logging enabled * Don't include PII in event properties * Don't use dynamic property names that aren't in your schema * Don't include empty or null values unnecessarily * Don't exceed reasonable property counts (keep under 50 per event) * Don't use very large arrays (may impact performance) ## Troubleshooting **Problem:** Console shows schema validation errors. **Solutions:** * Check property names match your dashboard schema exactly * Verify property types are correct (string vs int vs bool) * Remove any extra properties not in the schema * Check for typos in property names **Problem:** Creating EventProperties fails. **Solutions:** * Verify all values are supported types * Ensure no unsupported types like custom objects * Check nested EventProperties are valid **Problem:** Geo/ISP data not appearing. **Solutions:** * Ensure your schema includes the enrichment property * Contact support to verify enrichment is enabled * Check you're using the correct enrichment value constant ## Related Documentation Using properties with PageTracker Tracking custom events Content-based segmentation Common problems and solutions # Identity Management Source: https://docs.permutive.com/sdks/mobile/ios/core-concepts/identity-management Track users across sessions, devices, and platforms with aliases Identity management allows you to track users across different sessions, devices, and platforms by associating multiple identifiers (aliases) with a single user profile. ## Overview In the iOS SDK, you can set aliases using the `setIdentities(aliases:)` method. The SDK uses a singleton pattern via `Permutive.shared`. ## Key Concepts ## Setting Identity For apps with a single identifier type: ```swift Swift theme={"dark"} do { let alias = Alias(tag: "internal_id", identity: userId) try Permutive.shared.setIdentities(aliases: [alias]) } catch { print("Failed to set identity: \(error)") } ``` ```objectivec Objective-C theme={"dark"} NSError *error = nil; PermutiveAlias *alias = [[PermutiveAlias alloc] initWithTag:@"internal_id" identity:userId]; [Permutive.shared setIdentitiesWithAliases:@[alias] error:&error]; ``` For apps tracking multiple identifier types (email, user ID, etc.): ```swift Swift theme={"dark"} do { let aliases = [ // Highest priority: hashed email Alias(tag: "email_sha256", identity: emailHash), // Medium priority: internal user ID Alias(tag: "internal_id", identity: userId, priority: 1), // Lower priority: with expiry date Alias( tag: "session_id", identity: sessionId, priority: 2, expiry: Date().addingTimeInterval(86400 * 30) // 30 days ) ] try Permutive.shared.setIdentities(aliases: aliases) } catch { print("Failed to set identities: \(error)") } ``` ```objectivec Objective-C theme={"dark"} NSError *error = nil; // Highest priority: hashed email PermutiveAlias *emailAlias = [[PermutiveAlias alloc] initWithTag:@"email_sha256" identity:emailHash]; // Medium priority: internal user ID PermutiveAlias *idAlias = [[PermutiveAlias alloc] initWithTag:@"internal_id" identity:userId priority:1]; // Lower priority: with expiry NSDate *expiry = [NSDate dateWithTimeIntervalSinceNow:86400 * 30]; PermutiveAlias *sessionAlias = [[PermutiveAlias alloc] initWithTag:@"session_id" identity:sessionId priority:2 expiry:expiry]; [Permutive.shared setIdentitiesWithAliases:@[emailAlias, idAlias, sessionAlias] error:&error]; ``` ## Security Best Practices **Always hash** personally identifiable information like email addresses before sending to Permutive. ```swift theme={"dark"} import CryptoKit func hashEmail(_ email: String) -> String { let normalized = email.trimmingCharacters(in: .whitespaces).lowercased() let data = Data(normalized.utf8) let hash = SHA256.hash(data: data) return hash.compactMap { String(format: "%02x", $0) }.joined() } // Usage let emailHash = hashEmail("user@example.com") let alias = Alias(tag: "email_sha256", identity: emailHash) try? Permutive.shared.setIdentities(aliases: [alias]) ``` ```swift theme={"dark"} // DON'T DO THIS - sending PII let alias = Alias(tag: "email", identity: "user@example.com") // WRONG! ``` ### Standard Tag Names | Identifier Type | Recommended Tag | Notes | | -------------------- | -------------------------- | ------------------------------ | | SHA-256 hashed email | `email_sha256` | Most common | | Internal user ID | `internal_id` or `user_id` | Your system's ID | | Vendor identifier | `idfv` | `UIDevice.identifierForVendor` | | Customer ID | `customer_id` | E-commerce systems | | Subscriber ID | `subscriber_id` | Subscription services | ### Reserved Tags Some alias tags are reserved by the SDK and cannot be used: * `appnexus` * `amp` * `gigya` * `sailthru` * `aaid` Aliases with these tags will be ignored. ## IDFA Identity **Permutive recommends against using IDFA** due to Apple's App Tracking Transparency requirements. Consider using `identifierForVendor` or hashed email addresses instead. From v2.6.0, the IDFA, IDFV, and IP address can be **collected automatically** when enabled in your workspace config — no manual code required. See the [v2.6 Migration Guide](/sdks/mobile/ios/guides/migration/v2-6). If you opt into automatic collection, you generally don't need the manual `setIdentityForIDFA` flow below. If you do need to set IDFA manually, use the dedicated `setIdentityForIDFA` method: ```swift theme={"dark"} import AdSupport import AppTrackingTransparency import Permutive_iOS func setIDFAIdentity() { if #available(iOS 14, *) { ATTrackingManager.requestTrackingAuthorization { status in guard status == .authorized else { return } let idfa = ASIdentifierManager.shared().advertisingIdentifier do { try Permutive.shared.setIdentityForIDFA(idfa) } catch { // IDFA may be all zeros (tracking denied) print("Failed to set IDFA: \(error)") } } } } ``` Complete IDFA integration documentation ## Retrieving User ID Get the current Permutive user ID: ```swift Swift theme={"dark"} let userId = Permutive.shared.currentUserId print("Permutive User ID: \(userId)") ``` ```objectivec Objective-C theme={"dark"} NSString *userId = [Permutive.shared currentUserId]; NSLog(@"Permutive User ID: %@", userId); ``` ## Tracking User ID Changes Use `TriggerAction` to react when the user ID changes: ```swift Swift theme={"dark"} var userIdTrigger: TriggerAction? func watchUserIdChanges() { userIdTrigger = Permutive.shared.triggerProvider?.actionUpdateForUserIdentity { userId in print("User ID changed to: \(userId)") } } // Release when done deinit { userIdTrigger = nil } ``` ```objectivec Objective-C theme={"dark"} @property (nonatomic, strong) PermutiveTriggerAction *userIdTrigger; - (void)watchUserIdChanges { self.userIdTrigger = [Permutive.shared.triggerProvider actionUpdateForUserIdentity:^(NSString *userId) { NSLog(@"User ID changed to: %@", userId); }]; } ``` ## Common Patterns ```swift Swift theme={"dark"} func onLoginSuccess(userId: String, email: String) { let emailHash = hashEmail(email) do { let aliases = [ Alias(tag: "email_sha256", identity: emailHash), Alias(tag: "internal_id", identity: userId, priority: 1) ] try Permutive.shared.setIdentities(aliases: aliases) } catch { print("Failed to set identity: \(error)") } } ``` ```objectivec Objective-C theme={"dark"} - (void)onLoginSuccessWithUserId:(NSString *)userId email:(NSString *)email { NSString *emailHash = [self hashEmail:email]; NSError *error = nil; PermutiveAlias *emailAlias = [[PermutiveAlias alloc] initWithTag:@"email_sha256" identity:emailHash]; PermutiveAlias *idAlias = [[PermutiveAlias alloc] initWithTag:@"internal_id" identity:userId priority:1]; [Permutive.shared setIdentitiesWithAliases:@[emailAlias, idAlias] error:&error]; } ``` ```swift theme={"dark"} import UIKit import Permutive_iOS func setVendorIdentity() { guard let idfv = UIDevice.current.identifierForVendor?.uuidString else { return } let alias = Alias(tag: "idfv", identity: idfv, priority: 2) try? Permutive.shared.setIdentities(aliases: [alias]) } ``` ```swift theme={"dark"} class UserManager { // User starts as guest (anonymous) func onAppLaunch() { // SDK automatically creates anonymous ID // Optionally set vendor identifier setVendorIdentity() } // User logs in or signs up func onUserAuthenticated(userId: String, email: String) { let emailHash = hashEmail(email) // Permutive merges guest data with authenticated profile try? Permutive.shared.setIdentities(aliases: [ Alias(tag: "email_sha256", identity: emailHash), Alias(tag: "internal_id", identity: userId, priority: 1) ]) } } ``` ## Expiry Aliases can expire automatically, useful for GDPR compliance, session identifiers, and temporary access tokens. ```swift theme={"dark"} // Never expire (default) Alias(tag: "email_sha256", identity: emailHash) // Expire in 30 days let thirtyDaysFromNow = Date().addingTimeInterval(86400 * 30) Alias(tag: "session_id", identity: sessionId, expiry: thirtyDaysFromNow) // Expire at distant future (effectively never) Alias(tag: "permanent_id", identity: id, expiry: Date.distantFuture) ``` ## tvOS Considerations **tvOS Note:** Identity management works identically on tvOS. App Tracking Transparency and IDFA are available on tvOS from version 14.5 onwards. ## Troubleshooting **Problem:** Setting identity doesn't merge user data across devices. **Solutions:** * Check network connectivity * Verify the alias has been used in other sessions/devices * Enable debug logging: `options.logModes = LogMode.all` * Look for identity-related logs in the console **Problem:** Same user appears as two different users. **Solution:** Use consistent alias tags and values. When a user logs in on multiple devices, use the same alias (e.g., hashed email). **Problem:** Alias with certain tags is ignored. **Solution:** Avoid reserved tags: `appnexus`, `amp`, `gigya`, `sailthru`, `aaid`. Use custom tags instead. ## Best Practices * Hash PII (especially email addresses) before setting as identity * Use meaningful tag names that describe the identifier type * Set priorities based on reliability and persistence * Use expiry dates for temporary or less reliable identifiers * Set identity as early as possible in the user journey * Use `identifierForVendor` as a fallback identifier * Send unhashed email addresses or phone numbers * Use reserved tag names * Use generic tag names like "id1", "id2" * Set identity multiple times unnecessarily * Skip priority assignment when using multiple aliases * Rely on IDFA as your primary identifier ## Related Documentation Apple advertising identifier integration Understanding user segments React to cohort changes Solutions to common issues # Event Tracking Source: https://docs.permutive.com/sdks/mobile/ios/features/event-tracking Track custom events directly with the Permutive SDK While `PageTracker` is the recommended approach for most tracking, you can also track custom events directly using the `track(event:properties:)` method. ## When to Use Direct Event Tracking Use direct event tracking for: * Events that don't fit the page view model * Background events (app state changes, push notifications) * One-off events without context * Events outside of a view lifecycle For view-based tracking with engagement time, scroll depth, and contextual cohorts, use [PageTracker](/sdks/mobile/ios/features/page-tracking) instead. ## Tracking Events ### Basic Event ```swift Swift theme={"dark"} do { try Permutive.shared.track(event: "AppLaunch") } catch { print("Failed to track event: \(error)") } ``` ```objectivec Objective-C theme={"dark"} NSError *error = nil; [Permutive.shared trackWithEvent:@"AppLaunch" properties:nil error:&error]; if (error != nil) { NSLog(@"Failed to track event: %@", error); } ``` ### Event with Properties ```swift Swift theme={"dark"} do { let properties = try EventProperties([ "button_name": "subscribe", "position": "header", "is_premium": true ]) try Permutive.shared.track(event: "ButtonClick", properties: properties) } catch { print("Failed to track event: \(error)") } ``` ```objectivec Objective-C theme={"dark"} NSError *error = nil; PermutiveEventProperties *properties = [[PermutiveEventProperties alloc] init:@{ @"button_name": @"subscribe", @"position": @"header", @"is_premium": @YES } error:&error]; [Permutive.shared trackWithEvent:@"ButtonClick" properties:properties error:&error]; ``` ## Event Naming Rules Event names must follow these rules: * Only alphanumeric characters and underscores: `[a-zA-Z0-9_]` * Cannot start with a number * Case-sensitive ```swift theme={"dark"} "AppLaunch" // ✅ "button_click" // ✅ "VideoPlay2" // ✅ "user_subscription" // ✅ ``` ```swift theme={"dark"} "button-click" // ❌ Hyphen not allowed "2_event" // ❌ Cannot start with number "my event" // ❌ Spaces not allowed "événement" // ❌ Non-ASCII characters ``` ## Schema Validation Event properties **must match exactly** the schema defined in your Permutive dashboard. Mismatched properties cause event rejection. ### Enable Debug Logging ```swift theme={"dark"} options.logModes = LogMode.all ``` ### Schema Error Example When properties don't match the schema: ``` Permutive: [error] Encountered 1 event rejection! Permutive: [error] Error 1: A validation check carried out by the server did not succeed.(1007) Permutive: [error] Error 1: Schema validation failed with reason(s): [#: extraneous key [invalid_key] is not permitted] ``` ### Checking Events Were Accepted Successful events show: ``` Permutive: [info] Accepted: 1 / 1 ``` ## Common Event Types ### App Lifecycle Events ```swift Swift theme={"dark"} // App launch func applicationDidBecomeActive() { try? Permutive.shared.track( event: "AppActive", properties: try? EventProperties([ "launch_type": isFirstLaunch ? "fresh" : "resume" ]) ) } // App background func applicationWillResignActive() { try? Permutive.shared.track(event: "AppBackground") } ``` ```objectivec Objective-C theme={"dark"} // App launch - (void)applicationDidBecomeActive:(UIApplication *)application { NSError *error = nil; PermutiveEventProperties *props = [[PermutiveEventProperties alloc] init:@{@"launch_type": self.isFirstLaunch ? @"fresh" : @"resume"} error:&error]; [Permutive.shared trackWithEvent:@"AppActive" properties:props error:&error]; } ``` ### User Action Events ```swift Swift theme={"dark"} // Button click func onSubscribeButtonTapped() { try? Permutive.shared.track( event: "SubscribeClick", properties: try? EventProperties([ "plan": "premium", "source": "paywall" ]) ) } // Search func onSearch(query: String, resultCount: Int) { try? Permutive.shared.track( event: "Search", properties: try? EventProperties([ "query_length": query.count, "result_count": resultCount, "has_results": resultCount > 0 ]) ) } ``` ```objectivec Objective-C theme={"dark"} // Button click - (void)onSubscribeButtonTapped { NSError *error = nil; PermutiveEventProperties *props = [[PermutiveEventProperties alloc] init:@{@"plan": @"premium", @"source": @"paywall"} error:&error]; [Permutive.shared trackWithEvent:@"SubscribeClick" properties:props error:&error]; } ``` ### Push Notification Events ```swift theme={"dark"} func userNotificationCenter( _ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse ) { let userInfo = response.notification.request.content.userInfo try? Permutive.shared.track( event: "PushNotificationOpen", properties: try? EventProperties([ "campaign_id": userInfo["campaign_id"] as? String ?? "unknown", "action": response.actionIdentifier ]) ) } ``` ### Purchase Events ```swift Swift theme={"dark"} func onPurchaseComplete(product: SKProduct, transaction: SKPaymentTransaction) { try? Permutive.shared.track( event: "Purchase", properties: try? EventProperties([ "product_id": product.productIdentifier, "price": product.price.doubleValue, "currency": product.priceLocale.currencyCode ?? "USD", "transaction_id": transaction.transactionIdentifier ?? "" ]) ) } ``` ```objectivec Objective-C theme={"dark"} - (void)onPurchaseCompleteWithProduct:(SKProduct *)product transaction:(SKPaymentTransaction *)transaction { NSError *error = nil; PermutiveEventProperties *props = [[PermutiveEventProperties alloc] init:@{ @"product_id": product.productIdentifier, @"price": product.price, @"currency": product.priceLocale.currencyCode ?: @"USD", @"transaction_id": transaction.transactionIdentifier ?: @"" } error:&error]; [Permutive.shared trackWithEvent:@"Purchase" properties:props error:&error]; } ``` ## Event Enrichment Add server-side enrichment to events: ```swift theme={"dark"} let properties = try EventProperties([ "geo_info": EventProperties.geoInfoValue, "isp_info": EventProperties.ispInfoValue, "form_id": "contact_form" ]) try Permutive.shared.track(event: "FormSubmit", properties: properties) ``` See [Event Properties](/sdks/mobile/ios/core-concepts/event-properties) for all enrichment options. ## Batching and Delivery Events are: * Queued locally when tracked * Sent in batches to reduce network overhead * Persisted to disk if the app closes before sending * Retried automatically on network failure You don't need to manage batching - it's handled automatically. ## Error Handling ```swift Swift theme={"dark"} func trackEvent(name: String, properties: [String: Any]) { do { let eventProperties = try EventProperties(properties) try Permutive.shared.track(event: name, properties: eventProperties) } catch let error as NSError { // Log but don't crash - tracking failures shouldn't break app print("Event tracking failed: \(error.localizedDescription)") // Optionally report to error tracking // Crashlytics.log("Permutive track error: \(error)") } } ``` ```objectivec Objective-C theme={"dark"} - (void)trackEvent:(NSString *)name properties:(NSDictionary *)properties { NSError *error = nil; PermutiveEventProperties *eventProps = [[PermutiveEventProperties alloc] init:properties error:&error]; if (error != nil) { NSLog(@"Failed to create properties: %@", error); return; } [Permutive.shared trackWithEvent:name properties:eventProps error:&error]; if (error != nil) { NSLog(@"Event tracking failed: %@", error); } } ``` ## Best Practices * Use descriptive, consistent event names * Match your dashboard schema exactly * Test events with debug logging enabled * Handle errors gracefully * Use PageTracker for view-based tracking * Keep properties minimal and relevant * Don't include PII in event properties * Don't track too many fine-grained events * Don't use dynamic event names not in your schema * Don't block the UI waiting for tracking * Don't track sensitive data (passwords, tokens) ## Troubleshooting **Problem:** Tracked events don't show up. **Solutions:** * Enable debug logging and look for `Accepted: X / X` * Check for schema validation errors in logs * Wait up to 5 minutes for processing * Verify you're in the correct workspace **Problem:** Creating EventProperties throws an error. **Solutions:** * Verify all values are supported types * Check for unsupported custom objects * Ensure arrays contain only supported types **Problem:** Events sent but rejected by server. **Solutions:** * Check event name matches schema exactly * Verify property names and types * Remove properties not in the schema * Check for typos ## Related Documentation View-based tracking with PageTracker Structuring event metadata Track video content Common problems and solutions # Page Tracking Source: https://docs.permutive.com/sdks/mobile/ios/features/page-tracking Track page views and user engagement with PageTracker `PageTracker` is the recommended approach for tracking user interactions in your iOS and tvOS apps. It automatically generates `Pageview` events, measures engagement time, and enables contextual cohorts when URLs are provided. ## Overview `PageTracker` provides: * **Automatic `Pageview` events** on first resume * **Engagement time tracking** between resume and pause * **`PageViewComplete` events** when stopped * **Contextual cohort generation** when URLs are provided * **Linked event tracking** with consistent view IDs **Only one PageTracker (or MediaTracker) can be active at a time.** Creating a new tracker automatically stops any existing one. ## Creating a PageTracker ```swift Swift theme={"dark"} import Permutive_iOS class ArticleViewController: UIViewController { private var pageTracker: PageTrackerProtocol? override func viewDidLoad() { super.viewDidLoad() // Create event properties (optional) let properties = try? EventProperties([ "category": "sports", "subcategory": "football", "author": "Jane Smith", "premium": true ]) // Create context with page information let context = Context( title: "Match Preview: Team A vs Team B", url: URL(string: "https://example.com/sports/match-preview"), referrer: URL(string: "https://example.com/sports") ) // Create the PageTracker pageTracker = try? Permutive.shared.createPageTracker( properties: properties, context: context ) } } ``` ```objectivec Objective-C theme={"dark"} #import "ArticleViewController.h" @import Permutive_iOS; @interface ArticleViewController () @property (nonatomic, strong) NSObject *pageTracker; @end @implementation ArticleViewController - (void)viewDidLoad { [super viewDidLoad]; // Create event properties NSError *error = nil; PermutiveEventProperties *properties = [[PermutiveEventProperties alloc] init:@{ @"category": @"sports", @"subcategory": @"football", @"author": @"Jane Smith", @"premium": @YES } error:&error]; // Create context PermutiveContext *context = [[PermutiveContext alloc] initWithTitle:@"Match Preview: Team A vs Team B" url:[NSURL URLWithString:@"https://example.com/sports/match-preview"] referrer:[NSURL URLWithString:@"https://example.com/sports"]]; // Create the PageTracker self.pageTracker = [Permutive.shared createPageTrackerWithProperties:properties context:context error:&error]; } @end ``` ## PageTracker Lifecycle ### State Diagram ``` ┌──────────────────────────────────────────────────────────────┐ │ │ │ Created ──► resume() ──► Running ──► pause() ──► Paused │ │ │ │ │ │ │ │ │ │ ▼ ▼ │ │ stop() ◄──────────────────┘ │ │ │ │ │ ▼ │ │ Stopped │ │ │ └──────────────────────────────────────────────────────────────┘ ``` ### Lifecycle Methods | Method | Effect | Events Generated | | ---------- | ------------------------- | ---------------------------- | | `resume()` | Starts/resumes tracking | `Pageview` (first call only) | | `pause()` | Pauses engagement timer | None | | `stop()` | Ends tracking permanently | `PageViewComplete` | ### UIViewController Integration ```swift Swift theme={"dark"} class ArticleViewController: UIViewController { private var pageTracker: PageTrackerProtocol? override func viewDidLoad() { super.viewDidLoad() let context = Context( title: "Article Title", url: URL(string: "https://example.com/article"), referrer: nil ) pageTracker = try? Permutive.shared.createPageTracker( properties: nil, context: context ) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) // Start or resume tracking try? pageTracker?.resume() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) // Pause when view is not visible pageTracker?.pause() } deinit { // Stop tracking when view is deallocated pageTracker?.stop() } } ``` ```objectivec Objective-C theme={"dark"} @implementation ArticleViewController - (void)viewDidLoad { [super viewDidLoad]; PermutiveContext *context = [[PermutiveContext alloc] initWithTitle:@"Article Title" url:[NSURL URLWithString:@"https://example.com/article"] referrer:nil]; self.pageTracker = [Permutive.shared createPageTrackerWithProperties:nil context:context error:nil]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self.pageTracker resumeAndReturnError:nil]; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [self.pageTracker pause]; } - (void)dealloc { [self.pageTracker stop]; } @end ``` ## SwiftUI Integration For SwiftUI, use `onAppear` and `onDisappear` modifiers: ```swift theme={"dark"} import SwiftUI import Permutive_iOS struct ArticleView: View { let article: Article @StateObject private var tracker = PageTrackerWrapper() var body: some View { ScrollView { ArticleContent(article: article) } .onAppear { tracker.start( title: article.title, url: URL(string: article.url) ) } .onDisappear { tracker.stop() } } } class PageTrackerWrapper: ObservableObject { private var pageTracker: PageTrackerProtocol? func start(title: String, url: URL?) { let context = Context(title: title, url: url, referrer: nil) pageTracker = try? Permutive.shared.createPageTracker( properties: nil, context: context ) try? pageTracker?.resume() } func stop() { pageTracker?.stop() pageTracker = nil } } ``` ## Tracking Events Track additional events linked to the current page view: ```swift Swift theme={"dark"} // Track a link click try? pageTracker?.track( event: "LinkClick", properties: try? EventProperties([ "link_url": "https://example.com/related", "link_text": "Read More" ]) ) // Track a form submission try? pageTracker?.track( event: "FormSubmit", properties: try? EventProperties([ "form_id": "newsletter_signup", "email_provided": true ]) ) // Track without properties try? pageTracker?.track(event: "ShareButton", properties: nil) ``` ```objectivec Objective-C theme={"dark"} // Track a link click NSError *error = nil; PermutiveEventProperties *linkProps = [[PermutiveEventProperties alloc] init:@{@"link_url": @"https://example.com/related", @"link_text": @"Read More"} error:&error]; [self.pageTracker trackWithEvent:@"LinkClick" properties:linkProps error:&error]; // Track without properties [self.pageTracker trackWithEvent:@"ShareButton" properties:nil error:&error]; ``` Events tracked via `PageTracker.track()` include the same Context and view ID as the `Pageview` event, enabling linked analysis. ## Engagement Tracking ### Scroll Depth Track how much content users have viewed: ```swift Swift theme={"dark"} class ArticleViewController: UIViewController, UIScrollViewDelegate { private var pageTracker: PageTrackerProtocol? @IBOutlet weak var scrollView: UIScrollView! override func viewDidLoad() { super.viewDidLoad() scrollView.delegate = self // Setup pageTracker... } func scrollViewDidScroll(_ scrollView: UIScrollView) { let contentHeight = scrollView.contentSize.height - scrollView.bounds.height guard contentHeight > 0 else { return } let percentage = Float(scrollView.contentOffset.y / contentHeight) let clampedPercentage = min(max(percentage, 0), 1) pageTracker?.updateViewed(percentage: clampedPercentage) } } ``` ```objectivec Objective-C theme={"dark"} - (void)scrollViewDidScroll:(UIScrollView *)scrollView { CGFloat contentHeight = scrollView.contentSize.height - scrollView.bounds.size.height; if (contentHeight <= 0) return; float percentage = scrollView.contentOffset.y / contentHeight; percentage = MIN(MAX(percentage, 0), 1); [self.pageTracker updateViewedWithPercentage:percentage]; } ``` ### Engagement Time Engagement time is automatically tracked: * Timer starts when `resume()` is called * Timer pauses when `pause()` is called * Total engagement time sent with `PageViewComplete` event ## Context Object The `Context` object provides page metadata: | Property | Type | Description | | ---------- | -------- | ------------------------------------- | | `title` | `String` | Page title | | `url` | `URL?` | Page URL (enables contextual cohorts) | | `referrer` | `URL?` | Referring page URL | ```swift Swift theme={"dark"} let context = Context( title: "Article: iOS Development", url: URL(string: "https://example.com/ios-development"), referrer: URL(string: "https://example.com/tutorials") ) ``` ```objectivec Objective-C theme={"dark"} PermutiveContext *context = [[PermutiveContext alloc] initWithTitle:@"Article: iOS Development" url:[NSURL URLWithString:@"https://example.com/ios-development"] referrer:[NSURL URLWithString:@"https://example.com/tutorials"]]; ``` The `domain` is automatically inferred from the `url` if set. Always provide URLs to enable contextual cohort generation. ## Background Handling The SDK automatically handles app backgrounding: ```swift theme={"dark"} // When app enters background: // - Active PageTracker is automatically stopped // - PageViewComplete event is sent // When resuming, create a new PageTracker if needed func applicationWillEnterForeground() { if shouldResumeTracking { createAndResumePageTracker() } } ``` ## tvOS Considerations **tvOS Note:** PageTracker works identically on tvOS. Use focus-based navigation events instead of touch-based scrolling for engagement tracking. ```swift theme={"dark"} // tvOS focus tracking example func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) { // Track focus changes as navigation events if let focusedView = context.nextFocusedView { try? pageTracker?.track( event: "FocusChange", properties: try? EventProperties([ "focused_element": String(describing: type(of: focusedView)) ]) ) } } ``` ## Error Handling ```swift Swift theme={"dark"} do { let pageTracker = try Permutive.shared.createPageTracker( properties: properties, context: context ) try pageTracker.resume() } catch { print("PageTracker error: \(error)") // Handle error - tracking will be skipped } ``` ```objectivec Objective-C theme={"dark"} NSError *error = nil; NSObject *pageTracker = [Permutive.shared createPageTrackerWithProperties:properties context:context error:&error]; if (error != nil) { NSLog(@"PageTracker creation error: %@", error); return; } [pageTracker resumeAndReturnError:&error]; if (error != nil) { NSLog(@"PageTracker resume error: %@", error); } ``` ## Best Practices * Create PageTracker in `viewDidLoad` * Call `resume()` in `viewDidAppear` * Call `pause()` in `viewDidDisappear` * Call `stop()` in `deinit` * Always provide URLs for contextual targeting * Track meaningful events with descriptive names * Update scroll percentage as user scrolls * Don't create multiple PageTrackers simultaneously * Don't call `resume()` after `stop()` * Don't forget to call `stop()` when done * Don't track too many fine-grained events * Don't include PII in event properties ## Troubleshooting **Problem:** No Pageview event in logs. **Solutions:** * Ensure `resume()` is called after creating the PageTracker * Check that SDK is initialized before creating PageTracker * Verify no errors when creating PageTracker * Enable debug logging: `options.logModes = LogMode.all` **Problem:** PageTracker stops when navigating. **Cause:** Creating a new PageTracker stops any existing one. **Solution:** This is expected behavior. Only one tracker can be active. Stop the previous tracker explicitly if needed, or let the new one replace it. **Problem:** Events tracked via PageTracker are rejected. **Solutions:** * Check event names match your schema * Verify property names and types * Enable debug logging to see schema errors ## Related Documentation Track custom events Content-based targeting Ad targeting with PageTracker Structuring event data # Triggers Provider Source: https://docs.permutive.com/sdks/mobile/ios/features/triggers-provider React to cohort changes and user identity updates in real-time `TriggerProvider` enables reactive updates when users enter or exit cohorts, allowing your app to respond to segment changes in real-time. ## Overview `TriggerProvider` provides: * **Real-time cohort change notifications** * **Immediate callback when users enter/exit cohorts** * **Query-based triggers** for complex conditions * **User identity change monitoring** Use `TriggerProvider` instead of polling `cohorts` or `activations` for reactive behavior. ## Accessing TriggerProvider ```swift Swift theme={"dark"} let triggerProvider = Permutive.shared.triggerProvider ``` ```objectivec Objective-C theme={"dark"} id triggerProvider = Permutive.shared.triggerProvider; ``` ## Cohort Membership Triggers Watch for changes in cohort membership: ```swift Swift theme={"dark"} import Permutive_iOS class FeatureController { private var premiumTrigger: TriggerAction? func watchPremiumStatus() { premiumTrigger = Permutive.shared.triggerProvider?.action( boolFor: ["premium_subscriber"], action: { cohortId, isInCohort in print("Cohort '\(cohortId)' membership: \(isInCohort)") if isInCohort { self.enablePremiumFeatures() } else { self.disablePremiumFeatures() } } ) } private func enablePremiumFeatures() { // Show premium content } private func disablePremiumFeatures() { // Hide premium content } deinit { // Release trigger to stop watching premiumTrigger = nil } } ``` ```objectivec Objective-C theme={"dark"} @interface FeatureController () @property (nonatomic, strong) PermutiveTriggerAction *premiumTrigger; @end @implementation FeatureController - (void)watchPremiumStatus { NSSet *cohorts = [NSSet setWithObject:@"premium_subscriber"]; self.premiumTrigger = [Permutive.shared.triggerProvider actionWithBoolFor:cohorts action:^(NSString *cohortId, BOOL isInCohort) { NSLog(@"Cohort '%@' membership: %@", cohortId, isInCohort ? @"YES" : @"NO"); if (isInCohort) { [self enablePremiumFeatures]; } else { [self disablePremiumFeatures]; } }]; } - (void)dealloc { // Release trigger self.premiumTrigger = nil; } @end ``` ### Watching Multiple Cohorts ```swift Swift theme={"dark"} class ContentPersonalizer { private var contentTrigger: TriggerAction? func watchUserInterests() { contentTrigger = Permutive.shared.triggerProvider?.action( boolFor: ["sports_enthusiast", "tech_reader", "news_follower"], action: { cohortId, isInCohort in if isInCohort { switch cohortId { case "sports_enthusiast": self.boostSportsContent() case "tech_reader": self.boostTechContent() case "news_follower": self.boostNewsContent() default: break } } } ) } } ``` ```objectivec Objective-C theme={"dark"} - (void)watchUserInterests { NSSet *cohorts = [NSSet setWithObjects: @"sports_enthusiast", @"tech_reader", @"news_follower", nil]; self.contentTrigger = [Permutive.shared.triggerProvider actionWithBoolFor:cohorts action:^(NSString *cohortId, BOOL isInCohort) { if (isInCohort) { if ([cohortId isEqualToString:@"sports_enthusiast"]) { [self boostSportsContent]; } else if ([cohortId isEqualToString:@"tech_reader"]) { [self boostTechContent]; } else if ([cohortId isEqualToString:@"news_follower"]) { [self boostNewsContent]; } } }]; } ``` ## Query Triggers For more complex data, use query triggers that return dictionaries or integers: ### Dictionary Queries ```swift Swift theme={"dark"} class AnalyticsController { private var queryTrigger: TriggerAction? func watchComplexQueries() { queryTrigger = Permutive.shared.triggerProvider?.action( dictionaryFor: ["user_profile_query", "engagement_metrics"], action: { queryId, result in print("Query '\(queryId)' returned: \(result)") if queryId == "user_profile_query", let preferences = result["preferences"] as? [String: Any] { self.applyUserPreferences(preferences) } } ) } } ``` ```objectivec Objective-C theme={"dark"} - (void)watchComplexQueries { NSSet *queries = [NSSet setWithObjects:@"user_profile_query", @"engagement_metrics", nil]; self.queryTrigger = [Permutive.shared.triggerProvider actionWithDictionaryFor:queries action:^(NSString *queryId, NSDictionary, NSObject *> *result) { NSLog(@"Query '%@' returned: %@", queryId, result); if ([queryId isEqualToString:@"user_profile_query"]) { NSDictionary *preferences = result[@"preferences"]; if (preferences) { [self applyUserPreferences:preferences]; } } }]; } ``` ### Integer Queries ```swift Swift theme={"dark"} class ScoreController { private var scoreTrigger: TriggerAction? func watchEngagementScore() { scoreTrigger = Permutive.shared.triggerProvider?.action( intFor: ["engagement_score"], action: { queryId, score in print("Engagement score: \(score)") self.updateUIForScore(score) } ) } private func updateUIForScore(_ score: Int) { // Adjust UI based on engagement level } } ``` ```objectivec Objective-C theme={"dark"} - (void)watchEngagementScore { NSSet *queries = [NSSet setWithObject:@"engagement_score"]; self.scoreTrigger = [Permutive.shared.triggerProvider actionWithIntFor:queries action:^(NSString *queryId, NSInteger score) { NSLog(@"Engagement score: %ld", (long)score); [self updateUIForScore:score]; }]; } ``` ## User ID Triggers Monitor changes to the user identity: ```swift Swift theme={"dark"} class SessionController { private var userIdTrigger: TriggerAction? func watchUserIdentityChanges() { userIdTrigger = Permutive.shared.triggerProvider?.actionUpdateForUserIdentity { userId in print("User identity changed to: \(userId)") // Update analytics with new user ID Analytics.setUserId(userId) // Refresh user-specific data self.refreshUserData() } } deinit { userIdTrigger = nil } } ``` ```objectivec Objective-C theme={"dark"} @interface SessionController () @property (nonatomic, strong) PermutiveTriggerAction *userIdTrigger; @end @implementation SessionController - (void)watchUserIdentityChanges { self.userIdTrigger = [Permutive.shared.triggerProvider actionUpdateForUserIdentity:^(NSString *userId) { NSLog(@"User identity changed to: %@", userId); [Analytics setUserId:userId]; [self refreshUserData]; }]; } - (void)dealloc { self.userIdTrigger = nil; } @end ``` ## TriggerAction Lifecycle You must maintain a strong reference to `TriggerAction` objects. Releasing the reference stops the trigger. ### Keeping Triggers Active ```swift theme={"dark"} class TriggerManager { // Store triggers as instance properties private var cohortTrigger: TriggerAction? private var userIdTrigger: TriggerAction? func setupTriggers() { cohortTrigger = Permutive.shared.triggerProvider?.action( boolFor: ["cohort"], action: { _, _ in } ) userIdTrigger = Permutive.shared.triggerProvider?.actionUpdateForUserIdentity { _ in } } func stopWatching() { // Explicitly release triggers cohortTrigger = nil userIdTrigger = nil } deinit { // Automatically released when controller is deallocated } } ``` ## UIViewController Integration ```swift Swift theme={"dark"} class PersonalizedViewController: UIViewController { private var cohortTrigger: TriggerAction? override func viewDidLoad() { super.viewDidLoad() // Start watching cohorts cohortTrigger = Permutive.shared.triggerProvider?.action( boolFor: ["premium_user", "new_user"], action: { [weak self] cohortId, isInCohort in DispatchQueue.main.async { self?.handleCohortChange(cohortId: cohortId, isInCohort: isInCohort) } } ) } private func handleCohortChange(cohortId: String, isInCohort: Bool) { switch cohortId { case "premium_user": updatePremiumUI(enabled: isInCohort) case "new_user": showOnboarding(isNew: isInCohort) default: break } } deinit { cohortTrigger = nil } } ``` ```objectivec Objective-C theme={"dark"} @interface PersonalizedViewController () @property (nonatomic, strong) PermutiveTriggerAction *cohortTrigger; @end @implementation PersonalizedViewController - (void)viewDidLoad { [super viewDidLoad]; NSSet *cohorts = [NSSet setWithObjects:@"premium_user", @"new_user", nil]; __weak typeof(self) weakSelf = self; self.cohortTrigger = [Permutive.shared.triggerProvider actionWithBoolFor:cohorts action:^(NSString *cohortId, BOOL isInCohort) { dispatch_async(dispatch_get_main_queue(), ^{ [weakSelf handleCohortChange:cohortId isInCohort:isInCohort]; }); }]; } - (void)handleCohortChange:(NSString *)cohortId isInCohort:(BOOL)isInCohort { if ([cohortId isEqualToString:@"premium_user"]) { [self updatePremiumUI:isInCohort]; } else if ([cohortId isEqualToString:@"new_user"]) { [self showOnboarding:isInCohort]; } } - (void)dealloc { self.cohortTrigger = nil; } @end ``` ## SwiftUI Integration ```swift theme={"dark"} import SwiftUI import Permutive_iOS struct PersonalizedView: View { @StateObject private var triggerManager = TriggerManager() var body: some View { VStack { if triggerManager.isPremium { PremiumContentView() } else { StandardContentView() } } .onAppear { triggerManager.startWatching() } .onDisappear { triggerManager.stopWatching() } } } class TriggerManager: ObservableObject { @Published var isPremium = false private var trigger: TriggerAction? func startWatching() { trigger = Permutive.shared.triggerProvider?.action( boolFor: ["premium_subscriber"], action: { [weak self] _, isInCohort in DispatchQueue.main.async { self?.isPremium = isInCohort } } ) } func stopWatching() { trigger = nil } } ``` ## tvOS Considerations **tvOS Note:** TriggerProvider works identically on tvOS. Use it to update UI elements based on cohort membership in your focus-based navigation. ## Best Practices * Store TriggerAction references as instance properties * Use `[weak self]` in closures to avoid retain cycles * Dispatch UI updates to main thread * Release triggers when no longer needed * Group related cohorts in a single trigger * Don't create triggers without storing the reference * Don't create excessive numbers of triggers * Don't perform heavy work in trigger callbacks * Don't assume immediate callback on creation * Don't forget to handle both enter and exit cases ## Troubleshooting **Problem:** No callbacks when cohort membership changes. **Solutions:** * Verify you're storing a strong reference to TriggerAction * Check that cohort IDs match your dashboard * Enable debug logging to see cohort updates * Ensure SDK is initialized before creating triggers **Problem:** Trigger worked initially but stopped. **Cause:** TriggerAction reference was released. **Solution:** Store TriggerAction as an instance property, not a local variable. **Problem:** Callback fires but UI doesn't change. **Solution:** Dispatch UI updates to main thread: ```swift theme={"dark"} DispatchQueue.main.async { self.updateUI() } ``` ## Related Documentation Understanding user segments User identity tracking Trigger cohort qualification Common problems and solutions # Video Ad Tracking Source: https://docs.permutive.com/sdks/mobile/ios/features/video-ad-tracking Emit standard video ad events from iOS / tvOS using Permutive.shared.track() Track video ad events to measure ad engagement and enable ad-based cohort qualification. ## Overview **iOS / tvOS does not have a native ad tracker.** Unlike Android's `VideoAdTracker`, iOS does not provide a typed API for ad tracking. Emit the standard `VideoAdView`, `VideoAdCompletion`, and `VideoAdClicked` events using `Permutive.shared.track(event:properties:)` with properties nested under an `ad` parent per the [canonical schema](/sdks/ctv/video-tracking#ad-event-properties). The three schema events for ad tracking are: | Event | When to emit | | ----------------------- | ------------------- | | **`VideoAdView`** | Ad playback begins | | **`VideoAdCompletion`** | Ad finishes playing | | **`VideoAdClicked`** | User clicks the ad | All three emit `ad.*` properties (and optionally a `video.*` parent for the content the ad ran against). See the schema reference below. *** ## Tracking Ad Events ### VideoAdView Emit when the ad begins playback: ```swift theme={"dark"} import Permutive_iOS func trackAdView(ad: Ad) { try? Permutive.shared.track( event: "VideoAdView", properties: try? EventProperties([ "ad": [ "title": ad.title, "duration": ad.durationInSeconds, "muted": ad.isMuted, "campaign_id": ad.campaignId, "creative_id": ad.creativeId ] ]) ) } ``` ### VideoAdCompletion Emit when the ad finishes: ```swift theme={"dark"} func trackAdCompletion(ad: Ad) { try? Permutive.shared.track( event: "VideoAdCompletion", properties: try? EventProperties([ "ad": [ "title": ad.title, "duration": ad.durationInSeconds, "campaign_id": ad.campaignId, "creative_id": ad.creativeId ] ]) ) } ``` ### VideoAdClicked Emit when the user clicks through: ```swift theme={"dark"} func trackAdClicked(ad: Ad) { try? Permutive.shared.track( event: "VideoAdClicked", properties: try? EventProperties([ "ad": [ "title": ad.title, "campaign_id": ad.campaignId, "creative_id": ad.creativeId ] ]) ) } ``` **Skipped or errored ads:** the schema defines only `VideoAdView`, `VideoAdCompletion`, and `VideoAdClicked`. Treat a skip as no completion event (no `VideoAdCompletion`). If you need to capture skip/error signals for your own analytics, use a custom event name and document it in your workspace schema. *** ## Combining with Content Tracking Pause the content video tracker during ad breaks so engagement time isn't counted twice: ```swift theme={"dark"} class VideoPlayerController { private var mediaTracker: (NSObject & MediaTrackerProtocol)? private var isPlayingAd = false // Content tracking func onContentPlay() { guard !isPlayingAd else { return } mediaTracker?.play() } func onContentPause() { guard !isPlayingAd else { return } mediaTracker?.pause() } // Ad tracking func onAdBreakStarted() { isPlayingAd = true mediaTracker?.pause() // Pause content engagement during ads } func onAdBreakEnded() { isPlayingAd = false // Content resumes via onContentPlay() } func onAdStarted(_ ad: Ad) { try? Permutive.shared.track( event: "VideoAdView", properties: try? EventProperties([ "ad": [ "title": ad.title, "duration": ad.durationInSeconds, "campaign_id": ad.campaignId, "creative_id": ad.creativeId ] ]) ) } func onAdCompleted(_ ad: Ad) { try? Permutive.shared.track( event: "VideoAdCompletion", properties: try? EventProperties([ "ad": [ "title": ad.title, "campaign_id": ad.campaignId, "creative_id": ad.creativeId ] ]) ) } } ``` *** ## Google IMA Integration Hook `IMAAdsManagerDelegate` events to emit Permutive ad events: ```swift theme={"dark"} import GoogleInteractiveMediaAds import Permutive_iOS class VideoPlayerViewController: UIViewController, IMAAdsManagerDelegate { func adsManager(_ adsManager: IMAAdsManager, didReceive event: IMAAdEvent) { guard let imaAd = event.ad else { return } switch event.type { case .STARTED: emitAdView(imaAd) case .COMPLETE: emitAdCompletion(imaAd) case .CLICKED: emitAdClicked(imaAd) default: break } } private func emitAdView(_ ad: IMAAd) { try? Permutive.shared.track( event: "VideoAdView", properties: try? EventProperties([ "ad": [ "title": ad.adTitle, "duration": Int(ad.duration), "campaign_id": ad.adId, "creative_id": ad.creativeID ?? "" ] ]) ) } private func emitAdCompletion(_ ad: IMAAd) { try? Permutive.shared.track( event: "VideoAdCompletion", properties: try? EventProperties([ "ad": [ "title": ad.adTitle, "campaign_id": ad.adId, "creative_id": ad.creativeID ?? "" ] ]) ) } private func emitAdClicked(_ ad: IMAAd) { try? Permutive.shared.track( event: "VideoAdClicked", properties: try? EventProperties([ "ad": [ "title": ad.adTitle, "campaign_id": ad.adId, "creative_id": ad.creativeID ?? "" ] ]) ) } } ``` IMA's `adId` is the line-item / ad reference. Map it to whichever Permutive schema field reflects your taxonomy (`campaign_id` shown here, but use `creative_id` if that's a closer match). *** ## Schema Reference The canonical schema for ad events is defined in [CTV Video Tracking](/sdks/ctv/video-tracking#ad-event-properties). Summary: | Property | Type | Description | | ---------------- | ------ | ------------------------ | | `ad.title` | String | Ad title | | `ad.duration` | Int | Ad duration in seconds | | `ad.muted` | Bool | Whether the ad was muted | | `ad.campaign_id` | String | Campaign identifier | | `ad.creative_id` | String | Creative identifier | For `VideoAdCompletion`, the SDK does not auto-aggregate engagement on iOS (no native ad tracker means no `aggregations.VideoAdEngagement.*` enrichment) — emit only the `ad.*` properties. Ensure property names match your Permutive workspace schema. Mismatched properties are rejected. *** ## tvOS Considerations **tvOS Note:** Ad tracking on tvOS uses the same `Permutive.shared.track(event:properties:)` API. Use it from your TVMLKit or UIKit video player. *** ## Best Practices * Emit `VideoAdView` only when the ad is actually visible / playing * Nest all ad properties under an `ad` parent (e.g., `ad.title`, not flat `ad_title`) * Use the canonical event names — `VideoAdView`, `VideoAdCompletion`, `VideoAdClicked` * Pause the content `MediaTracker` during ad breaks so content engagement isn't inflated * Pass duration in seconds (Int), not milliseconds * Don't invent event names like `VideoAdImpression` or `VideoAdSkipped` unless they're configured in your workspace schema * Don't flatten ad properties at the top level (the schema requires `ad.*` nesting) * Don't track an ad impression before the ad actually starts playing * Don't include PII in ad properties *** ## Troubleshooting **Problem:** Ad events do not show in the Permutive dashboard. **Solutions:** * Confirm `Permutive.configure(...)` ran before `track(...)` is called * Verify event names match: `VideoAdView`, `VideoAdCompletion`, `VideoAdClicked` * Check that `ad.*` property nesting matches your workspace schema * Enable debug logging and watch for schema validation errors **Problem:** Logs show events being rejected. **Solutions:** * Ensure all properties are nested under `ad` (not at the top level) * Check property types — `duration` must be Int, `muted` must be Bool * Verify the event name spelling exactly matches the schema **Problem:** Permutive targeting does not appear on GAM ad requests. **Solutions:** * Call `googleCustomTargeting(adTargetable:)` before the ad request * Pass an active `MediaTracker` or `PageTracker` as `adTargetable` so targeting reflects the current session * Confirm the targeting dictionary is applied to the `GAMRequest` *** ## Related Documentation Track video content Canonical event and property reference GAM integration Custom event tracking # Video Tracking Source: https://docs.permutive.com/sdks/mobile/ios/features/video-tracking Track video playback, engagement, and completion metrics The `MediaTracker` API allows you to track video content events with Permutive. Use this for tracking video playback, engagement, and completion metrics. **Connected TV Integration** Video event tracking described below is available once the connected TV integration has been enabled. Please contact your Customer Success Manager (CSM) to enable this feature. ## Overview `MediaTracker` provides comprehensive video tracking capabilities: * **Automatic lifecycle tracking** - Tracks Videoview and VideoCompletion events * **Engagement metrics** - Measures time spent watching and completion percentage * **Custom events** - Track custom video-related events * **Rich metadata** - Support for extensive video properties *** ## Video Events MediaTracker automatically tracks two event types: ### 1. Videoview Event Tracked when MediaTracker is created: ``` Event: Videoview Properties: - [your custom properties] - [standard video properties] ``` ### 2. VideoCompletion Event Tracked when video is stopped: ``` Event: VideoCompletion Properties: - aggregations.VideoEngagement.engaged_time: Total time spent watching (seconds) - aggregations.VideoEngagement.completion: Percentage of video viewed (0.0 - 1.0) - [your custom properties] - [standard video properties] ``` > 📘 Note > > The `aggregations.VideoEngagement.completion` property requires a known duration to be set. Pass `duration` (in seconds) when calling `createVideoTracker(...)`. *** ## Basic Usage ### Creating a Video Tracker Create a video tracker when playback begins. The iOS SDK does not auto-nest publisher properties under `video` like Android does — hand-build the nested `EventProperties` dictionary so the emitted event matches the canonical schema (`video.title`, `video.content_type`, …). See [CTV Video Tracking](/sdks/ctv/video-tracking#video-event-schema) for the full schema. ```swift Swift theme={"dark"} import Permutive_iOS import AVKit class VideoPlayerViewController: UIViewController { private var mediaTracker: (NSObject & MediaTrackerProtocol)? private var player: AVPlayer! override func viewDidLoad() { super.viewDidLoad() let videoProps = try? EventProperties([ "video": [ "title": "Sample Video", "genre": ["Documentary"], "content_type": ["Education"], "runtime": 120 ] ]) // `duration` is a TimeInterval (seconds), not milliseconds. mediaTracker = try? Permutive.shared.createVideoTracker( duration: 120, // 2 minutes properties: videoProps ) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) mediaTracker?.stop() // Important: Always stop to send VideoCompletion } } ``` ```objectivec Objective-C theme={"dark"} #import "VideoPlayerViewController.h" @import Permutive_iOS; @import AVKit; @interface VideoPlayerViewController () @property (nonatomic, strong) NSObject *mediaTracker; @property (nonatomic, strong) AVPlayer *player; @end @implementation VideoPlayerViewController - (void)viewDidLoad { [super viewDidLoad]; NSError *error = nil; PermutiveEventProperties *videoProps = [[PermutiveEventProperties alloc] init:@{ @"video": @{ @"title": @"Sample Video", @"genre": @[@"Documentary"], @"content_type": @[@"Education"], @"runtime": @120 } } error:&error]; // duration is in seconds. self.mediaTracker = [Permutive.shared createVideoTrackerWithDuration:120 properties:videoProps context:nil error:&error]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self.mediaTracker stop]; // Important: Always stop to send VideoCompletion } @end ``` *** ## MediaTracker Lifecycle ### Expected Usage Flow 1. **Create** MediaTracker instance with properties 2. **Call** `play()` when playback begins 3. **Track buffering** with `pause()` / `play()` 4. **Track scrubbing** with `play(position)` 5. **Call** `stop()` when playback completes > ⚠️ Single Instance Limitation > > Only a single instance of `PageTracker` or `MediaTracker` is available at any time. Creating a new tracker automatically stops any existing one. ### Playback Control Methods #### play() Call when video starts playing: ```swift theme={"dark"} mediaTracker?.play() ``` #### play(position:) Call when playback position changes (e.g., seeking). Position is a `TimeInterval` in seconds. ```swift theme={"dark"} mediaTracker?.play(position: 30) // Seek to 30 seconds ``` #### pause() Call when video is paused or buffering: ```swift theme={"dark"} mediaTracker?.pause() ``` #### stop() Call when video playback completes or user exits: ```swift theme={"dark"} mediaTracker?.stop() // Sends VideoCompletion event ``` #### set(duration:) Call to update the video duration if it wasn't known when the tracker was created: ```swift theme={"dark"} mediaTracker?.set(duration: 240) // 4 minutes ``` > ⚠️ Important: Always Call stop() > > Failing to call `stop()` will prevent VideoCompletion events from being tracked. *** ## Video Properties The iOS SDK accepts an untyped `EventProperties` dictionary. Build the nested shape that matches the [canonical schema](/sdks/ctv/video-tracking#video-event-schema) — properties under a top-level `video` parent, in `snake_case`. There is no auto-mapping or auto-nesting like on Android. ### Standard Video Properties ```swift theme={"dark"} let videoProps = try? EventProperties([ "video": [ "title": "Video Title", "genre": ["Action", "Thriller"], "content_type": ["Movie", "Feature"], "age_rating": "PG-13", "runtime": 7200, // Runtime in seconds "country": "US", "original_language": "en", "audio_language": "en", "subtitles": [ "enabled": true, "language": "en" ], "season_number": 1, "episode_number": 5, "consecutive_episodes": 3, "iab_categories": ["IAB1", "IAB2"] ] ]) ``` ### Schema Reference | Property | Type | Description | | ---------------------------- | --------- | --------------------------------------- | | `video.title` | String | Video title | | `video.genre` | \[String] | Video genres (e.g., "Drama", "Comedy") | | `video.content_type` | \[String] | Content types (e.g., "Movie", "Series") | | `video.age_rating` | String | Age rating (e.g., "PG-13", "TV-MA") | | `video.runtime` | Int | Runtime in **seconds** | | `video.country` | String | Origin country | | `video.original_language` | String | Original language code | | `video.audio_language` | String | Audio language code | | `video.subtitles.enabled` | Bool | Whether subtitles are enabled | | `video.subtitles.language` | String | Subtitle language code | | `video.season_number` | Int | Season number (for series) | | `video.episode_number` | Int | Episode number (for series) | | `video.consecutive_episodes` | Int | Consecutive episodes watched | | `video.iab_categories` | \[String] | IAB content taxonomy categories | > 💡 Best Practice > > Track as many properties as possible to enable richer cohort creation and insights. *** ## Page Context (Optional) If the video is displayed within a page context (e.g., embedded in an article), pass a `Context` to associate the tracker with that page: ```swift theme={"dark"} let videoProps = try? EventProperties([ "video": ["title": "Tutorial Video"] ]) let context = Context( title: "How to Use iOS SDK", url: URL(string: "https://example.com/tutorial"), referrer: URL(string: "https://example.com/docs") ) mediaTracker = try? Permutive.shared.createVideoTracker( duration: 120, properties: videoProps, context: context ) ``` ### Context Reference | Property | Type | Description | | ------------ | ------- | ------------------ | | **title** | String? | Page title | | **url** | URL? | Page URL | | **referrer** | URL? | Referring page URL | Providing a `url` in `Context` enables contextual cohort generation for the video content. *** ## Custom Properties The iOS SDK does not take a separate "custom properties" parameter — add custom keys directly to the same `EventProperties` dictionary, alongside the `video` schema fields. Custom keys are emitted at the top level of the event: ```swift theme={"dark"} let props = try? EventProperties([ "video": [ "title": "Episode 5", "season_number": 1, "episode_number": 5 ], "video_id": "vid_12345", "playlist_id": "playlist_abc", "is_premium_content": true, "content_partner": "PartnerXYZ" ]) mediaTracker = try? Permutive.shared.createVideoTracker( duration: 120, properties: props ) ``` *** ## Complete Example ```swift Swift theme={"dark"} import Permutive_iOS import AVKit class VideoPlayerViewController: UIViewController { private var mediaTracker: (NSObject & MediaTrackerProtocol)? private var player: AVPlayer! private var playerObserver: Any? override func viewDidLoad() { super.viewDidLoad() // Set up AVPlayer setupPlayer() } private func setupPlayer() { let url = URL(string: "https://example.com/video.mp4")! player = AVPlayer(url: url) let playerViewController = AVPlayerViewController() playerViewController.player = player addChild(playerViewController) view.addSubview(playerViewController.view) playerViewController.view.frame = view.bounds // Get duration once available, then set up tracker player.currentItem?.asset.loadValuesAsynchronously(forKeys: ["duration"]) { [weak self] in guard let self = self, let duration = self.player.currentItem?.duration, !duration.isIndefinite else { return } let durationSeconds = CMTimeGetSeconds(duration) DispatchQueue.main.async { let props = try? EventProperties([ "video": [ "title": "Introduction to iOS", "genre": ["Educational", "Technology"], "content_type": ["Tutorial"], "age_rating": "G", "runtime": 180, "country": "US", "original_language": "en", "audio_language": "en", "subtitles": ["enabled": false], "iab_categories": ["IAB19"] ], "video_id": "vid_tutorial_001", "is_premium": false ]) let context = Context( title: "iOS Tutorial Page", url: URL(string: "https://example.com/tutorials/ios"), referrer: URL(string: "https://example.com/home") ) self.mediaTracker = try? Permutive.shared.createVideoTracker( duration: durationSeconds, properties: props, context: context ) self.observePlayer() } } } private func observePlayer() { playerObserver = player.observe(\.timeControlStatus) { [weak self] player, _ in switch player.timeControlStatus { case .playing: self?.mediaTracker?.play() case .paused: self?.mediaTracker?.pause() case .waitingToPlayAtSpecifiedRate: break // Buffering @unknown default: break } } } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) mediaTracker?.stop() // Send VideoCompletion event } deinit { if let observer = playerObserver { player.removeTimeObserver(observer) } } } ``` ```objectivec Objective-C theme={"dark"} @implementation VideoPlayerViewController - (void)viewDidLoad { [super viewDidLoad]; [self setupPlayer]; } - (void)setupPlayer { NSURL *url = [NSURL URLWithString:@"https://example.com/video.mp4"]; self.player = [AVPlayer playerWithURL:url]; AVPlayerViewController *playerVC = [[AVPlayerViewController alloc] init]; playerVC.player = self.player; [self addChildViewController:playerVC]; [self.view addSubview:playerVC.view]; playerVC.view.frame = self.view.bounds; // Get duration once available [self.player.currentItem.asset loadValuesAsynchronouslyForKeys:@[@"duration"] completionHandler:^{ CMTime duration = self.player.currentItem.duration; if (CMTIME_IS_INDEFINITE(duration)) return; NSTimeInterval durationSeconds = CMTimeGetSeconds(duration); dispatch_async(dispatch_get_main_queue(), ^{ NSError *error = nil; PermutiveEventProperties *props = [[PermutiveEventProperties alloc] init:@{ @"video": @{ @"title": @"Introduction to iOS", @"genre": @[@"Educational", @"Technology"], @"content_type": @[@"Tutorial"], @"age_rating": @"G", @"runtime": @180, @"country": @"US", @"original_language": @"en", @"audio_language": @"en", @"subtitles": @{@"enabled": @NO}, @"iab_categories": @[@"IAB19"] }, @"video_id": @"vid_tutorial_001", @"is_premium": @NO } error:&error]; PermutiveContext *context = [[PermutiveContext alloc] initWithTitle:@"iOS Tutorial Page" url:[NSURL URLWithString:@"https://example.com/tutorials/ios"] referrer:[NSURL URLWithString:@"https://example.com/home"]]; self.mediaTracker = [Permutive.shared createVideoTrackerWithDuration:durationSeconds properties:props context:context error:&error]; [self observePlayer]; }); }]; } - (void)observePlayer { [self.player addObserver:self forKeyPath:@"timeControlStatus" options:NSKeyValueObservingOptionNew context:nil]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"timeControlStatus"]) { if (self.player.timeControlStatus == AVPlayerTimeControlStatusPlaying) { [self.mediaTracker play]; } else if (self.player.timeControlStatus == AVPlayerTimeControlStatusPaused) { [self.mediaTracker pause]; } } } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self.mediaTracker stop]; // Send VideoCompletion event } - (void)dealloc { [self.player removeObserver:self forKeyPath:@"timeControlStatus"]; } @end ``` *** ## Tracking Custom Events Track custom video-related events using the `track(event:properties:)` method. Both parameters are required (pass `nil` for `properties` if there are none). ```swift theme={"dark"} // User shares video try? mediaTracker?.track( event: "VideoShared", properties: try? EventProperties([ "share_method": "twitter", "video_position_seconds": 45 ]) ) // User adds to favorites try? mediaTracker?.track(event: "VideoAddedToFavorites", properties: nil) // Quality changed try? mediaTracker?.track( event: "VideoQualityChanged", properties: try? EventProperties([ "from_quality": "720p", "to_quality": "1080p" ]) ) ``` *** ## Video Ad Tracking **iOS does not have a native AdTracker.** Emit standard `VideoAdView`, `VideoAdCompletion`, and `VideoAdClicked` events using `Permutive.shared.track(event:properties:)` with properties nested under an `ad` parent per the [canonical schema](/sdks/ctv/video-tracking#ad-event-properties). ```swift theme={"dark"} // When the ad starts try? Permutive.shared.track( event: "VideoAdView", properties: try? EventProperties([ "ad": [ "title": "Product Ad", "duration": 15, "muted": false, "campaign_id": "campaign_123", "creative_id": "creative_456" ] ]) ) // When the ad completes try? Permutive.shared.track( event: "VideoAdCompletion", properties: try? EventProperties([ "ad": [ "title": "Product Ad", "campaign_id": "campaign_123", "creative_id": "creative_456" ] ]) ) ``` See [Video Ad Tracking](/sdks/mobile/ios/features/video-ad-tracking) for the full guide. *** ## Use Cases ```swift theme={"dark"} let props = try? EventProperties([ "video": [ "title": movie.title, "genre": movie.genres, "content_type": ["Movie", "Feature"], "age_rating": movie.rating, "runtime": movie.runtimeSeconds, "country": movie.country, "original_language": movie.language, "iab_categories": movie.iabCategories ] ]) mediaTracker = try? Permutive.shared.createVideoTracker( duration: movieDurationSeconds, properties: props ) ``` ```swift theme={"dark"} let props = try? EventProperties([ "video": [ "title": tutorial.title, "genre": ["Educational"], "content_type": ["Tutorial"], "runtime": tutorial.runtimeSeconds ], "course_id": tutorial.courseId, "module_id": tutorial.moduleId, "difficulty_level": tutorial.difficulty ]) mediaTracker = try? Permutive.shared.createVideoTracker( duration: tutorialDurationSeconds, properties: props ) ``` ```swift theme={"dark"} let props = try? EventProperties([ "video": [ "title": episode.title, "genre": series.genres, "content_type": ["Series", "Episode"], "season_number": episode.season, "episode_number": episode.number, "consecutive_episodes": userWatchData.consecutiveCount, "runtime": episode.runtimeSeconds ] ]) mediaTracker = try? Permutive.shared.createVideoTracker( duration: episodeDurationSeconds, properties: props ) ``` *** ## tvOS Considerations **tvOS Note:** MediaTracker works identically on tvOS. Use the same APIs with your TVUIKit video player or AVPlayerViewController. ```swift theme={"dark"} // tvOS with AVPlayerViewController class TVVideoPlayerViewController: UIViewController { private var mediaTracker: MediaTrackerProtocol? override func viewDidLoad() { super.viewDidLoad() let playerVC = AVPlayerViewController() playerVC.player = AVPlayer(url: videoURL) present(playerVC, animated: true) { self.setupMediaTracker() playerVC.player?.play() } } } ``` *** ## Troubleshooting **Problem:** VideoCompletion events not appearing. **Cause:** `stop()` not called. **Solution:** Always call `stop()` in `viewWillDisappear`: ```swift theme={"dark"} override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) mediaTracker?.stop() // REQUIRED } ``` **Problem:** Engaged time is inaccurate. **Causes:** 1. Not calling `play()` when video plays 2. Not calling `pause()` when video pauses/buffers **Solution:** Track all play/pause events: ```swift theme={"dark"} playerObserver = player.observe(\.timeControlStatus) { [weak self] player, _ in switch player.timeControlStatus { case .playing: try? self?.mediaTracker?.play() case .paused: self?.mediaTracker?.pause() default: break } } ``` **Problem:** `aggregations.VideoEngagement.completion` is missing or nil. **Cause:** Duration not provided when creating the video tracker. **Solution:** Always provide `duration` (in seconds): ```swift theme={"dark"} mediaTracker = try? Permutive.shared.createVideoTracker( duration: videoDurationSeconds // REQUIRED for completion ) ``` **Problem:** Error about multiple trackers. **Cause:** Creating a new PageTracker or video tracker without closing the existing one. **Solution:** Close the existing tracker first: ```swift theme={"dark"} // Close old tracker mediaTracker?.stop() // Now create new tracker mediaTracker = try? Permutive.shared.createVideoTracker(...) ``` See [Common Errors](/sdks/mobile/ios/troubleshooting/common-errors) for more troubleshooting. *** ## Best Practices * Always call `stop()` when video finishes * Provide duration for completion tracking * Track play/pause events accurately * Include as many video properties as possible * Close tracker before creating a new one * Track buffering with pause/play * Forget to call `stop()` * Create multiple MediaTrackers simultaneously * Skip play/pause lifecycle events * Omit video duration *** ## Related Documentation Track video advertisements Track non-video content Custom properties Solve common issues *** ## API Reference ### MediaTrackerProtocol * `play()` — Start/resume video playback * `play(position: TimeInterval)` — Start at a specific position (seconds) * `pause()` — Pause video playback * `stop()` — Complete video tracking (sends `VideoCompletion`) * `set(duration: TimeInterval)` — Update video duration (seconds), if it wasn't known at creation * `track(event: String, properties: EventProperties?) throws` — Track a custom event ### Creating a Video Tracker * `Permutive.shared.createVideoTracker(duration: TimeInterval, properties: EventProperties?, context: Context?) throws -> NSObject & MediaTrackerProtocol` # Initialization Source: https://docs.permutive.com/sdks/mobile/ios/getting-started/initialization Configure and start the Permutive SDK The SDK must be initialized before tracking events or accessing cohort data. Initialization is asynchronous and should happen early in your app lifecycle. ## Basic Initialization ```swift Swift theme={"dark"} import Permutive_iOS func initializePermutive() { guard let options = Options( apiKey: "", organisationId: "", workspaceId: "" ) else { print("Failed to create Permutive options") return } Permutive.shared.start(with: options) { error in if let error = error { print("Permutive initialization failed: \(error.localizedDescription)") return } print("Permutive SDK ready") } } ``` ```objectivec Objective-C theme={"dark"} @import Permutive_iOS; - (void)initializePermutive { PermutiveOptions *options = [[PermutiveOptions alloc] initWithApiKey:@"" organisationId:@"" workspaceId:@""]; if (options == nil) { NSLog(@"Failed to create Permutive options"); return; } [[Permutive shared] startWith:options context:nil completion:^(NSError *error) { if (error != nil) { NSLog(@"Permutive initialization failed: %@", error.localizedDescription); return; } NSLog(@"Permutive SDK ready"); }]; } ``` ## Configuration Options The `Options` class accepts several configuration parameters: ### Required Parameters | Parameter | Type | Description | | ---------------- | -------- | ------------------------------ | | `apiKey` | `String` | Your Permutive API key | | `organisationId` | `String` | Your Permutive organisation ID | | `workspaceId` | `String` | Your Permutive workspace ID | ### Optional Configuration ```swift Swift theme={"dark"} guard let options = Options( apiKey: "", organisationId: "", workspaceId: "" ) else { return } // Enable debug logging options.logModes = LogMode.all // Start with options Permutive.shared.start(with: options) { error in // Handle completion } ``` ```objectivec Objective-C theme={"dark"} PermutiveOptions *options = [[PermutiveOptions alloc] initWithApiKey:@"" organisationId:@"" workspaceId:@""]; // Enable debug logging options.logModes = PermutiveLogModeAll; [[Permutive shared] startWith:options context:nil completion:^(NSError *error) { // Handle completion }]; ``` ### Log Modes Control what the SDK logs to the console: | Mode | Description | | --------------- | ------------------------------------------ | | `LogMode.none` | No logging (default for production) | | `LogMode.all` | All messages (recommended for development) | | `LogMode.info` | Informational messages only | | `LogMode.error` | Error messages only | ## Where to Initialize ### UIKit - AppDelegate ```swift Swift theme={"dark"} import UIKit import Permutive_iOS @main class AppDelegate: UIResponder, UIApplicationDelegate { func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { initializePermutive() return true } private func initializePermutive() { guard let options = Options( apiKey: "", organisationId: "", workspaceId: "" ) else { return } Permutive.shared.start(with: options) { error in if let error = error { print("Permutive error: \(error)") } } } } ``` ```objectivec Objective-C theme={"dark"} #import "AppDelegate.h" @import Permutive_iOS; @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self initializePermutive]; return YES; } - (void)initializePermutive { PermutiveOptions *options = [[PermutiveOptions alloc] initWithApiKey:@"" organisationId:@"" workspaceId:@""]; [[Permutive shared] startWith:options context:nil completion:^(NSError *error) { if (error != nil) { NSLog(@"Permutive error: %@", error); } }]; } @end ``` ### SwiftUI - App Struct ```swift theme={"dark"} import SwiftUI import Permutive_iOS @main struct MyApp: App { init() { initializePermutive() } var body: some Scene { WindowGroup { ContentView() } } private func initializePermutive() { guard let options = Options( apiKey: "", organisationId: "", workspaceId: "" ) else { return } Permutive.shared.start(with: options) { error in if let error = error { print("Permutive error: \(error)") } } } } ``` ## Consent Considerations The Permutive SDK assumes consent has been obtained before initialization. If your app requires user consent for tracking, complete your consent flow before calling `start(with:)`. The SDK does not call Apple's App Tracking Transparency (ATT) framework. If you need ATT consent: ```swift theme={"dark"} import AppTrackingTransparency func requestConsentThenInitialize() { if #available(iOS 14, *) { ATTrackingManager.requestTrackingAuthorization { status in switch status { case .authorized: self.initializePermutive() case .denied, .restricted: // Handle accordingly - you may still initialize // Permutive without IDFA tracking self.initializePermutive() case .notDetermined: break @unknown default: break } } } else { initializePermutive() } } ``` **IDFA is not required.** Permutive works effectively without IDFA. Consider using `identifierForVendor` or hashed email addresses instead. See [IDFA Provider](/sdks/mobile/ios/integrations/idfa-provider) for details. ## Initialization with Context You can provide initial context during initialization: ```swift Swift theme={"dark"} let context = Context( title: "App Launch", url: URL(string: "https://example.com/app"), referrer: nil ) Permutive.shared.start(with: options, context: context) { error in // Handle completion } ``` ```objectivec Objective-C theme={"dark"} PermutiveContext *context = [[PermutiveContext alloc] initWithTitle:@"App Launch" url:[NSURL URLWithString:@"https://example.com/app"] referrer:nil]; [[Permutive shared] startWith:options context:context completion:^(NSError *error) { // Handle completion }]; ``` ## Checking Initialization Status The SDK provides properties to check its current state: ```swift theme={"dark"} // Get the current user ID (available after initialization) let userId = Permutive.shared.currentUserId // Check current cohorts let cohorts = Permutive.shared.cohorts ``` ## Error Handling Handle initialization errors appropriately: ```swift Swift theme={"dark"} Permutive.shared.start(with: options) { error in if let error = error { // Log the error for debugging print("Permutive initialization failed: \(error.localizedDescription)") // Optionally report to your error tracking service // ErrorTracker.report(error) // The app can continue without Permutive // SDK calls will be no-ops or return empty data return } // SDK is ready - proceed with tracking print("Permutive ready, user ID: \(Permutive.shared.currentUserId)") } ``` ```objectivec Objective-C theme={"dark"} [[Permutive shared] startWith:options context:nil completion:^(NSError *error) { if (error != nil) { NSLog(@"Permutive initialization failed: %@", error.localizedDescription); return; } NSLog(@"Permutive ready, user ID: %@", [[Permutive shared] currentUserId]); }]; ``` ## tvOS Considerations **tvOS Note:** The initialization process is identical on tvOS. The SDK automatically handles platform differences. App Tracking Transparency and IDFA are available on tvOS from version 14.5 onwards. ## Best Practices * Initialize as early as possible in the app lifecycle * Handle initialization errors gracefully * Complete consent flows before initialization * Enable debug logging during development * Store credentials securely (consider using a configuration file or build settings) * Don't initialize multiple times * Don't block the main thread waiting for initialization * Don't hard-code credentials in source control * Don't assume immediate availability of cohort data after initialization ## Troubleshooting **Problem:** No error, but SDK doesn't seem to work. **Solutions:** * Enable debug logging: `options.logModes = LogMode.all` * Check credentials are correct * Verify network connectivity **Problem:** Initialization fails with credential error. **Solutions:** * Verify API key, organization ID, and workspace ID from your dashboard * Check for extra whitespace or newlines in credential strings * Ensure credentials match the correct environment (staging vs production) **Problem:** Events tracked before initialization completes are lost. **Solution:** Wait for the completion callback before tracking: ```swift theme={"dark"} Permutive.shared.start(with: options) { error in guard error == nil else { return } // Now safe to track self.startTracking() } ``` ## Next Steps Verify your integration is working Track page views and engagement # Installation Source: https://docs.permutive.com/sdks/mobile/ios/getting-started/installation Add the Permutive SDK to your iOS or tvOS project The Permutive iOS SDK can be installed via Swift Package Manager or CocoaPods. ## Requirements | Requirement | Version | | ----------- | --------------------------- | | iOS | 12.0+ | | tvOS | 12.0+ | | Xcode | 13.0+ | | Swift | 5.0+ | | CocoaPods | 1.9.1+ (if using CocoaPods) | ## Swift Package Manager Swift Package Manager is the recommended installation method for new projects. In Xcode, go to **File → Add Package Dependencies** In the search field, enter: ``` https://github.com/permutive-engineering/permutive-ios-spm ``` Choose a dependency rule: * **Up to Next Major Version**: `2.2.0` (recommended) * **Exact Version**: `2.2.0` Select **Permutive\_iOS** and add it to your target ### Package.swift (for packages) If you're adding Permutive to a Swift package, add it to your `Package.swift`: ```swift theme={"dark"} dependencies: [ .package(url: "https://github.com/permutive-engineering/permutive-ios-spm", from: "2.2.0") ], targets: [ .target( name: "YourTarget", dependencies: [ .product(name: "Permutive_iOS", package: "permutive-ios-spm") ] ) ] ``` ## CocoaPods ### Basic Installation Add the Permutive pod to your `Podfile`: ```ruby theme={"dark"} target 'YourApp' do platform :ios, '12.0' pod 'Permutive_iOS', '~> 2.2.0' end ``` Then install: ```bash theme={"dark"} pod install ``` ### tvOS Installation For tvOS targets, specify the tvOS platform: ```ruby theme={"dark"} target 'YourTVApp' do platform :tvos, '12.0' pod 'Permutive_iOS', '~> 2.2.0' end ``` ### Multi-Platform Podfile For apps supporting both iOS and tvOS: ```ruby theme={"dark"} # Shared dependencies def shared_pods pod 'Permutive_iOS', '~> 2.2.0' end target 'YourApp-iOS' do platform :ios, '12.0' shared_pods end target 'YourApp-tvOS' do platform :tvos, '12.0' shared_pods end ``` ### Updating the SDK To update to the latest version: ```bash theme={"dark"} pod update Permutive_iOS ``` ## Importing the SDK After installation, import Permutive in your Swift or Objective-C files: ```swift Swift theme={"dark"} import Permutive_iOS ``` ```objectivec Objective-C theme={"dark"} @import Permutive_iOS; ``` ## Verifying Installation After installing, verify the SDK is accessible: ```swift Swift theme={"dark"} import Permutive_iOS // This should compile without errors let _ = Permutive.shared ``` ```objectivec Objective-C theme={"dark"} @import Permutive_iOS; // This should compile without errors [Permutive shared]; ``` Build your project (⌘B) to confirm there are no import errors. ## Mac Catalyst Support The SDK supports Mac Catalyst. When using Swift Package Manager, add the package as normal - it works for both iOS and Mac Catalyst targets. For CocoaPods, the standard iOS pod works with Catalyst projects. ## Troubleshooting **Problem:** `Unable to find a specification for 'Permutive_iOS'` **Solutions:** * Update your CocoaPods repo: `pod repo update` * Ensure CocoaPods is version 1.9.1 or later: `pod --version` * Try clearing the cache: `pod cache clean --all` **Problem:** Xcode cannot resolve the package. **Solutions:** * Check your network connection * Reset package caches: **File → Packages → Reset Package Caches** * Verify the URL is correct: `https://github.com/permutive-engineering/permutive-ios-spm` **Problem:** `No such module 'Permutive_iOS'` **Solutions:** * Clean build folder: **Product → Clean Build Folder** (⇧⌘K) * For CocoaPods: Ensure you opened the `.xcworkspace` file, not `.xcodeproj` * Restart Xcode **Problem:** Bitcode-related build errors. **Solution:** Bitcode is no longer required for App Store submission. Disable it in your target's Build Settings: set **Enable Bitcode** to **No**. ## Next Steps Configure and start the SDK Complete setup walkthrough # Quick Start Source: https://docs.permutive.com/sdks/mobile/ios/getting-started/quick-start Get up and running with the Permutive iOS SDK in minutes This guide walks you through installing the SDK, initializing it, and tracking your first page view. ## Prerequisites * Xcode 13.0 or later * iOS 12.0+ or tvOS 12.0+ deployment target * Your Permutive **API Key**, **Organization ID**, and **Workspace ID** (from your dashboard) ## Step 1: Install the SDK 1. In Xcode, go to **File → Add Package Dependencies** 2. Enter the package URL: ``` https://github.com/permutive-engineering/permutive-ios-spm ``` 3. Select **Permutive\_iOS** as the package product 4. Click **Add Package** Add to your `Podfile`: ```ruby theme={"dark"} target 'YourApp' do platform :ios, '12.0' pod 'Permutive_iOS', '~> 2.2.0' end ``` Then run: ```bash theme={"dark"} pod install ``` ## Step 2: Initialize the SDK Initialize Permutive early in your app lifecycle, typically in your `AppDelegate` or `SceneDelegate`. ```swift Swift theme={"dark"} import UIKit import Permutive_iOS @main class AppDelegate: UIResponder, UIApplicationDelegate { func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { initializePermutive() return true } private func initializePermutive() { guard let options = Options( apiKey: "", organisationId: "", workspaceId: "" ) else { print("Failed to create Permutive options") return } Permutive.shared.start(with: options) { error in if let error = error { print("Permutive initialization failed: \(error.localizedDescription)") return } print("Permutive SDK ready") } } } ``` ```objectivec Objective-C theme={"dark"} #import "AppDelegate.h" @import Permutive_iOS; @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self initializePermutive]; return YES; } - (void)initializePermutive { PermutiveOptions *options = [[PermutiveOptions alloc] initWithApiKey:@"" organisationId:@"" workspaceId:@""]; if (options == nil) { NSLog(@"Failed to create Permutive options"); return; } [[Permutive shared] startWith:options context:nil completion:^(NSError *error) { if (error != nil) { NSLog(@"Permutive initialization failed: %@", error.localizedDescription); return; } NSLog(@"Permutive SDK ready"); }]; } @end ``` ## Step 3: Track Your First Page View Use `PageTracker` to track page views. This is the recommended approach for most tracking scenarios. ```swift Swift theme={"dark"} import UIKit import Permutive_iOS class ArticleViewController: UIViewController { private var pageTracker: PageTrackerProtocol? override func viewDidLoad() { super.viewDidLoad() // Create event properties let properties = try? EventProperties([ "category": "news", "author": "Jane Smith" ]) // Create page context let context = Context( title: "Breaking News: Tech Advances", url: URL(string: "https://example.com/articles/tech-advances"), referrer: nil ) // Create the page tracker pageTracker = try? Permutive.shared.createPageTracker( properties: properties, context: context ) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) try? pageTracker?.resume() // Starts tracking, fires Pageview event } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) pageTracker?.pause() // Pauses engagement timer } deinit { pageTracker?.stop() // Stops tracking, fires PageViewComplete event } } ``` ```objectivec Objective-C theme={"dark"} #import "ArticleViewController.h" @import Permutive_iOS; @interface ArticleViewController () @property (nonatomic, strong) NSObject *pageTracker; @end @implementation ArticleViewController - (void)viewDidLoad { [super viewDidLoad]; // Create event properties NSError *error = nil; PermutiveEventProperties *properties = [[PermutiveEventProperties alloc] init:@{@"category": @"news", @"author": @"Jane Smith"} error:&error]; // Create page context PermutiveContext *context = [[PermutiveContext alloc] initWithTitle:@"Breaking News: Tech Advances" url:[NSURL URLWithString:@"https://example.com/articles/tech-advances"] referrer:nil]; // Create the page tracker self.pageTracker = [[Permutive shared] createPageTrackerWithProperties:properties context:context error:&error]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self.pageTracker resumeAndReturnError:nil]; // Starts tracking } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [self.pageTracker pause]; // Pauses engagement timer } - (void)dealloc { [self.pageTracker stop]; // Stops tracking } @end ``` ## Step 4: Verify the Integration Enable debug logging to confirm events are being sent: ```swift Swift theme={"dark"} guard let options = Options( apiKey: "", organisationId: "", workspaceId: "" ) else { return } // Enable all logging options.logModes = LogMode.all Permutive.shared.start(with: options) { error in // ... } ``` ```objectivec Objective-C theme={"dark"} PermutiveOptions *options = [[PermutiveOptions alloc] initWithApiKey:@"" organisationId:@"" workspaceId:@""]; options.logModes = PermutiveLogModeAll; [[Permutive shared] startWith:options context:nil completion:^(NSError *error) { // ... }]; ``` Run your app and check the Xcode console for: ``` Permutive: [info] SDK ready Permutive: [info] Accepted: 1 / 1 ``` The "Accepted: 1 / 1" message confirms your event was successfully sent to Permutive servers. ## Next Steps CocoaPods and SPM configuration options Advanced SDK configuration Complete PageTracker guide Track users across sessions # Verification Source: https://docs.permutive.com/sdks/mobile/ios/getting-started/verification Verify your Permutive iOS SDK integration is working correctly After installing and initializing the SDK, verify that events are being sent correctly before releasing to production. ## Enable Debug Logging Enable debug logging to see SDK activity in the Xcode console: ```swift Swift theme={"dark"} guard let options = Options( apiKey: "", organisationId: "", workspaceId: "" ) else { return } // Enable all log messages options.logModes = LogMode.all Permutive.shared.start(with: options) { error in // ... } ``` ```objectivec Objective-C theme={"dark"} PermutiveOptions *options = [[PermutiveOptions alloc] initWithApiKey:@"" organisationId:@"" workspaceId:@""]; // Enable all log messages options.logModes = PermutiveLogModeAll; [[Permutive shared] startWith:options context:nil completion:^(NSError *error) { // ... }]; ``` ## What to Look For ### Successful Initialization When the SDK initializes successfully, you'll see: ``` Permutive: [info] SDK ready ``` ### Successful Event Tracking When events are accepted by Permutive servers: ``` Permutive: [info] Accepted: 1 / 1 ``` The format is `Accepted: X / Y` where X is the number of events accepted and Y is the total sent. ### Schema Validation Errors If event properties don't match your schema: ``` Permutive: [error] Encountered 1 event rejection! Permutive: [error] Error 1: A validation check carried out by the server did not succeed.(1007) Permutive: [error] Error 1: Schema validation failed with reason(s): [#: 1 schema violations found;#: extraneous key [invalid_key] is not permitted] ``` Schema validation errors mean events are being rejected. Fix the property names or types to match your event schema in the Permutive dashboard. ## Verification Checklist Look for `SDK ready` in the console after app launch. Navigate through your app and look for `Accepted: X / X` messages. Ensure there are no `event rejection` error messages. Log into your Permutive dashboard and verify events appear (may take up to 5 minutes). ## Verification Code Example Add this verification helper during development: ```swift Swift theme={"dark"} import Permutive_iOS class PermutiveVerification { static func verify() { // Check user ID let userId = Permutive.shared.currentUserId print("✅ Permutive User ID: \(userId)") // Check cohorts let cohorts = Permutive.shared.cohorts print("✅ Current cohorts: \(cohorts)") // Check activations let activations = Permutive.shared.activations print("✅ Activations: \(activations)") // Track a test event do { let properties = try EventProperties([ "verification_test": true, "timestamp": Date().timeIntervalSince1970 ]) try Permutive.shared.track(event: "VerificationTest", properties: properties) print("✅ Test event sent") } catch { print("❌ Failed to send test event: \(error)") } } } // Call after initialization completes Permutive.shared.start(with: options) { error in guard error == nil else { return } PermutiveVerification.verify() } ``` ```objectivec Objective-C theme={"dark"} @import Permutive_iOS; @interface PermutiveVerification : NSObject + (void)verify; @end @implementation PermutiveVerification + (void)verify { // Check user ID NSString *userId = [[Permutive shared] currentUserId]; NSLog(@"✅ Permutive User ID: %@", userId); // Check cohorts NSSet *cohorts = [[Permutive shared] cohorts]; NSLog(@"✅ Current cohorts: %@", cohorts); // Check activations NSDictionary *activations = [[Permutive shared] activations]; NSLog(@"✅ Activations: %@", activations); // Track a test event NSError *error = nil; PermutiveEventProperties *properties = [[PermutiveEventProperties alloc] init:@{@"verification_test": @YES} error:&error]; [[Permutive shared] trackWithEvent:@"VerificationTest" properties:properties error:&error]; if (error == nil) { NSLog(@"✅ Test event sent"); } else { NSLog(@"❌ Failed to send test event: %@", error); } } @end ``` ## PageTracker Verification Verify PageTracker is working correctly: ```swift Swift theme={"dark"} class TestViewController: UIViewController { private var pageTracker: PageTrackerProtocol? override func viewDidLoad() { super.viewDidLoad() let properties = try? EventProperties(["test_page": true]) let context = Context( title: "Verification Test Page", url: URL(string: "https://example.com/test"), referrer: nil ) pageTracker = try? Permutive.shared.createPageTracker( properties: properties, context: context ) print("✅ PageTracker created") } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) do { try pageTracker?.resume() print("✅ PageTracker resumed - Pageview event should be sent") } catch { print("❌ PageTracker resume failed: \(error)") } } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) pageTracker?.pause() print("✅ PageTracker paused") } deinit { pageTracker?.stop() print("✅ PageTracker stopped - PageViewComplete event should be sent") } } ``` ```objectivec Objective-C theme={"dark"} @interface TestViewController () @property (nonatomic, strong) NSObject *pageTracker; @end @implementation TestViewController - (void)viewDidLoad { [super viewDidLoad]; NSError *error = nil; PermutiveEventProperties *properties = [[PermutiveEventProperties alloc] init:@{@"test_page": @YES} error:&error]; PermutiveContext *context = [[PermutiveContext alloc] initWithTitle:@"Verification Test Page" url:[NSURL URLWithString:@"https://example.com/test"] referrer:nil]; self.pageTracker = [[Permutive shared] createPageTrackerWithProperties:properties context:context error:&error]; NSLog(@"✅ PageTracker created"); } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSError *error = nil; [self.pageTracker resumeAndReturnError:&error]; if (error == nil) { NSLog(@"✅ PageTracker resumed"); } else { NSLog(@"❌ PageTracker resume failed: %@", error); } } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [self.pageTracker pause]; NSLog(@"✅ PageTracker paused"); } - (void)dealloc { [self.pageTracker stop]; NSLog(@"✅ PageTracker stopped"); } @end ``` ## Common Verification Issues **Problem:** Console is silent, no Permutive logs appear. **Solutions:** * Confirm `logModes = LogMode.all` is set before calling `start()` * Check Xcode console filter isn't hiding messages * Verify SDK is actually being initialized (add a breakpoint) **Problem:** Events sent but rejected by server. **Solutions:** * Check event name matches schema (alphanumeric and underscores only) * Verify property names and types match your Permutive dashboard schema * Remove any extra properties not in the schema **Problem:** Console shows `Accepted` but events not in dashboard. **Solutions:** * Wait up to 5 minutes for events to process * Verify you're looking at the correct workspace * Check the date range filter in the dashboard * Confirm credentials match the dashboard environment **Problem:** `cohorts` returns an empty set. **Solutions:** * Cohorts are evaluated server-side and may take time to populate * Ensure you've tracked sufficient events to qualify for cohorts * Check with your Customer Success Manager that cohorts are configured ## Disable Logging for Production Always disable debug logging before releasing to production to avoid performance overhead and prevent sensitive data from appearing in device logs. ```swift Swift theme={"dark"} #if DEBUG options.logModes = LogMode.all #else options.logModes = LogMode.none #endif ``` ```objectivec Objective-C theme={"dark"} #ifdef DEBUG options.logModes = PermutiveLogModeAll; #else options.logModes = PermutiveLogModeNone; #endif ``` ## Next Steps Once verification passes: Implement page tracking throughout your app Set up user identity tracking Integrate with GAM for ad targeting Troubleshoot common problems # Migration Guide: v2.6.x Source: https://docs.permutive.com/sdks/mobile/ios/guides/migration/v2-6 Upgrade to Permutive iOS SDK v2.6.x and opt into config-driven automatic ID collection This guide covers upgrading to Permutive iOS SDK v2.6.x, which introduces **config-driven automatic ID collection**. When enabled for your workspace, the SDK automatically collects the user's IDFA, IDFV, and IP address as identity aliases — with no provider code required. **Migration Difficulty:** Easy | **Estimated Time:** 15 minutes | **Breaking Changes:** None ## What's New in v2.6.0 The SDK captures the IDFA as an `idfa` alias when enabled in your workspace config and the user has authorized tracking The SDK captures the `identifierForVendor` as an `idfv` alias — no user permission required The SDK captures the user's IP as an `ip_address` alias from an internal endpoint — no app changes required **Good news:** This release is fully backward compatible with no breaking changes or deprecations. Automatic collection is **off by default** and is enabled remotely through your dashboard. ## How Automatic ID Collection Works Collection is driven by your workspace configuration, not by app code. Once you enable it for your workspace in the dashboard, the SDK picks it up on its next config refresh. * Stored as an alias with tag **`idfa`**, the priority defined in your config. * **Only collected when the user has authorized tracking** via App Tracking Transparency (ATT status `authorized`). Requires iOS 14+. * If ATT status is **not determined**, collection is skipped until the user responds — the SDK does **not** present the ATT prompt itself. * If ATT status is **denied** or **restricted**, no alias is stored and any existing `idfa` alias is cleared. * A zeroed-out IDFA (`00000000-0000-0000-0000-000000000000`) is treated as no IDFA — nothing is stored. * If an alias with the same tag and value already exists, storage is skipped to avoid duplicates. * Stored as an alias with tag **`idfv`**, the priority defined in your config. * Uses `identifierForVendor` — **no user permission is required**, and it works on both iOS and tvOS. * Stored as an alias with tag **`ip_address`**, the priority defined in your config. * Retrieved from an internal endpoint and updated when the IP changes. * **No app or privacy-manifest changes are required** for IP collection. ## Standard Upgrade Upgrading the SDK requires only a dependency bump. Update the package to `2.6.0` (or later) in Xcode via **File → Packages → Update to Latest Package Versions**, or pin the version in `Package.swift`: ```swift theme={"dark"} .package(url: "https://github.com/permutive/permutive-ios", from: "2.6.0") ``` Update your `Podfile`: ```ruby theme={"dark"} pod 'Permutive_iOS', '~> 2.6.0' ``` Then run: ```bash theme={"dark"} pod update Permutive_iOS ``` Automatic collection stays **off** until you enable it for your workspace in the dashboard, so no further changes are needed to complete the upgrade. ## Opting Into IDFA Collection The steps below are **only required if you opt into automatic IDFA collection**. They do not apply to a standard version upgrade, and they are not needed for automatic IDFV or IP address collection (which require no app changes). The Permutive SDK links the `AppTrackingTransparency` framework so it can read the ATT status. This affects your App Store submission and privacy declarations even if your app never used ATT before — follow the path that matches your app. ### If you already use App Tracking Transparency Your app already declares `NSUserTrackingUsageDescription` and requests authorization, so the only requirement is to keep your **privacy manifest** consistent. Xcode **merges** the SDK's `PrivacyInfo.xcprivacy` with your app's at build time. Ensure your `NSPrivacyCollectedDataTypes` entries match what data your app actually collects. ### If you opt into IDFA collection but do not already use App Tracking Transparency To collect the IDFA, the SDK reads the ATT authorization status and the advertising identifier. If your app does not already use ATT, you need to add a tracking usage description and decide how your app declares tracking before enabling IDFA collection. The presence of `AppTrackingTransparency` symbols in the binary requires a `NSUserTrackingUsageDescription` key in `Info.plist` to pass App Store review: ```xml theme={"dark"} NSUserTrackingUsageDescription We use this identifier to deliver and measure personalized advertising. ``` The value can be any descriptive string — **it will not be shown to users** unless your app explicitly calls `ATTrackingManager.requestTrackingAuthorization()`. Without this key, the app submission will be rejected regardless of whether IDFA collection is active. The Permutive SDK does **not** present the ATT prompt for you. If you want IDFA to actually be collected, your app must request authorization (and be granted it) — otherwise only IDFV/IP are collected. In App Store Connect, under **App Privacy → Data Types**, declare: * **`Identifiers` → `Device ID`** — collected, linked to the user's identity, used for tracking * **Purposes:** whichever apply to your use of Permutive (typically Third-Party Advertising, Analytics) Keep your privacy declarations in sync with what the SDK actually collects. Misdeclaring collected identifiers can lead to App Store rejections. ## Verifying Collection Enable developer logging and look for the identity being set: ```swift theme={"dark"} Permutive.shared.setDeveloperMode(enabled: true) ``` Look for log lines such as: ``` IDFA auto-collection success ``` If IDFA is skipped, the log states why (for example, `ATT not yet determined by user`, `ATT status is denied`, or `IDFA is zeroed out`). See the [Verification Guide](/sdks/mobile/ios/getting-started/verification) for complete steps. ## Manual vs. Automatic IDFA Collection If you currently set the IDFA yourself (for example via `setIdentityForIDFA` — see the [IDFA Provider](/sdks/mobile/ios/integrations/idfa-provider) guide), we recommend **removing the manual code** once automatic collection is enabled, unless you have a specific use case for it. Automatic, config-driven collection covers the same capture, lets you enable or disable it without shipping an app update, and is managed centrally from the dashboard. Running both is safe in the meantime — automatic collection skips storage when an `idfa` alias with the same value already exists, so you won't get duplicate identities during the transition. | | Manual (`setIdentityForIDFA`) | Automatic (config-driven) | | ----------------------------------------- | ----------------------------- | ------------------------- | | Enablement | App code | Workspace config (remote) | | App update needed to toggle | Yes | No | | ATT authorization required | Yes (iOS 14+) | Yes (iOS 14+) | | `NSUserTrackingUsageDescription` required | Yes | Yes | | Available on tvOS | Yes (tvOS 14.5+) | Yes (tvOS 14.5+) | | Alias tag | `idfa` | `idfa` | ## Rollback If you need to roll back to v2.5.x, pin the previous version: ```swift theme={"dark"} .package(url: "https://github.com/permutive/permutive-ios", from: "2.5.2") ``` ```ruby theme={"dark"} pod 'Permutive_iOS', '~> 2.5.2' ``` You can leave the `NSUserTrackingUsageDescription` key in your `Info.plist` — it is harmless when collection is disabled. ## Getting Help Manual IDFA capture, ATT, and privacy considerations How aliases and identities work Verify identity capture Solutions to common issues # IDFA Provider Source: https://docs.permutive.com/sdks/mobile/ios/integrations/idfa-provider Apple advertising identifier integration and privacy considerations This guide covers using the Identifier for Advertisers (IDFA) with the Permutive SDK, including App Tracking Transparency requirements and privacy best practices. ## Should you collect IDFA? IDFA is opt-in only, it's gated behind Apple's App Tracking Transparency (ATT) prompt, so it's never available for 100% of your users. When a user consents, the IDFA can be collected as an additional, stable identifier for that user. **Config-driven auto-collection is available from v2.6.0.** If your workspace has it enabled, the SDK can automatically collect the IDFA (and IDFV / IP) as aliases, no manual `setIdentityForIDFA` code required. The manual approach below is still supported. See the [v2.6 Migration Guide](/sdks/mobile/ios/guides/migration/v2-6) for setup and the privacy/App Store implications. ## Why opting in can be worth it 1. **Consent rates are rising, not collapsing.** The industry-wide ATT opt-in rate reached roughly **35% by Q2 2025**, up from \~34% in 2023 and \~34.5% in 2024, a steady upward trend as prompt design and onboarding flows have matured. 2. **A stable, consented identifier.** For users who consent, the IDFA stays consistent for that user, giving a reliable signal to resolve their identity accurately. 3. **A standard iOS identifier.** IDFA uses Apple's own identifier format, so it fits into your existing identity setup without custom handling. 4. **Complements your other identifiers.** IDFA adds to the identity you already collect (hashed email, IDFV) rather than replacing it, helping build a more complete picture of the consenting share of your audience. ## What to weigh before opting in 1. **Coverage is partial.** Even at healthy opt-in rates, a meaningful share of users will decline, so IDFA should never be your only identifier. Pair it with hashed email and IDFV (see [Recommended Alternatives](#recommended-alternatives)). 2. **Extra implementation.** Requires integrating the ATT framework and requesting permission at the right moment. 3. **App Store compliance.** You must justify IDFA usage and declare it accurately in App Store Connect. ## Recommended Alternatives ### 1. Identifier for Vendor (IDFV) A consistent identifier across your apps from the same vendor: ```swift theme={"dark"} import UIKit import Permutive_iOS func setVendorIdentity() { guard let idfv = UIDevice.current.identifierForVendor?.uuidString else { return } let alias = Alias(tag: "idfv", identity: idfv, priority: 2) try? Permutive.shared.setIdentities(aliases: [alias]) } ``` **Benefits:** * No user permission required * Consistent across app installs (until uninstall) * Available on both iOS and tvOS * No App Store justification needed ### 2. Hashed Email Address The most reliable cross-device identifier: ```swift theme={"dark"} import CryptoKit import Permutive_iOS func setEmailIdentity(email: String) { let normalized = email.trimmingCharacters(in: .whitespaces).lowercased() let data = Data(normalized.utf8) let hash = SHA256.hash(data: data) let emailHash = hash.compactMap { String(format: "%02x", $0) }.joined() let alias = Alias(tag: "email_sha256", identity: emailHash, priority: 0) try? Permutive.shared.setIdentities(aliases: [alias]) } ``` **Benefits:** * Works across devices and platforms * Highest priority for identity resolution * No Apple framework dependencies * User provided and expected ### 3. Internal User ID Your system's user identifier: ```swift theme={"dark"} func setUserIdentity(userId: String) { let alias = Alias(tag: "internal_id", identity: userId, priority: 1) try? Permutive.shared.setIdentities(aliases: [alias]) } ``` ## App Tracking Transparency If you still need IDFA, follow these steps: ### 1. Add Usage Description Add to your `Info.plist`: ```xml theme={"dark"} NSUserTrackingUsageDescription We use this identifier to personalize your ad experience. ``` ### 2. Request Permission ```swift Swift theme={"dark"} import AppTrackingTransparency import AdSupport import Permutive_iOS class TrackingPermissionManager { func requestTrackingPermission() { // Only available on iOS 14+ if #available(iOS 14, *) { ATTrackingManager.requestTrackingAuthorization { status in switch status { case .authorized: self.setIDFAIdentity() case .denied: print("User denied tracking") case .restricted: print("Tracking restricted") case .notDetermined: print("Not determined") @unknown default: break } } } else { // iOS 13 and earlier - no ATT required setIDFAIdentity() } } private func setIDFAIdentity() { let idfa = ASIdentifierManager.shared().advertisingIdentifier // Check if IDFA is valid (not all zeros) guard idfa != UUID(uuidString: "00000000-0000-0000-0000-000000000000") else { print("IDFA not available (tracking denied or restricted)") return } do { try Permutive.shared.setIdentityForIDFA(idfa) print("IDFA set successfully") } catch { print("Failed to set IDFA: \(error)") } } } ``` ```objectivec Objective-C theme={"dark"} #import #import @import Permutive_iOS; @implementation TrackingPermissionManager - (void)requestTrackingPermission { if (@available(iOS 14, *)) { [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) { if (status == ATTrackingManagerAuthorizationStatusAuthorized) { [self setIDFAIdentity]; } else { NSLog(@"Tracking not authorized: %ld", (long)status); } }]; } else { // iOS 13 and earlier [self setIDFAIdentity]; } } - (void)setIDFAIdentity { NSUUID *idfa = [[ASIdentifierManager sharedManager] advertisingIdentifier]; // Check if IDFA is valid NSUUID *zeroUUID = [[NSUUID alloc] initWithUUIDString:@"00000000-0000-0000-0000-000000000000"]; if ([idfa isEqual:zeroUUID]) { NSLog(@"IDFA not available"); return; } NSError *error = nil; [Permutive.shared setIdentityForIDFA:idfa error:&error]; if (error) { NSLog(@"Failed to set IDFA: %@", error); } else { NSLog(@"IDFA set successfully"); } } @end ``` ### 3. Check Status Before Requesting ```swift theme={"dark"} func checkAndRequestTracking() { if #available(iOS 14, *) { let status = ATTrackingManager.trackingAuthorizationStatus switch status { case .notDetermined: // Can request permission requestTrackingPermission() case .authorized: // Already authorized setIDFAIdentity() case .denied, .restricted: // Cannot use IDFA useAlternativeIdentity() @unknown default: break } } } ``` ### 4. When to Request Don't request tracking permission immediately on app launch. Wait for an appropriate moment when the user understands the value. ```swift theme={"dark"} // Good: Request after user has engaged with the app func onFirstContentView() { // User has started using the app requestTrackingPermission() } // Good: Request when user is about to see ads func onPrepareToPresentAds() { requestTrackingPermission() } // Bad: Requesting immediately on launch func applicationDidFinishLaunching() { // Don't do this - user hasn't understood the app yet // requestTrackingPermission() } ``` ## Using setIdentityForIDFA The SDK provides a dedicated method for IDFA: ```swift theme={"dark"} do { try Permutive.shared.setIdentityForIDFA(idfa) } catch { // IDFA was invalid (all zeros) or otherwise unusable print("setIdentityForIDFA failed: \(error)") } ``` This method: * Sets an alias with the reserved tag `"idfa"` * Validates the IDFA is not all zeros * Throws an error if IDFA is invalid ## Apple Privacy Information When submitting to the App Store, you must declare data collection. For apps using Permutive: ### Without IDFA If you're NOT using IDFA, Permutive's SDK data collection is: * **Identifiers:** Device ID (vendor identifier if used) * **Usage Data:** Product interaction * **Purpose:** Analytics, advertising ### With IDFA If you ARE using IDFA, also declare: * **Identifiers:** Device ID (IDFA) * **Purpose:** Third-party advertising, tracking For complete privacy information, see Apple's [App Store data collection policies](https://developer.apple.com/app-store/app-privacy-details/). ## tvOS Considerations **tvOS Note:** App Tracking Transparency and IDFA are available on tvOS from version 14.5 onwards. ```swift theme={"dark"} #if os(tvOS) // tvOS - use vendor identifier if let idfv = UIDevice.current.identifierForVendor?.uuidString { let alias = Alias(tag: "idfv", identity: idfv, priority: 2) try? Permutive.shared.setIdentities(aliases: [alias]) } #else // iOS - can request IDFA if needed requestTrackingPermission() #endif ``` ## Best Practices * Use `identifierForVendor` as a reliable alternative * Use hashed email for cross-device identity * Request ATT permission at an appropriate time * Explain the value to users before requesting * Handle all authorization statuses gracefully * Have fallback identifiers when IDFA unavailable * Don't rely solely on IDFA for identity * Don't request permission immediately on launch * Don't assume IDFA is available * Don't forget to update App Store privacy declarations ## Troubleshooting **Problem:** IDFA call fails with error. **Cause:** IDFA is all zeros (tracking denied/restricted). **Solution:** Check authorization status first and use alternative identifiers: ```swift theme={"dark"} if ATTrackingManager.trackingAuthorizationStatus == .authorized { try Permutive.shared.setIdentityForIDFA(idfa) } else { setAlternativeIdentity() } ``` **Problem:** `requestTrackingAuthorization` doesn't show prompt. **Possible Causes:** * Missing `NSUserTrackingUsageDescription` in Info.plist * User already responded (check status first) * Running on iOS 13 or earlier * Running on simulator (may behave differently) **Problem:** App Store rejects app for tracking without permission. **Solution:** Ensure you: * Request ATT permission before accessing IDFA * Have clear `NSUserTrackingUsageDescription` * Accurately declare data usage in App Store Connect ## Related Documentation All identity options SDK setup and consent Ad targeting Common problems # Integrations Source: https://docs.permutive.com/sdks/mobile/ios/integrations/overview Connect the iOS SDK with ad platforms and tracking providers The Permutive iOS SDK integrates with major ad platforms to enable audience targeting. For detailed setup instructions, see the main integration documentation for each platform. ## Ad Server Integrations Set up GAM targeting for iOS apps Configure Xandr ad platform targeting ## SDK Providers Apple advertising identifier tracking and App Tracking Transparency # iOS SDK Source: https://docs.permutive.com/sdks/mobile/ios/overview Integrate Permutive into your iOS and tvOS applications for user segmentation, personalization, and ad targeting ## Overview The Permutive iOS SDK enables user segmentation, personalization, and ad targeting in your iOS and tvOS applications. Track user behavior, manage identities across devices, and deliver targeted advertising through integrations with major ad platforms. **Current Version:** 2.2.0 **Platform Support:** The SDK supports iOS 12.0+ and tvOS 12.0+. Both platforms share the same API with minor differences noted in the documentation. ## Getting Started New to the Permutive iOS SDK? Start here. Get up and running with your first page view in minutes CocoaPods and Swift Package Manager setup SDK initialization patterns and configuration Verify your integration is working correctly ## Core Concepts Understand the fundamental concepts of the Permutive SDK. Track users across devices and sessions Structure and validate event data Understanding user segmentation Content-based real-time targeting ## Features Detailed guides for specific SDK features. Track pageviews and user engagement (recommended approach) Track custom events Track video content viewing Track video advertisement engagement React to cohort changes in real-time ## Integrations Connect Permutive with ad platforms and other services. Google Ad Manager, Xandr, and more Apple advertising identifier tracking ## Common Tasks **PageTracker is the recommended approach** for tracking user interactions as it integrates with Permutive's standard events and provides richer insights. ```swift Swift theme={"dark"} import UIKit import Permutive_iOS class ArticleViewController: UIViewController { private var pageTracker: PageTrackerProtocol? override func viewDidLoad() { super.viewDidLoad() let properties = try? EventProperties([ "category": "sports", "author": "John Doe" ]) let context = Context( title: "Article Title", url: URL(string: "https://example.com/article"), referrer: nil ) pageTracker = try? Permutive.shared.createPageTracker( properties: properties, context: context ) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) try? pageTracker?.resume() } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) pageTracker?.pause() } deinit { pageTracker?.stop() } } ``` ```objectivec Objective-C theme={"dark"} #import "ArticleViewController.h" @import Permutive_iOS; @interface ArticleViewController () @property (nonatomic, strong) NSObject *pageTracker; @end @implementation ArticleViewController - (void)viewDidLoad { [super viewDidLoad]; NSError *error = nil; PermutiveEventProperties *properties = [[PermutiveEventProperties alloc] init:@{@"category": @"sports", @"author": @"John Doe"} error:&error]; PermutiveContext *context = [[PermutiveContext alloc] initWithTitle:@"Article Title" url:[NSURL URLWithString:@"https://example.com/article"] referrer:nil]; self.pageTracker = [Permutive.shared createPageTrackerWithProperties:properties context:context error:&error]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self.pageTracker resumeAndReturnError:nil]; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; [self.pageTracker pause]; } - (void)dealloc { [self.pageTracker stop]; } @end ``` ```swift Swift theme={"dark"} let aliases = [ Alias(tag: "email_sha256", identity: emailHash), Alias(tag: "internal_id", identity: userId, priority: 1) ] try? Permutive.shared.setIdentities(aliases: aliases) ``` ```objectivec Objective-C theme={"dark"} PermutiveAlias *emailAlias = [[PermutiveAlias alloc] initWithTag:@"email_sha256" identity:emailHash]; PermutiveAlias *idAlias = [[PermutiveAlias alloc] initWithTag:@"internal_id" identity:userId priority:1]; [Permutive.shared setIdentitiesWithAliases:@[emailAlias, idAlias] error:nil]; ``` ```swift Swift theme={"dark"} var trigger: TriggerAction? func watchCohort() { trigger = Permutive.shared.triggerProvider?.action( boolFor: ["premium_user"], action: { query, isInCohort in if isInCohort { self.showPremiumFeatures() } } ) } // Release trigger when done deinit { trigger = nil } ``` ```objectivec Objective-C theme={"dark"} // Hold strong reference @property (nonatomic, strong) PermutiveTriggerAction *trigger; - (void)watchCohort { NSSet *cohorts = [NSSet setWithObject:@"premium_user"]; self.trigger = [Permutive.shared.triggerProvider actionWithBoolFor:cohorts action:^(NSString *cohort, BOOL result) { if (result) { [self showPremiumFeatures]; } }]; } ``` ```swift Swift theme={"dark"} let adRequest = GAMRequest() adRequest.customTargeting = Permutive.shared.googleCustomTargeting( adTargetable: pageTracker ) bannerView.load(adRequest) ``` ```objectivec Objective-C theme={"dark"} GAMRequest *adRequest = [GAMRequest request]; adRequest.customTargeting = [Permutive.shared googleCustomTargetingWithAdTargetable:self.pageTracker]; [self.bannerView loadRequest:adRequest]; ``` ## Requirements | Requirement | Version | | ----------- | ------- | | iOS | 12.0+ | | tvOS | 12.0+ | | Xcode | 13.0+ | | Swift | 5.0+ | Add the package URL in Xcode: ``` https://github.com/permutive-engineering/permutive-ios-spm ``` Select `Permutive_iOS` as the package product. ```ruby theme={"dark"} target 'YourApp' do platform :ios, '12.0' pod 'Permutive_iOS', '~> 2.2.0' end ``` ## Key Concepts ## Additional Resources API documentation and technical reference. * **[API Reference](https://storage.googleapis.com/permutive-ios-refdocs/html/index.html)** - Complete API documentation * **[Apple Privacy Information](/sdks/mobile/ios/integrations/idfa-provider#apple-privacy)** - App Store privacy requirements **tvOS Differences:** The tvOS SDK shares the same API as iOS, with the following exceptions: * IDFA/App Tracking Transparency is not available on tvOS * UI patterns use focus-based navigation instead of touch ## FAQ **PageTracker** is the recommended approach for most use cases. It automatically tracks Pageview events, measures engagement time, and enables contextual cohorts when URLs are provided. **Direct event tracking** via `track(event:properties:)` is for specialized cases where you need to track custom events that don't fit the page model. Enable logging in your initialization options: ```swift theme={"dark"} let options = Options( apiKey: "", organisationId: "" ) options.logModes = LogMode.all ``` Messages will appear in the Xcode console. Use hashed emails (SHA-256), internal user IDs, or the vendor identifier (`identifierForVendor`). Never send plain text PII. See the [Identity Management](/sdks/mobile/ios/core-concepts/identity-management) guide for details. Permutive recommends against using IDFA due to Apple's App Tracking Transparency requirements. Consider using `identifierForVendor` or hashed email addresses instead. See the [IDFA Provider](/sdks/mobile/ios/integrations/idfa-provider) guide if you do need IDFA. ## Getting Help * **[Troubleshooting Guide](/sdks/mobile/ios/troubleshooting/common-errors)** - Solutions to common issues * **Customer Success Manager (CSM)** - For enabling features and exploring use cases * **[Technical Services](mailto:technical-services@permutive.com)** - For technical setup and configuration * **[Support](mailto:support@permutive.com)** - For troubleshooting and help ## Privacy & Compliance Permutive is designed with privacy in mind: GDPR compliant, CCPA compliant, no PII storage, user consent respected, and transparent data usage. # Release Notes Source: https://docs.permutive.com/sdks/mobile/ios/release-notes/index iOS SDK updates and announcements * Fixes memory accumulation from user state and reduces unnecessary CoreData writes **(Fix)** * Config-driven automatic ID collection **(New)** * Defaults to the Core Engine when no A/B test configuration applies **(Fix)** * Persists Core Engine state in file storage instead of UserDefaults to fit within the tvOS UserDefaults size limit **(Fix)** * Improves Core Engine state persistence for workspaces with large query sets **(Fix)** * Fixes Core Data memory accumulation with large query sets **(Fix)** * Include contextual cohorts in classification model inputs **(Update)** * Adds native cohort evaluation to replace JavaScript evaluation **(New)** * Fixes race condition in third-party data refresh during startup **(Fix)** * Fixes duplicate audience matching API calls **(Fix)** * Fixes duplicate geoIP calls on SDK startup **(Fix)** * Adds support for contextual cohorts and enrichment using the new contextual data platform **(New)** * Adds caching to reduce load when fetching activations for appending to Google Ad Manager ad requests **(Update)** * Resolves a crash in CoreData due to conflicting fetchRequests **(Fix)** * Reduces the number of events being persisted on device by aggregating SegmentEntry and Exit events into PageViewComplete **(Update)** * Add a configuration option to disable low memory mode **(Update)** * Drops support for iOS 11. iOS 12 is now the minimum version supported **(Update)** * Update privacy manifest UserDefaults key **(Fix)** * Update privacy manifest to include UserDefaults usage **(Update)** * Add privacy manifest to bundle **(New)** * Fixed an issue where the SDK would repeatedly retry some network calls if the connection was blocked **(Fix)** * Fixed issue with Classification Model configuration options **(Fix)** * Updated API to retrieve activations **(New)** * Updated API to retrieve cohorts, including classification models if supported **(New)** * Updated trigger action API to return as String type rather than Int **(Update)** * Added support for classification models **(New)** Please reach out to your CSM if you wish to enable this feature * Deprecated segments property **(Deprecation)** * Deprecated reactions property **(Deprecation)** * API to identify a user with the IDFA tag and UUID **(New)** * Fixed an issue where writing to standard defaults can cause a crash in certain cases **(Fix)** * Updates MediaTracker video event names to drop CTV prefix **(Update)** * Improved developer logging **(New)** * Fixed an issue which allowed event tracking before ready **(Fix)** * API to expose current user ID **(New)** * API offering setting Alias priority and expiry for use with identity calls **(New)** * Addition allow event enrichment with IBM Watson **(New)** * Addition to integrate use of Third Party Data when activated **(New)** * Fixed an issue which could cause Core Data crash **(Fix)** * Fixed an issue which used invalid bundle version information **(Fix)** * Performance improvements and bug fixes **(Fix)** * Addition of video tracking support **(New)** * Addition of GAM helper API **(New)** * Production v1 release * Improved PageTracker API **(Fix)** * PageTracker refreshes session expiry **(Fix)** * Improved PageTracker concurrency handling **(Fix)** * Prevent tracking of events with empty names **(Fix)** * Moved identity addition to SDK queue **(Fix)** * Aliases published on SDK start **(Update)** * Metric tracking duration fix for identity and PageTracker **(Fix)** * User settings load after persistence initialisation **(Fix)** * Addition of State Sync feature **(New)** * Addition of Identity feature **(New)** * Fix an issue converting Ints to Booleans in EventProperties **(Fix)** * Fix Metric schema issue **(Fix)** * Make error thrown from Permutive API more informative **(Update)** * Fix call stack used with SDK errors **(Fix)** * Fix to allow partial connection information use **(Fix)** * PageTracker inherits from NSObject to allow strong references **(Fix)** * Fix an issue with ABI stability affecting some users **(Fix)** * Added support for PageView Engagement **(New)** * Added support for Metric Persistency **(New)** * Restoration of all user data from persistence **(Fix)** * Addition of canary deployment feature **(New)** * Renaming of SDK Error class **(Fix)** * High CPU usage bug fix **(Fix)** * Addition of SDK reset API **(New)** * Removal of persistent data of previous alpha SDK on start **(New)** * Event schema errors reported in SDK log **(New)** * SDK persists session **(New)** * Simplified EdgeProcessor logic **(Update)** * Change of Context API **(Update)** * Updated PageTracker API naming **(Update)** * API Added documentation **(New)** * Addition Background awareness **(New)** Allows tracking of activity in background for better performances. * Addition CoreData Migration **(New)** Allows upgrading of Data Model without reinstalling the application. * Addition of PageView Tracking **(New)** Allows tracking of a PageView and PageViewComplete. * Addition of Lookalike model **(New)** * Addition of Mac Catalyst support **(New)** Allows support of OS X platform. * API typo fix **(Fix)** * API to return SDK version **(New)** Allows SDK users to interrogate SDK for version. * Addition of tvOS platform **(New)** Allows release of a tvOS compatible SDK. * Addition of tvOS integration test target **(New)** Allow running integration tests on tvOS simulator as XCTest target. * Addition of tvOS integration app **(New)** Allows running integration tests on tvOS devices. * Deactivates SDK on low system memory **(Fix)** # Common Issues Source: https://docs.permutive.com/sdks/mobile/ios/troubleshooting/common-errors Solutions to common Permutive iOS SDK problems This guide covers common issues and their solutions when integrating the Permutive iOS SDK. ## Installation Issues **Error:** `Unable to find a specification for 'Permutive_iOS'` **Solutions:** 1. Update your CocoaPods repo: ```bash theme={"dark"} pod repo update ``` 2. Ensure CocoaPods is version 1.9.1 or later: ```bash theme={"dark"} pod --version ``` 3. Clear the cache: ```bash theme={"dark"} pod cache clean --all pod install ``` **Error:** Xcode cannot resolve the package. **Solutions:** 1. Check your network connection 2. Reset package caches: **File → Packages → Reset Package Caches** 3. Verify the URL is correct: `https://github.com/permutive-engineering/permutive-ios-spm` 4. Try removing and re-adding the package **Error:** `No such module 'Permutive_iOS'` **Solutions:** 1. Clean build folder: **Product → Clean Build Folder** (⇧⌘K) 2. For CocoaPods: Ensure you opened the `.xcworkspace` file, not `.xcodeproj` 3. Restart Xcode 4. For SPM: Reset package caches **Error:** Bitcode-related build errors. **Solution:** Disable Bitcode in your target's Build Settings: * Set **Enable Bitcode** to **No** Bitcode is no longer required for App Store submission. **Error:** Duplicate symbol errors during linking. **Solutions:** 1. Ensure you're not mixing CocoaPods and SPM for Permutive 2. Check for conflicting library versions 3. Clean derived data: `rm -rf ~/Library/Developer/Xcode/DerivedData` ## Initialization Issues **Problem:** `Options(apiKey:organisationId:workspaceId:)` returns nil. **Causes:** * Empty or whitespace-only credentials * Invalid credential format **Solution:** Verify your credentials: ```swift theme={"dark"} let apiKey = "".trimmingCharacters(in: .whitespacesAndNewlines) let orgId = "".trimmingCharacters(in: .whitespacesAndNewlines) let workspaceId = "".trimmingCharacters(in: .whitespacesAndNewlines) guard !apiKey.isEmpty, !orgId.isEmpty, !workspaceId.isEmpty else { print("Credentials are empty") return } guard let options = Options(apiKey: apiKey, organisationId: orgId, workspaceId: workspaceId) else { print("Invalid credential format") return } ``` **Problem:** No error, but SDK doesn't seem to work. **Solution:** Enable debug logging and check for errors: ```swift theme={"dark"} options.logModes = LogMode.all Permutive.shared.start(with: options) { error in if let error = error { print("Initialization failed: \(error)") } else { print("SDK initialized successfully") } } ``` **Problem:** Initialization fails with network error. **Solutions:** 1. Check device network connectivity 2. Verify no firewall is blocking Permutive domains 3. Check if running on simulator with network issues 4. Retry initialization: ```swift theme={"dark"} Permutive.shared.start(with: options) { error in if error != nil { // Retry after delay DispatchQueue.main.asyncAfter(deadline: .now() + 2) { Permutive.shared.start(with: options, completion: nil) } } } ``` **Problem:** Events tracked before initialization completes are lost. **Solution:** Wait for the completion callback: ```swift theme={"dark"} Permutive.shared.start(with: options) { error in guard error == nil else { return } // Now safe to track self.startTracking() } ``` ## Tracking Issues **Error:** ``` Permutive: [error] Schema validation failed with reason(s): [#: extraneous key [invalid_key] is not permitted] ``` **Causes:** * Property names don't match schema * Property types don't match schema * Extra properties not in schema **Solutions:** 1. Check your Permutive dashboard for the correct schema 2. Verify property names exactly (case-sensitive) 3. Remove any extra properties 4. Verify property types match (string, int, bool, etc.) **Error:** `EventProperties` constructor throws an exception. **Causes:** * Unsupported value types * Invalid nested objects **Solutions:** Ensure all values are supported types: ```swift theme={"dark"} // Supported types: // String, Int, Float, Double, Bool, Date, EventProperties, and arrays of these // ❌ Wrong - Custom object let props = try EventProperties(["user": myUserObject]) // ✅ Correct - Primitive types let props = try EventProperties([ "user_id": user.id, // String "user_name": user.name, // String "age": user.age // Int ]) ``` **Problem:** No Pageview event in logs. **Solutions:** 1. Ensure `resume()` is called: ```swift theme={"dark"} override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) try? pageTracker?.resume() // Must call this } ``` 2. Check PageTracker was created successfully 3. Verify SDK is initialized before creating PageTracker 4. Enable debug logging **Problem:** PageTracker stops when navigating or creating new trackers. **Cause:** Only one PageTracker/MediaTracker can be active at a time. **Solution:** This is expected behavior. Creating a new tracker stops any existing one. Design your flow accordingly. **Problem:** Console is silent, no Permutive logs appear. **Solutions:** 1. Confirm `logModes` is set before `start()`: ```swift theme={"dark"} options.logModes = LogMode.all // Must set before start() Permutive.shared.start(with: options) { ... } ``` 2. Check Xcode console filter isn't hiding messages 3. Verify SDK is actually being initialized ## Cohort and Activation Issues **Problem:** `Permutive.shared.cohorts` returns an empty set. **Causes:** * SDK just initialized (cohorts not yet synced) * No events tracked yet * User doesn't qualify for any cohorts **Solutions:** 1. Wait a few seconds after initialization 2. Track some events to generate data 3. Use TriggerProvider for reactive updates 4. Check your dashboard for cohort definitions **Problem:** User is in a cohort but it doesn't appear in activations. **Cause:** Not all cohorts are activated for all platforms. **Solution:** Check your Permutive dashboard configuration. Activations must be explicitly configured for each platform (GAM, Xandr, etc.). **Problem:** `dfp_contextual` or `appnexus_adserver_contextual` is empty. **Solutions:** 1. Verify contextual features are enabled (contact your CSM) 2. Update to SDK 2.0.0+ 3. Ensure PageTracker has a valid, publicly accessible URL 4. Allow time for content analysis (1-2 seconds) 5. Check debug logs for classification errors ## Identity Issues **Error:** `setIdentityForIDFA` throws an error. **Cause:** IDFA is all zeros (tracking denied/restricted). **Solution:** ```swift theme={"dark"} if #available(iOS 14, *) { let status = ATTrackingManager.trackingAuthorizationStatus guard status == .authorized else { // Use alternative identity return } } let idfa = ASIdentifierManager.shared().advertisingIdentifier guard idfa != UUID(uuidString: "00000000-0000-0000-0000-000000000000") else { // IDFA not available return } try Permutive.shared.setIdentityForIDFA(idfa) ``` **Problem:** Alias with certain tags is ignored. **Cause:** Reserved tags: `appnexus`, `amp`, `gigya`, `sailthru`, `aaid` **Solution:** Use custom tag names: ```swift theme={"dark"} // ❌ Wrong Alias(tag: "appnexus", identity: id) // Reserved, will be ignored // ✅ Correct Alias(tag: "my_appnexus_id", identity: id) ``` **Problem:** Same user appears as different users on different devices. **Solution:** Use consistent identifiers that work across devices: ```swift theme={"dark"} // Best: Hashed email (works across all devices) Alias(tag: "email_sha256", identity: hashedEmail, priority: 0) // Good: Your internal user ID Alias(tag: "internal_id", identity: userId, priority: 1) ``` ## Ad Integration Issues **Problem:** `googleCustomTargeting` returns empty or minimal data. **Solutions:** 1. Verify SDK is initialized before calling 2. Check that cohorts exist for the user 3. Wait for cohort data to sync after initialization 4. Enable debug logging to see activations **Problem:** `prmtvvid` key not in targeting dictionary. **Solution:** Pass the active PageTracker: ```swift theme={"dark"} // ❌ Wrong - no view ID Permutive.shared.googleCustomTargeting(adTargetable: nil) // ✅ Correct - includes view ID Permutive.shared.googleCustomTargeting(adTargetable: pageTracker) ``` ## TriggerProvider Issues **Problem:** No callbacks when cohort membership changes. **Solutions:** 1. Store a strong reference to TriggerAction: ```swift theme={"dark"} class MyController { // Store as property, not local variable private var trigger: TriggerAction? func setup() { trigger = Permutive.shared.triggerProvider?.action(...) } } ``` 2. Verify cohort IDs match your dashboard exactly 3. Enable debug logging to see cohort updates **Problem:** Trigger worked initially but stopped. **Cause:** TriggerAction reference was released. **Solution:** Keep TriggerAction as an instance property for the lifetime you need it. ## Debug Logging Enable comprehensive logging for troubleshooting: ```swift theme={"dark"} guard let options = Options( apiKey: "", organisationId: "", workspaceId: "" ) else { return } // Enable all logging options.logModes = LogMode.all Permutive.shared.start(with: options) { error in // Check console for detailed logs } ``` ### Log Messages to Look For | Message | Meaning | | -------------------------- | ------------------------- | | `SDK ready` | Initialization successful | | `Accepted: X / X` | Events sent successfully | | `event rejection` | Schema validation failed | | `Schema validation failed` | Property mismatch | Always disable debug logging in production builds to avoid performance overhead. ```swift theme={"dark"} #if DEBUG options.logModes = LogMode.all #else options.logModes = LogMode.none #endif ``` ## Getting Help If you can't resolve an issue: 1. **Enable debug logging** and capture relevant logs 2. **Check SDK version** - ensure you're on the latest 3. **Contact support:** * [Support](mailto:support@permutive.com) Include in your support request: * SDK version * iOS/tvOS version * Xcode version * Relevant logs * Steps to reproduce ## Related Documentation Setup troubleshooting Configuration options Verify your integration Schema requirements # React Native Source: https://docs.permutive.com/sdks/mobile/react-native Use Permutive native Android and iOS SDKs from within your React Native application ## Overview Permutive does not provide an official React Native library, but you can utilise our native Android and iOS SDKs from within your React Native application. This involves creating Android and iOS Native Modules for communicating from JS to our Native SDKs. The full guide from React Native can be found [here](https://reactnative.dev/docs/legacy/native-modules-intro). The functions in the modules created for both platforms must share equivalent function signatures to allow the JS code to call a single function which will work on both platforms. ## Android Native Module The Android Native Module uses our native Android SDK, the full documentation for this can be found [here](/sdks/mobile/android/overview). It's recommended to use Android Studio for developing the Android native parts of your React Native application. It provides code syntax feedback, errors and code completion making it much easier to write your Android code. You can download the latest version [here](https://developer.android.com/studio). Start by importing the project into Android Studio from your `android/app` folder. ### Add the Permutive Dependency To start with, add the Permutive dependency to your Android app module in your React Native project. To do this add the dependency to the `build.gradle` which you can find in the `android/app` folder in your project. ```kotlin theme={"dark"} dependencies { // ... implementation("com.permutive.android:core:") // ... } ``` ### Create the Permutive Native Module file Then create a native module for Permutive in the Android app source code, this should be at `android/app/src/main/java//`. In this example the file is called `PermutiveModule.kt`. This file provides the bridging between the JS functions and the Native Android functions. It extends `ReactContextBaseJavaModule`. Each function you would like to expose in JS must be annotated with `@ReactMethod`. The bridging code can only return and receive specific types when interfacing with JS, for full details see the React Native official documentation for Android Native Modules [here](https://reactnative.dev/docs/legacy/native-modules-android). The following sample code demonstrates a Native Module which exposes a subset of the Permutive SDK functionality to JS: ```kotlin PermutiveModule.kt theme={"dark"} package com.permutive.reactnativesample import android.net.Uri import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.Callback import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.ReadableMap import com.facebook.react.bridge.WritableArray import com.facebook.react.bridge.WritableMap import com.permutive.android.EventProperties import com.permutive.android.PageTracker import com.permutive.android.Permutive import java.util.UUID class PermutiveModule( private val reactContext: ReactApplicationContext ) : ReactContextBaseJavaModule(reactContext) { override fun getName(): String = "PermutiveModule" private lateinit var permutive: Permutive private var pageTracker: PageTracker? = null @ReactMethod(isBlockingSynchronousMethod = true) fun initializePermutive( workspaceId: String, apiKey: String, ) { permutive = Permutive( context = reactContext, workspaceId = UUID.fromString(workspaceId), apiKey = UUID.fromString(apiKey), ) } @ReactMethod fun trackPageView( title: String?, url: String?, referrer: String?, eventProperties: ReadableMap? ) { pageTracker = permutive.trackPage( title = title, url = url?.let { Uri.parse(it) }, referrer = referrer?.let { Uri.parse(it) }, eventProperties = eventProperties?.let { EventProperties.from( *it.toHashMap().map { (k, v) -> k to v }.toTypedArray() ) } ) } @ReactMethod fun trackPageviewComplete() { pageTracker?.close() } @ReactMethod(isBlockingSynchronousMethod = true) fun currentCohorts(): WritableArray { val array = Arguments.createArray() permutive.currentCohorts.forEach { array.pushString(it) } return array } @ReactMethod(isBlockingSynchronousMethod = true) fun currentActivations(): WritableMap { val map = Arguments.createMap() permutive.currentActivations.forEach { (activationId, cohorts) -> val array = Arguments.createArray() cohorts.forEach { array.pushString(it) } map.putArray(activationId, array) } return map } @ReactMethod fun listenForCohortUpdates(callback: Callback) { permutive.triggersProvider().cohorts { cohorts -> val array = Arguments.createArray() cohorts.forEach { array.pushString(it) } callback.invoke(array) } } } ``` ### Register the Native Module On Android you need to register the Native Module with the Android application to allow the JS code to call it. To do this create a `ReactPackage` for your application where you can add your new Native Module. First create a file in your `android/app/src/main/java//` folder, in this example it's called `PermutiveAppPackage.kt`. Create a class which extends `ReactPackage` and override the `createNativeModules` method and add your Native Module to the list returned by the function. The following sample code demonstrates this: ```kotlin PermutiveAppPackage.kt theme={"dark"} package com.permutive.reactnativesample import android.view.View import com.facebook.react.ReactPackage import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.uimanager.ReactShadowNode import com.facebook.react.uimanager.ViewManager class PermutiveAppPackage : ReactPackage { override fun createViewManagers( reactContext: ReactApplicationContext ): MutableList>> = mutableListOf() override fun createNativeModules( reactContext: ReactApplicationContext ): MutableList = listOf(PermutiveModule(reactContext)).toMutableList() } ``` You then need to add this package to the list of packages returned by the `ReactNativeHost`'s `getPackages` function. This can be found in the `MainApplication` file in your `android/app/src/main/java//` folder. The following sample code shows this: ```kotlin MainApplication.kt theme={"dark"} package com.permutive.reactnativesample // ... class MainApplication : Application(), ReactApplication { override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper( this, object : DefaultReactNativeHost(this) { override fun getPackages(): List { // Packages that cannot be autolinked yet can be added manually here, for example: // packages.add(new MyReactNativePackage()); return PackageList(this).packages.apply { // Add your custom package here add(PermutiveAppPackage()) } } // ... } ) // ... } ``` Your Android native code is now callable from JS! How to do this is explained in a section below. ## iOS Native Module The iOS Native Module sample uses our native iOS SDK, the full documentation for this can be found [here](/sdks/mobile/ios/overview). It's recommended to use Xcode for developing the iOS native parts of your React Native application. It provides code syntax feedback, errors and code completion making it much easier to write your iOS code. You can download the latest version [here](https://developer.apple.com/xcode/). Start by importing the iOS part of the React Native project into Xcode. The easiest way to do this is by navigating to your React Native project folder and opening the `ios/.xcworkspace` file. ### Add the Permutive dependency Add the Permutive iOS dependency to your project using Swift Package Manager. To do this, go to `File -> Add Package Dependencies`. Then enter `https://github.com/permutive-engineering/permutive-ios-spm` in the Search or Enter Package URL bar, on the top right, then select Add Package, select Package Product Permutive\_iOS and add to your React Native app Target, then select Add Package. ### Create the Native Module files This guide uses Swift files which interact with the Permutive SDK and Objective-C bridging to connect these to JS. Add a new Swift file to your project in the `ios/` folder. This should be the same name as your Android module file if you've already created that, for this sample we'll use 'PermutiveModule' again so we create `PermutiveModule.swift`. Your project should have an Objective-C bridging header file which you can find at `ios//-Bridging-Header.h`. You need to import the `ReactBridgeModule` in that file as shown here: ```objectivec -Bridging-Header.h theme={"dark"} // // Use this file to import your target's public headers that you would like to expose to Swift. // #import "React/RCTBridgeModule.h" ``` You'll also need an Objective-C file which is used to expose the Swift methods to JS. Create a new Objective-C file with the same name as your Native Module file in the `ios/` folder. In this sample that is `PermutiveModule.m`. The Swift class needs to be annotated as an Objective-C exposed class and the methods need to be annotated too. Only specific types can be used when receiving parameters and returning from functions for communicating with JS. For full details on this see the official React Native iOS Native Modules documentation [here](https://reactnative.dev/docs/native-modules-ios). This sample shows a Swift module with the equivalent functionality to the Android Native Module: ```swift PermutiveModule.swift theme={"dark"} import Foundation import Permutive_iOS @objc(PermutiveModule) class PermutiveModule: NSObject { private var pageTracker: PageTrackerProtocol? = nil @objc func initializePermutive( _ workspaceId: NSString, _ key: NSString ) { guard let options = Options( apiKey: key as String, organisationId: workspaceId as String, workspaceId: workspaceId as String ) else { print("Error creating Permutive Options") return } Permutive.shared.start(with: options) { error in guard error != nil else { return } print("Error starting Permutive: \(String(describing: error))") return } } @objc func trackPageView( _ title: NSString?, _ url: NSString?, _ referrer: NSString?, _ eventProperties: NSDictionary? ) { do { // Convert NSDictionary to EventProperties let properties = try EventProperties( (eventProperties as? [String: Any])? .compactMapValues { propertyName in propertyName as? AnyHashable }.reduce(into: [:]) { outputDictionary, inputDictionary in outputDictionary[inputDictionary.key] = inputDictionary.value } ?? [:] ) let context = (title != nil && url != nil && referrer != nil) ? Context( title: title! as String, url: URL(string: url! as String)!, referrer: URL(string: referrer! as String)! ) : nil pageTracker = try Permutive.shared.createPageTracker( properties: properties, context: context ) } catch { print("Error starting page tracker: \(String(describing: error))") } } @objc func trackPageviewComplete() { pageTracker?.stop() } @objc func currentCohorts() -> NSArray { return Array(Permutive.shared.cohorts) as NSArray } @objc func currentActivations() -> NSDictionary { return Permutive.shared.activations as NSDictionary } @objc func listenForCohortUpdates(_ callback: @escaping RCTResponseSenderBlock) { let queryRange = Array().map { queryId in String(queryId) } let action = Permutive.shared.triggerProvider?.action(boolFor: Set(queryRange)) { (_, _) in callback([Array(Permutive.shared.cohorts) as NSArray]) } } } ``` To be able to call this code from JS it requires bridging through Objective-C code. To do this within the Objective-C native module file you created before, you import the same `RCTBridgeModule` and create an `RCT_EXTERN_MODULE` interface which references your Swift class. Two Objective-C functions are used to expose the Swift methods in this example: * `RCT_EXTERN_METHOD` - Exposes an asynchronous method to JS. It takes an argument in the following form: `functionName:(Parameter1Type)parameter1InternalName :(Parameter2Type)parameter2InternalName`. * `RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD` - Exposes a synchronous method to JS. It takes arguments in the same form as `RCT_EXTERN_METHOD`. The following sample shows the Objective-C code used to expose the sample Swift class: ```objectivec PermutiveModule.m theme={"dark"} #import #import "React/RCTBridgeModule.h" @interface RCT_EXTERN_MODULE(PermutiveModule, NSObject) RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(initializePermutive:(NSString)workspaceId :(NSString)key) RCT_EXTERN_METHOD(trackPageView:(NSString)title :(NSString)url :(NSString)referrer :(NSDictionary)eventProperties) RCT_EXTERN_METHOD(trackPageviewComplete) RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(currentCohorts) RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(currentActivations) RCT_EXTERN_METHOD(listenForCohortUpdates:(RCTResponseSenderBlock)callback) @end ``` Your iOS code is now ready to be called from JS! ## Calling Native code from JS To consume your native code from JS you can create a wrapper module around your native module. Create a new JS file in your `app/` folder with the same name as your Native Module files. In this sample that is `PermutiveModule.js` with the following content: ```javascript PermutiveModule.js theme={"dark"} import {NativeModules} from 'react-native'; const {PermutiveModule} = NativeModules; export default PermutiveModule; ``` You can then import this module into your React files and call into the native functions. This sample shows calling into the `PermutiveModule` in the `useEffect` function to initialize the Permutive SDK, track a Pageview, set the latest cohorts to some state and also listen for changes: ```javascript theme={"dark"} import PermutiveModule from '../PermutiveModule' export default function HomeScreen() { const [currentCohorts, setCurrentCohorts] = useState(''); useEffect(() => { PermutiveModule.initializePermutive("e0039147-51e7-4224-a814-0e2d438aabcd", "da4d09b5-843a-4bd5-bd79-8cea7f69f730"); PermutiveModule.trackPageView("Page title", null, null, null); setCurrentCohorts(PermutiveModule.currentCohorts().join(', ')); PermutiveModule.listenForCohortUpdates((cohorts: any) => setCurrentCohorts(cohorts.join(', '))) }, []) return ( // ... ) } ``` You can utilise these functions to implement Permutive within your app. If you require more functionality you can add it by creating new functions in the React Native Modules for each platform which call the relevant Permutive SDK functions. Your React Native app is now ready to finish your Permutive integration. # Cohorts and Activations Source: https://docs.permutive.com/sdks/web/javascript-sdk/core-concepts/cohorts-and-activations Understanding user segmentation in the Permutive JavaScript SDK Cohorts are the core mechanism for user segmentation in Permutive. The SDK provides methods to check cohort membership and react to changes in real-time. ## What are Cohorts ## Cohort Types ## How Cohorts Work 1. **Events are tracked** via the SDK (pageviews, custom events, etc.) 2. **Rules are evaluated** against user behavior 3. **Users enter/exit cohorts** based on rule matching 4. **Cohorts are stored locally** in the browser 5. **Cohorts are passed to ad platforms** for targeting ``` User views sports article → Track Pageview event ↓ Permutive evaluates rules ↓ User enters "sports_enthusiast" cohort ↓ Cohort stored in localStorage ↓ Cohort passed to ad requests ``` ## Activations ### Activation Types ## Cohorts vs Activations ## Accessing Cohorts ### Get All Segments ```javascript theme={"dark"} // Get all cohorts the user belongs to permutive.segments(function(segments) { console.log('All segments:', segments); // Output: [12345, 67890, 11111, ...] }); ``` ### Get Platform-Specific Segments ```javascript theme={"dark"} // Get cohorts activated for Google Ad Manager (DFP) permutive.segments(function(segments) { console.log('DFP segments:', segments); }, 'dfp'); // Get cohorts activated for AppNexus permutive.segments(function(segments) { console.log('AppNexus segments:', segments); }, 'appnexus'); ``` ### Check Single Cohort Membership ```javascript theme={"dark"} // Check if user is in a specific cohort permutive.segment(12345, function(inCohort) { if (inCohort) { console.log('User is in cohort 12345'); showPremiumContent(); } else { console.log('User is NOT in cohort 12345'); showStandardContent(); } }); ``` ### Wait for Cohort Data Use `ready()` with the `'realtime'` stage to ensure cohort data is available: ```javascript theme={"dark"} permutive.ready(function() { // Cohort data is now available permutive.segments(function(segments) { console.log('Segments (after ready):', segments); }); }, 'realtime'); ``` ## Real-Time Triggers React to cohort changes as they happen: ```javascript theme={"dark"} // Trigger when user enters/exits a cohort permutive.trigger(12345, 'result', function(obj) { if (obj.result) { console.log('User just ENTERED cohort 12345'); } else { console.log('User just EXITED cohort 12345'); } }); ``` See [Real-Time Triggers](/sdks/web/javascript-sdk/features/real-time-triggers) for more details. ## localStorage Structure Cohorts are stored in localStorage for fast access and ad targeting: | Key | Purpose | | ------------- | ----------------------------------- | | `_psegs` | All cohorts | | `_pdfps` | Google Ad Manager (DFP) activations | | `_papns` | AppNexus activations | | `_prubicons` | Rubicon activations | | `_ppubmatics` | PubMatic activations | ```javascript theme={"dark"} // Direct localStorage access (for debugging) var allCohorts = JSON.parse(localStorage.getItem('_psegs') || '[]'); var dfpCohorts = JSON.parse(localStorage.getItem('_pdfps') || '[]'); ``` Always use the SDK methods (`segments()`, `segment()`) rather than reading localStorage directly in production code. The SDK handles synchronization and edge cases. ## Using Cohorts with Ad Platforms ### Google Ad Manager ```javascript theme={"dark"} // Using the DFP addon (automatic) permutive.addon('web', { page: { type: 'article' } }); // Targeting is set automatically // Manual targeting permutive.segments(function(segments) { googletag.cmd.push(function() { googletag.pubads().setTargeting('permutive', segments); }); }, 'dfp'); ``` ### Prebid.js Cohorts are automatically passed to Prebid via the RTD module. See [Prebid Integration](/sdks/web/javascript-sdk/integrations/prebid). ### AppNexus/Xandr ```javascript theme={"dark"} permutive.segments(function(segments) { // Use segments for AppNexus targeting apntag.setKeywords('permutive', segments); }, 'appnexus'); ``` ## Cohort Patterns ```javascript theme={"dark"} // Personalize content based on cohorts permutive.ready(function() { permutive.segment(12345, function(isPremiumUser) { if (isPremiumUser) { showPremiumLayout(); hideAds(); } }); }, 'realtime'); ``` ```javascript theme={"dark"} // Adjust paywall based on user segments permutive.segments(function(segments) { var isHighValueUser = segments.includes(54321); var isFrequentReader = segments.includes(98765); if (isHighValueUser && !isFrequentReader) { showSoftPaywall(); } }); ``` ```javascript theme={"dark"} // Use cohorts for A/B test targeting permutive.segment(11111, function(inTestGroup) { if (inTestGroup) { runExperimentVariantB(); } else { runExperimentVariantA(); } }); ``` ```javascript theme={"dark"} // Pass cohorts to analytics permutive.segments(function(segments) { analytics.identify({ permutive_cohorts: segments.slice(0, 10) // Top 10 cohorts }); }); ``` ## Event-Based Cohort Updates Cohorts update in real-time as users take actions: ```javascript theme={"dark"} // User reads an article about sports permutive.addon('web', { page: { type: 'article', article: { categories: ['sports', 'football'] } } }); // Moments later, check if they entered the sports cohort permutive.trigger(12345, 'segment', function(inSportsCohort) { if (inSportsCohort) { console.log('User just qualified for sports cohort!'); } }); ``` ## Debugging Cohorts Enable debug mode to see cohort activity: ``` ?__permutive.loggingEnabled=true ``` Console output includes: ``` [Permutive] Segments updated: [12345, 67890, ...] [Permutive] User entered segment: 12345 [Permutive] DFP segments: [12345] ``` ## Troubleshooting **Problem:** No cohorts even after tracking events. **Solutions:** * Use `ready('realtime')` to wait for data * Check that events are being tracked (debug mode) * Verify cohorts are configured in dashboard * Ensure user hasn't opted out (consent) * Allow time for cohort processing (seconds to minutes) **Problem:** Events tracked but user not in cohort. **Solutions:** * Verify cohort rules in dashboard * Check event properties match rule conditions * Confirm cohort is active (not paused/archived) * Check cohort minimum threshold/frequency **Problem:** User in cohort but activation array differs. **Solutions:** * Check that cohort is activated for the platform * Verify activation configuration in dashboard * Use correct type parameter: `segments(cb, 'dfp')` **Problem:** Cohorts not updating after new events. **Solutions:** * Cohorts update in real-time but may take a few seconds * Refresh page to trigger re-evaluation * Check that new events are actually being tracked * Use `trigger()` for real-time change detection ## Related Documentation React to cohort changes Build cohorts in dashboard Content-based cohorts GAM targeting setup # Consent Management Source: https://docs.permutive.com/sdks/web/javascript-sdk/core-concepts/consent-management Handle user consent with the Permutive JavaScript SDK The Permutive SDK provides consent mechanisms for data controllers to signal user consent status. This page covers how to implement consent handling in your web integration. ## Overview Permutive operates as a **data processor**, processing personal data on behalf of publishers who act as data controllers. Publishers are responsible for obtaining user consent and signaling that consent to Permutive. For complete information about Permutive's consent mechanisms, see the [Consent documentation](/governance/consent). ## Consent Modes Permutive provides two consent modes that publishers can configure for their deployment. ### Consent-by-Default By default, Permutive assumes the data controller has consent to track their users' data. In this configuration mode, the collection of user data starts from the first time Permutive's SDK loads without requiring a consent token. ```html theme={"dark"} ``` ### Consent-by-Token As a data controller, you may need to receive consent from the user before tracking data against them. When `consentRequired: true`, no user data is collected until the data controller has received consent from the user and passed the user's consent token to the Permutive SDK. Once the SDK has been granted consent, it will start collecting user data from this moment on. The user can revoke this consent at any point. ```html theme={"dark"} ``` No user data will be tracked by the SDK until it receives a consent token for the user. With `consentRequired: true`, events are **discarded** until consent is granted—they are not queued. ## The consent() Method ### Granting Consent Once you have obtained consent for the user, pass it to Permutive by calling the SDK `consent` method: ```javascript theme={"dark"} permutive.consent({ opt_in: true, token: "YOUR_CONSENT_TOKEN_HERE" }); ``` From this point on, the SDK will track user event data—or until the user wishes to opt out. | Parameter | Type | Description | | --------- | ------- | -------------------------------------------- | | `opt_in` | boolean | Whether user has consented | | `token` | string | Consent token (required when `opt_in: true`) | ### About the Consent Token The consent token can have **any value**. Generally, this is used if an ID gets generated when the user gives consent via a Consent Management Platform to provide some transparency, but the actual value has no meaning to Permutive. As long as `opt_in` is `true` and the `token` is not null, Permutive considers consent to be given. ```javascript theme={"dark"} // Example: Using a fixed token value permutive.consent({ opt_in: true, token: "CONSENT_CAPTURED" }); // Example: Using a CMP-generated token permutive.consent({ opt_in: true, token: cmp.getConsentId() }); ``` Some customers use a fixed value like `"CONSENT_CAPTURED"` for simplicity. Choose whatever approach works best for your consent management workflow. ## Opt Out You may choose or be required to offer users the option to opt out of tracking. All future tracking is then disabled for the user until the point they opt back in. Whether the SDK is configured in consent-by-default or consent-by-token mode, a user can be opted out by setting the consent `opt_in` field to `false`: ```javascript theme={"dark"} permutive.consent({ opt_in: false }); ``` ### What Happens on Opt Out 1. **Tracking stops** - No new events are sent 2. **State reset** - User ID and session cleared 3. **localStorage cleared** - Cohorts and identity data removed 4. **Cookies cleared** - `permutive-id` cookie deleted ### Opt Back In Users can opt back in by calling `consent()` with `opt_in: true`: ```javascript theme={"dark"} permutive.consent({ opt_in: true, token: "CONSENT_CAPTURED" }); ``` After opting out and back in, the user receives a new user ID. Their previous behavioral history is not linked to the new profile. ## CMP Integration Examples ### Generic CMP Pattern ```javascript theme={"dark"} // Check CMP consent status function handleConsent() { if (cmp.hasUserConsented()) { permutive.consent({ opt_in: true, token: cmp.getConsentToken() || "CONSENT_CAPTURED" }); } else { permutive.consent({ opt_in: false }); } } // Listen for consent changes cmp.onConsentChange(handleConsent); ``` ### OneTrust Example ```javascript theme={"dark"} function OptanonWrapper() { // Check if relevant category is consented if (OnetrustActiveGroups.includes('C0002')) { permutive.consent({ opt_in: true, token: 'onetrust_consent' }); } else { permutive.consent({ opt_in: false }); } } ``` ### Cookiebot Example ```javascript theme={"dark"} window.addEventListener('CookiebotOnAccept', function() { if (Cookiebot.consent.statistics) { permutive.consent({ opt_in: true, token: 'cookiebot_consent' }); } }); window.addEventListener('CookiebotOnDecline', function() { permutive.consent({ opt_in: false }); }); ``` ## TCF Registration For customers who must use a consent management platform that relies on the Global Vendor List (GVL), Permutive is registered on the GVL for IAB Europe's Transparency & Consent Framework (TCF). | Detail | Value | | --------------- | ------------------------------------------------------- | | **GVL ID** | 361 | | **Purpose** | Purpose 1 (store and/or access information on a device) | | **TCF Version** | 2.3 | As a data processor, Permutive primarily relies on consent obtained by the publisher through the consent mechanisms described above (`consent-by-default` or `consent-by-token`). The TCF registration is provided for customers with specific GVL requirements. ## Consent State ### Consent Persistence Consent status is stored in localStorage and persists across sessions. The SDK automatically respects the stored consent state on subsequent page loads. ### Checking Consent Status ```javascript theme={"dark"} // Consent is stored in localStorage var consentData = localStorage.getItem('permutive-consent'); if (consentData) { var consent = JSON.parse(consentData); console.log('Consent status:', consent.opt_in); } ``` ## Troubleshooting **Problem:** SDK loaded but no events appearing. **Solutions:** * Verify `consent()` is called with `opt_in: true` and a non-null `token` * [Enable debug mode](/sdks/web/javascript-sdk/getting-started/verification#debug-mode) (`?__permutive.loggingEnabled=true`) to see consent status * Check localStorage for `permutive-consent` key **Problem:** User must consent on every page load. **Solutions:** * Check localStorage is not being cleared * Verify CMP integration is consistent across pages * Ensure consent token is not null ## Related Documentation Platform consent mechanisms SDK initialization options # Contextual Data Source: https://docs.permutive.com/sdks/web/javascript-sdk/core-concepts/contextual-data Content-based real-time targeting with the Permutive JavaScript SDK Contextual targeting allows you to reach users based on the content they're currently viewing, without relying on user history or cookies. ## Overview ## Privacy Benefits ## Behavioral vs Contextual ## Implementation ### Page Properties The primary way to enable contextual targeting is through page properties: ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article', article: { title: 'Electric Vehicle Sales Surge in 2024', categories: ['automotive', 'electric-vehicles', 'business'], tags: ['EV', 'Tesla', 'sustainability', 'green-tech'], authors: ['Jane Smith'], publishedAt: '2024-01-15T10:00:00Z' } } }); ``` These properties are analyzed to generate contextual cohorts like: * `automotive_electric_vehicles` * `business_sustainability` * `technology_green_tech` ### Content Classifications Permutive can integrate with content classification providers for automated contextual data: ```javascript theme={"dark"} // Watson classifications are added automatically // when configured in your Permutive dashboard permutive.addon('web', { page: { type: 'article', // Watson adds categories, concepts, entities } }); ``` ```javascript theme={"dark"} // Pass your own classifications permutive.addon('web', { page: { type: 'article', classifications: { iab_categories: ['IAB19-6', 'IAB13-7'], sentiment: 'positive', topics: ['technology', 'innovation'] } } }); ``` ### URL-Based Context The SDK automatically captures URL context: ```javascript theme={"dark"} // Current URL is automatically included // https://example.com/technology/electric-vehicles/article-123 // You can also specify explicit context permutive.addon('web', { context: { url: 'https://example.com/technology/article', title: 'Article Title', referrer: document.referrer }, page: { type: 'article' } }); ``` ## Contextual Cohorts ### How They Work 1. **Page loads** with content metadata 2. **SDK sends context** to Permutive 3. **Real-time analysis** determines applicable contextual cohorts 4. **Cohorts returned** immediately (milliseconds) 5. **Ad requests** include contextual targeting ``` Page about electric vehicles loads ↓ SDK sends: categories: ['automotive', 'electric-vehicles'] ↓ Permutive returns: contextual cohort 'auto_ev_interest' ↓ Ad request includes contextual targeting ``` ### Accessing Contextual Cohorts Contextual cohorts are included in segment calls: ```javascript theme={"dark"} // Get all segments including contextual permutive.segments(function(segments) { console.log('All segments (incl. contextual):', segments); }); // Get DFP segments (includes contextual activations) permutive.segments(function(segments) { console.log('DFP segments:', segments); // Includes both behavioral and contextual }, 'dfp'); ``` ### Contextual-Only Targeting For users without behavioral data (new visitors, no consent): ```javascript theme={"dark"} // Contextual cohorts work without user consent // because they're based on content, not user history permutive.addon('web', { page: { type: 'article', article: { categories: ['finance', 'investing'] } } }); // Even without consent, contextual targeting is available ``` ## Page Property Best Practices ### Article Pages ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article', article: { // Required title: 'Article Title', categories: ['primary-category', 'secondary-category'], // Recommended tags: ['keyword1', 'keyword2', 'keyword3'], authors: ['Author Name'], publishedAt: '2024-01-15T10:00:00Z', // Optional - enhances targeting id: 'article-123', section: 'Technology', premium: false, wordCount: 1500, readTime: 7 } } }); ``` ### Video Pages ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'video', video: { title: 'Video Title', id: 'video-123', categories: ['entertainment', 'music'], duration: 180, // seconds series: 'Music Reviews', season: 2, episode: 5 } } }); ``` ### Homepage ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'homepage' } }); ``` ### Section/Category Page ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'section', section: { name: 'Technology', path: '/technology', depth: 1 } } }); ``` ## Use Cases Use contextual data to ensure ads appear alongside appropriate content: ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article', article: { categories: ['news', 'politics'], // Contextual cohorts can be used for // brand safety exclusions brand_safe: true, content_rating: 'general' } } }); ``` Reach users without cookies or consent: ```javascript theme={"dark"} // Works for all users regardless of consent status permutive.addon('web', { page: { type: 'article', article: { categories: ['travel', 'europe'], // Contextual targeting doesn't require // user tracking or consent } } }); ``` Align ads with editorial content: ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article', article: { title: 'Best Running Shoes for 2024', categories: ['fitness', 'gear-reviews'], tags: ['running', 'shoes', 'athletics'], // Ads for running products can target // this contextual signal } } }); ``` Target based on timely content: ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article', article: { categories: ['holidays', 'gift-guides'], seasonal: 'christmas', // Seasonal advertisers can target // holiday content } } }); ``` ## Combining Behavioral and Contextual The most effective targeting often combines both signals: ```javascript theme={"dark"} permutive.ready(function() { permutive.segments(function(allSegments) { // allSegments includes both: // - Behavioral cohorts (user history) // - Contextual cohorts (current page) // Use for comprehensive targeting googletag.cmd.push(function() { googletag.pubads().setTargeting('permutive', allSegments); }); }); }, 'realtime'); ``` ## Contextual Data Flow ``` ┌─────────────────┐ │ Page Load │ │ (with context) │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ SDK Captures │ │ page properties │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Permutive │ │ Classification │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Contextual │ │ Cohorts Returned│ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Ad Request │ │ (with cohorts) │ └─────────────────┘ ``` ## Troubleshooting **Problem:** Page properties set but no contextual cohorts. **Solutions:** * Verify page properties in debug mode * Check that contextual cohorts are configured in dashboard * Ensure categories match configured cohort rules * Allow a few seconds for processing **Problem:** Page classified incorrectly. **Solutions:** * Review page property values * Use more specific categories/tags * Check for conflicting signals in page data * Contact support for classification tuning **Problem:** Contextual cohorts exist but ads not targeting. **Solutions:** * Verify contextual cohorts are activated for the ad platform * Check `_pdfps` (or platform-specific key) in localStorage * Confirm ad server is reading targeting values * Test with debug mode enabled ## Related Documentation Build contextual cohorts Configure page properties Contextual targeting in GAM Content classification integrations # Event Properties Source: https://docs.permutive.com/sdks/web/javascript-sdk/core-concepts/event-properties Structure and validate event data in the Permutive JavaScript SDK Events in Permutive consist of a name and properties object. Properties provide the context and data that powers cohort building and insights. ## Event Structure Every event has a name and properties: ```javascript theme={"dark"} permutive.track('EventName', { property1: 'value', property2: 123, nested: { property3: true } }); ``` ### Automatic Properties The SDK automatically adds these properties to every event: | Property | Type | Description | | ------------------- | --------- | ------------------------------------ | | `time` | timestamp | When the event occurred | | `session_id` | string | Current session identifier | | `view_id` | string | Current pageview identifier | | `client.url` | string | Current page URL | | `client.referrer` | string | Referring URL | | `client.user_agent` | string | Browser user agent | | `client.viewport` | object | Browser viewport dimensions | | `geo_info` | object | Geographic information (server-side) | ## Property Types Permutive supports several property types: ```javascript theme={"dark"} permutive.track('Article', { title: 'Breaking News', category: 'technology', author: 'Jane Smith' }); ``` ```javascript theme={"dark"} permutive.track('Purchase', { price: 29.99, quantity: 2, discount_percentage: 10 }); ``` ```javascript theme={"dark"} permutive.track('UserAction', { is_subscriber: true, has_ad_blocker: false, accepted_cookies: true }); ``` ```javascript theme={"dark"} permutive.track('Article', { categories: ['technology', 'business', 'startups'], tags: ['AI', 'funding', 'silicon-valley'], authors: ['Jane Smith', 'John Doe'] }); ``` ```javascript theme={"dark"} permutive.track('Article', { article: { title: 'Breaking News', id: 'article-123', publishedAt: '2024-01-15T10:00:00Z' }, author: { name: 'Jane Smith', id: 'author-456' } }); ``` ```javascript theme={"dark"} permutive.track('Subscription', { start_date: '2024-01-15', expiry_date: '2025-01-15', created_at: new Date().toISOString() }); ``` Dates should be passed as ISO 8601 strings for consistent parsing. ## Standard Events The SDK defines standard event types with expected properties: ### Pageview Tracked automatically by the web addon: ```javascript theme={"dark"} // Automatic via web addon permutive.addon('web', { page: { type: 'article', article: { title: 'Article Title', categories: ['news'], authors: ['Author Name'], publishedAt: '2024-01-15' } } }); ``` ### Custom Event For non-standard tracking: ```javascript theme={"dark"} permutive.track('CustomEvent', { action: 'button_click', element_id: 'signup-button', page_section: 'header' }); ``` ## Property Naming Conventions Follow these conventions for consistent data: | Convention | Example | Notes | | ----------- | --------------- | ------------------------------- | | snake\_case | `article_title` | Preferred for custom properties | | camelCase | `articleTitle` | Also supported | | Lowercase | `category` | Use for simple properties | Property names are case-sensitive. `category` and `Category` are different properties. Be consistent across your implementation. ### Reserved Property Names Avoid these reserved property names: * `time` - Automatically set by SDK * `session_id` - Automatically set * `view_id` - Automatically set * `client` - Reserved for client context * `geo_info` - Reserved for geographic data ## Nested Properties You can nest properties up to 3 levels deep: ```javascript theme={"dark"} permutive.track('Article', { content: { article: { metadata: { word_count: 1500, read_time: 7 } } } }); ``` Deeply nested properties work in cohort building, but keep structures simple when possible for easier querying. ## Arrays Arrays are useful for multi-value properties: ```javascript theme={"dark"} permutive.track('Article', { // Array of strings categories: ['technology', 'business'], // Array of numbers related_article_ids: [123, 456, 789], // Array of objects authors: [ { name: 'Jane Smith', id: 'author-1' }, { name: 'John Doe', id: 'author-2' } ] }); ``` ### Array Limitations * Maximum 100 items per array * Array items should be of consistent type * Nested arrays are not recommended ## Page Properties vs Event Properties Set via the web addon, applied to all events on the page: ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article', article: { title: 'Article Title', categories: ['news'] } } }); // These page properties are included in: // - Pageview event // - PageviewEngagement events // - PageviewComplete event ``` Set per-event, specific to that event: ```javascript theme={"dark"} permutive.track('VideoPlay', { video_id: 'video-123', video_title: 'Demo Video', autoplay: false }); // These properties only apply to this event ``` ## Best Practices ```javascript theme={"dark"} // Good permutive.track('Article', { article_category: 'technology', article_author: 'Jane Smith', is_premium_content: true }); // Avoid permutive.track('Article', { cat: 'tech', auth: 'JS', prem: 1 }); ``` Use the same property names and types across all events: ```javascript theme={"dark"} // Consistent naming permutive.track('Article', { article_id: '123' }); permutive.track('VideoPlay', { video_id: '456' }); // Avoid mixing permutive.track('Article', { article_id: '123' }); permutive.track('Article', { articleId: '456' }); // Different convention! ``` Never include personally identifiable information: ```javascript theme={"dark"} // Good - use hashed or anonymized values permutive.track('Login', { user_type: 'subscriber', account_age_days: 365 }); // Never do this permutive.track('Login', { email: 'user@example.com', // PII! name: 'John Smith' // PII! }); ``` ```javascript theme={"dark"} // Good - correct types permutive.track('Purchase', { price: 29.99, // Number quantity: 2, // Number product_name: 'Widget', // String in_stock: true // Boolean }); // Avoid - incorrect types permutive.track('Purchase', { price: '29.99', // Should be number quantity: '2', // Should be number in_stock: 'true' // Should be boolean }); ``` Keep events focused with relevant properties only: ```javascript theme={"dark"} // Good - focused properties permutive.track('ArticleRead', { article_id: '123', category: 'technology', read_percentage: 75 }); // Avoid - too many properties permutive.track('ArticleRead', { article_id: '123', category: 'technology', // ... 50 more properties }); ``` ## Property Validation The SDK validates properties before sending: * Invalid property types are converted or dropped * Very long strings may be truncated * Invalid JSON structures are rejected Enable debug mode to see validation messages: ``` ?__permutive.loggingEnabled=true ``` ## Troubleshooting **Problem:** Event tracked but properties missing in dashboard. **Solutions:** * Check property names match dashboard schema * Verify property types are correct * Enable debug mode to see what's sent * Allow time for data processing **Problem:** Numbers stored as strings or vice versa. **Solutions:** * Explicitly cast types before tracking * Use `parseInt()` or `parseFloat()` for numbers * Avoid string representations of numbers **Problem:** Can't build cohorts on deeply nested properties. **Solution:** Flatten structure or limit nesting to 2 levels: ```javascript theme={"dark"} // Instead of deep nesting { a: { b: { c: { d: 'value' } } } } // Use flatter structure { a_b_c_d: 'value' } ``` ## Related Documentation Track custom events Configure page properties Build cohorts from properties Event schema documentation # Identity Management Source: https://docs.permutive.com/sdks/web/javascript-sdk/core-concepts/identity-management Track users across sessions and devices with the Permutive JavaScript SDK The Permutive SDK provides identity management to track users across sessions and connect data from multiple devices or touchpoints. ## Overview ## User ID Every user is automatically assigned a unique **user ID** by the SDK. This ID: * Is generated on first visit and persisted in a first-party cookie * Is unique to your domain (not shared across publishers) * Survives browser sessions and cookie clearing in most cases * Is used to track behavior and build cohorts ```javascript theme={"dark"} // Get the current user ID from localStorage var userId = localStorage.getItem('permutive-id'); console.log('User ID:', userId); ``` The user ID is stored in localStorage with the key `permutive-id`. It works in all browsers, including Safari and Firefox which block third-party cookies. ## Aliases ### Setting Aliases Use `permutive.identify()` to set user aliases: ```javascript theme={"dark"} // Set a single alias permutive.identify([ { tag: 'email_sha256', id: 'a1b2c3d4e5f6g7h8i9j0...' } ]); // Set multiple aliases permutive.identify([ { tag: 'email_sha256', id: 'a1b2c3d4e5f6g7h8i9j0...' }, { tag: 'internal_id', id: 'user_12345', priority: 1 }, { tag: 'subscriber_id', id: 'sub_789', priority: 2 } ]); ``` ### Identity Record Structure Each identity record has the following fields: | Field | Type | Required | Description | | ---------- | ------ | -------- | ------------------------------------------------ | | `tag` | string | Yes | The type of identifier (configured in dashboard) | | `id` | string | Yes | The actual identifier value | | `priority` | number | No | Resolution priority (lower = higher priority) | | `expiry` | number | No | Expiration timestamp (Unix milliseconds) | ## Setting Identity ### Basic Usage ```javascript theme={"dark"} // When user logs in function onUserLogin(user) { permutive.identify([ { tag: 'email_sha256', id: hashSHA256(user.email) } ]); } ``` ### With Priority ```javascript theme={"dark"} // Set identities with explicit priority permutive.identify([ { tag: 'email_sha256', id: emailHash, priority: 0 }, // Highest priority { tag: 'internal_id', id: 'user_12345', priority: 1 }, { tag: 'device_id', id: 'device_abc', priority: 2 } // Lowest priority ]); ``` ### With Expiry Set an expiration time for temporary identities: ```javascript theme={"dark"} // Identity expires in 24 hours var expiryTime = Date.now() + (24 * 60 * 60 * 1000); permutive.identify([ { tag: 'session_token', id: 'session_abc123', expiry: expiryTime } ]); ``` ## Identity Resolution When you call `identify()`: ```javascript theme={"dark"} permutive.identify([ { tag: 'email_sha256', id: 'abc123...' } ]); ``` The SDK: 1. Sends the alias to Permutive servers 2. Server checks if this alias exists for another user 3. If found, user profiles are merged 4. SDK receives updated cohorts and state 5. Future events are linked to the resolved identity ## Common Identity Patterns ```javascript theme={"dark"} // Track login and set identity function handleLogin(user) { // Set identity permutive.identify([ { tag: 'email_sha256', id: hashSHA256(user.email) }, { tag: 'internal_id', id: user.id } ]); // Track login event permutive.track('Login', { method: 'email', is_new_user: user.isNew }); } ``` ```javascript theme={"dark"} // Set identity on new registration function handleRegistration(user) { permutive.identify([ { tag: 'email_sha256', id: hashSHA256(user.email) }, { tag: 'internal_id', id: user.id } ]); permutive.track('Registration', { signup_source: user.source, plan_type: user.plan }); } ``` ```javascript theme={"dark"} // Set identity from newsletter form function handleNewsletterSignup(email) { permutive.identify([ { tag: 'email_sha256', id: hashSHA256(email) } ]); permutive.track('NewsletterSignup', { list: 'daily_digest' }); } ``` ```javascript theme={"dark"} // Set identity from third-party provider (e.g., ID5, RampID) function setThirdPartyIdentity(providerId, identityValue) { permutive.identify([ { tag: providerId, id: identityValue } ]); } // Example with ID5 setThirdPartyIdentity('id5', 'ID5-abc123...'); ``` ## Hashing Requirements **Never send plain-text PII** to Permutive. Always hash email addresses and other personal identifiers before passing them to `identify()`. ### SHA-256 Hashing Email addresses should be hashed using SHA-256: ```javascript theme={"dark"} // Using Web Crypto API (modern browsers) async function hashSHA256(email) { const normalized = email.toLowerCase().trim(); const encoder = new TextEncoder(); const data = encoder.encode(normalized); const hashBuffer = await crypto.subtle.digest('SHA-256', data); const hashArray = Array.from(new Uint8Array(hashBuffer)); return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); } // Usage hashSHA256('user@example.com').then(function(hash) { permutive.identify([ { tag: 'email_sha256', id: hash } ]); }); ``` ### Hashing Best Practices 1. **Normalize before hashing**: Lowercase and trim whitespace 2. **Use consistent algorithm**: SHA-256 is standard 3. **Hash on server if possible**: Avoids exposing raw email in client code 4. **Same hash everywhere**: Use identical normalization across all touchpoints ## Resetting Identity When a user logs out, reset their identity: ```javascript theme={"dark"} function handleLogout() { // Reset all SDK state permutive.reset(); // Or just track logout without reset // permutive.track('Logout', {}); } ``` `permutive.reset()` clears all user data including cohorts and identity. Use it when a user explicitly logs out or requests data deletion. ## Cross-Domain Identity For tracking users across multiple domains you own: ```javascript theme={"dark"} // On domain-a.com permutive.identify([ { tag: 'cross_domain_id', id: 'shared_user_123' } ]); // On domain-b.com (same identifier) permutive.identify([ { tag: 'cross_domain_id', id: 'shared_user_123' } ]); // Permutive will link the user profiles ``` Cross-domain identity requires using the same identity tag and value across both domains. Contact your Customer Success Manager to configure cross-domain tracking. ## Identity Events Listen for identity changes: ```javascript theme={"dark"} // Listen for identity resolution events permutive.on('SegmentEntry', function(event) { // User entered a new segment after identity resolution console.log('Entered segment:', event.properties.segment); }); ``` ## Troubleshooting **Problem:** User appears as new visitor on each session. **Solutions:** * Check that cookies aren't being blocked * Verify cookie domain configuration * Check for browser privacy settings (private mode) * Ensure `permutive-id` cookie exists in Application > Cookies **Problem:** Calling `identify()` but user data not connected. **Solutions:** * Verify identity tags are configured in dashboard * Check that identity values match exactly (case-sensitive) * Enable debug mode to see identify requests * Ensure SDK is initialized before calling identify **Problem:** Same person appears as multiple users. **Solutions:** * Ensure consistent identity values across touchpoints * Use same hashing algorithm everywhere * Set identity as early as possible in the session * Check priority settings if multiple identities conflict ## Related Documentation Structure event data correctly How identity affects cohort membership Identity and GDPR consent Platform identity resolution # Event Tracking Source: https://docs.permutive.com/sdks/web/javascript-sdk/features/event-tracking Track custom events with the Permutive JavaScript SDK Use `track()`, `on()`, and `once()` to track custom events and listen for SDK events. ## Tracking Events ### Basic Usage ```javascript theme={"dark"} permutive.track('EventName', { property1: 'value', property2: 123 }); ``` ### With Callback ```javascript theme={"dark"} permutive.track('Purchase', { product_id: 'SKU123', price: 29.99 }, { success: function(event) { console.log('Event tracked:', event); }, error: function(errorType, errorMsg) { console.error('Tracking failed:', errorType, errorMsg); } }); ``` ### With Beacon API Force use of the Beacon API for reliable tracking during page unload: ```javascript theme={"dark"} permutive.track('PageExit', { time_on_page: 120 }, { useBeacon: true }); ``` ## track() Method ### Signature ```javascript theme={"dark"} permutive.track(eventName, properties, options) ``` ### Parameters | Parameter | Type | Required | Description | | ------------ | ------ | -------- | ----------------- | | `eventName` | string | Yes | Name of the event | | `properties` | object | No | Event properties | | `options` | object | No | Tracking options | ### Options | Option | Type | Description | | ----------- | -------- | ----------------------------- | | `success` | function | Called when event is accepted | | `error` | function | Called on tracking error | | `useBeacon` | boolean | Force Beacon API usage | ### Return Value Returns a Promise that resolves with the event object: ```javascript theme={"dark"} permutive.track('MyEvent', { data: 'value' }) .then(function(event) { console.log('Tracked:', event); }) .catch(function(error) { console.error('Error:', error); }); ``` ## Event Examples ```javascript theme={"dark"} // Product view permutive.track('ProductView', { product_id: 'SKU123', product_name: 'Blue Widget', category: 'Widgets', price: 29.99, currency: 'USD' }); // Add to cart permutive.track('AddToCart', { product_id: 'SKU123', quantity: 2, cart_value: 59.98 }); // Purchase permutive.track('Purchase', { order_id: 'ORDER-456', products: [ { id: 'SKU123', quantity: 2 } ], total: 59.98, currency: 'USD', payment_method: 'credit_card' }); ``` ```javascript theme={"dark"} // Login permutive.track('Login', { method: 'email', is_new_session: true }); // Registration permutive.track('Registration', { signup_source: 'homepage_banner', plan_type: 'premium' }); // Search permutive.track('Search', { query: 'blue widgets', results_count: 42 }); // Share permutive.track('Share', { content_type: 'article', content_id: 'article-123', platform: 'twitter' }); ``` ```javascript theme={"dark"} // Button click permutive.track('ButtonClick', { button_id: 'cta-signup', button_text: 'Start Free Trial', page_section: 'hero' }); // Tab switch permutive.track('TabSwitch', { from_tab: 'overview', to_tab: 'pricing' }); // Modal view permutive.track('ModalView', { modal_name: 'newsletter-popup', trigger: 'exit_intent' }); ``` ```javascript theme={"dark"} // Subscription start permutive.track('SubscriptionStart', { plan_id: 'premium-monthly', plan_name: 'Premium', price: 9.99, billing_period: 'monthly' }); // Paywall hit permutive.track('PaywallHit', { paywall_type: 'hard', article_id: 'article-123', articles_read: 5 }); // Trial started permutive.track('TrialStarted', { trial_length_days: 14, plan_type: 'premium' }); ``` ## Event Listeners ### on() - Persistent Listener Listen for all occurrences of an event: ```javascript theme={"dark"} // Listen for all Pageview events permutive.on('Pageview', function(event, error) { if (error) { console.error('Event error:', error); return; } console.log('Pageview tracked:', event); }); // Listen for custom events permutive.on('Purchase', function(event) { analytics.trackRevenue(event.properties.total); }); ``` ### once() - Single Listener Listen for only the first occurrence: ```javascript theme={"dark"} // Listen for first Pageview only permutive.once('Pageview', function(event) { console.log('First pageview:', event); }); // Wait for SDK ready event permutive.once('permutive:ready', function() { console.log('SDK is ready'); }); ``` ### Pattern Matching Use regex to match multiple event types: ```javascript theme={"dark"} // Listen for all events starting with "Video" permutive.on(/^Video/, function(event) { console.log('Video event:', event.name, event); }); // Listen for all Purchase-related events permutive.on(/Purchase|AddToCart|Checkout/, function(event) { console.log('E-commerce event:', event.name); }); ``` ### Removing Listeners Store the listener reference to remove later: ```javascript theme={"dark"} var listener = permutive.on('Pageview', function(event) { console.log('Pageview:', event); }); // Later, remove the listener listener.remove(); ``` ## Standard Event Types The SDK defines these standard event types: | Event | Description | | -------------------- | ------------------------- | | `Pageview` | Page load (via web addon) | | `PageviewEngagement` | User engagement signals | | `PageviewComplete` | User leaves page | | `FormSubmission` | Form submitted | | `LinkClick` | Link clicked | | `Videoview` | Video playback started | | `VideoEngagement` | Video engagement signals | | `VideoCompletion` | Video completed | | `SegmentEntry` | User entered a cohort | | `SegmentExit` | User exited a cohort | Standard events (Pageview, etc.) are tracked automatically by the web addon. Use `track()` for custom events. ## SDK Events Listen for internal SDK events: ```javascript theme={"dark"} // SDK ready permutive.on('permutive:ready', function() { console.log('SDK initialized'); }); // Realtime data ready permutive.on('permutive:realtime', function() { console.log('Realtime cohort data available'); }); // SDK reset permutive.on('permutive:reset', function() { console.log('SDK state was reset'); }); // Web addon ready permutive.on('permutive:web:ready', function() { console.log('Web addon initialized'); }); ``` ## Best Practices ```javascript theme={"dark"} // Good - clear, specific names permutive.track('ArticleRead', {...}); permutive.track('NewsletterSignup', {...}); permutive.track('PremiumTrialStarted', {...}); // Avoid - vague or generic names permutive.track('Click', {...}); permutive.track('Event', {...}); permutive.track('Action', {...}); ``` ```javascript theme={"dark"} // Good - actionable properties permutive.track('Purchase', { order_id: 'ORDER-123', total: 99.99, currency: 'USD', product_count: 3, coupon_used: true }); // Avoid - too few or irrelevant properties permutive.track('Purchase', { timestamp: Date.now() // Already captured automatically }); ``` ```javascript theme={"dark"} // Good - track when action completes function onPurchaseComplete(order) { permutive.track('Purchase', { order_id: order.id, total: order.total }); } // Avoid - tracking before action completes function onCheckoutClick() { permutive.track('Purchase', {...}); // Purchase hasn't happened yet! processPayment(); } ``` Track meaningful user actions, not every interaction: ```javascript theme={"dark"} // Good - meaningful actions permutive.track('AddToCart', {...}); permutive.track('Search', {...}); // Avoid - excessive tracking permutive.track('MouseMove', {...}); permutive.track('Scroll', {...}); permutive.track('Focus', {...}); ``` ```javascript theme={"dark"} permutive.track('ImportantEvent', properties, { success: function(event) { // Event tracked successfully }, error: function(type, msg) { // Log or handle error errorLogger.log('Permutive tracking failed', type, msg); } }); ``` ## Tracking on Page Unload For events that must be tracked when the user leaves: ```javascript theme={"dark"} // Use Beacon API for reliable delivery window.addEventListener('beforeunload', function() { permutive.track('PageExit', { time_on_page: calculateTimeOnPage() }, { useBeacon: true }); }); ``` The web addon automatically tracks `PageviewComplete` on page unload, so you don't need to track basic exit events manually. ## Debugging Enable debug mode to see all tracked events: ``` ?__permutive.loggingEnabled=true ``` Console output: ``` [Permutive] Tracking: Purchase {order_id: "123", total: 99.99} [Permutive] Event accepted: 1/1 ``` ## Troubleshooting **Problem:** `track()` called but no events in dashboard. **Solutions:** * Check consent is granted (if `consentRequired: true`) * Verify SDK is initialized * Enable debug mode to see if events are accepted * Check for JavaScript errors before track call * Allow processing time (events may take minutes to appear) **Problem:** Success/error callbacks not called. **Solutions:** * Verify callback functions are defined correctly * Check for errors in callback code * Ensure SDK is loaded before tracking **Problem:** Event tracked but some properties missing. **Solutions:** * Check property names match expected schema * Verify property types are correct * Look for undefined values being passed * Check for typos in property names ## Related Documentation Structure event data Automatic pageview tracking Events power cohort membership Event schema documentation # Pageview Tracking Source: https://docs.permutive.com/sdks/web/javascript-sdk/features/pageview-tracking Track pageviews and user engagement with the Permutive JavaScript SDK The web addon provides automatic pageview tracking, engagement measurement, and standard event capture. This is the recommended way to track user activity. ## Web Addon The web addon automatically tracks: * **Pageview** - When a page loads * **PageviewEngagement** - Periodic engagement signals * **PageviewComplete** - When user leaves the page * **FormSubmission** - When forms are submitted * **LinkClick** - When links are clicked ### Basic Setup ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article' } }); ``` This single call enables all automatic tracking features. ### With Page Properties ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article', article: { title: 'Article Title', categories: ['news', 'technology'], authors: ['Jane Smith'], publishedAt: '2024-01-15T10:00:00Z' } } }); ``` ## Page Properties Page properties provide context about the content and are included in Pageview events. ### Standard Properties | Property | Type | Description | | -------------- | ------ | --------------------------------------------- | | `page.type` | string | Page type (article, homepage, section, video) | | `page.article` | object | Article-specific metadata | | `page.video` | object | Video-specific metadata | | `page.section` | object | Section/category metadata | ### Article Properties ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article', article: { // Identification id: 'article-123', title: 'Breaking News: Tech Advances', // Categorization categories: ['technology', 'business'], tags: ['AI', 'startups', 'innovation'], section: 'Technology', // Attribution authors: ['Jane Smith', 'John Doe'], publishedAt: '2024-01-15T10:00:00Z', modifiedAt: '2024-01-15T14:30:00Z', // Content attributes wordCount: 1500, readTime: 7, premium: false, sponsored: false } } }); ``` ### Video Properties ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'video', video: { id: 'video-456', title: 'Product Demo', duration: 180, categories: ['product', 'tutorial'], series: 'How-To Guides', season: 1, episode: 5 } } }); ``` ### Homepage ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'homepage' } }); ``` ### Section Page ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'section', section: { name: 'Technology', path: '/technology', level: 1 } } }); ``` ## Automatic Events ### Pageview Event Fired immediately when `addon('web')` is called: ```javascript theme={"dark"} // Event structure { name: 'Pageview', properties: { type: 'article', article: { title: 'Article Title', categories: ['news'] }, client: { url: 'https://example.com/article', referrer: 'https://google.com', user_agent: '...' } } } ``` ### PageviewEngagement Event Fired periodically while the user is on the page: ```javascript theme={"dark"} // Event structure { name: 'PageviewEngagement', properties: { engaged_time: 30, // seconds completion: 0.5 // scroll depth (0-1) } } ``` ### PageviewComplete Event Fired when the user leaves the page: ```javascript theme={"dark"} // Event structure { name: 'PageviewComplete', properties: { total_engaged_time: 120, completion: 0.85 } } ``` ### FormSubmission Event Fired when a form is submitted: ```javascript theme={"dark"} // Event structure { name: 'FormSubmission', properties: { form_id: 'newsletter-signup', form_name: 'Newsletter', // Form data (excluding sensitive fields) } } ``` Sensitive fields (password, credit card, etc.) are automatically excluded from FormSubmission events. ### LinkClick Event Fired for outbound link clicks: ```javascript theme={"dark"} // Event structure { name: 'LinkClick', properties: { href: 'https://external-site.com/page', text: 'Click here' } } ``` ## Engagement Configuration ### Engagement Interval Control how often PageviewEngagement events fire: ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article' }, eventInterval: 5000 // milliseconds (default: 5000) }); ``` ### Custom Engagement Detection Override the default engagement detection: ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article' }, addPageEngagementDetection: function(callback) { // Custom engagement detection // Call callback() when user is engaged document.addEventListener('mousemove', callback); document.addEventListener('scroll', callback); document.addEventListener('keypress', callback); // Return cleanup function return function() { document.removeEventListener('mousemove', callback); document.removeEventListener('scroll', callback); document.removeEventListener('keypress', callback); }; } }); ``` ### Custom Completion Logic Override the default scroll completion calculation: ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article' }, getPageCompletion: function() { // Custom completion calculation var scrollTop = window.pageYOffset; var docHeight = document.documentElement.scrollHeight; var winHeight = window.innerHeight; return scrollTop / (docHeight - winHeight); } }); ``` ## Context Override Override automatic context detection: ```javascript theme={"dark"} permutive.addon('web', { context: { url: 'https://example.com/canonical-url', title: 'Custom Page Title', referrer: 'https://custom-referrer.com' }, page: { type: 'article' } }); ``` ## SPA Support For Single Page Applications (React, Vue, Angular), call `permutive.addon('web', {...})` on navigation to track a new Pageview: ### Basic SPA Pattern ```javascript theme={"dark"} // Initial page load permutive.addon('web', { page: getPageProperties() }); // On navigation function navigateToNewPage() { permutive.addon('web', { page: getNewPageProperties() }); } ``` ### React Integration ```javascript theme={"dark"} import { useEffect } from 'react'; import { useLocation } from 'react-router-dom'; function PermutiveTracker() { const location = useLocation(); useEffect(() => { // Track pageview on initial load and route changes permutive.addon('web', { page: getPageProperties(location) }); }, [location]); return null; } function getPageProperties(location) { return { type: 'article', article: { title: document.title, // ... other properties } }; } ``` ### Vue Integration ```javascript theme={"dark"} // In router/index.js or main.js import router from './router'; router.afterEach((to, from) => { // Track pageview on every navigation permutive.addon('web', { page: getPageProperties(to) }); }); function getPageProperties(route) { return { type: route.meta.pageType || 'page', // ... other properties }; } ``` ### Angular Integration ```typescript theme={"dark"} // In app.component.ts import { Router, NavigationEnd } from '@angular/router'; import { filter } from 'rxjs/operators'; @Component({...}) export class AppComponent { constructor(private router: Router) { this.router.events .pipe(filter(event => event instanceof NavigationEnd)) .subscribe((event: NavigationEnd) => { this.trackPageview(event.urlAfterRedirects); }); } private trackPageview(url: string): void { const pageProps = this.getPageProperties(url); // Track pageview on every navigation (window as any).permutive.addon('web', { page: pageProps }); } } ``` ## Infinite Scroll For infinite scroll pages, track additional content loads: ```javascript theme={"dark"} var webAddon = permutive.addon('web', { page: { type: 'feed' }, dirtyEvents: ['contentLoaded'] // Custom dirty events }); // When new content loads function onNewContentLoaded() { // Mark page as "dirty" to trigger new engagement tracking webAddon.markDirty('contentLoaded'); } ``` ## Filtering Events Filter which events are tracked: ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article' }, filterPermutiveEvent: { // Only track form submissions with certain IDs FormSubmission: function(event) { return event.target.id !== 'search-form'; }, // Only track external links LinkClick: function(event) { var href = event.target.href; return href && !href.includes(window.location.hostname); } } }); ``` ## Debugging Enable debug mode to see pageview tracking in action: ``` ?__permutive.loggingEnabled=true ``` Console output: ``` [Permutive] Web addon initialized [Permutive] Tracking: Pageview {type: "article", ...} [Permutive] Event accepted: 1/1 [Permutive] Tracking: PageviewEngagement {engaged_time: 5} ``` ## Troubleshooting **Problem:** No Pageview event in debug output. **Solutions:** * Verify `permutive.addon('web', {...})` is called * Check that consent is granted (if `consentRequired: true`) * Ensure SDK is loaded before addon call * Look for JavaScript errors in console **Problem:** No PageviewEngagement events. **Solutions:** * Check `eventInterval` isn't set too high * Verify user is actively engaging (scroll, mouse move) * Ensure page stays open long enough * Test with debug mode enabled **Problem:** Only first pageview tracked in SPA. **Solutions:** * Call `permutive.addon('web', { page: {...} })` on each route change * Verify the addon call receives updated page properties * Check that router events are firing correctly **Problem:** Multiple Pageview events per page. **Solutions:** * Ensure `addon('web')` called only once * Check for multiple SDK initializations * Verify not calling both addon and track manually ## Related Documentation Track custom events Structure event data Page-based targeting Track video content # Real-Time Triggers Source: https://docs.permutive.com/sdks/web/javascript-sdk/features/real-time-triggers React to cohort changes with trigger(), segment(), segments(), and query() The SDK provides methods to check cohort membership and react to changes in real-time, enabling dynamic personalization and targeting. ## Overview Real-time triggers enable: * **Personalization** based on cohort membership * **Dynamic content** that updates as users qualify for cohorts * **Conditional logic** for features, ads, and experiences * **Real-time reactions** to cohort entry and exit ## trigger() React to cohort entry and exit events in real-time. The handler fires whenever the user's membership in the specified segment changes. ### Usage ```javascript theme={"dark"} permutive.trigger(segmentCode, parameterName, handler); ``` ### Parameters | Parameter | Type | Description | | --------------- | -------- | ------------------------------------------------------------------------ | | `segmentCode` | number | The cohort ID to watch | | `parameterName` | string | Parameter name (typically `"result"`) | | `handler` | function | Callback on segment transitions (receives object with `result` property) | ### Example ```javascript theme={"dark"} // React when user enters or exits a cohort permutive.trigger(1010, 'result', function(obj) { if (obj.result) { // User just entered the cohort showEmailSubscriptionPopup(); permutive.track('ShownEmailSubscriptionPopup', {}); } else { // User exited the cohort hideEmailSubscriptionPopup(); } }); ``` The handler receives `{ result: true }` when the user **enters** the segment and `{ result: false }` when they **exit**. For many segments (e.g., "5+ pageviews"), once a user qualifies they typically stay qualified, so you may only see the entry event. To check current membership at any time, use `permutive.segment()`. ### Trigger Patterns ```javascript theme={"dark"} // Show personalized content when user qualifies permutive.trigger(12345, 'result', function(obj) { if (obj.result) { document.getElementById('premium-section').style.display = 'block'; document.getElementById('standard-section').style.display = 'none'; } }); ``` ```javascript theme={"dark"} // Enable beta features for beta testers cohort permutive.trigger(54321, 'result', function(obj) { if (obj.result) { enableBetaFeatures(); } }); ``` ```javascript theme={"dark"} // Show targeted message when user enters cohort permutive.trigger(98765, 'result', function(obj) { if (obj.result) { showNotification('Special offer just for you!'); } }); ``` ## segment() Check if the user is in a specific cohort. ### Usage ```javascript theme={"dark"} permutive.segment(segmentCode, handler); ``` ### Parameters | Parameter | Type | Description | | ------------- | -------- | ---------------------------- | | `segmentCode` | number | The cohort ID to check | | `handler` | function | Callback with boolean result | ### Example ```javascript theme={"dark"} // Check if user is in cohort 12345 permutive.segment(12345, function(inCohort) { if (inCohort) { console.log('User is in cohort 12345'); } else { console.log('User is NOT in cohort 12345'); } }); ``` ### Promise Form ```javascript theme={"dark"} permutive.segment(12345) .then(function(inCohort) { console.log('In cohort:', inCohort); }); ``` ### Segment Patterns ```javascript theme={"dark"} // Show content based on cohort membership permutive.segment(12345, function(isPremium) { if (isPremium) { renderPremiumArticle(); } else { renderPaywall(); } }); ``` ```javascript theme={"dark"} // Use cohort for A/B test assignment permutive.segment(11111, function(inTestGroup) { if (inTestGroup) { runVariantB(); } else { runVariantA(); } }); ``` ```javascript theme={"dark"} // Tag analytics with cohort membership permutive.segment(12345, function(inCohort) { analytics.identify({ is_premium_user: inCohort }); }); ``` ## segments() Get all cohorts the user belongs to. ### Usage ```javascript theme={"dark"} permutive.segments(handler, type); ``` ### Parameters | Parameter | Type | Description | | --------- | -------- | ------------------------------------------------------------------------ | | `handler` | function | Callback with segments array | | `type` | string | Optional: 'all' (default) or platform-specific ('dfp', 'appnexus', etc.) | ### Example ```javascript theme={"dark"} // Get all segments permutive.segments(function(segments) { console.log('All segments:', segments); // Output: [12345, 67890, 11111, ...] }); // Get DFP-activated segments permutive.segments(function(segments) { console.log('DFP segments:', segments); }, 'dfp'); ``` ### Promise Form ```javascript theme={"dark"} permutive.segments() .then(function(segments) { console.log('Segments:', segments); }); ``` ### Segments Patterns ```javascript theme={"dark"} // Pass segments to ad server permutive.segments(function(segments) { googletag.cmd.push(function() { googletag.pubads().setTargeting('permutive', segments); }); }, 'dfp'); ``` ```javascript theme={"dark"} // Send segments to analytics permutive.segments(function(segments) { analytics.track('PageView', { permutive_cohorts: segments, cohort_count: segments.length }); }); ``` ```javascript theme={"dark"} // Personalize based on any matching cohort permutive.segments(function(segments) { var premiumCohorts = [12345, 67890, 11111]; var isPremium = premiumCohorts.some(function(id) { return segments.includes(id); }); if (isPremium) { showPremiumExperience(); } }); ``` ## query() Execute advanced queries against cohort data. ### Usage ```javascript theme={"dark"} permutive.query(queryCode, handler); ``` The `query()` method is reserved for future functionality and currently returns `{ result: false }`. Use `segment()` to check individual cohort membership or `segments()` to get all cohorts. ## ready() Wait for SDK initialization or realtime data before checking cohorts. ### Usage ```javascript theme={"dark"} permutive.ready(callback, stage); ``` ### Parameters | Parameter | Type | Description | | ---------- | -------- | ------------------------------------- | | `callback` | function | Called when stage is reached | | `stage` | string | 'initialised' (default) or 'realtime' | ### Example ```javascript theme={"dark"} // Wait for SDK initialization permutive.ready(function() { console.log('SDK is ready'); }); // Wait for realtime cohort data permutive.ready(function() { permutive.segments(function(segments) { // Segments are now populated console.log('Realtime segments:', segments); }); }, 'realtime'); ``` ### Stages | Stage | When Fires | | ------------- | ---------------------------------------- | | `initialised` | SDK has loaded and initialized | | `realtime` | Real-time cohort data has been processed | ### Ready Patterns ```javascript theme={"dark"} // Ensure cohort data is available permutive.ready(function() { permutive.segments(function(segments) { initializeTargeting(segments); }); }, 'realtime'); ``` ```javascript theme={"dark"} // Initialize components after SDK ready permutive.ready(function() { setupPersonalization(); initializeAnalytics(); loadAdTargeting(); }); ``` ## readyWithTimeout() If you need to delay critical actions (like ad requests) until Permutive is ready, use `readyWithTimeout` to set a maximum wait time. This ensures your page functions even if the SDK takes longer than expected to load. ### Setup First, add the `readyWithTimeout` function to your Permutive tag: ```javascript theme={"dark"} window.permutive.readyWithTimeout = function(e, i, t) { var u = !1, n = function() { u || (e(), u = !0) }; (t = t || 1/0) !== 1/0 && window.setTimeout(n, t), permutive.ready(n, i) }; ``` ### Usage ```javascript theme={"dark"} permutive.readyWithTimeout(callback, stage, timeoutMs); ``` ### Parameters | Parameter | Type | Description | | ----------- | -------- | ----------------------------------------------------------- | | `callback` | function | Called when ready or timeout is reached | | `stage` | string | 'initialised' or 'realtime' | | `timeoutMs` | number | Maximum milliseconds to wait before calling callback anyway | ### Example ```javascript theme={"dark"} // Wait up to 1500ms for realtime data, then proceed regardless window.permutive.readyWithTimeout(function() { console.log('Permutive is ready (or timed out)'); // Proceed with ad requests initializeAdRequests(); }, 'realtime', 1500); ``` Using `readyWithTimeout` is strongly recommended when delaying ad requests, as it ensures ads will still load even if there's a network issue with the Permutive SDK. ## Combining Methods ### Check Multiple Cohorts ```javascript theme={"dark"} function checkUserStatus() { var status = { isPremium: false, isEngaged: false, isNewUser: false }; var checks = 3; var complete = function() { checks--; if (checks === 0) { applyPersonalization(status); } }; permutive.segment(12345, function(result) { status.isPremium = result; complete(); }); permutive.segment(67890, function(result) { status.isEngaged = result; complete(); }); permutive.segment(11111, function(result) { status.isNewUser = result; complete(); }); } ``` ### Watch and Initial Check ```javascript theme={"dark"} // Check initial state AND watch for when segment becomes true function setupCohortTracking(cohortId, onEnter) { // Initial check permutive.segment(cohortId, function(inCohort) { if (inCohort) { onEnter(); } }); // Watch for entry (fires once when segment becomes true) permutive.trigger(cohortId, 'result', function(obj) { if (obj.result) { onEnter(); } }); } // Usage setupCohortTracking(12345, function() { showPremiumContent(); }); ``` ## Timing Considerations ### Why Use ready()? ```javascript theme={"dark"} // Without ready() - segments might be empty permutive.segments(function(segments) { console.log(segments); // Might be [] }); // With ready('realtime') - segments are populated permutive.ready(function() { permutive.segments(function(segments) { console.log(segments); // [12345, 67890, ...] }); }, 'realtime'); ``` ### Trigger Timing Triggers fire whenever segment membership changes on the current page: * `{ result: true }` when user **enters** the segment * `{ result: false }` when user **exits** the segment ```javascript theme={"dark"} // This fires on segment transitions permutive.trigger(12345, 'result', function(obj) { if (obj.result) { // User entered cohort 12345 } else { // User exited cohort 12345 } }); ``` Triggers only fire for segment transitions caused by events tracked on the current page via the `track` method. For checking a user's current cohort membership at any time, use `permutive.segment()` instead. ## Debugging Enable debug mode to see cohort activity: ``` ?__permutive.loggingEnabled=true ``` Console output: ``` [Permutive] Segments: [12345, 67890, 11111] [Permutive] User entered segment: 12345 [Permutive] User exited segment: 67890 ``` ## Troubleshooting **Problem:** No cohorts returned even though user should qualify. **Solutions:** * Use `ready('realtime')` before calling `segments()` * Verify events are being tracked * Check cohorts are configured and active in dashboard * Ensure consent is granted (if `consentRequired: true`) * Allow time for cohort processing **Problem:** Trigger callback not being called. **Solutions:** * Verify cohort ID is correct * Check that cohort rules would qualify the user * Enable debug mode to see cohort changes * Ensure SDK is initialized before setting trigger **Problem:** User should be in cohort but check returns false. **Solutions:** * Verify cohort ID matches dashboard * Check cohort is active (not paused/archived) * Ensure user meets cohort criteria * Use `ready('realtime')` to ensure data is loaded **Problem:** Cohort membership not updating. **Solutions:** * Refresh page to trigger re-evaluation * Verify new events are being tracked * Use `trigger()` with `'result'` parameter for real-time updates * Check for SDK errors in console ## Related Documentation Understanding cohort system Events that power cohorts Use cohorts for ad targeting Build cohorts in dashboard # Video Tracking Source: https://docs.permutive.com/sdks/web/javascript-sdk/features/video-tracking Track video content viewing with the Permutive JavaScript SDK using the CTV addon Track video engagement to build video-based cohorts and understand viewing behavior. ## Overview Video tracking in the Permutive JavaScript SDK is handled by the **CTV addon**. The addon manages engagement timing automatically and tracks the standard Permutive video events — `Videoview` and `VideoCompletion` — without requiring manual interval logic or custom event construction. The CTV addon is the correct approach for video tracking on: * Standard web pages with embedded video players * Web-based CTV applications (Samsung Tizen, LG WebOS, HbbTV) The CTV addon must be enabled for your project before use. Enable it in the Permutive dashboard under **Settings > Integrations**. ## Initializing the CTV Addon Initialize the addon when a user starts watching a video. Calling `permutive.addon("ctv", ...)` automatically tracks a `Videoview` event and prepares the addon for engagement tracking. ```javascript theme={"dark"} permutive.addon("ctv", { duration: 3600000, // Content duration in milliseconds videoProperties: { title: "Breaking Bad - S1E1", genre: ["Drama", "Thriller"], content_type: ["Series", "Episode"], age_rating: "TV-MA", runtime: 3600, season_number: 1, episode_number: 1, audio_language: "en", iab_categories: ["IAB1-1"] } }) ``` After initialization, the addon is available at `permutive.addons.ctv`. ### Duration Pass `duration` in **milliseconds**. This is required for the `VideoCompletion` event to include an accurate `completion` percentage. If the duration is not known at initialization time, you can set it later using `.setDuration()` before calling `.stop()`. ```javascript theme={"dark"} // Set duration later when it becomes available permutive.addons.ctv.setDuration(videoDurationMs) ``` ## Player Lifecycle Map your video player's events to the CTV addon methods. The addon handles engagement timing internally — there is no need to implement your own interval or timer logic. | Addon Method | When to Call | Effect | | :------------------ | :----------------------------------- | :-------------------------------------------------- | | `.play(position?)` | Player starts playing or user scrubs | Starts or resumes engagement timer | | `.pause(position?)` | Player pauses | Pauses engagement timer | | `.stop(position?)` | User exits or content ends | Tracks `VideoCompletion` with aggregated engagement | Positions are passed in **milliseconds**. ### Generic HTML5 Video Example ```javascript theme={"dark"} // Initialize the addon when the user starts a video permutive.addon("ctv", { duration: videoElement.duration * 1000, videoProperties: { title: "My Video Title", genre: ["Documentary"] } }) videoElement.addEventListener("play", function () { permutive.addons.ctv.play(videoElement.currentTime * 1000) }) videoElement.addEventListener("pause", function () { permutive.addons.ctv.pause(videoElement.currentTime * 1000) }) videoElement.addEventListener("ended", function () { permutive.addons.ctv.stop(videoElement.currentTime * 1000) }) ``` ### Handling Duration Not Yet Available If `duration` is not available when the video starts (common with live streams or when metadata loads asynchronously), initialize without it and call `.setDuration()` once the value is known: ```javascript theme={"dark"} videoElement.addEventListener("loadedmetadata", function () { permutive.addons.ctv.setDuration(videoElement.duration * 1000) }) ``` ### Handling Seek and Scrub When a user seeks to a new position, call `.play()` with the new position. This correctly updates the engagement tracker's reference point without inflating engaged time: ```javascript theme={"dark"} videoElement.addEventListener("seeked", function () { permutive.addons.ctv.play(videoElement.currentTime * 1000) }) ``` ### Handling Buffering Pause engagement tracking while the player is buffering so that buffer time is not counted as active engagement: ```javascript theme={"dark"} videoElement.addEventListener("waiting", function () { permutive.addons.ctv.pause() }) videoElement.addEventListener("playing", function () { permutive.addons.ctv.play(videoElement.currentTime * 1000) }) ``` ## Video Ad Tracking Track video ad events using the addon's `.track()` method. For mid-roll ads, pause the main content tracker while the ad plays and resume it when the ad finishes. ### Ad Event Reference | Event | When to Track | | :------------------ | :------------------------------- | | `VideoAdView` | When a video ad starts playing | | `VideoAdCompletion` | When a video ad finishes playing | | `VideoAdClicked` | When a user clicks on a video ad | ### Ad Event Properties Properties for video ad events are nested under the `ad` key: | Property | Type | Description | | :--------------- | :------ | :-------------------------------- | | `ad.title` | string | Title of the video advert | | `ad.duration` | number | Duration of the advert in seconds | | `ad.muted` | boolean | Whether the advert was muted | | `ad.campaign_id` | string | Campaign identifier | | `ad.creative_id` | string | Creative identifier | ### Pre-roll Ads ```javascript theme={"dark"} // Track the ad before initializing the main content const onPrerollStart = function (ad) { permutive.addons.ctv.track("VideoAdView", { ad: { title: ad.title, duration: ad.duration, campaign_id: ad.campaignId, creative_id: ad.creativeId } }) } const onPrerollEnd = function (ad) { permutive.addons.ctv.track("VideoAdCompletion", { ad: { title: ad.title, duration: ad.duration } }) // Now initialize the main content and start tracking permutive.addon("ctv", { duration: videoDurationMs, videoProperties: { title: "Show Title" } }) permutive.addons.ctv.play() } ``` ### Mid-roll Ads ```javascript theme={"dark"} const onMidrollStart = function (ad) { // Pause main content engagement tracking permutive.addons.ctv.pause() permutive.addons.ctv.track("VideoAdView", { ad: { title: ad.title, duration: ad.duration, campaign_id: ad.campaignId } }) } const onMidrollEnd = function (ad) { permutive.addons.ctv.track("VideoAdCompletion", { ad: { title: ad.title, duration: ad.duration } }) // Resume main content engagement tracking permutive.addons.ctv.play(videoElement.currentTime * 1000) } ``` ### Post-roll Ads ```javascript theme={"dark"} const onContentEnd = function () { // Complete main content tracking before post-roll permutive.addons.ctv.stop() } const onPostrollStart = function (ad) { permutive.addons.ctv.track("VideoAdView", { ad: { title: ad.title, duration: ad.duration } }) } ``` ## Video Properties Pass video metadata under the `videoProperties` key when initializing the addon. All properties are optional but richer metadata enables more precise cohort building. | Property | Type | Description | | :--------------------- | :-------- | :---------------------------------------- | | `title` | string | Video title | | `genre` | string\[] | List of genres | | `content_type` | string\[] | List of content types | | `age_rating` | string | Age rating (e.g. "PG-13", "TV-MA") | | `runtime` | number | Runtime in seconds | | `country` | string | Origin country | | `original_language` | string | Original language of the content | | `audio_language` | string | Language of the audio track being watched | | `subtitles.enabled` | boolean | Whether subtitles were enabled | | `subtitles.language` | string | Subtitle language | | `season_number` | number | Season number | | `episode_number` | number | Episode number | | `consecutive_episodes` | number | Number of episodes watched consecutively | | `iab_categories` | string\[] | IAB content taxonomy categories | Properties are type-checked by the addon. A property with the wrong type (for example, passing `audio_language` as an array instead of a string) will be silently removed from the event rather than causing a schema error. ## Complete Working Example A full integration with an HTML5 video element, including ad break handling: ```javascript theme={"dark"} var videoElement = document.getElementById("my-video") var videoDurationMs = 0 // Step 1: Capture duration when metadata loads videoElement.addEventListener("loadedmetadata", function () { videoDurationMs = videoElement.duration * 1000 }) // Step 2: Initialize the CTV addon when playback begins // This automatically tracks the Videoview event. videoElement.addEventListener("play", function () { if (!permutive.addons.ctv) { permutive.addon("ctv", { duration: videoDurationMs, videoProperties: { title: videoElement.dataset.title, genre: ["Drama"], season_number: 1, episode_number: 1, audio_language: "en" } }) } permutive.addons.ctv.play(videoElement.currentTime * 1000) }) // Step 3: Pause engagement tracking when player pauses videoElement.addEventListener("pause", function () { if (permutive.addons.ctv) { permutive.addons.ctv.pause(videoElement.currentTime * 1000) } }) // Step 4: Handle buffering videoElement.addEventListener("waiting", function () { if (permutive.addons.ctv) { permutive.addons.ctv.pause() } }) videoElement.addEventListener("playing", function () { if (permutive.addons.ctv) { permutive.addons.ctv.play(videoElement.currentTime * 1000) } }) // Step 5: Handle seeking videoElement.addEventListener("seeked", function () { if (permutive.addons.ctv) { permutive.addons.ctv.play(videoElement.currentTime * 1000) } }) // Step 6: Stop tracking when content ends (tracks VideoCompletion) videoElement.addEventListener("ended", function () { if (permutive.addons.ctv) { permutive.addons.ctv.stop(videoElement.currentTime * 1000) } }) // Ad tracking helpers function onAdStart(ad) { if (permutive.addons.ctv) { permutive.addons.ctv.pause() permutive.addons.ctv.track("VideoAdView", { ad: { title: ad.title, duration: ad.duration, campaign_id: ad.campaignId } }) } } function onAdEnd(ad) { if (permutive.addons.ctv) { permutive.addons.ctv.track("VideoAdCompletion", { ad: { title: ad.title, duration: ad.duration } }) permutive.addons.ctv.play(videoElement.currentTime * 1000) } } ``` ## Engagement Tracking Patterns The CTV addon tracks engagement automatically. You do not need to implement interval timers or milestone checks manually. The addon's internal engagement model works as follows: 1. **Play** — When `.play()` is called, the engagement clock starts. 2. **Pause** — When `.pause()` is called, the clock stops. Time elapsed since the last `.play()` call is accumulated. 3. **Resume** — When `.play()` is called again, the clock resumes from the current accumulated total. 4. **Stop** — When `.stop()` is called, the final accumulated `engaged_time` and the calculated `completion` percentage are included in the `VideoCompletion` event automatically. This means skipped content (via seeking) is never counted as engaged time, and buffering time can be excluded by calling `.pause()` during buffer states. ## Best Practices The `VideoCompletion` event is only tracked when `.stop()` is called. Make sure you call it not just when a video ends naturally, but also when a user navigates away or closes the player early. ```javascript theme={"dark"} // On natural end videoElement.addEventListener("ended", function () { permutive.addons.ctv.stop(videoElement.currentTime * 1000) }) // On user navigation / page unload window.addEventListener("beforeunload", function () { if (permutive.addons.ctv) { permutive.addons.ctv.stop() } }) ``` The `completion` percentage in `VideoCompletion` is calculated from the duration you provide. Set `duration` in milliseconds at initialization, or call `.setDuration()` once metadata is available. Do not guess or hardcode durations. ```javascript theme={"dark"} videoElement.addEventListener("loadedmetadata", function () { permutive.addons.ctv.setDuration(videoElement.duration * 1000) }) ``` Call `permutive.addon("ctv", ...)` once per piece of content. Calling it again reinitializes the addon and tracks a new `Videoview` event. If the user watches multiple videos in a session, reinitialize for each new piece of content after calling `.stop()` on the previous one. ```javascript theme={"dark"} // When moving to next video permutive.addons.ctv.stop() permutive.addon("ctv", { duration: nextVideoDurationMs, videoProperties: { title: "Next Video Title" } }) ``` Call `.pause()` when a mid-roll ad starts so that ad playback time is not included in the main content's `engaged_time`. Resume with `.play()` when the ad finishes. Every property you pass in `videoProperties` becomes available for cohort building and targeting. At minimum, include `title`. Adding `genre`, `content_type`, `season_number`, and `episode_number` significantly increases the granularity of video-based cohorts. ## Troubleshooting **Problem:** The `Videoview` event is not visible in Permutive analytics. **Solutions:** * Confirm the CTV addon is enabled under **Settings > Integrations** in the Permutive dashboard. * Verify `permutive.addon("ctv", ...)` is being called in the browser console — it should not throw an error. * Check that the SDK is initialized before `permutive.addon("ctv", ...)` is called. * Allow 5-10 minutes for events to appear in the dashboard after they fire. **Problem:** Calling `permutive.addons.ctv.play()` throws because the addon is not available. **Solutions:** * Ensure `permutive.addon("ctv", ...)` has been called before accessing `permutive.addons.ctv`. * Add a guard check: `if (permutive.addons.ctv) { ... }` before calling addon methods. * Confirm the CTV addon is enabled in your workspace settings — it will not be registered if it has not been enabled. **Problem:** The `VideoCompletion` event has a `completion` value of `0` or is missing it. **Solutions:** * Ensure `duration` is passed (in milliseconds) when calling `permutive.addon("ctv", ...)`. * If duration is not available at initialization, call `.setDuration(ms)` before `.stop()` is called. * Verify that the duration value is in milliseconds, not seconds. **Problem:** The `engaged_time` in `VideoCompletion` includes buffering or paused time. **Solutions:** * Call `.pause()` when the player enters a buffering state (`waiting` event on HTML5 video). * Call `.pause()` when the player is paused by the user. * Verify that `.pause()` is being called before ad breaks start. **Problem:** Multiple `Videoview` events fire for a single piece of content. **Solutions:** * `permutive.addon("ctv", ...)` fires a `Videoview` each time it is called. Guard the initialization so it only runs once per content session. * Use a flag or check `permutive.addons.ctv` to avoid re-initializing on repeated `play` events from the player. ## Related Documentation Video event schema and cross-platform tracking patterns Pre-built integrations for JW Player, Brightcove, YouTube, and more General event tracking with the JavaScript SDK Full CTV addon reference for web-based CTV platforms # Initialization Source: https://docs.permutive.com/sdks/web/javascript-sdk/getting-started/initialization Configure and initialize the Permutive JavaScript SDK The SDK initializes automatically when the `live.js` script loads. Configuration options are passed in the loader script and control SDK behavior. ## Basic Initialization The standard initialization passes your credentials and optional configuration: ```html theme={"dark"} ``` ## Configuration Options Pass configuration options as the fourth argument to the loader function: ```javascript theme={"dark"} !function(e,o,n,i){/* ... */}(window.permutive,"","", { // Configuration options here consentRequired: false, loggingEnabled: false }); ``` ### Available Options | Option | Type | Default | Description | | ----------------- | ------- | -------------- | ---------------------------------------------------------------------------- | | `consentRequired` | boolean | `false` | When `true`, SDK waits for consent before tracking | | `loggingEnabled` | boolean | `false` | Enable console logging (also enabled via `?__permutive.loggingEnabled=true`) | | `cookieDomain` | string | Current domain | Domain for the Permutive cookie | | `requestTimeout` | number | `5000` | API request timeout in milliseconds | ### Consent-Required Mode When `consentRequired: true`, the SDK will not track any events until consent is granted: ```html theme={"dark"} ``` With `consentRequired: true`, all tracking is blocked until `permutive.consent()` is called with `opt_in: true`. Events called before consent are not queued - they are discarded. ## Web Addon Configuration The web addon is initialized separately and handles automatic tracking. Call it after the SDK loads: ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article', article: { title: 'Article Title', categories: ['news', 'technology'], authors: ['John Smith'], publishedAt: '2024-01-15T10:00:00Z' } } }); ``` ### Web Addon Options | Option | Type | Description | | --------------- | ------ | -------------------------------------------------------------- | | `page` | object | Custom page properties to include in Pageview events | | `page.type` | string | Page type (e.g., 'article', 'homepage', 'section') | | `page.article` | object | Article-specific metadata | | `page.video` | object | Video-specific metadata | | `eventInterval` | number | Milliseconds between PageviewEngagement events (default: 5000) | | `context` | object | Override default context (title, URL, referrer) | ### Page Property Examples ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article', article: { title: 'Breaking News: Tech Advances', id: 'article-123', categories: ['technology', 'business'], tags: ['AI', 'startups', 'innovation'], authors: ['Jane Smith', 'John Doe'], publishedAt: '2024-01-15T10:00:00Z', modifiedAt: '2024-01-15T14:30:00Z', premium: true, wordCount: 1500 } } }); ``` ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'homepage' } }); ``` ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'section', section: { name: 'Technology', path: '/technology' } } }); ``` ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'video', video: { title: 'Product Demo', id: 'video-456', duration: 180, categories: ['product', 'tutorial'] } } }); ``` ## Waiting for SDK Ready Use `permutive.ready()` to execute code after the SDK initializes: ```javascript theme={"dark"} // Wait for SDK initialization permutive.ready(function() { console.log('Permutive SDK is ready'); // Safe to access cohorts, etc. permutive.segments(function(segments) { console.log('User cohorts:', segments); }); }); ``` ### Ready Stages The `ready()` method accepts an optional stage parameter: ```javascript theme={"dark"} // Default: Wait for basic initialization permutive.ready(function() { // SDK initialized, can track events }, 'initialised'); // Wait for real-time cohort data permutive.ready(function() { // Real-time cohort data is available permutive.segments(function(segments) { // segments array is populated }); }, 'realtime'); ``` | Stage | When Fires | | ------------- | ---------------------------------------- | | `initialised` | SDK has loaded and initialized (default) | | `realtime` | Real-time cohort data has been processed | ## Initialization Order For optimal performance, follow this initialization order: The inline loader script runs first, creating the queue and stub methods. If using `consentRequired: true`, call `permutive.consent()` after obtaining user consent. If you have user identity (e.g., logged-in user), call `permutive.identify()` early. Initialize the web addon to start tracking pageviews and engagement. Initialize other addons (DFP, Prebid, etc.) after the web addon. ### Example: Complete Initialization ```html theme={"dark"} ``` ## Cookie Configuration The SDK uses a first-party cookie to persist user identity. Configure the cookie domain for cross-subdomain tracking: ```javascript theme={"dark"} !function(e,o,n,i){/* ... */}(window.permutive,"","", { cookieDomain: '.example.com' // Shared across subdomains }); ``` Set `cookieDomain` to your root domain (with leading dot) to track users across subdomains like `www.example.com` and `blog.example.com`. ## Environment Configuration For testing environments, you may want different configurations: ```javascript theme={"dark"} var config = { loggingEnabled: window.location.hostname !== 'www.example.com' }; !function(e,o,n,i){/* ... */}(window.permutive,"","", config); ``` ## Troubleshooting Initialization **Problem:** No console messages, `permutive.ready()` callback never fires. **Solutions:** * Check that API key and workspace ID are correct * Verify `live.js` is loading (check Network tab) * Look for errors in the console * Ensure the loader script runs before `live.js` **Problem:** Events are called but nothing appears in dashboard. **Cause:** `consentRequired: true` but `consent()` was not called. **Solution:** Call `permutive.consent({ opt_in: true, token: '...' })` after obtaining user consent. **Problem:** SDK initializes but no Pageview events. **Solutions:** * Verify `permutive.addon('web', {...})` is called * Check that consent has been granted (if `consentRequired: true`) * Enable debug mode to see detailed logging **Problem:** Calling `segments()` in `ready()` callback returns empty array. **Solution:** Use the `'realtime'` stage: ```javascript theme={"dark"} permutive.ready(function() { permutive.segments(function(s) { console.log(s); // Now populated }); }, 'realtime'); ``` ## Next Steps Verify your integration is working Customize pageview tracking # Installation Source: https://docs.permutive.com/sdks/web/javascript-sdk/getting-started/installation Install the Permutive JavaScript SDK via script tag or tag manager The Permutive JavaScript SDK can be installed using several methods. Choose the approach that best fits your deployment workflow. ## Installation Methods The script tag method is the recommended approach for most websites. It provides the fastest load time and simplest setup. ### Basic Installation Add to the `` section of your page, as early as possible: ```html theme={"dark"} ``` Replace: * `` - Your [public API key](https://dash.permutive.com/settings/keys/) * `` - Your [workspace ID](https://dash.permutive.com/settings/project/) * `` - Your organization ID Place the SDK script **before** any other scripts that depend on it (like ad scripts or analytics). The SDK uses a queue-based pattern so methods can be called immediately, but the actual SDK must load before events are sent. ### With Configuration Options Pass configuration options as the fourth argument: ```html theme={"dark"} ``` Use GTM when you need to manage tags centrally without code deployments. ### Setup Steps 1. **Create a Custom HTML Tag** * Go to Tags > New > Custom HTML * Name it "Permutive SDK" 2. **Add the SDK Code** ```html theme={"dark"} ``` 3. **Create Variables** (optional but recommended) * Go to Variables > User-Defined Variables > New * Create Constant variables for API Key and Workspace ID * Reference them as `{{Permutive API Key}}` and `{{Permutive Workspace ID}}` 4. **Set the Trigger** * Use "All Pages" trigger * Set **Tag Firing Priority** to a high number (e.g., 9999) to ensure it loads first 5. **Create Web Addon Tag** ```html theme={"dark"} ``` For detailed GTM setup including Data Layer integration, see the [Tag Manager Guide](/sdks/web/javascript-sdk/guides/tag-managers). ### Tealium 1. Create a JavaScript Code extension 2. Set execution order to "Before Load Rules" 3. Add the SDK code with Tealium data layer variables ### Adobe Launch 1. Create a Core extension action for Custom Code 2. Place in the `` using the Document section 3. Set rule order to 1 (earliest execution) See the [Tag Manager Guide](/sdks/web/javascript-sdk/guides/tag-managers) for detailed instructions for each platform. ## Understanding the Loader Script The inline loader script (the first ` ``` Replace ``, ``, and `` with your credentials from the Permutive dashboard. **Queue-Based Loading:** The SDK uses a queue-based pattern. You can call SDK methods immediately - they're queued and executed once the SDK loads. ## Step 2: Initialize the Web Addon The web addon provides automatic pageview tracking, engagement measurement, and more. Add this code after the SDK script: ```html theme={"dark"} ``` Customize the `page` object with your content metadata. This enables contextual targeting and richer insights. ## Step 3: Complete Example Here's a complete example with the SDK, web addon, and page properties: ```html theme={"dark"} My Article ``` ## Step 4: Verify the Integration 1. **Enable Debug Mode**: Add `?__permutive.loggingEnabled=true` to your page URL 2. **Open Developer Console**: Press F12 or right-click and select "Inspect", then go to the Console tab 3. **Look for SDK Messages**: You should see messages like: ``` [Permutive] SDK initialized [Permutive] Pageview tracked ``` 4. **Check localStorage**: In Developer Tools, go to Application > Local Storage and look for keys starting with `permutive-` or `_p` If you see "Permutive SDK initialized" and "Pageview tracked" messages, your integration is working correctly. ## What Happens Automatically With the web addon configured, the SDK automatically: * **Tracks Pageview events** when the page loads * **Measures engagement time** (PageviewEngagement events) * **Tracks page completion** (PageviewComplete when user leaves) * **Captures form submissions** (FormSubmission events) * **Records link clicks** (LinkClick events for outbound links) ## Next Steps Tag manager options Advanced SDK configuration Customize pageview tracking Track users across sessions # Verification Source: https://docs.permutive.com/sdks/web/javascript-sdk/getting-started/verification Verify your Permutive JavaScript SDK integration is working correctly After installing the SDK, verify that events are being tracked and data is flowing correctly. For a visual, no-code way to validate your deployment, use the [Permutive Chrome Extension](/sdks/web/javascript-sdk/troubleshooting/chrome-extension). It provides real-time visibility into event requests, cohort membership, ad targeting, and Prebid configuration directly in the browser. ## Quick Verification Add `?__permutive.loggingEnabled=true` to any page URL where the SDK is installed. Press F12 (or right-click > Inspect) and go to the **Console** tab. Look for messages starting with `[Permutive]`: ``` [Permutive] SDK initialized [Permutive] User ID: abc123... [Permutive] Pageview tracked ``` Log in to your Permutive dashboard and check that events appear in the Events view. ## Debug Mode Debug mode provides detailed logging of SDK activity. Enable it by adding the query parameter to your URL: ``` https://yoursite.com/page?__permutive.loggingEnabled=true ``` Or by setting `loggingEnabled: true` in the object at the end of the tag: ```html theme={"dark"} ``` ### Debug Output With debug mode enabled, you'll see console messages for: * SDK initialization status * User ID and session information * Events being tracked * Cohort/segment data * API requests and responses * Addon initialization Example debug output: ``` [Permutive] SDK initialized [Permutive] User ID: 7f8e9d0c-1b2a-3c4d-5e6f-7g8h9i0j1k2l [Permutive] Config: {apiKey: "...", workspaceId: "..."} [Permutive] Web addon initialized [Permutive] Tracking: Pageview {type: "article", ...} [Permutive] Event accepted: 1/1 [Permutive] Segments: [12345, 67890, ...] ``` ## Checking localStorage The SDK stores data in localStorage. Open Developer Tools > Application > Local Storage and look for these keys: | Key | Purpose | | ------------------- | -------------------------- | | `permutive-id` | User ID | | `permutive-consent` | Consent status | | `permutive-session` | Session data | | `_psegs` | Current segments (cohorts) | | `_pdfps` | DFP/GAM segments | | `_papns` | AppNexus segments | | `_prubicons` | Rubicon segments | Segment keys starting with `_p` contain arrays of cohort IDs that are passed to different ad platforms. ## Checking Network Requests In Developer Tools > Network tab, filter by `permutive` to see SDK requests: | Request | Purpose | | --------- | --------------- | | `live.js` | SDK script load | | `/track` | Event tracking | | `/v2.0/*` | API requests | ### Successful Event Tracking A successful track request shows: * Status: `200` or `202` * Response includes event acknowledgment ## Verification Checklist Use this checklist to verify your integration: * [ ] `permutive` object exists on `window` * [ ] `live.js` loads without errors (check Network tab) * [ ] No Content Security Policy errors in console * [ ] Debug mode shows "SDK initialized" message * [ ] `permutive-id` key exists in localStorage * [ ] User ID is consistent across page loads * [ ] If setting identity, `identify()` call completes * [ ] Pageview event fires on page load * [ ] Debug mode shows "Event accepted: 1/1" * [ ] Network tab shows successful `/track` requests * [ ] Events appear in Permutive dashboard * [ ] `_psegs` key exists in localStorage * [ ] `permutive.segments()` returns array of cohort IDs * [ ] Cohorts update after qualifying behavior * [ ] Platform-specific segment keys exist (e.g., `_pdfps` for GAM) * [ ] Ad requests include Permutive targeting * [ ] Debug mode shows addon initialization ## Console Commands Use these commands in the browser console to verify SDK state: ```javascript theme={"dark"} // Check if SDK is loaded console.log('SDK loaded:', !!window.permutive); // Get user ID console.log('User ID:', window.permutive?.user?.()); // Get current segments permutive.segments(function(segs) { console.log('All segments:', segs); }); // Get DFP segments permutive.segments(function(segs) { console.log('DFP segments:', segs); }, 'dfp'); // Check SDK readiness permutive.ready(function() { console.log('SDK is ready'); }); // Check realtime data permutive.ready(function() { console.log('Realtime data ready'); }, 'realtime'); ``` ## Common Verification Issues **Problem:** Adding `?__permutive.loggingEnabled=true` shows no messages. **Solutions:** * Verify the SDK loader script is on the page * Check that `live.js` is loading (Network tab) * Look for JavaScript errors that might block SDK * Try clearing browser cache and reloading **Problem:** Debug mode shows events accepted but nothing in dashboard. **Solutions:** * Wait a few minutes - there may be processing delay * Verify you're viewing the correct workspace * Check the event date/time filters in dashboard * Confirm API key matches the workspace **Problem:** `permutive.segments()` returns empty array. **Possible causes:** * User hasn't qualified for any cohorts yet * Real-time data hasn't loaded - use `ready('realtime')` callback * No cohorts are configured in dashboard * Consent not granted (if `consentRequired: true`) **Problem:** Different user ID on each visit. **Possible causes:** * Cookies are being blocked or cleared * Private/incognito browsing mode * Cookie domain mismatch * Third-party cookie blocking (SDK uses first-party, so this is rare) **Solution:** Check localStorage persistence and cookie settings. **Problem:** Network requests fail with CORS errors. **Solution:** This usually indicates a configuration issue. Contact support with: * Your domain * API key (first few characters) * Screenshot of the error ## Testing Specific Features ### Testing Pageview Tracking ```javascript theme={"dark"} // Check if web addon is tracking permutive.on('Pageview', function(event) { console.log('Pageview tracked:', event); }); // Manually trigger a pageview (useful for SPAs) permutive.addon('web', { page: { type: 'article' } }); ``` ### Testing Identity ```javascript theme={"dark"} // Set identity and verify permutive.identify([ { tag: 'test_id', id: 'test123' } ]); // Check it was set (after a moment) setTimeout(function() { console.log('Identity set'); }, 1000); ``` ### Testing Triggers ```javascript theme={"dark"} // Watch for segment entry permutive.trigger(12345, 'segment', function(result) { console.log('Segment trigger:', result); }); // Check segment membership permutive.segment(12345, function(inSegment) { console.log('In segment 12345:', inSegment); }); ``` ## Dashboard Verification In your Permutive dashboard: 1. **Events** - Verify events are appearing with correct properties 2. **Sources** - Check your website appears as an active source 3. **Cohorts** - Verify users are qualifying for expected cohorts 4. **Activations** - Check that cohorts are being sent to ad platforms ## Production Verification Before launching to production: * [ ] Remove any test/debug code * [ ] Verify `loggingEnabled: false` (or omitted) in production config * [ ] Test on multiple browsers (Chrome, Safari, Firefox, Edge) * [ ] Test on mobile devices * [ ] Verify consent flow works correctly (if applicable) * [ ] Check page load performance impact is acceptable * [ ] Validate ad targeting is working correctly ## Getting Help If verification fails: 1. Use the [Chrome Extension](/sdks/web/javascript-sdk/troubleshooting/chrome-extension) for a visual overview of event requests, cohort membership, and ad targeting 2. Check the [Troubleshooting Guide](/sdks/web/javascript-sdk/troubleshooting/common-errors) 3. Gather debug output and network logs 4. Contact your Customer Success Manager or [technical-services@permutive.com](mailto:technical-services@permutive.com) ## Next Steps Configure pageview tracking Track custom events Set up user identity Configure ad targeting # Tag Manager Integration Source: https://docs.permutive.com/sdks/web/javascript-sdk/guides/tag-managers Deploy the Permutive JavaScript SDK via Google Tag Manager, Tealium, and Adobe Launch This guide covers deploying the Permutive SDK through popular tag management systems. ## Overview When deploying via tag manager: * **High priority execution** - Permutive should load before ad tags * **Data layer integration** - Pass page properties from your data layer * **Consistent deployment** - Use variables for credentials and properties ## Google Tag Manager ### Step 1: Create Permutive SDK Tag 1. Go to **Tags > New** 2. Click **Tag Configuration > Custom HTML** 3. Name it "Permutive SDK" 4. Add the SDK code: ```html theme={"dark"} ``` ### Step 2: Create Credential Variables 1. Go to **Variables > User-Defined Variables > New** 2. Create a **Constant** variable for API Key: * Name: `Permutive API Key` * Value: Your API key 3. Create a **Constant** variable for Workspace ID: * Name: `Permutive Workspace ID` * Value: Your workspace ID ### Step 3: Set Up Trigger 1. Under the tag, click **Triggering** 2. Select **All Pages** trigger 3. Click **Advanced Settings > Tag Firing Options** 4. Set **Tag firing priority** to `9999` (highest priority) ### Step 4: Create Web Addon Tag 1. Create another Custom HTML tag named "Permutive Web Addon" 2. Add the web addon code: ```html theme={"dark"} ``` 3. Set trigger to **All Pages** 4. Set **Tag Sequencing** to fire after "Permutive SDK" tag ### Step 5: Create Data Layer Variables Create variables to read from your data layer: 1. **Page Type** (Data Layer Variable) * Variable Name: `page.type` 2. **Page Title** (Data Layer Variable) * Variable Name: `page.title` * Or use built-in `{{Page Title}}` 3. **Page Categories** (Custom JavaScript Variable) ```javascript theme={"dark"} function() { var dl = window.dataLayer || []; for (var i = 0; i < dl.length; i++) { if (dl[i].page && dl[i].page.categories) { return dl[i].page.categories; } } return []; } ``` ### Step 6: Data Layer Setup On your website, push data to the data layer before GTM loads: ```html theme={"dark"} ``` ### Complete GTM Example **Tag 1: Permutive SDK** ```html theme={"dark"} ``` * Trigger: All Pages * Priority: 9999 **Tag 2: Permutive Web Addon** ```html theme={"dark"} ``` * Trigger: All Pages * Tag Sequencing: After "Permutive SDK" ## Tealium ### Step 1: Create JavaScript Code Extension 1. Go to **Extensions > Add Extension** 2. Select **JavaScript Code** 3. Set **Scope** to **Pre Loader** (runs before all tags) 4. Add the SDK code: ```javascript theme={"dark"} // Permutive SDK Loader !function(e,o,n,i){if(!e){e=e||{},window.permutive=e,e.q=[];var t=function(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,function(e){return(e^(window.crypto||window.msCrypto).getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)})};e.config=i||{},e.config.apiKey=o,e.config.workspaceId=n,e.config.environment=e.config.environment||"production",(window.crypto||window.msCrypto)&&(e.config.viewId=t());for(var g=["addon","identify","track","trigger","query","segment","segments","ready","on","once","user","consent"],r=0;r Add Rule** 2. Name it "Permutive SDK" 3. Under **Events**, add: * Extension: Core * Event Type: Library Loaded (Page Top) ### Step 2: Add SDK Action 1. Under **Actions**, click **Add** 2. Select: * Extension: Core * Action Type: Custom Code * Language: JavaScript 3. Check **Execute globally** 4. Add the SDK code: ```javascript theme={"dark"} // Permutive SDK !function(e,o,n,i){if(!e){e=e||{},window.permutive=e,e.q=[];var t=function(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,function(e){return(e^(window.crypto||window.msCrypto).getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)})};e.config=i||{},e.config.apiKey=o,e.config.workspaceId=n,e.config.environment=e.config.environment||"production",(window.crypto||window.msCrypto)&&(e.config.viewId=t());for(var g=["addon","identify","track","trigger","query","segment","segments","ready","on","once","user","consent"],r=0;r Add Data Element** 2. Create these data elements: * `Permutive API Key` (Constant) * `Permutive Workspace ID` (Constant) * `Permutive Organization ID` (Constant) * `Page Type` (JavaScript Variable or Custom Code) * `Page Categories` (JavaScript Variable or Custom Code) ### Step 4: Create Web Addon Rule 1. Create a new rule "Permutive Web Addon" 2. Event: DOM Ready 3. Add Custom Code action: ```javascript theme={"dark"} permutive.addon('web', { page: { type: _satellite.getVar('Page Type') || 'page', article: { title: document.title, categories: _satellite.getVar('Page Categories') || [] } } }); ``` ### Step 5: Set Rule Order 1. Set "Permutive SDK" rule order to **1** 2. Set "Permutive Web Addon" rule order to **10** ## Data Layer Best Practices ### Standardize Your Data Layer Use consistent property names across your site: ```javascript theme={"dark"} // Recommended data layer structure window.dataLayer = window.dataLayer || []; dataLayer.push({ event: 'pageview', page: { type: 'article', // article, homepage, section, video title: 'Page Title', categories: ['cat1', 'cat2'], tags: ['tag1', 'tag2'], authors: ['Author Name'] }, user: { isLoggedIn: true, hashedEmail: 'abc123...' // SHA-256 hashed } }); ``` ### Handle Identity in Tag Manager ```javascript theme={"dark"} // In a separate tag, after SDK loads if (dataLayer) { for (var i = 0; i < dataLayer.length; i++) { var data = dataLayer[i]; if (data.user && data.user.hashedEmail) { permutive.identify([ { tag: 'email_sha256', id: data.user.hashedEmail } ]); break; } } } ``` ## Troubleshooting Tag Manager Deployments **Problem:** Web addon fires before SDK loads. **Solutions:** * Use tag sequencing/ordering features * Set SDK tag priority to highest (e.g., 9999) * Use `permutive.ready()` in dependent tags **Problem:** Data layer variables return undefined. **Solutions:** * Verify data layer is populated before GTM loads * Check variable names match exactly * Use default values: `{{Variable}} || 'default'` * Debug with GTM Preview mode **Problem:** SDK sometimes loads, sometimes doesn't. **Solutions:** * Check trigger conditions * Verify no JavaScript errors blocking execution * Ensure async script loading works in your environment * Test with tag manager debug/preview mode **Problem:** Events not tracking with consent management. **Solution:** Create a consent tag that fires before web addon: ```javascript theme={"dark"} // Consent Tag (fire when CMP grants consent) permutive.consent({ opt_in: true, token: 'consent_token' }); ``` Then use tag sequencing to fire web addon after consent. ## Best Practices Never hardcode credentials directly in tags. Use tag manager variables for easy updates across environments. Always test tag firing order and variable values in the tag manager's preview mode before publishing. Keep notes on: * Tag names and purposes * Variable mappings * Data layer requirements * Firing order dependencies Use tag manager's versioning features and maintain a changelog of deployments. ## Related Documentation All installation methods Configuration options Verify your integration Consent with tag managers # Integrations Source: https://docs.permutive.com/sdks/web/javascript-sdk/integrations/overview Connect the JavaScript SDK with ad platforms and header bidding solutions The Permutive JavaScript SDK integrates with major ad platforms to enable audience targeting. For detailed setup instructions, see the main integration documentation for each platform. ## Ad Server Integrations Set up GPT targeting and key-value pairs for web Configure Xandr ad platform targeting ## Bidstream Integrations RTD module and header bidding configuration # JavaScript SDK Source: https://docs.permutive.com/sdks/web/javascript-sdk/overview Integrate Permutive into your website for user segmentation, personalization, and ad targeting ## Overview The Permutive JavaScript SDK enables user segmentation, personalization, and ad targeting on your website. Track user behavior, manage identities, and deliver targeted advertising through integrations with major ad platforms including Google Ad Manager, Prebid.js, and Xandr. **Current Version:** See your Permutive dashboard for the latest version. **Browser Support:** The SDK supports all modern browsers including Chrome, Firefox, Safari, and Edge. It is designed to work in environments without third-party cookies. ## Getting Started New to the Permutive JavaScript SDK? Start here. Get up and running with your first pageview in minutes Script tag and tag manager installation options SDK initialization and configuration options Verify your integration is working correctly ## Core Concepts Understand the fundamental concepts of the Permutive SDK. Track users across sessions and devices Structure and validate event data Understanding user segmentation Content-based real-time targeting GDPR, TCF, and consent handling ## Features Detailed guides for specific SDK features. Automatic and manual pageview tracking with the web addon Track custom events with track(), on(), and once() Track video content viewing React to cohort changes with trigger(), segment(), and query() ## Integrations Connect Permutive with ad platforms and header bidding solutions. Google Ad Manager, Prebid.js, Xandr, and more ## Common Tasks The web addon automatically tracks pageviews when configured. You can also include custom page properties. ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article', article: { title: 'Breaking News: Tech Advances', categories: ['technology', 'news'], authors: ['Jane Smith'], publishedAt: '2024-01-15' } } }); ``` ```javascript theme={"dark"} permutive.track('Purchase', { product_id: 'SKU123', product_name: 'Premium Subscription', price: 9.99, currency: 'USD' }); ``` ```javascript theme={"dark"} // Simple identity permutive.identify([ { tag: 'email_sha256', id: 'a1b2c3d4e5f6...' }, { tag: 'internal_id', id: 'user_12345', priority: 1 } ]); ``` ```javascript theme={"dark"} // Check if user is in a specific cohort permutive.segment(12345, function(inCohort) { if (inCohort) { showPremiumContent(); } }); // Get all cohorts permutive.segments(function(segments) { console.log('User cohorts:', segments); }); ``` ```javascript theme={"dark"} // Using the DFP addon (automatically sets targeting) permutive.addon('web', { page: { /* ... */ } }); // Or manually get segments for GPT permutive.segments(function(segments) { googletag.cmd.push(function() { googletag.pubads().setTargeting('permutive', segments); }); }, 'dfp'); ``` ## Key API Methods | Method | Purpose | | ----------------------------------------- | ------------------------------ | | `permutive.track(event, properties)` | Track custom events | | `permutive.identify(identities)` | Set user identities | | `permutive.trigger(code, param, handler)` | React to cohort entry/exit | | `permutive.segment(code, handler)` | Check single cohort membership | | `permutive.segments(handler, type)` | Get all cohorts | | `permutive.query(code, handler)` | Reserved for future use | | `permutive.ready(callback, stage)` | Wait for SDK initialization | | `permutive.consent(data)` | Set consent status | | `permutive.addon(name, options)` | Initialize addons | | `permutive.on(event, callback)` | Persistent event listener | | `permutive.once(event, callback)` | Single event listener | | `permutive.reset()` | Clear user data | ## Requirements | Requirement | Details | | ------------ | ----------------------------------------------------- | | Browsers | Modern browsers (Chrome, Firefox, Safari, Edge) | | JavaScript | ES5+ (SDK is transpiled for compatibility) | | Cookies | First-party cookies required for identity persistence | | localStorage | Required for cohort storage | ## Installation Add to the `` of your page, as early as possible: ```html theme={"dark"} ``` Replace ``, ``, and `` with your credentials from the Permutive dashboard. Add the Permutive tag as a high-priority tag that fires on all pages. See the [Tag Manager Guide](/sdks/web/javascript-sdk/guides/tag-managers) for GTM, Tealium, and Adobe Launch setup. ## Key Concepts The SDK uses a queue-based loading pattern. Methods called before the SDK loads are queued and executed in order once the SDK initializes. This means you can safely call SDK methods immediately without waiting for load. * **Cohorts**: All segments a user belongs to * **Activations**: Cohorts configured for specific ad platforms (e.g., `dfp` for Google Ad Manager) * **Behavioral**: Based on user history (e.g., "sports enthusiast") * **Contextual**: Based on current page content (e.g., viewing sports article right now) The `web` addon provides automatic tracking for pageviews, engagement time, form submissions, and link clicks. It's the recommended way to track user activity. ## Additional Resources * **[Tag Manager Setup](/sdks/web/javascript-sdk/guides/tag-managers)** - GTM, Tealium, Adobe Launch * **[Single Page Applications](/sdks/web/javascript-sdk/features/pageview-tracking#spa-support)** - React, Vue, Angular integration * **[Prebid Integration](/integrations/advertising/bidstream/prebid)** - RTD and Identity Manager modules * **[Consent](/governance/consent)** - Consent mechanisms and GDPR compliance ## FAQ Yes. The Permutive SDK uses first-party cookies and localStorage, which are not affected by third-party cookie restrictions. It works effectively in Safari, Firefox, and other browsers that block third-party cookies. Add `?__permutive.loggingEnabled=true` to any page URL to enable debug logging in the browser console. You'll see detailed information about events, cohorts, and SDK activity. The **web addon** automatically tracks standard events (Pageview, PageviewEngagement, FormSubmission, LinkClick) with minimal configuration. Use `track()` for custom events that don't fit these patterns. Set `consentRequired: true` in your configuration, then call `permutive.consent({ opt_in: true, token: '...' })` after obtaining user consent. See the [Consent Management](/sdks/web/javascript-sdk/core-concepts/consent-management) guide for details. Yes. Call `permutive.addon('web', { page: {...} })` on each route change to track new pageviews. See [Pageview Tracking](/sdks/web/javascript-sdk/features/pageview-tracking#spa-support) for details. ## Getting Help * **[Chrome Extension](/sdks/web/javascript-sdk/troubleshooting/chrome-extension)** - Visual deployment validation and debugging * **[Troubleshooting Guide](/sdks/web/javascript-sdk/troubleshooting/common-errors)** - Solutions to common issues * **Customer Success Manager** - Your primary contact * **[Technical Services](mailto:technical-services@permutive.com)** - Technical integration support ## Privacy & Compliance Permutive is designed with privacy in mind: GDPR compliant, CCPA compliant, no PII storage, user consent respected, and transparent data usage. # Release notes Source: https://docs.permutive.com/sdks/web/javascript-sdk/release-notes/index # Changelog All notable changes to this project will be documented in this file. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning. ## @permutive/javascript-sdk 20.65.5 (2026-04-22) ### Bug Fixes * **javascript-sdk:** align stackadapt addon key with extensions platform (#3432) ## @permutive/javascript-sdk 20.65.4 (2026-04-16) ### Build System * consolidate yarn version source of truth (#3417) ## @permutive/javascript-sdk 20.65.3 (2026-04-14) ### Performance Improvements * **SlotClicked:** listen to blur and visibilitychange (#2825) ## @permutive/javascript-sdk 20.65.2 (2026-04-01) ### Bug Fixes * **dfp:** add `rts` to all segments Set (#3396) ## @permutive/javascript-sdk 20.65.1 (2026-03-27) ### Bug Fixes * **javascript-sdk:** make reset() wait for segmentation service reinitialisation (#3356) ## @permutive/javascript-sdk 20.65.0 (2026-03-18) ### Features * **equativ:** add cookie sync addon (#3316) ## @permutive/javascript-sdk 20.64.1 (2026-03-16) ### Bug Fixes * **query-language:** evaluate invalidated TPD queries (#3334) ## @permutive/javascript-sdk 20.64.0 (2026-03-12) ### Features * **javascript-sdk:** add stackadapt cookie sync addon (#3321) ## @permutive/javascript-sdk 20.63.2 (2026-03-03) ### Bug Fixes * **sdk:** add polyfills for babel runtime (#3195) ## @permutive/javascript-sdk 20.63.1 (2026-02-20) ### Reverts * **classifications:** feat(classifications): provide contextual cohorts (#3220) (#3220, #3221, #3200, #3143, #3226, #3224, #3228, #3227) ## @permutive/javascript-sdk 20.63.0 (2026-02-19) ### Features * **activations:** update block list (#3226) ## @permutive/javascript-sdk 20.62.3 (2026-02-04) ### Bug Fixes * do not await contextual data indefinitely (#3205) * regression in audience matching query processing (#3206) ## @permutive/javascript-sdk 20.62.3 (2026-02-04) ### Bug Fixes * regression in audience matching query processing (#3206) ## @permutive/javascript-sdk 20.62.2 (2026-01-29) ### Bug Fixes * **user-groups:** set api backoff call to 10 mins (#3175, #3180) ## @permutive/javascript-sdk 20.62.1 (2026-01-15) ### Bug Fixes * **javascript sdk:** report cloud lift errors (#3087) ## @permutive/javascript-sdk 20.62.0 (2026-01-15) ### Features * **Adform:** adds support for Adform ID sync (#3179) ## @permutive/javascript-sdk 20.61.9 (2026-01-13) ### Reverts * **release:** Revert "chore(release): nx release \[skip ci]" (67ef979d1) ## @permutive/javascript-sdk 20.61.8 (2026-01-12) ### Reverts * **release:** Revert "chore(release): nx release \[skip ci]" (ca95e1cf5) ## @permutive/javascript-sdk 20.61.7 (2026-01-12) ### Bug Fixes * noop (#3169) ## @permutive/javascript-sdk 20.61.5 (2026-01-09) ### Bug Fixes * noop (#3157) (f3e30da) ## @permutive/javascript-sdk 20.61.4 (2026-01-09) ### Bug Fixes * noop (#3155) (9d9916f) ## @permutive/javascript-sdk 20.61.3 (2026-01-09) ### Bug Fixes * noop (#3153) (c8fbcc8) ## @permutive/javascript-sdk 20.61.2 (2026-01-09) ### Bug Fixes * escape URL components (#3036) (45610cd) ## @permutive/javascript-sdk 20.61.1 (2026-01-05) ### Bug Fixes * **web-sdk-config:** skip curation\_platforms segment when id is missing (#3145) (dedcbf9) # @permutive/javascript-sdk 20.61.0 (2025-12-16) ### Features * **cohort-activation:** update configuration (#3129) (78ad6da) ## @permutive/javascript-sdk 20.60.1 (2025-12-15) ### Bug Fixes * **overlays:** wrap key as string (#3127) (4d2c404) # @permutive/javascript-sdk 20.60.0 (2025-12-05) ### Features * **appnexus pixel:** Add GDPR parameters to app nexus Pixel URL (#3045) (a8c037f) ## @permutive/javascript-sdk 20.59.1 (2025-12-04) ### Bug Fixes * **activations:** allow event tracking for dfp (#3103) (8db1186) # @permutive/javascript-sdk 20.59.0 (2025-12-04) ### Features * sdk config change (#3102) (bc10662) # @permutive/javascript-sdk 20.58.0 (2025-12-03) ### Features * **APS:** use ortb2 user.ext.eids format for APS ids (#2960) (ddf9257) # @permutive/javascript-sdk 20.57.0 (2025-12-01) ### Features * use cloud-lift-rust (#3043) (f70c16c) # @permutive/javascript-sdk 20.56.0 (2025-11-28) ### Features * **appnexus-adserver:** add disable setKeywords configuration (#3062) (56d8bc9) # @permutive/javascript-sdk 20.55.0 (2025-11-05) ### Features * **query-language:** support user-group cohorts ifp (#3024) (fd29b78) # @permutive/javascript-sdk 20.54.0 (2025-10-23) ### Features * **prebid\_monitoring:** bid adapters improvements (#3025) (4ff7205) ## @permutive/javascript-sdk 20.53.1 (2025-10-16) ### Bug Fixes * use dedicated MessageChannel for master - worker comms (#3021) (8e9b638) # @permutive/javascript-sdk 20.53.0 (2025-10-15) ### Features * **prebid\_monitoring:** includes `s2s_config` to prebid monitoring (#3016) (28ef4b7) ## @permutive/javascript-sdk 20.52.1 (2025-10-08) ### Bug Fixes * **monitoring:** video player metrics are incorrectly populated (#3006) (8dc77a7) # @permutive/javascript-sdk 20.52.0 (2025-10-02) ### Features * **prebid\_monitoring:** increase sampling from 1% to 5% (#2994) (8f79001) # @permutive/javascript-sdk 20.51.0 (2025-09-25) ### Features * **video-player-monitoring:** add additional metrics (#2991) (bb5683c) ## @permutive/javascript-sdk 20.50.1 (2025-09-23) ### Build System * **deps:** align babel versions (#2988) (432fecd) # @permutive/javascript-sdk 20.50.0 (2025-09-18) ### Features * **prebid\_monitoring:** include auctionDelay and jwp to rtd json string (#2984) (67576b6) # @permutive/javascript-sdk 20.49.0 (2025-09-12) ### Features * **metrics:** Prebid monitoring config metric - `sdk_prebid_monitoring_config_count_total` (#2971) (9ce1544) # @permutive/javascript-sdk 20.48.0 (2025-09-12) ### Features * **monitoring:** Video player metrics (#2974) (f318b53) # @permutive/javascript-sdk 20.47.0 (2025-08-19) ### Features * **cohorts:** add user-group cohort support (#2949) (d7a5e2d) ## @permutive/javascript-sdk 20.46.1 (2025-08-18) ### Bug Fixes * **adswizz:** use adswizz\_keyvalue integration key (#2951) (879fc08) # @permutive/javascript-sdk 20.46.0 (2025-08-14) ### Features * **AdsWizz:** create ad-server integration (#2942) (76cb01b) # @permutive/javascript-sdk 20.45.0 (2025-08-06) ### Features * **APS:** adds new integration with Amazon Publisher Services (#2938) (f00c5fc) # @permutive/javascript-sdk 20.44.0 (2025-08-01) ### Features * **logger:** add badges (#2937) (563c1d6) # @permutive/javascript-sdk 20.43.0 (2025-07-24) ### Features * **metrics:** inject geo `country` into MetricReporter (#2929) (8b8be5e) # @permutive/javascript-sdk 20.42.0 (2025-07-23) ### Features * **prebid\_monitoring:** send fine-grained ortb2 targeting metrics (#2920) (8911252) ## @permutive/javascript-sdk 20.41.1 (2025-07-09) ### Bug Fixes * **xandr-adserver:** improve mobile device click detection (#2905) (783acb3) # @permutive/javascript-sdk 20.41.0 (2025-07-07) ### Features * **curation:** Add curation everyone cohort (#2910) (907c197) # @permutive/javascript-sdk 20.40.0 (2025-06-25) ### Features * **appnexus:** support SPA use-case (#2895) (9ced311) ## @permutive/javascript-sdk 20.39.5 (2025-06-24) ### Bug Fixes * **contextual:** wire up cohort activations (#2904) (ab8122b) ## @permutive/javascript-sdk 20.39.4 (2025-06-20) ### Bug Fixes * **brightcove:** Update iframe src check for brightcove to also handle undefined (#2903) (fadcd92) ## @permutive/javascript-sdk 20.39.3 (2025-06-18) ### Reverts * Revert "fix(contextual): publish activated cohorts (#2900)" (c17e57e), closes #2900 ## @permutive/javascript-sdk 20.39.2 (2025-06-18) ### Bug Fixes * **contextual:** publish activated cohorts (#2900) (1158924) ## @permutive/javascript-sdk 20.39.1 (2025-06-18) ### Bug Fixes * **event-upload:** serialize state explicitly (#2901) (9924888) # @permutive/javascript-sdk 20.39.0 (2025-06-06) ### Features * **events-cache:** report age metrics (#2894) (e5376a1) ## @permutive/javascript-sdk 20.38.1 (2025-05-28) ### Bug Fixes * **xandr:** add gdpr params to identity request (#2887) (b383547) # @permutive/javascript-sdk 20.38.0 (2025-05-20) ### Features * **curation:** Activate segmented clean\_room cohorts (#2883) (50ff04d) ## @permutive/javascript-sdk 20.37.8 (2025-05-14) ### Bug Fixes * **contextual:** set max time to wait for tag call (#2881) (2039ff7) ## @permutive/javascript-sdk 20.37.7 (2025-05-01) ### Bug Fixes * **identity-manager:** add provider field to ID5 API call (#2875) (8d14831) ## @permutive/javascript-sdk 20.37.6 (2025-04-15) ### Bug Fixes * **tiktok-pixel:** update hashed email tag (#2796) (4537938) ## @permutive/javascript-sdk 20.37.5 (2025-04-11) ### Bug Fixes * **ctv:** enable for all organizations (#2872) (a4950d2) ## @permutive/javascript-sdk 20.37.4 (2025-04-11) ### Bug Fixes * **appnexus:** improve `AppNexusAdImpression` tracking (#2866) (a1ce544) ## @permutive/javascript-sdk 20.37.3 (2025-04-08) ### Performance Improvements * **memory:** create new task every iteration (#2871) (7e1e18b) ## @permutive/javascript-sdk 20.37.2 (2025-04-07) ### Performance Improvements * **tbt:** break up long running initialisation task (#2870) (f859a5c) ## @permutive/javascript-sdk 20.37.1 (2025-04-02) ### Performance Improvements * **pinterest:** filter request by tagId (#2867) (a46de51) # @permutive/javascript-sdk 20.37.0 (2025-03-20) ### Features * **tracking\_pixel:** Add support for tcf\_vendor\_id config (#2760) (f5232c4) ## @permutive/javascript-sdk 20.36.1 (2025-03-19) ### Bug Fixes * **context:** make available to custom scripts (#2832) (77e678c) # @permutive/javascript-sdk 20.36.0 (2025-03-18) ### Features * initialises new curation pipes (#2850) (212740a) # @permutive/javascript-sdk 20.35.0 (2025-03-17) ### Features * **tpd-usage:** only track usage for configured segments (#2849) (acf6d5f) ## @permutive/javascript-sdk 20.34.6 (2025-02-26) ### Bug Fixes * **identity-manager:** add identity based guard to liveramp provider (#2841) (fc3661d) ## @permutive/javascript-sdk 20.34.5 (2025-02-20) ### Code Refactoring * **processing-result:** sequence error result (ac0770e) ### Performance Improvements * **segmentation:** only process newly produced transition events (b903827) * **segmentation:** reword error on exceeding max iterations (90cdcf8) * **segmentation:** set maximum iterations for producing transition events (af91eb2) ## @permutive/javascript-sdk 20.34.4 (2025-02-20) ### Bug Fixes * **xandr:** activate classification cohorts (#2836) (d3a9471) ## @permutive/javascript-sdk 20.34.3 (2025-02-19) ### Performance Improvements * **events-cache:** block some events from persistence for specific workspaces (#2837) (fe1ff6e) ## @permutive/javascript-sdk 20.34.2 (2025-02-11) ### Performance Improvements * **lal:** refresh cached models (#2834) (c9dacc3) ## @permutive/javascript-sdk 20.34.1 (2025-02-07) ### Performance Improvements * **identity-manager:** improve caching across Pageviews (#2822) (f90fa7c) # @permutive/javascript-sdk 20.34.0 (2025-02-05) ### Features * **context:** allow values to override client properties (#2787) (0270cad) # @permutive/javascript-sdk 20.33.0 (2025-02-05) ### Features * **resource-timings:** track metric for asset (#2819) (c8a2d5a) # @permutive/javascript-sdk 20.32.0 (2025-01-22) ### Features * **cloudlift:** rename cloudlift endpoint (#2816) (f8824fb) ## @permutive/javascript-sdk 20.31.3 (2025-01-21) ### Performance Improvements * **build-service:** increase parallel scale to 5000 instances (#2815) (48bef2c) ## @permutive/javascript-sdk 20.31.2 (2025-01-20) ### Performance Improvements * **facebook pixel:** run fbq in separate macrotask to reduce tbt (#2666) (38496fe) ## @permutive/javascript-sdk 20.31.1 (2025-01-20) ### Bug Fixes * **doubleclick-pixel:** remove gdpr params for workspace (#2814) (0c06da2) # @permutive/javascript-sdk 20.31.0 (2025-01-17) ### Features * release identity manager (#2808) (e3bb82b) # @permutive/javascript-sdk 20.30.0 (2025-01-16) ### Features * integrate contextual data platform (#2788) (4937f77) ## @permutive/javascript-sdk 20.29.5 (2025-01-16) ### Bug Fixes * **identity-manager:** replace config values as strings (#2813) (c82e26c) ## @permutive/javascript-sdk 20.29.4 (2025-01-16) ### Bug Fixes * **prebid\_analytics:** normalize user\_id (#2811) (8d71b32) ## @permutive/javascript-sdk 20.29.3 (2025-01-13) ### Bug Fixes * **identity-manager:** liveramp rate limiting (#2805) (8e30f0d) ## @permutive/javascript-sdk 20.29.2 (2025-01-13) ### Bug Fixes * **build-service:** encode compressed asset as base64 (#2803) (fac8b9f) ## @permutive/javascript-sdk 20.29.1 (2025-01-09) ### Build System * **compression:** move to after asset verification (#2801) (b0936b3) # @permutive/javascript-sdk 20.29.0 (2025-01-09) ### Features * **prebid\_analytics:** prebidauctions\_events down sampling (#2795) (43a267d) ## @permutive/javascript-sdk 20.28.1 (2025-01-08) ### Bug Fixes * **appnexus\_adserver:** correctly re-implement `defineTag` (#2800) (8aef12c) # @permutive/javascript-sdk 20.28.0 (2025-01-07) ### Features * **prebid\_analytics:** save storage cost by opting out selected customers (#2797) (e17df7f) # @permutive/javascript-sdk 20.27.0 (2025-01-06) ### Features * **prebid\_analytics:** Separate from clean\_room (#2792) (a9b3b83) ## @permutive/javascript-sdk 20.26.2 (2024-12-18) ### Bug Fixes * **builds:** increase payload size limit (#2784) (400c480) ## @permutive/javascript-sdk 20.26.1 (2024-12-13) ### Bug Fixes * **transpilation:** produce compliant syntax for targets (#2785) (6caa1ce) # @permutive/javascript-sdk 20.26.0 (2024-12-11) ### Features * **build-service:** set invoker policy (#2782) (6cc5ec9) # @permutive/javascript-sdk 20.25.0 (2024-12-10) ### Features * enable cloudlift (#2755) (7b94d50) # @permutive/javascript-sdk 20.24.0 (2024-12-04) ### Features * **prebid:** Add advertiser domains to prebidbid event (#2776) (21ccc88) ## @permutive/javascript-sdk 20.23.4 (2024-12-02) ### Build System * **ctv:** modify allowed workspace list (#2779) (3e75751) ## @permutive/javascript-sdk 20.23.3 (2024-12-02) ### Bug Fixes * **facebook\_pixel:** call config value initialiser when firing fb pixel (#2774) (6f2f37f) ## @permutive/javascript-sdk 20.23.2 (2024-11-26) ### Bug Fixes * **facebook\_pixel:** fix config value not being initialised before constructor super call (#2772) (34b2658) ## @permutive/javascript-sdk 20.23.1 (2024-11-26) ### Build System * **build-service:** create http server (#2745) (aa21b0f), closes #2715 #2716 #2736 # @permutive/javascript-sdk 20.23.0 (2024-11-22) ### Features * **identity-manager:** allow geo restriction of id providers (#2762) (20ebbda) # @permutive/javascript-sdk 20.22.0 (2024-11-18) ### Features * **identity-manager:** liveramp id provider (#2757) (2406213) ## @permutive/javascript-sdk 20.21.1 (2024-11-18) ### Bug Fixes * **ampscript:** remove public version assignment (#2770) (b2b482f) # @permutive/javascript-sdk 20.21.0 (2024-11-14) ### Features * **facebook\_pixel\_v2:** Decide segment attach method using new core setup configuration value (#2769) (41571d4) ## @permutive/javascript-sdk 20.20.1 (2024-11-13) ### Bug Fixes * remove redundant Safari IndexedDB workaround (#2768) (2695071) # @permutive/javascript-sdk 20.20.0 (2024-11-13) ### Features * add sdkVersion to public API (#2767) (3225861) ## @permutive/javascript-sdk 20.19.2 (2024-11-05) ### Performance Improvements * **ql:** prevent unnecessary event replay (#2758) (5120b8f) ## @permutive/javascript-sdk 20.19.1 (2024-11-05) ### Bug Fixes * first page state partially lost in some scenarios (#2761) (ea47c69) # @permutive/javascript-sdk 20.19.0 (2024-10-30) ### Features * **identity manager:** add support for id5 (#2730) (acc5eac) # @permutive/javascript-sdk 20.18.0 (2024-10-30) ### Features * **clean\_room:** remove `pass_cohorts_to_prebid` flag (#2751) (8de4298) # @permutive/javascript-sdk 20.17.0 (2024-10-29) ### Build System * **sdks:** Enable streaming logs in grunt parallel build step (#2749) (ad8b4c7) ## @permutive/javascript-sdk 20.16.5 (2024-10-29) ### Build System * modify CTV build configuration (#2748) (00560fa) ## @permutive/javascript-sdk 20.16.4 (2024-10-25) ### Bug Fixes * **cloud lift:** ensure empty config adheres to type declaration (#2731) (e4592e8) ## @permutive/javascript-sdk 20.16.3 (2024-10-24) ### Build System * separate production and development steps (#2739) (6025044), closes #2715 #2716 #2736 ## @permutive/javascript-sdk 20.16.1 (2024-10-16) ### Bug Fixes * **secure signals:** removes suffixes from std audiences (#2734) (5afcabd) # 20.16.0 (2024-10-10) ### Features * **identity-manager:** implement basic id manager framework (#2721) (4429d6b) ## 20.15.2 (2024-10-10) ### Bug Fixes * replay degraded engagement queries (#2728) (b77ba69) ## 20.15.1 (2024-10-09) ### Bug Fixes * engagement queries degraded (#2724) (0bcc4f4) # 20.15.0 (2024-10-09) ### Features * **secure signals:** create addon (#2659 (7647c3f) ## 20.14.1 (2024-10-08) ### Bug Fixes * **cloud-lift:** add v1 version (#2723) (c611313) # 20.14.0 (2024-10-04) ### Built SDKs * **sdks:** filter org ctv sdks (#2722) (181a06d) # 20.13.0 (2024-10-02) ### Built SDKs * **sdks:** output specific ctv versions (tizen, lg webos) (#2714) (9c98989) # 20.12.0 (2024-10-01) ### Features * segmentation correctness metrics (#2717) (5a22353) ## 20.11.3 (2024-09-27) ### Performance Improvements * **QL:** use query.append where possible instead of state munger (#2709) (6de241b) ## 20.11.2 (2024-09-24) ### Performance Improvements * **QL:** compact historical QL state (#2706) (c0dc5ed) ## 20.11.1 (2024-09-24) ### Bug Fixes * do not report an error on bad query code (#2707) (34c7101) # 20.11.0 (2024-09-18) ### Features * **runtime:** use package implementation (#2682) (1d5d623) # 20.10.0 (2024-09-16) ### Code Refactoring * **metrics:** process on-worker metrics more eagerly (#2694) (830a599) ### Features * **cloud-lift:** include checksums in metadata (#2698) (dd430f0) ## 20.9.2 (2024-09-05) ### Bug Fixes * **tpd:** data not resolved when no identities are available (#2690) (5c56f75) ## 20.9.1 (2024-09-05) ### Bug Fixes * **ampscript:** local segmentation failing (#2692) (68219da) ### Code Refactoring * remove extension from import (b80c49b) # 20.9.0 (2024-08-21) ### Features * cloud lift (#2680) (967898d) ## 20.8.2 (2024-08-15) ### Reverts * revert cloud lift release temporarily (#2678) (258165d), closes #2676 #2665 ## 20.8.1 (2024-08-14) ### Performance Improvements * optimise 2pd/3pd processing (#2676) (c467c39) # 20.8.0 (2024-08-14) ### Features * cloud lift (#2665) (f314934), closes #2663 ## 20.7.2 (2024-08-07) ### Performance Improvements * **tpd:** re-enable optimisations (#2669) (bdd35b9) ## 20.7.1 (2024-08-06) ### Bug Fixes * **tpd:** tpd unreliably processed (#2668) (d11d7c2) # 20.7.0 (2024-07-31) ### Features * clean\_room v4 default (#2661) (a397665) ## 20.6.1 (2024-07-26) ### Bug Fixes * **google-rtd:** add gdpr to cookie matching pixel (#2655) (d6b742) # 20.6.0 (2024-07-26) ### Features * **clean-room:** expose advertiser programmatic cohorts (#2645) (7ed5200) ## 20.5.2 (2024-07-19) ### Bug Fixes * **facebook\_pixel:** add new custom event (#2649) (c568e7b) ## 20.5.1 (2024-07-17) ### Bug Fixes * **ctv:** allow event property client as input to addon (#2641) (0db4500) # 20.5.0 (2024-07-15) ### Features * **pubmatic:** set gdpr parameters from tcf (#2642) (16cb950) ## 20.4.6 (2024-07-10) ### Bug Fixes * use setIntervalSdk to ensure compute is measure (#2635) (d816f3e) ### Performance Improvements * load watson data earlier (#2634) (8ab2b5b) ## 20.4.5 (2024-07-09) ### Performance Improvements * **worker:** run most worker features immediately (#2631) (38b0288) ## 20.4.4 (2024-07-04) ### Performance Improvements * **worker:** run worker functions earlier (#2630) (1a2e608) ## 20.4.3 (2024-07-03) ### Code Refactoring * **clean room:** drops unused either wrapper (#2615) (8dffe19) ### Performance Improvements * **appnexus pixel:** optimise init with large no cohorts (#2629) (af92612) ## 20.4.2 (2024-07-01) ### Bug Fixes * **dfp:** not possible to drop out of cloud cohorts during pv (#2623) (1f4ddc9) ### Performance Improvements * **clm:** run segmentation immediately (#2624) (e95262b) ## 20.4.1 (2024-06-26) ### Performance Improvements * **state-sync:** defer crdt computation to not block worker context (#2617) (a457a5a) # 20.4.0 (2024-06-26) ### Features * add additional critical path metrics (#2620) (d78dbeb) ## 20.3.2 (2024-06-26) ### Bug Fixes * **metrics:** event upload success/fail metrics inverted (#2618) (41a337f) ## 20.3.1 (2024-06-26) ### Performance Improvements * **tpd:** eliminate some redundant tpd re-processing (#2613) (2555006) # 20.3.0 (2024-06-25) ### Features * **clean-room:** frequency control using segmentation v4 (#2607) (b04c756) ## 20.2.2 (2024-06-25) ### Performance Improvements * **metrics:** add timings to critical path (#2593) (52f91f6) ## 20.2.1 (2024-06-21) ### Bug Fixes * **event-upload:** `client` properties can be overridden (#2609) (fa505f8) # 20.2.0 (2024-06-20) ### Features * **clean-room:** set v4 segmentation to default (#2606) (cd9e96c) ## 20.1.1 (2024-06-18) ### Bug Fixes * **tcf:** port to using `addEventListener` from `getTCData` (#2598) (f350f9e) # 20.1.0 (2024-06-11) ### Code Refactoring * **ref:** replace `fold` with `scan` (#2587) (2fddebd) ### Features * **metrics:** synchronise time between main and worker contexts (#2591) (138c63a) ## 20.0.3 (2024-06-07) ### Performance Improvements * do not persist AdRequest events in events cache (#2588) (0e99e1c) ## 20.0.2 (2024-06-07) ### Performance Improvements * **lal:** persist lal separately from core state (#2586) (870b36c) ## 20.0.1 (2024-06-06) ### Performance Improvements * **timings:** record and surface timings and traces (#2567) (808c04a) ## 20.0.0 (2024-06-03) ### Performance Improvements * move majority of SDK compute to a background thread (#2296) (810f7fb), closes #2415 ## 19.120.1 (2024-05-29) ### Bug Fixes * ensure privacy sandbox topics compute is measured (9fdfa97) * **idb:** recreate object stores during upgrade transaction for safari (5036ea8) # 19.120.0 (2024-05-21) ### Features * **api metrics:** use native number and boolean metric labels (22ee3fd) ### Performance Improvements * **cohort metrics:** optimise implementation (#2549) (e2a18d3) # 19.119.0 (2024-05-20) ### Features * **metrics:** always track replay count forcing 0 when no events on device (#2546) (f9d99f4) # 19.118.0 (2024-05-20) ### Features * add cohort membership metrics (#2542) (8fc6e8e) * **queries:** add metric for occurrences of queries requiring replay (#2541) (4a7b8b9) # 19.117.0 (2024-05-16) ### Features * **reaction:** disable event upload for specific organisation (#2536) (3b8fabf) ## 19.116.1 (2024-05-15) ### Code Refactoring * simplify metrics reporting (917e81c) ### Performance Improvements * **metrics:** aggregate counter metrics client-side (8ee0260) * **metrics:** stop sending requests if the api is unhealthy (e2e4929) # 19.116.0 (2024-04-30) ### Features * **provider:** enable ip provider (#2496) (1396e30) # 19.115.0 (2024-04-29) ### Features * **ip identity provider:** Add IP Address identity provider (#2480) (81c3bca) ## 19.114.2 (2024-04-26) ### Bug Fixes * **privacy sandbox topics:** event upload failing prior to topic retrieval (#2493) (6134080) ## 19.114.1 (2024-04-24) ### Bug Fixes * **trade desk:** fix id sync for non-gdpr users (#2486) (3e50913) # 19.114.0 (2024-04-23) ### Features * **clean-room:** default to api v3 for advertiser segmentation (#2482) (a78a1a6) # 19.113.0 (2024-04-19) ### Features * **worker:** report errors and metrics on health of web worker (#2447) (c9be1a7) # 19.112.0 (2024-04-17) ### Features * **privacy-sandbox-topics:** rename topics event field to avoid collisions (#2462) (4e86426) ## 19.111.1 (2024-04-16) ### Bug Fixes * **privacy sandbox topics:** enrichment broken on second+ pv (d7086df) # 19.111.0 (2024-04-12) ### Features * **privacy-sandbox-topics:** add topics to be tracked in events (#2456) (df908a1) # 19.110.0 (2024-04-11) ### Features * **clean-room:** enable api v3 for advertiser segmentation (#2435) (daf8da8), closes #2440 # 19.109.0 (2024-04-05) ### Features * **programmatic:** suffixed segment support (#2442) (f33942a) # 19.108.0 (2024-04-04) ### Features * **programmatic:** don't wait for all addons to load (#2438) (ee56899) ## 19.107.1 (2024-04-03) ### Bug Fixes * **ssp:** affinity cohorts not being applied (#2433) (ada3460) # 19.107.0 (2024-03-28) ### Features * **programmatic:** custom cohort mapping + affinity cohorts via config flag (#2426) (d9d53e3) ## 19.106.1 (2024-03-26) ### Reverts * Reverts "feat(programmatic): add affinity cohorts and custom cohort mapping (fbf774f)" # 19.106.0 (2024-03-26) ### Features * **programmatic:** add affinity cohorts and custom cohort mapping (fbf774f) ## 19.105.3 (2024-03-20) ### Performance Improvements * **audience matching:** eliminate unnecessary usage reporting (db39e83) ## 19.105.2 (2024-03-20) ### Performance Improvements * **audience matching:** do not report usage for 'pug' provider (bc5ef95) ## 19.105.1 (2024-02-13) ### Bug Fixes * **globalThis:** remove usage for compatibility with older environments (#2396) (de5089e) # 19.105.0 (2024-02-12) ### Features * **contextual:** support tag v2 by expanding addon (#2357) (d28714a) # 19.104.0 (2024-01-23) ### Features * **tiktok:** Add TikTok pixel addon (#2358) (82b26f1) ## 19.103.4 (2024-01-16) ### Bug Fixes * **Reaction:** on disabled event upload, call event handler locally for processing (#2372) (7bf31f1) ## 19.103.3 (2024-01-16) ### Bug Fixes * **api:** enable CTV SDKs to use https by using host with non-expired certificate (#2265) (ea8953b) ## 19.103.2 (2024-01-12) ### Performance Improvements * **dfp:** improve `SlotClicked` tracking reliability (#2370) (d7946c5) ## 19.103.1 (2024-01-08) ### Bug Fixes * **dfp:** duplicate SlotClicked events (#2369) (7b7d676) # 19.103.0 (2024-01-05) ### Features * **analytics:** wait for cohorts before uploading events (#2356) ## 19.102.1 (2023-12-11) ### Bug Fixes * **contextual:** parse input without throwing error (fee4c6d) # 19.102.0 (2023-12-05) ### Features * **contextual:** create addon and apply cohorts to events (#2331) (12e295b) # 19.101.0 (2023-11-15) ### Features * **query-language:** support larger queries via update qlr to v10.0.2 (#2311) (16572d6) ## 19.100.3 (2023-11-10) ### Bug Fixes * **appnexus-adserver:** remove customer specific check (#2304) (e0a1955) ## 19.100.2 (2023-11-10) ### Performance Improvements * **configuration:** dedupe inlined config (#2297) (83aada6) ## 19.100.1 (2023-11-10) ### Bug Fixes * **identities:** let insecure aliases (i.e `email_sha256`) trigger update by filtering writes only (#2291) (c0f6f59) # 19.100.0 (2023-11-09) ### Features * **prebid-identifiers:** track eid source for analytic purposes # 19.99.0 (2023-11-08) ### Features * **query-language:** interpret front compression via update qlr to v9.0.0 (#2282) (f81d368) ## 19.98.2 (2023-11-06) ### Bug Fixes * do not fetch advertiser cohorts until SDK is realtime (#2287) (06102c6) ## 19.98.1 (2023-10-27) ### Bug Fixes * **event-persistence:** prevent `PrebidAuctions` analytic event from being persisted (#2273) (8204b24) # 19.98.0 (2023-10-16) ### Features * **ql:** Bump ql-runtime to v8.0.2 - adding binary string equality (#2242) (54a9c76) # Chrome Extension Source: https://docs.permutive.com/sdks/web/javascript-sdk/troubleshooting/chrome-extension Validate your Permutive deployment by inspecting data collection, schema errors, cohort membership, and ad request targeting ## Overview The Permutive Chrome Extension allows publishers to validate their Permutive deployment directly in the browser. It provides real-time visibility into what data is being collected, whether there are any schema errors, which cohorts a user belongs to, and whether ad requests are being targeted correctly with Permutive cohorts. The extension is a diagnostic and debugging tool designed for publishers and their technical teams to verify that their Permutive SDK integration is functioning correctly across event tracking, cohort evaluation, ad targeting, and Prebid configuration. ## Why Use the Chrome Extension? **Validate your deployment in real time** — Instantly verify that Permutive is collecting data correctly on any page with the **web JavaScript SDK** deployed. See every event request, its payload, and whether it was accepted or rejected. **Debug schema and tracking errors** — When events are rejected, the extension shows exactly why, including detailed error messages for schema rejections. **Verify ad targeting** — Confirm that Google Ad Manager ad units are being targeted with Permutive cohorts. See at a glance whether each ad request used real-time cohorts, cached cohorts from a previous page, or was not targeted at all. **Inspect Prebid configuration** — Check that Prebid.js is correctly configured for header bidding with Permutive cohorts, including module installation status, bidder configuration, and which cohort types are being passed in auctions. ## Concepts ### Definitions * **Organization ID**: Your unique organization identifier. You can find this ID by clicking your name in the top right corner and click Workspace Settings from the drop-down menu. Required to configure the extension. * **Event Request**: A data payload sent from the Permutive SDK to Permutive's servers when a user action is tracked (e.g., Pageview, VideoView, or custom events). * **Schema Rejection**: An event that was rejected because its properties did not match the expected schema definition. The extension displays detailed error messages explaining the mismatch. See [Creating & Updating Event Schema](/guides/connectivity/events/create-update-event-schema) for how to define and modify schemas. * **Real-Time Targeting**: Ad units targeted with cohorts computed by the SDK on the current page before the ad request was sent. Indicated by a star icon in the Ads view. * **Cached Targeting**: Ad units targeted with cohort data from local storage that was computed on a previous page, sent before the SDK was initialized on the current page. Indicated by a check icon in the Ads view. * **Identity Request**: An event that passes user aliases (identifiers) to Permutive for identity resolution. ## Workflows ### Installation and Setup After installing the Permutive Chrome Extension from the Chrome Web Store, you configure it with your Organization ID. This links the extension to your Permutive workspace and enables the diagnostic features. Chrome Extension config module. ### Opening the Extension When the web JavaScript SDK that is associated with your organization is detected, the Permutive extension icon should automatically turn black and the extension should pop open on the right. You can minimize it by clicking on the black Permutive icon. Chrome Extension detecting the web JavaScript SDK and opening. If the web JavaScript SDK is not detected, the Permutive icon should be grey. Chrome Extension not detecting the web JavaScript SDK and staying closed. ### Inspecting Event & Identity Requests The Requests view shows all Permutive event and identity requests on the current and previous pageview. Each event request displays a status indicator: a check for successful requests or an "x" for rejected requests. Clicking on an event name reveals its full payload and properties. For rejected events, the extension shows the specific reason for rejection, such as an unknown event type or a schema validation error. The example below shows an event being rejected because it was not defined in the schema. Chrome Extension showing an accepted and rejected event. The example below shows an event being rejected because an extra property was added that was not defined in the schema. Chrome Extension showing a rejected event because an extra property was added to the payload. For identity requests, you can click the request to view further details: the identities collected, priority of each, and the actual identity value. You may notice that not every page-load will have an identity request. For SDK performance reasons, identity requests only fire when a brand new identifier is collected (e.g. an identifier type that hasn't been attributed to a user before or the value of an identifier has been updated). Chrome Extension showing an identity request. Events are generally rejected for one of two reasons: the event type is unknown, or the event properties fail schema validation. For schema rejections, the extension shows detailed error messages when you inspect the event properties. To fix schema issues, see [Creating & Updating Event Schema](/guides/connectivity/events/create-update-event-schema). ### Viewing Cohort Membership The Cohorts view displays all cohorts that the current user falls into. Cohort codes are shown, which can be looked up in the Permutive Dashboard to find the corresponding cohort names. Chrome Extension showing searching for a cohort code within the cohorts tab. ### Verifying Ad Targeting The Ads view provides an overview of all Google Ad Manager ad requests on the current page and whether they were targeted with Permutive cohorts. Each ad request shows a status indicator: * **Star** — Targeted with real-time cohorts computed on the current page before the ad request was sent. * **Check** — Targeted with cached cohorts from local storage, sent before the SDK was initialized on the current page. * **Circled exclamation point** — Not targeted with Permutive cohorts. Clicking on an individual ad request reveals more details, including targeting keys. The "i" info icon next to the cohorts parameter shows where Permutive cohorts appear in the ad request. When cohorts listed under the `cust_params.permutive` property have the `rts` flag appended, it means the ad was targeted with real-time cohorts. The example below shows an ad having a star as the status indicator. This is because the `rts` flag was included in the cohort list, marking them as real-time cohorts. Chrome Extension showing an event being targeted with Permutive cohorts in real-time. ### Checking Prebid Configuration The Prebid view allows you to verify your Prebid.js header bidding integration with Permutive. It shows: * Whether the default required Prebid modules are installed correctly * Defined bidders and their configuration * Which cohort types (custom and standard) are being passed in auctions Status indicators help diagnose configuration issues: * All red "x" marks under "Cohorts Passed In Auctions" indicate a possible incorrect installation. * Red exclamation marks under "Prebid Configuration Checks" combined with green checks under "Cohorts Passed in Auctions" indicate a custom configuration. ### Reviewing Consent Framework The extension also shows consent framework behavior on the current page, indicating whether a user has given consent and whether consent is required. How consent is setup and managed is up to your legal and developer teams. The example below shows a deployment where consent is not required and the SDK does not wait on consent to be given. Chrome Extension showing a deployment where consent is not required and the SDK does not wait on consent to be given. The example below shows a deployment where consent is required and the SDK does wait for consent to be given (and consent has been given). Chrome Extension showing a deployment where consent is required and the SDK waits on consent to be given (and it is given). ## Troubleshooting The extension icon turns black when it detects Permutive on the current page. If the icon remains grey: **Solution:** * Verify that the Permutive SDK is deployed on the page by checking the browser developer console for Permutive-related scripts * Ensure you have entered the correct Organization ID in the extension options (right-click the extension icon > Manage Extension) * Try refreshing the page after configuring the extension # Common Errors Source: https://docs.permutive.com/sdks/web/javascript-sdk/troubleshooting/common-errors Troubleshoot common issues with the Permutive JavaScript SDK This guide covers common issues and their solutions when integrating the Permutive JavaScript SDK. The [Permutive Chrome Extension](/sdks/web/javascript-sdk/troubleshooting/chrome-extension) provides a visual way to inspect event requests, schema errors, cohort membership, and ad targeting without using the browser developer console. ## Installation Issues **Symptoms:** * `window.permutive` is undefined * No console messages with debug mode * Network tab shows 404 for live.js **Solutions:** 1. **Verify script URL** ```html theme={"dark"} ``` 2. **Check for Content Security Policy (CSP) issues** ``` Content-Security-Policy: script-src 'self' https://*.permutive.app; connect-src 'self' https://*.permutive.com https://*.permutive.app; ``` 3. **Ensure loader script runs first** The inline loader script must execute before the SDK script loads. 4. **Check for JavaScript errors** Earlier errors may prevent subsequent scripts from running. **Symptoms:** * Syntax errors in console * `permutive is not defined` errors **Solutions:** 1. **Verify loader script is complete** Ensure the entire loader script is copied correctly: ```javascript theme={"dark"} !function(e,o,n,i){if(!e){e=e||{},window.permutive=e,e.q=[];var t=function(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,function(e){return(e^(window.crypto||window.msCrypto).getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)})};e.config=i||{},e.config.apiKey=o,e.config.workspaceId=n,e.config.environment=e.config.environment||"production",(window.crypto||window.msCrypto)&&(e.config.viewId=t());for(var g=["addon","identify","track","trigger","query","segment","segments","ready","on","once","user","consent"],r=0;r","",{}); ``` 2. **Check for quote issues** Template engines may escape quotes incorrectly. 3. **Verify API key and workspace ID** Ensure credentials don't contain special characters that need escaping. **Symptoms:** * Console error: "Refused to load the script..." * Console error: "Blocked by Content Security Policy" **Solution:** Add Permutive domains to your Content Security Policy: ``` Content-Security-Policy: script-src 'self' https://*.permutive.app; connect-src 'self' https://*.permutive.com https://*.permutive.app; img-src 'self' https://*.permutive.com; ``` **Symptoms:** * SDK loads on some browsers but not others * Network requests blocked **Solutions:** 1. Most ad blockers do not block Permutive as it's a first-party tool 2. If blocked, users will still see the site but without personalization 3. Test with ad blockers disabled to confirm this is the cause ## Tracking Issues **Symptoms:** * `track()` called successfully * Debug mode shows "Event accepted" * Nothing in Permutive dashboard **Solutions:** 1. **Wait for processing** Events may take a few minutes to appear in the dashboard. 2. **Check date/time filters** Ensure dashboard filters include the current time period. 3. **Verify workspace** Confirm you're viewing the correct workspace in the dashboard. 4. **Check consent status** If `consentRequired: true`, ensure consent was granted: ```javascript theme={"dark"} permutive.consent({ opt_in: true, token: 'abc' }); ``` **Symptoms:** * No Pageview events * Other events work but not pageviews **Solutions:** 1. **Verify web addon is called** ```javascript theme={"dark"} permutive.addon('web', { page: { type: 'article' } }); ``` 2. **Check consent timing** If using consent, call `consent()` before `addon('web')`: ```javascript theme={"dark"} permutive.consent({ opt_in: true, token: 'abc' }); permutive.addon('web', { page: { type: 'article' } }); ``` 3. **Check for duplicate calls** Ensure `addon('web')` isn't called multiple times. **Symptoms:** * `consentRequired: true` configured * Events called but not tracked * No errors in console **Cause:** Events called before `consent({ opt_in: true })` are discarded (not queued). **Solution:** Always call `consent()` before tracking: ```javascript theme={"dark"} // 1. Grant consent first permutive.consent({ opt_in: true, token: 'abc' }); // 2. Then initialize tracking permutive.addon('web', { page: { type: 'article' } }); ``` **Symptoms:** * First pageview tracks * Subsequent route changes don't track **Solution:** Call `permutive.addon('web', {...})` on each route change: ```javascript theme={"dark"} // On route change router.onRouteChange(function() { permutive.addon('web', { page: getPageProps() }); }); ``` **Symptoms:** * Different user ID on each visit * User history not persisting **Causes:** * Cookies being blocked or cleared * Private browsing mode * Cookie domain mismatch **Solutions:** 1. **Check cookie exists** Look for `permutive-id` in Application > Cookies 2. **Verify cookie domain** Ensure domain matches your configuration: ```javascript theme={"dark"} { cookieDomain: '.example.com' } ``` 3. **Check browser settings** Some browsers aggressively clear first-party cookies. ## Targeting Issues **Symptoms:** * `permutive.segments()` returns `[]` * localStorage keys empty **Solutions:** 1. **Wait for realtime data** ```javascript theme={"dark"} permutive.ready(function() { permutive.segments(function(segs) { console.log(segs); // Now populated }); }, 'realtime'); ``` 2. **Verify cohorts exist** Check that cohorts are configured in dashboard. 3. **Confirm user qualifies** User must meet cohort criteria to be included. 4. **Check consent** Cohorts require consent to be tracked and calculated. 5. **Allow processing time** New cohort membership may take a few seconds. **Symptoms:** * Segments exist in localStorage * Targeting set on GPT * Line items not matching **Solutions:** 1. **Verify key name** Ensure GAM key is exactly `permutive` (case-sensitive). 2. **Check value format** Values should be strings, not numbers. 3. **Verify targeting in Google Publisher Console** Add `?googfc` to URL and check targeting values. 4. **Confirm line item targeting** Double-check line item uses correct key-value targeting. **Symptoms:** * RTD module configured * Bid requests missing Permutive data **Solutions:** 1. **Verify RTD module included** Check Prebid build includes `permutiveRtdProvider`. 2. **Check configuration** ```javascript theme={"dark"} pbjs.setConfig({ realTimeData: { auctionDelay: 200, dataProviders: [{ name: 'permutive', waitForIt: true // Critical! }] } }); ``` 3. **Add TCF exception if needed** ```javascript theme={"dark"} vendorExceptions: ['permutive'] ``` 4. **Check localStorage** Verify segment data exists in `_pdfps`, `_papns`, etc. **Symptoms:** * User should qualify for new cohorts * Segments remain unchanged **Solutions:** 1. **Verify events are tracking** Check dashboard for incoming events. 2. **Check cohort rules** Ensure user behavior matches cohort criteria. 3. **Allow processing time** Cohort updates may take a few seconds to minutes. 4. **Refresh page** Page reload triggers re-evaluation. 5. **Use triggers for real-time** ```javascript theme={"dark"} permutive.trigger(12345, 'segment', function(inCohort) { console.log('Cohort status:', inCohort); }); ``` ## Console Errors **Cause:** SDK not loaded when method called. **Solutions:** 1. Ensure loader script runs before any `permutive.*` calls 2. Check for script loading errors 3. Verify script placement in `` **Causes:** * Network connectivity issues * API endpoint unreachable * Request blocked **Solutions:** 1. Check network connectivity 2. Verify no proxy/firewall blocking 3. Check CSP configuration 4. Enable debug mode for details **Cause:** Missing or incorrect API key/workspace ID. **Solutions:** 1. Verify API key from dashboard 2. Verify workspace ID from dashboard 3. Check for extra whitespace or newlines 4. Ensure credentials match environment (production vs staging) ## Debug Mode Enable debug mode to diagnose issues: ``` https://yoursite.com/page?__permutive.loggingEnabled=true ``` Debug output includes: * SDK initialization status * Event tracking confirmations * Segment calculations * API request/response details * Error messages ## Getting More Help If you can't resolve the issue: 1. **Gather diagnostic information:** * Debug mode console output * Network tab screenshots * localStorage contents * Browser and version 2. **Contact support:** * Customer Success Manager * [Technical Services](mailto:technical-services@permutive.com) ## Related Documentation Visual deployment validation tool Installation options Verify integration Consent handling Understanding cohorts