NOTE : This section explains how custom fields are handled in Pixel Apps. The logic may change when we encrypt the custom fields that cxscript would send to an external API endpoint.When the user’s browser receives the CXScript Javascript, embedded within is a public key (randomly assigned by the script server, but for the OptumID API endpoint will additionally have whatever public key is provided). This public key is then used to encrypt the data field of the custom data fields JSON, which is in the following format:
{
"encryptedData": {
"encryptionType": "ecc",
"type": "customData",
"clientPublicKey": "...",
"iv": "...",
"cipherText": "_",
"serverPublicKey": "..."
}
}Example :
{
"encryptedData": {
"encryptionType": "ecc",
"type": "customData",
"clientPublicKey": "abcdefghijklmnopqrstuvwxyz",
"iv": "nuUp3pA26DzIttUU",
"cipherText": "y25GnJPEVlwhxcllB8kq5m02o5EGA+vlfGP/B7DU3F6T0AjSUO9bLTZxlEU87GCPcOlo8BZylpLUwYlaEZQtb2LbwKJiwhE/4tUxhmG7m77M7f5AFKLE0uoU5g==",
"serverPublicKey": "abcdefghijklmnopqrstuvwxyz"
}
}The privateKeyName field specifies a md5 hash of the private key corresponding to the public key used, which is then used by the CSE transformer to identify the appropriate key for decryption. The encryptionType field specifies the type of encryption used. The default is currently ECC. The type field specifies the type of encrypted data being sent. For the purposes of custom data fields, this value will always be "customData".
In the CSE transformer, the following is the process to decode the custom data fields:
The CX Script is asynchronous. You cannot use window.Rakanto.event('sendCustomData') right after the call to bootstrap the script, the script hasn't been downloaded, and the method hasn't been loaded yet. You CAN however send events using the following method.
NOTE THE Rakanto() method at the bottom.
```html
<html>
<head>
<title>CX Script Page</title>
<script type='text/javascript'>
sessionStorage.setItem('darids','UHG.Optum.Pixel.CXScriptExample.CustomData');
(function (cx, darids, demarcs) {
window['RakantoObject'] = 'Rakanto';
window['Rakanto'] = window['Rakanto'] || function () {
(window['Rakanto'].q = window['Rakanto'].q || []).push([Date.now()].concat(Array.prototype.slice.call(arguments)))
}, window['Rakanto'].l = Date.now(),window['Rakanto'].demarcs = demarcs;
a = document.createElement('script'),
a.src = cx,
a.id = 'rakanto',
a.async = true,
(darids)? a.setAttribute('data-px-darids', darids):null,
m = document.getElementsByTagName('script')[0],
m.parentNode.insertBefore(a, m);
})('https://repo-stg.rakanto.com/rakanto/cx/cx.js');
Rakanto('sendCustomData', {}, {namespace:'UHG.Optum.Pixel.CXScriptExample.CustomData', foo: 'bar'});
</script>
</head>
<body>
<h1>Pixel Customer Experience Script</h1>
</p>(This example will also send custom data upon initialization)</p>
</body>
</html>
```You are allowed to send any supported javascript datatype as the value in a custom field.
window.Rakanto.event("sendCustomData", { namespace: "UHG.Optum.ETIPS", myCar: {make: "saab",newHotness: "yep", convertible: true } )The custom fields will be presented in kibana in their entireity, in this case an object to, CF_UHG.Optum.ETIPS.myCar: {make: "saab",newHotness: "yep", convertible: true } and sub values to, CF_UHG.Optum.ETIPS.myCar.make = 'saab', CF_UHG.Optum.ETIPS.myCar.newHotness = 'yep', CF_UHG.Optum.ETIPS.myCar.convertible = true.
Recent versions of Safari, Firefox and other browsers have implemented privacy enhancing features. These changes will prevent tracking users across sites or storing JavaScript set cookie data for more than a day on Safari. The CX script will continue to work as before when browsing on Chrome, however Google have plans to implement these features in the near future.
The most significant barrier in tracking users across sites is cookie partitioning. Cookies are small bits of data which are set by the first or third party server or in the page JavaScript. We store a unique ID to track a user's journey in cookies. Cookie partitioning severely restricts 3rd party cookie functionality. In the past a third party cookie set on site "optum.com" could be read on site "uhg.com", this is no longer the case in Safari. Cookie partitioning restricts cookies set on a site to be specific to that domain alone. The files are cached in the namespace for the specific domain you are visiting. Cookies are unique to the current domain. In addition, cookies expiration has been changed. 3rd party server/JavaScript set cookies now may only exist for a maximum of 1 day, First party Javascript set cookies 1 week, and unlimited days for server set cookies. This change will pose a challenge for tracking a user who browses the site, every few days, only eventually logging in.
Partitioning is meant to block 3rd party scripts from setting/reading cookies across domains. Pixel has been updated to set its required tracking Id in the first party domain. For browsers affected by partitioning, Pixel tracking cookies will have an expiration of 1 week from the most recent visit.
If you would like a longer cookie duration, create a CNAME subdomain for your domain. The new subdomain you create should be called pixel-repo.YOURSITE.COM => it should be an alias for, or a proxy to repo.rakanto.com. Then in the invocation code, set the flag: "enable_extended_tracking_cookie=true". The script will automaticly attempt to get a longer duration cookie set at pixel-repo.YOURSITE.COM.
If you see errors in the network tab, with addresses such as: /cookie?ubrid=v2.0-dd5085874041091045b30964c8b28043-1363-1369-1670812376128-0000803080-1673452061936 The cname alias isn't in place, yet the script is trying it. In this case remove the enable_extended_tracking_cookie flag, or set it to false.
for example:
<head>
<script type='text/javascript'>
window.localStorage.setItem("DARID","UHG.Optum.Rx.MemberPortal.Stage");
(function (cx, darids, demarcs) {
window['Rakanto'] = window['Rakanto'] || function () {
(window['Rakanto'].q = window['Rakanto'].q || []).push([Date.now()].concat(Array.prototype.slice.call(arguments)))
},
window['Rakanto'].l = Date.now() ,window['Rakanto'].demarcs = demarcs,
window['Rakanto'].enable_extended_tracking_cookie = true; // have the CX script use the CNAME subdomain to extend the life of the cookie.
a = document.createElement('script'),
a.src = cx,
a.id = 'rakanto',
a.async = true,
(darids)? a.setAttribute('data-px-darids', darids):null,
m = document.getElementsByTagName('script')[0],
m.parentNode.insertBefore(a, m);
})('https://repo-stg.rakanto.com/rakanto/cx/cx.js', 'UHG.Optum.Rx.MemberPortal.Stage',['/member/signin?v=3','https://st1.healthsafe-id.com/rt/username/orx/en']);
</script>
</head>Make a C record in the DNS entry.
pixel-repo.YOURDOMAIN is an alias for repo.rakanto.comThe cx script will ping an endpoint at pixel-repo.YOURDOMAIN in an attempt to set the cookie longer than a week. If the CNAME isn't present the ubrid will expire after one week. If the CNAME isn't present, the cx script will STILL attempt to call pixel-repo.YOURDOMAIN. In this case, you will see errors regarding the non-resolving link.
UHG has many domains, these domains often link to one another. The organization wants a user's journey to be tracked from as they navigate between sites. The only way to send identifying information from one site to another in Safari is via a querystring. To enable this, Pixel can automatically decorate the outbound queyrstring with the tracking Id.
To use this feature, provide the pixel script an initialization list of "demarcs" or demarcation URLs. When the browsers "load" event is fired, links specified in the "demarcs" list are appended with tracking queryparams
Ex. "https://another-domain.com/deeplink?ref_rakantoid=v2.0-4ec78459be48f63a57f7b1a54db37ef4-4800-4803-1661802403458-0000000252-1661803310002" .If the target site is instrumented with the cx script, the ref_rakantoid from the first site will be used as the UBRID from that point forward. A "referrerUbrid" event will be sent indicating the OriginalUbrid, and the New Ubrid for that user.
Please consider using "demarcs" when calling HSID for login. Doing so enables user information, such as username, to be added to future "logged in" client side events. You will then be able to identify users by username on their journey through your site.
Example with 2 demarcs, a relative one where your site handles a 302 redirect, '/member/signin?v=3', and a direct one 'https://st1.healthsafe-id.com/rt/username/orx/en' if either site contain query parameters, the UBRID will be appended to those. The demarcs must be a left anchored match of the domain (if provided) and the pathname.
The following example will work on a optumrx page, appending the the 'signin' links, and 'forgot username' links with the querystring.
https://chp-stage.optumrx.com/public/landing
<html>
<head>
<script type='text/javascript'>
window.localStorage.setItem("DARID","UHG.Optum.Rx.MemberPortal.Stage");
(function (cx, darids, demarcs) {
window['RakantoObject'] = 'Rakanto';
window['Rakanto'] = window['Rakanto'] || function () {
(window['Rakanto'].q = window['Rakanto'].q || []).push([Date.now()].concat(Array.prototype.slice.call(arguments)))
}, window['Rakanto'].l = Date.now(), window['Rakanto'].demarcs = demarcs;
a = document.createElement('script'),
a.src = cx,
a.id = 'rakanto',
a.async = true,
(darids)? a.setAttribute('data-px-darids', darids):null,
m = document.getElementsByTagName('script')[0],
m.parentNode.insertBefore(a, m);
})('https://repo.rakanto.com/rakanto/cx/cx.js', 'UHG.Optum.Rx.MemberPortal.Stage',['/member/signin?v=3','https://st1.healthsafe-id.com/rt/username/orx/en']);
</script>
</head>
<body>
<h1>Pixel Customer Experience Script</h1>
<h2>Setting demarc endpoints</h2>
</body>
</html>The endpoint must respond to POST and GET requests. The POST returns a '{}', the GET returns a PNG image (a single pixel).
For example here is our Openresty (nginx configuration)
location /cx_collector/ {
set $json_log '';
# Add headers to the response
add_header Access-Control-Allow-Origin *;
# nginx_node_id: unique identifier of the nginx node - meaningless value. It is set in the host_vars/{host} file
# Is to make it easy to see which NGINX node the request went through to help tracing and troubleshooting
add_header X-ps-id {{nginx_node_id}};
client_max_body_size {{max_request_body_size}};
client_body_buffer_size {{max_request_buffer_size}};
# This handles the request counter increment
rewrite_by_lua_file request_helper.lua;
# https://agentzh.blogspot.com/2011/03/how-nginx-location-if-works.html
if ($request_method = POST ) {
echo_read_request_body;
echo "{}";
}
if ($request_method = GET ) {
rewrite ^ /pixel.png break;
}
# Create the CSEvent
log_by_lua_file log_helper.lua;
}To send data to an additional API endpoint ( yours ) simply add the code mentioned in the above scriptlet WITH A TRAILING SLASH, // Add this if you want to send data to your own endpoint as well. window['Rakanto'].Endpoints = [{ api_endpoint: 'https://YOUR-ENDPOINT/', public_key: 'YOUR-KEY-TEXT', encryption_type: 'ecc' }];
Scriplet is a mechanism used by Authentication Services like HSID and OptumID. Scriplet takes the UBRID and other information and makes it available in a cookie, and allows sending data to multiple endpoints.
In the event the RakantoClientSideData cookie value cannot be accessed in the backend code, please add the following logic:
document.cookie = 'RakantoClientSideData=' + btoa(JSON.stringify(value)) + '; expires=' + now.toUTCString() +';path=/';btoa function is added to encode the JSON string to Base64 string that would be decoded again to the original string in (for example) Java code.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Scriptlet Sample Code</title>
<script type='text/javascript'>
window.optumPageDataLayer = {
darids: ['UHG.Optum.Pixel', 'UHG.Optum.Pixel.CXScriptTest.{{app_env}}', 'UHG.Optum.Pixel.CXScriptTest.{{app_env}}.DataLayer','UHG.Optum.Pixel.CXScriptTest.{{app_env}}.CustomData','UHG.Optum.Pixel.CXScriptTest.{{app_env}}.Async']
};
(function (cx, darids, a, m) {
window['RakantoObject'] = 'Rakanto';
window['Rakanto'] = window['Rakanto'] || function () {
(window['Rakanto'].q = window['Rakanto'].q || []).push([Date.now()].concat(Array.prototype.slice.call(arguments)))
},
window['Rakanto'].l = Date.now();
//Scriptlet Begin
Object.defineProperty(window['Rakanto'],'ClientSideData',{
_ClientSideData: {},
get() { return this._ClientSideData; },
set(value) {
this._ClientSideData = value;
var now = new Date();
var minutes = 30;
var expireTime = now.getTime() + minutes * 60 * 1000;
now.setTime(expireTime);
document.cookie = 'RakantoClientSideData=' + JSON.stringify(value) + '; expires=' + now.toUTCString() +';path=/';
}
});
// Add this if you want to send data to your own endpoint as well.
window['Rakanto'].Endpoints = [{
api_endpoint: 'https://YOUR-ENDPOINT/',
public_key: 'YOUR-KEY-TEXT',
encryption_type: 'ecc'
}];];
//Scriptlet End
a = document.createElement('script'), m = document.getElementsByTagName('script')[0];
a.id = 'rakanto';
a.src = cx;
if (darids){a.setAttribute('data-px-darids', darids)};
a.async = 1;
m.parentNode.insertBefore(a, m)
})('https://repo-stg.rakanto.com/rakanto/cx/cx.js');
Rakanto('sendCustomData', {}, {namespace:'UHG.Optum.Pixel.CXScriptTest.stage.CustomData',foo: 'bar'});
</script>
</head>
<body>
<p>CX Script Scriplet Example!</p>
</body>
</html>Here is an example below of a JSON message that the browser will send while running the CXScript.
{
"browserEvent": "sendCustomData",
"browserEventTimestamp": 1755535058943,
"pixelSessionId": "op-e8ae8461-e423-4b33-81e9-a5d97a9e11c5",
"ubrid": "v2.0-3b5b2a48f86c6480d1253740469349b0-1166-1470139-1747630801963-0000047051-1747678518377",
"cookiesEnabled": "Y",
"networkLatencyMillis": 66,
"timeOnPageTotalMillis": 8771,
"timeOnSessionMillis": 8771,
"cxScriptMetadata": {
"cxScriptGitHash": "602314b",
"cxScriptDownloadTime": 332,
"cxScriptExecutionTimeMillis": 10,
"cxScriptVersion": "4.1.0"
},
"mid": "",
"screenRes": "982x1728",
"colorDepth": 24,
"referralURL": "",
"browserWidth": 1511,
"browserHeight": 529,
"event_enqueued_count": 5,
"event_sent_count": 5,
"darids": [
"UHG.Optum.Pixel",
"UHG.Optum.Pixel.CXScriptTest.dev",
"UHG.Optum.Pixel.CXScriptTest.dev.DataLayer",
"UHG.Optum.Pixel.CXScriptTest.dev.CustomData",
"UHG.Optum.Pixel.CXScriptTest.dev.Async",
"UHG.Optum.Pixel.SelfService"
],
"encryptedData": {
"encryptionType": "ecc",
"type": "customData",
"clientPublicKey": "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEIocPulYmS1qBlGszphHzt2pXTEpZp1+8qODmZCdb1bem3pKUq5thQkAM+p2YPfM2eXGWKOQI4jLXm4oFdCWOcH0EKCZ6ry0DfnmfNw+jjrF4EStdHPEQ9/QSPxDqjEG6",
"iv": "N61tCvqx/HrEWO80",
"cipherText": "ZzbkSk/mvhinFMPI2f6eDwYRSwJpD3P8WW8z0utlxXFhusvw8MMvMVXmywzPU5SdeegZqV2altnO5nmyIcPDmNeXyHm0wJjyUi2Ujhx2ky3tLPqbU9hKpIc9fA==",
"serverPublicKey": "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAELBaIFhbuCjbIMa2Qu8q80o0px6oJZKQ9IBhmhBuVFMx/uUv0qJtspuTx6moNRNXUrL+ZVO/TUDFFaf/KZh7hu5ALPQ10WENbW+4oIot+CWcT1tts4kc1a4S0IzXhsLMb"
}
}Some end users have supplemental identity profiles they would like to track. For instance, a company which is acquired by a larger entity might like to capture, the old identity as well as the new one when tracking metrics. To capture the additional authorization system and user information, call the method below, and pass in a userIdentity object.
{ authSystem: "Active_directory" userId: "bob.dobbs", ...otherUserInformation }Required keys in the userIdentityObject are 'authSystem', and 'userId', otherUserInformation are additional key value pairs will be passed along as well. The data is sent as a 'setUserIdentity' event. Data contained in the event will supplement the user lookup service, and add these parameters to the user information, viewable in Kibana.
Multiple userIdentityObjects may be sent in. You might want to do this if you have multiple authSystems a user is associated with, and you need to capture an additional userId for the additional system.
window.Rakanto.event("setUserIdentity",{ authSystem: "Active_directory" userId: "bob.dobbs", ...otheruserinformation });
the userIdentity is { authSystem: "Active_directory" userId: "bob.dobbs" }
one or more userIdentityes may be sent at a time.
window.Rakanto.event("setUserIdentity",{ authSystem: "ActiveDirectory" userId: "bob.dobbs" }, { authSystem: "LethargicDirectory" userId: "bob.dobbs2" });event: startPageLoad
description: when the script is first parsed. A list of the last 10 or fewer URLs visited and the timestamp of when they visited it by the user this session as a JSON array. pageUrl is the page viewed. pageViewDurationMillis is time on page updated every 1/10th second. unixTimestampMillis is milliseconds since Jan 1, 1970. Ex:
"userPageHistory": [
{"pageUrl":"<https://rcpt-cs-tms.ocp-ctc-core-nonprod.optum.com/ubes/>","pageViewDurationMillis": 1000,""unixTimestampMillis": 1582050811076},
{"pageUrl":"<https://rcpt-cs-tms.ocp-ctc-core-nonprod.optum.com/ubes/>","pageViewDurationMillis": 1000,""unixTimestampMillis": 1582050862150}
]