Skip to main content

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:
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:
m.global.addFields({ permutive: permutive })

Configuration

FieldTypeRequiredDescription
apiKeyStringYesYour Permutive workspace API key
workspaceIdStringYesYour Permutive workspace ID
debugModeBooleanNoEnable schema validation logging
controlStringYesSet to "run" to start the client
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:
m.global.permutive.identity = "some_default_identity"

Tagged Identities

Set identities with tags and optional priority:
' 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

FieldTypeRequiredDescription
tagStringYesIdentifier type (must be configured in Dashboard)
idStringYesThe identifier value
priorityIntegerNoPriority for identity resolution (lower = higher priority)
Configure identity tags in your Dashboard identifiers settings 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:
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:
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:
' 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:
m.global.permutive.event = {
    name: "Videoview"
    properties: {
        title: "Video Title"
        geo_info: "$ip_geo_info"
    }
}

ISP Information

Add ISP data based on IP:
m.global.permutive.event = {
    name: "Videoview"
    properties: {
        title: "Video Title"
        isp_info: "$ip_isp_info"
    }
}
ConstantDescription
$ip_geo_infoGeographic information derived from IP address
$ip_isp_infoInternet Service Provider information derived from IP

Debug Mode

Enable debug mode during development to validate event schemas:
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>
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:
cohorts = m.global.permutive.cohorts
' cohorts is an array of strings: ["cohort_1", "cohort_2", ...]

Get User ID

Retrieve the Permutive user ID:
userId = m.global.permutive.userId
' userId is a string

Attach Permutive targeting data to GAM ad requests:

Get Key-Values

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

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 for more on custom parameters.

Reset User State

Clear all Permutive data from disk and memory:
m.global.permutive.reset = true
Use this when:
  • User logs out
  • User requests data deletion
  • Testing fresh state

Complete Example

' 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

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

FieldTypeAccessDescription
apiKeyStringWriteWorkspace API key
workspaceIdStringWriteWorkspace ID
debugModeBooleanWriteEnable schema validation
controlStringWriteSet to “run” to start
identityString/ObjectWriteSet user identity
view_idStringWriteCurrent view session ID
eventObjectWriteTrack an event
resetBooleanWriteClear all state
cohortsString[]ReadUser’s cohort IDs
userIdStringReadPermutive user ID
gamKeyValuesObjectReadGAM targeting key-values