Customer Experience Script

This documentation explains how to integrate your webpages/portal with Pixel Customer Experience Script (CX Script)

The CX Script is a javascript that runs in client's browser. CX Script gathers timings and metrics for any page on which it is placed. It also serves to correlate the user between sites on which it is placed. It also allows HTML pages to pass CustomData. The metrics collected by the script along with Custom Fields sent by the page are sent to Pixel as Client Side Events (CSE) or simply Events

Other Topics

Customer Experience Script Repo

NOTE : To allow correlation of various ID's in use throughout UHG, the CX Script must be called from the pixel repo servers using above URLs. 
Environment URL Description
Stage https://stage-repo.rakanto.com/rakanto/cx/cx.js Point to this URL for your lower environment
Prod https://repo.rakanto.com/rakanto/cx/cx.js Point to this URL for your production deployment

A Simple Example

  • Make remote calls to the Pixel Repo URLs. Do not download the script and place in your source control.

  • Make CX Script the first thing in your HTML. This helps CX Script to collect accurate metrics on the page.

      <html>
        <head>
          <script type="text/javascript">
                (function (cx, darids, a, m) {
                  i = window; s = document, o = 'script', r = 'Rakanto',
                    i['RakantoObject'] = r; i[r] = i[r] || function () {
                      (i[r].q = i[r].q || []).push([1 * Date.now()].concat(Array.prototype.slice.call(arguments)))
                    }, i[r].l = 1 * new Date(); a = s.createElement(o),
                      m = s.getElementsByTagName(o)[0]; a.id = 'rakanto'; a.src = cx;if (darids){a.setAttribute('data-px-darids', darids)}; a.async = 1; m.parentNode.insertBefore(a, m)
                })('https://stage-repo.rakanto.com/rakanto/cx/cx.js', 'UHG.Optum.Pixel.CXScriptExample');
          </script>
        </head>
    
        <body>
          <h1>Pixel Customer Experience Script - A simple example</h1>
        </body>
      </html>
    

Just copy the above code in a HTML file and open it in a browser. That's it!!!

UHG.Optum.Pixel.CXScriptExample in the example is Digital Analytics Reporting IDs (DARIDs). Details about DARID can be found in later sections.

Viewing collected data in Kibana

Data collected by the CX Script in lower environment can be viewed and analyzed in the Pixel Self Service kibana.

Click on CSE Self Service to View the Kibana Desktop

(Login with your MSID and Password)

Filter in kibana on YOUR-DARID. Details about DARID can be found in later sections.

Note : Please reach out to Pixel team if you need help accessing/using the above kibana

Universal Browser ID (UBRID)

  • The Universal Browser ID is an Anonymous browser id embedded in the script and stored in a cookie.
  • UBRID represents an Instance of the browser
    • Chrome and Firefox running in same computer will generate different UBRIDs
    • Chrome running in regular mode and in incognito mode will genereate different UBRIDs in the same computer
  • Shared by ALL users of that browser instance on that device
    • If Jane Doe and her husband use the same browser to login to their respective accounts, they both will get the same UBRID assigned

How UBRID works in the browser

UBRID is a GUID embedded in CX Script source code, this allows it to act as a "Super Cookie"

Browser gets the CX Script from the Pixel Repo Server that runs on the Rakanto domain UBRID is also set in a cookie in the browser by Rakanto Server. Expiration for that cookie is two years

The expiration of the script itself is set at 30 minutes. So every 30 minutes, the browser will check whether the script has changed. This comes in handy when we release new version of script. Browser will not have stale version for more than 30 minutes

When the browser checks for update, if the script has changed, a new script will be downloaded by the browser. The repo server will check for the cookie and if it is present, it will get the UBRID from the cookie and set it in the Script. If the cookie is not present or if the UBRID is not present in the cookie, a new UBRID will be generated.

The Repo server will set the UBRID in the CX Script itself. So the script has it until browser tries to download the new version of the script

The CX Script stores the UBRID in a global variable window.Rakanto.ClientSideData.ubrid

The CX Script creates another cookie in the website's domain and it places the UBRID along with other information in that cookie. This cookie is used for our Server Side Processing of the events. This code is also known as Scriptlet

Digital Analytics Reporting ID (DARID)

The Digital Analytics Reporting Id, is a tag which identifies a business unit within UHG. These IDs are used to identify and group your events in Pixel System.

Note : DARIDS need to be AllowListed! Work with Pixel Team to get the DARID(s) you should tag your pages with
  • An event can contain any number of DARIDs
  • For the events to flow through, at least one of the DARIDs should be AllowListed in Pixel Environment
  • Work with Pixel Team to finalize your DARID BEFORE you start your integration work
  • DARIDS should have minimum 3 characters
  • Only latin alpha numeric and period(.) are allowed. Anything else is not allowed
  • DARIDs are case insensitive
    Examples:
    • Valid.darid1
    • validDarid
    • Invalid-darid
    • invalid_darid
    • InvalidDarid∆
  • If a DARID is invalid, the CX Script will log the error message to the browser console

Examples

Example 1: login page in MnR Member Medicare solution implementation would tag the page with the following DARID UHG.UHC.MnR.MedicareSolutions.Member.LoginPage

Note: Pixel will be generating more DARIDs based on the DARID provided.  For example, the above DARID UHG.UHC.MnR.MedicareSolutions.Member.LoginPage will be parsed into
    UHG.UHC
    UHG.UHC.MnR
    UHG.UHC.MnR.MedicareSolutions
    UHG.UHC.MnR.MedicareSolutions.Member
    UHG.UHC.MnR.MedicareSolutions.Member.LoginPage

Example 2: login page shared by Mnr Medicare Solutions Member page and Acquisition page may pass two DARIDs UHG.UHC.MnR.MedicareSolutions.Member.LoginPage and UHG.UHC.MnR.MedicareSolutions.Acquisition.LoginPage

Note : Pixel will be generating the following DARIDs from the DARIDs provided above
    UHG.UHC
    UHG.UHC.MnR
    UHG.UHC.MnR.MedicareSolutions
    UHG.UHC.MnR.MedicareSolutions.Member
    UHG.UHC.MnR.MedicareSolutions.Member.LoginPage
    UHG.UHC.MnR.MedicareSolutions.Acquisition
    UHG.UHC.MnR.MedicareSolutions.Acquisition.LoginPage

Stage Environment

In stage environment, the CX Script will automatically send in a special DARID UHG.Optum.Pixel.SelfService along with the DARIDs set in the page.
This DARID (UHG.Optum.Pixel.SelfService) is allowlisted in Pixel Environment allowing events to flow through the Pixel Pipeline in the stage environment without needing any additional setup.

Production Environment

Clients will need to work with Pixel team to make sure their primary DARID is Allowlisted in Pixel Production environment.

How the CX Script chooses which darid to use.

Darids may be declared in different places, depending on their expected lifetime, when the CX Script reports something, the darid set used, is chosen in the following precidence, highest to lowest.

  1. As a direct input into an api call.

    See custom data

  2. From attributes of the script tag The code to include the script on your page. This is the easiest way call the CX Script.

    A Simple Example

  3. From a session object Store the DARIDs in sessionStorage. CX Script will use the DARIDs defined in Session Storage

     <html>
       <head>
           <script type="text/javascript">   
             sessionStorage.setItem('darids','UHG.Optum.Pixel.CXScriptExample.DaridInSessionStorage');
             (function (cx, darids, a, m) {
               i = window; s = document, o = 'script', r = 'Rakanto',
                 i['RakantoObject'] = r; i[r] = i[r] || function () {
                   (i[r].q = i[r].q || []).push([1 * Date.now()].concat(Array.prototype.slice.call(arguments)))
                 }, i[r].l = 1 * new Date(); a = s.createElement(o),
                   m = s.getElementsByTagName(o)[0]; a.id = 'rakanto'; a.src = cx;if (darids){a.setAttribute('data-px-darids', darids)}; a.async = 1; m.parentNode.insertBefore(a, m)
             })('https://stage-repo.rakanto.com/rakanto/cx/cx.js');
           </script>
       </head>
     
       <body>
           <h1>Pixel Customer Experience Script</h1>
           <h2>Darids from session object</h2>
       </body>
     </html>
     
    
  4. From the page data layer.

    Pass one or more DARIDs using the data layer (window.optumPageDataLayer). The window.optumPageDataLayer is used to set DARIDs that gets sent in the pixel call

    Pixel Customer Experience Script

    Darids from Page Data Layer

Sample Code

Test Site

In stage and qa environments there are test pages, which send requests to kibana. You can model your calls after these pages if you would like.

https://stage-repo.rakanto.com/rakanto/js-test

Custom Data

In addition to darids partitioning your data for reporting, custom data may be sent along as well. The purpose of which may be for A/B testing; reporting on site specific metric; or just to capture site specific information

The custom data is a object in the form of

CUSTOM_DATA_OBJECT = {'namespace':'darid_from_darid_list_on_page','key1':'value1','key2':'value2', ... }

custom fields will show up in Event as:
    CF_*namespace*_key1=value1
    CF_*namespace*_key2=value2
  1. The custom data object must not exceed 3000 bytes in total. for example the following string is 62 bytes long:

     {'namespace':'darid_from_darid_list_on_page','key1':'value1'}  
    

    If the field exceeds that length, an error message is placed in javascript log, and the base64 encoded px_custom_data is set to cx_error_${event}` = 'send custom data size exceeded limit' this will show up in event.

  2. The namespace is a required field and is used to generate a namespaced custom field as part of the event. These fields will be top level searchable fields in kibana.

  3. The namespace provided in pxCustomData should match one of the DARIDs in the "darids" list

  4. The namespace will be used to coin the field name for the custom field It will take the form CF_namespace_key1=value1

  5. If namespace is not provided in pxCustomData, you will see "you must include a namespace in the custom data message." in console log You also will see an error message "error: Custom fields namespace is empty" in the event

  6. If the provided namespace does not match with one of the DARIDs, you will get an error message "error: Custom fields namespace is invalid" in the Event

    NOTE :

Anti-patterns

NOTE : Please do not send custom data messages from events which will rapidly fire, such as mouse movements. 
We have tested against very large loads, however when we tested this exact scenario, one browser was generating over 1000 requests per second.

Sample implementation of custom data:

CUSTOM_DATA_OBJECT = {'namespace':'darid_from_darid_list_on_page','key1':'value1','key2':'value2', ... }
   
  1. apicall

    Make an method call from your javascript.

     window.pxSendCustomData(CUSTOM_DATA_OBJECT)
    
  2. sessionStorage.getItem('pxCustomData');

    Session storage only accepts STRINGS. So a STRING must be made from your CUSTOM_DATA_OBJECT

    To do this, JSON.stringify your CUSTOM_DATA_OBJECT first.

    Assign that string to the sessionStorage.pxCustomData object, the custom data will be sent on each pixel request, overriding any custom data on optumPageDataLayer.

     sessionStorage.setItem( 'pxCustomData', JSON.stringify( CUSTOM_DATA_OBJECT ) )
     
     Example
     
     <html>
       <head>
         <script type="text/javascript">
           sessionStorage.setItem('darids','UHG.Optum.Pixel.CXScriptExample.CustomData');
           custom_data_object = {'namespace':'UHG.Optum.Pixel.CXScriptExample.CustomData','key1':'value1'}
           sessionStorage.setItem('pxCustomData', JSON.stringify(custom_data_object) );
           (function (cx, darids, a, m) {
             i = window; s = document, o = 'script', r = 'Rakanto',
               i['RakantoObject'] = r; i[r] = i[r] || function () {
                 (i[r].q = i[r].q || []).push([1 * Date.now()].concat(Array.prototype.slice.call(arguments)))
               }, i[r].l = 1 * new Date(); a = s.createElement(o),
                 m = s.getElementsByTagName(o)[0]; a.id = 'rakanto'; a.src = cx;if (darids){a.setAttribute('data-px-darids', darids)}; a.async = 1; m.parentNode.insertBefore(a, m)
           })('https://stage-repo.rakanto.com/rakanto/cx/cx.js');
         </script>
       </head>
       <body>
           <h1>Pixel Customer Experience Script</h1>
           <h2>Custom Data</h2>
       </body>
     </html>
    
  3. window.optumPageDataLayer

    The window.optumPageDataLayer can also have a darid list, used for calls on the page. If custom data is specified, those key values are sent as well.

    To use custom data, add an element called pxCustomData to the optumPageDataLayer, the value must be in the form of a javascript object. the custom data will be sent with each pixel request.

     <html>
       <head>
         <title>CX Script Page</title>
         <script type="text/javascript">
           window.optumPageDataLayer = {
             darids: ['UHG.Optum.Pixel.CXScriptExample.CustomData'],
             pxCustomData: {'namespace':'UHG.Optum.Pixel.CXScriptExample.CustomData','key1':'value1'}
           };
           (function (cx, darids, a, m) {
             i = window; s = document, o = 'script', r = 'Rakanto',
               i['RakantoObject'] = r; i[r] = i[r] || function () {
                 (i[r].q = i[r].q || []).push([1 * Date.now()].concat(Array.prototype.slice.call(arguments)))
               }, i[r].l = 1 * new Date(); a = s.createElement(o),
                 m = s.getElementsByTagName(o)[0]; a.id = 'rakanto'; a.src = cx;if (darids){a.setAttribute('data-px-darids', darids)}; a.async = 1; m.parentNode.insertBefore(a, m)
           })('https://stage-repo.rakanto.com/rakanto/cx/cx.js');
         </script>
       </head>
       <body>
          <h1>Pixel Customer Experience Script</h1>
          </p>(Darids from a Window Page Data Layer)</p>
       </body>
     </html>
    
  4. Sending an event RIGHT AT CODE INVOCATION.

    The CX Script is asynchronodataus, you cannot use window.pxSendCustomData, right after the call for the code, that method hasn't been loaded yet. You CAN however send events using the following method.

     NOTE THE Rakanto() method at the bottom.
    
     <html>
       <head>
         <title>CX Script Page</title>
         <script type="text/javascript">
    
           sessionStorage.setItem('darids','UHG.Optum.Pixel.CXScriptExample.CustomData');
           (function (cx, darids, a, m) {
             i = window; s = document, o = 'script', r = 'Rakanto',
               i['RakantoObject'] = r; i[r] = i[r] || function () {
                 (i[r].q = i[r].q || []).push([1 * Date.now()].concat(Array.prototype.slice.call(arguments)))
               }, i[r].l = 1 * new Date(); a = s.createElement(o),
                 m = s.getElementsByTagName(o)[0]; a.id = 'rakanto'; a.src = cx;if (darids){a.setAttribute('data-px-darids', darids)}; a.async = 1; m.parentNode.insertBefore(a, m)
           })('https://stage-repo.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 initiation)</p>
       </body>
     </html>
    
  5. Using in an Angular site.

    Here’s the snippet of code an Angular developer would need to add to the component, directive, service, or where ever they will be sending custom data using the window.

    This extends the Window global interface with a CX Script method reference you will use.

    pxSendCustomData(CUSTOM_DATA_OBJECT) method:

     declare global {
       interface Window { pxSendCustomData: any; }
     }
    

    If you would prefer to use the optumPageDataLayer to send in data only on page app startload, load, and onbeforeunload, use the following.

     declare global {
       interface Window { optumPageDataLayer: any; }
     }
    

Testing the custom data

  • open chrome inspector and view the network tab, click record

  • in a chrome console window, type:

    window.pxSendCustomData({'namespace':'darid1','key1':'value1'})
    
  • To verify custom data, in osx:

    in chrome inspector, look at the get request issued. copy the px_custom_data value, from the querystring in headers

    run the command in osx terminal
    
    pbpaste | base64 -D -
    

    This will base64 decode the value, your original hash data will show up.

Caching

The CX Script server includes a header

add_header Cache-Control 'private';

This header makes it so the CX Script is not cached by edge caches, and is only cached by the browser. The CX Script is custom for each user. There are variables which are inserted into the script by the CX server, These variables are used for a correlation id, api endpoints and other things.

When the cache expires a session is checked when the new file is downloaded, based on the session values, variables are inserted into the new script.

Versioning

The CX Script has multiple versions. If you would like to pin the version of the CX Script, append the version number to the url. You may find the version in the cx code, look for: exports={name:'@pixel/customer_experience',version:'VERSION_HERE' Ex. https://stage-repo.rakanto.com/rakanto/cx/cx-3.1.1.js

TIP : Not recommended.  Pinning to a specific version limits you from benefitted from the latest features that gets added to CX Script.
      Please work with Pixel team if you have an absolute need to pin to a specific version
      

Variables from CX Script

The following bolded fields are searchable in Kibana.

browserEvent

  • cx variable name: px_be
  • event: Fired for all events.
  • The event which the CX Script is responding to. The event can be either:
    • click ( when something is clicked, we currently are not supporting this)
    • load (after page is rendered and visible to user.)
    • startPageLoad (when page first starts to be loaded and our script gets first loaded for that page. This is why you want the script at the top of the header section, it will enable you to find out now long your dependant assets take to download.)
    • visit-timeout ( when a page times out, session expired)
    • xmlHttpLoaded ( used for tracking xhr requests, may or may not be enabled. )

browserEventTimestamp

  • cx variable name: px_t
  • event: all
  • description: When the event was fired, in milliseconds since epoch

pxCustomData

  • cx variable name: px_custom_data

  • event: all

  • description:

    • create a object in the form of

      {'namespace':'YOURDARID, 'key1':'value1', ... }
      
    • assign it to sessionStorage.pxCustomData, or pass it in via a page data layer, or set it with an api call.

    • it will show up in the pixel call as a base64 encoded representation of the object.

    • it will show up in Kibana as:

      CF_YOURDARID_key1
      

networkLatency

  • cx variable name: px_net
  • event: all
  • description: Time delta between unload of previous page, and startPageLoad of current one. A measure for the most recent of how long it takes from the browser to make a call to the cse collector, and get a returned HTTP response. Indication of how much latency there is between a users browser and the pixel webserver handling the request.

optumPixelId

  • cx variable name: px_id
  • event: all
  • description: Deprecated, but remains for now.UUID generated for this user for this session. Ubrid will supercede this, or perhaps this will become the value of ubrid.

timeBetweenPages

  • cx variable name: px_iplt
  • event: load
  • description: Intrapage load time, fired on load event. Gives the delta from unload to render of next page. Time it took to click a link and go to next page and start to render the page.

timeOnPageRender

  • cx variable name: px_rt
  • event: load
  • description: delta between when landed on page and load (page rendered) event fired. How long between when you landed on the page and the page was rendered and visible to the user.

timeOnPageTotal

  • cx variable name: px_et
  • event: all
  • description: time since script was parsed.

timeOnPageUser

  • cx variable name: px_ut
  • event: beforeunload
  • description: user time: time from loaded to unloaded(time a user could actually do something ). Total time a user was on a page from rendered page to clicked to see next page. Basically, how much time did a user look at content on rendered pages, where the user entered, then left the page.

timeOnSite

  • cx variable name: px_ets
  • event: all
  • description: Total time in the site for this session. Since the first page they went to and the last page they viewed before they left the site or we timed them out.

xmlHttpLoaded

  • cx variable name: px_url_xhr
  • event: all xhr requests ( ajax calls )
  • description: Returns the url of any ajax calls, as well as when they were sent.

Ubrid

  • cx variable name: px_ubrid
  • event: all
  • description: The universal browser id, a unique identifier for the browser to persist across sessions and sites.

HttpURL

  • cx variable name: px_url
  • event: all
  • description: Returns the page the script is firing from

CXScriptGitHash

  • cx variable name: px_git_hash
  • event: all
  • description: Returns the Git Hash of the CX Script

PreviousPageHostAndPath

  • cx variable name: px_prev_pg
  • event: startPageLoad
  • description: Returns the url of the previous page visited, instrumented with CX Script

TimeOnPreviousPage

  • cx variable name: px_prev_pgt
  • event: startPageLoad
  • description: Total time spent in the previous page visited, instrumented with CX Script.
  • It is accurate to 1/10 of a second

CXScriptDownloadTime

  • cx variable name: px_script_dlt
  • event: all TBD
  • description: Time taken by the page to download the CX Script

userPageHistory

  • cx variable name: px_pt

  • event: startPageLoad

  • description: when the script is first parsed. A list of the last 10 or less 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 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}
        ]
    

Release notes:

Version 3.1.1

  • px_custom_data fields are now encrypted using a random 4096bit key.

    Version 3.1.0

  • Documentation is more professional looking using markdown backed templating.
  • Added async support to invocation of script, allowing events to be queued while the cx.js is download and run .
  • Added configurable send/check interval to the cx.js.
  • Added event queing for cx.js.
  • Added structure to pass Ubrid to any server via window.Rakanto.ClientSideData object.
  • Added git commit hash for the to Window.Rakanto.ClientSideData object.
  • Added mechanism to retrieve data back from CSE-Listener events.
  • Added network latency calucuations to all calls. The latency for the most recent call is sent in the next call.
  • Added Saucelabs integration tests for sync and async usages of script, Tested on 1207 browser/os/version combinations of safari, chrome, ie, edge, firefox, samsung; on, android, ios, windows, osx.
  • Limited size of POST customdata requests to 8k, GET custom data requests to 2K
  • Changed backend call to the CSE-Listener to be async XHR POST based with fallback logic on POST requests to use async XHR GET requests.
  • Changed CSE-Listener to support multiple methods for api calls.
  • Added better darid validation.
  • Added an automatic darid for stage, and qa events. Easier setup for new customers.
  • Changed how darids are namespaced, went to a model simiar to java namespaces, company.division.team.application.page.thing ...
  • More understandable customdata darid rules and error messages. Custom Data events now require you to choose from available page darids, and set the customdata namespace to that value.
  • Updated cx.js dependancies to newer versions.
  • Updated support for docker environment for testing.