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

# Roku

> Integrate Permutive on Roku using the Permutive Roku Client

<CardGroup cols={3}>
  <Card title="Installation" href="#installation" icon="download" />

  <Card title="Event Tracking" href="#event-tracking" icon="bolt" />

  <Card title="GAM Targeting" href="#google-ad-manager-targeting" icon="bullseye" />
</CardGroup>

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

<Info>
  **BrightScript/SceneGraph:** The Roku Client is implemented as a SceneGraph Task component (`PermutiveTask.xml`) that runs asynchronously alongside your channel.
</Info>

## 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) |

<Info>
  Configure identity tags in your [Dashboard identifiers settings](https://dash.permutive.com/settings/identifiers) before using them.
</Info>

***

## 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()
```

<Warning>
  **Always set a new view\_id** when the user starts watching new content. This links all events for that viewing session.
</Warning>

### Tracking Events

Track events with a name and properties:

```brightscript theme={"dark"}
m.global.permutive.event = {
    name: "Videoview"
    properties: {
        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: {
        title: m.video.title
        genre: m.video.genre
        runtime: m.video.duration
    }
}

' When video completes
m.global.permutive.event = {
    name: "VideoCompletion"
    properties: {
        title: m.video.title
        completion: 0.95  ' 95% watched
        engaged_time: 3420  ' seconds
    }
}

' When ad starts
m.global.permutive.event = {
    name: "VideoAdView"
    properties: {
        ad_id: "creative_123"
        ad_position: "midroll"
    }
}

' When ad completes
m.global.permutive.event = {
    name: "VideoAdCompletion"
    properties: {
        ad_id: "creative_123"
    }
}
```

***

## 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: {
        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: {
        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: <list of schema errors>
```

<Warning>
  **Disable in production:** Debug mode adds latency due to schema validation. Only use during development.
</Warning>

***

## 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: {
            title: m.currentVideo.title
            genre: m.currentVideo.genre
            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: {
            title: m.currentVideo.title
            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_id: ad.id
            ad_position: ad.position
            ad_duration: ad.duration
        }
    }
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

<AccordionGroup>
  <Accordion title="Events not being tracked">
    **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
  </Accordion>

  <Accordion title="Schema validation errors">
    **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
  </Accordion>

  <Accordion title="GAM targeting not working">
    **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
  </Accordion>

  <Accordion title="Cohorts empty">
    **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
  </Accordion>
</AccordionGroup>

***

## 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

<CardGroup cols={2}>
  <Card title="CTV Overview" icon="tv" href="/sdks/ctv/overview">
    Platform selection guide
  </Card>

  <Card title="Video Tracking" icon="video" href="/sdks/ctv/video-tracking">
    Video event best practices
  </Card>

  <Card title="Google Ad Manager" icon="google" href="/integrations/advertising/ad-servers/google-ad-manager">
    GAM integration guide
  </Card>

  <Card title="Identity Management" icon="user" href="/products/signals/identity/identity-manager">
    Cross-platform identity
  </Card>
</CardGroup>
