JS Tracker
- JS Tracker
- Tracker Workflow
- JS Tracker in Cluster Mode
- Creating a Script and Obtaining the Snippet
- Connecting the Tracker
- Configuring the Tracker
- Batch Sending of Events
- Overriding the Data Sending Method
- Custom Buffer Cleanup
- Progressive Retry Timing for Failed Requests
- Passing the Anonymous User ID Between Subdomains
- X-Request-Id Header
- Processing the Received Data
- Test Event
- Custom Event Collection
- Configuring CORS for Cross-Domain Data Exchange
- Type Mismatch When Receiving Null in Activity
The JS tracker collects statistics about website visitor actions. It allows you to track events such as clicks, page views, form submissions, and other interactions with a web page. This information is useful for analytics, identifying user scenarios, and optimizing site performance.
The data collected by the tracker is automatically sent to the Operavix system, where it is combined with data from other systems.
Tracker Workflow
Four components work together to track user journeys:
- Snippet — a small code fragment that contains the tracker connection settings
- Tracker — a JS script that collects data and sends it to the server
- Tracking module on the Operavix platform (AutomationWebhook) — the component that receives data from the tracker
- Automation module (Automation) — the component where the New events trigger block is configured to receive tracker events, and a scripts process the incoming data
How it works:
- The snippet is embedded into the code of the site.
- When the site is opened, the snippet begins loading the tracker script. If loading fails, the snippet retries a specified interval. Multiple paths/URLs can be specified, and the snippet will try them sequentially until one loads successfully.
- The tracker collects events into a buffer and sends it to the server either at defined intervals or once the buffer reaches the set size.
- If events occur before the tracker loads, the snippet intercepts them and temporarily stores them until they can be passed to the tracker
- If sending fails, the tracker retries either with every new event or at scheduled intervals
- Multiple analytics server URLs may be defined. For each data send, one URL is randomly chosen. If the attempt fails, the tracker immediately tries the next URL, skipping the failed one. If all URLs fail, the retry interval is determined by the errorSendTimeout parameter (default: 15 seconds). Once a URL is validated by a successful send, all further data is sent only to that URL. If a validated URL fails even once, it is removed from the pool, and a new one is selected
- The server accumulates incoming requests and triggers an automation script to process the data.
Tracker configuration parameters are described in detail in the Tracker Setup section.
JS Tracker in Cluster Mode
The system supports working with multiple analytics collection servers. This ensures fault tolerance, scalability for high data flow, and prevents data loss.
Tracker behavior in cluster mode:
- If one node is unavailable, the tracker redirects data to another active node
- If duplicate data is created due to network instability, all duplicates are removed before being passed to the automation script
- If a failure occurs, once the node recovers it resumes receiving data. The storage structure is restored and all UUIDs are re-registered automatically
The JS tracker requires a dedicated port, isolated from the web interface port. This port must use a separate SSL certificate, different from the one used for the web interface. When multiple analytics servers are configured, using a separate restricted port for the JS Tracker improves security and system isolation.
Creating a Script and Obtaining the Snippet
To collect and process data, you need to create a script.
Use the New events block as the trigger.
The following block parameters are shown in the Operavix web interface:
- Web application name — by default, the current domain is used
- UUID — tracker data identifier, used as the public data ID
- Analytics server address — by default, the current domain is used. The server is used for event delivery
- Embed code (snippet)
If you add a web application name, it will also appear in the embed code. The snippet can be viewed and copied, but not edited.
- UUIDs are unique per tracker. Two identical UUIDs cannot exist.
- For the scripts to work correctly, the web application name and UUID must match both in the block and on the website.
In cluster mode, available analytics server addresses are usually shown in the left panel of the block. They can be edited or added manually:
- Click Edit in the block parameters.
- Click + Add under Analytics server address and enter the required URL.
- Click X to delete, and Apply to save.
The New events block starts the script when new events are received from the server.
When data is collected using the JS tracker and passed to the script, it may be transformed into a flattened structure where nested objects use dot notation. For example, if event contains a nested element object, it will appear as event.element in tracker output.
The New events block returns the following fields:
| Field | Type | Description | Example |
|---|---|---|---|
app | Information about the site integrating the tracker | ||
app.name | string | Application name. Specified in the configuration file; by default, the current domain is used | "myshop.com" |
campaign | UTM tags extracted from the URL | ||
campaign.source | string null | Traffic source, advertising platform | "google" |
campaign.medium | string null | Type of advertising | "cpc" |
campaign.name | string null | Name of the advertising campaign | "summer_sale_2024" |
campaign.term | string null | Keyword | "buy+phone" |
campaign.content | string null | Additional ad information | "banner_top" |
event | Event information | ||
event.name | string | Event name | "ProductView" |
event.props | array (string) | Dictionary with custom user data | ″{\"type\":\"phone\"}″ |
event.time | number | Timestamp of method call (number of milliseconds since January 1, 1970, 00:00:00 UTC) | 1718902800000 |
event.element | Information about the DOM element where the event occurred: button, link, field, etc. | ||
event.element.id | string null | Element identifier (only the id attribute is used) | "product-card" |
event.element.name | string null | Element name/data. Calculated individually for each element type | "Product card" |
event.element.webctrl_selector | string null | Method for identifying the element on the page https://docs.uipath.com/studio/docs/about-selectors#webctrl | "<webctrl ... />" |
id | string | String split into two parts by an underscore (_). The first part is generated once when the tracker starts and remains unchanged during the session. The second part increments by 1 with each new event | "b56b6da5-40cf-4e7a-a44d-128f81ca98e2_15" |
lib | Tracker information | ||
lib.version | string | Tracker version number | "1.25.4-p1" |
navigator | User agent state and properties | ||
navigator.user_agent | string | User agent string for the browser | "Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1" |
navigator.language | string | User's preferred language, typically the browser UI language | "en-US" |
person | User information | ||
person.user_id | string null | Registered user identifier | "user_789" |
person.anonymous_id | string | Generated anonymous UUID v4 identifier | "a1b2c3d4-e5f6-7890-g1h2-i3j4k5l6m7n8" |
person.ids | array (string) | Dictionary of identifiers received from third-party trackers integrated into the page | |
page | Web page information | ||
page.title | string | Browser tab title | "Xiaomi 13T Pro - MyShop" |
page.referrer | string | URL from which the user navigated to the current page | "https://www.google.com/" |
page.url | string | Current page URL | "https://myshop.com/products/xiaomi-13t-pro" |
page.path | string | URL segment between host and parameters | "/products/xiaomi-13t-pro" |
screen | Screen information | ||
screen.width | number | Screen width in pixels | 375 |
screen.height | number | Screen height in pixels | 812 |
time_zone | string | Time zone | "Europe/London" |
General information such as page load time or operation duration of page elements is not stored.
The tracker also does not collect geolocation data.
To exclude certain events from collection, add a filterFn predicate in the snippet. See the Configuring the Tracker section.
If real site data has not yet arrived, you can test the New events block using a test event. This allows you to configure mapping prior to receiving actual data.
Further script configuration is covered in Processing the Received Data.
Connecting the Tracker
To connect the tracker, copy the snippet code from the New events block parameters and embed it in your webpage.
When the site opens, the snippet loads the tracker script according to the configured parameters.
Configuring the Tracker
The tracker settings are configured inside the snippet.
The tracker can be configured using the following parameters:
app— name of the web application. By default, the current domain is used as the nameserverUrl— analytics server address. Can be provided as a string or an array of stringsuuid— tracker data identifier. Used as the public ID of the data. The UUID is used to match the tracker configuration with the script ID that will process the datasend(data,url,headers) — method for sending tracker data. Allows customization of the data transmission method, such as fixing the data format or modifying request headers. The function must return aPromiseobject to track the execution of the requestbufferLifetime— time interval in milliseconds between sending accumulated events. After the specified time, the buffered data is automatically sent to the server. Default value —InfinitybufferLimit— maximum number of events that can be stored in the buffer. When this limit is reached, the accumulated data is sent to the server. Default value —1bufferMaxSize— maximum memory size in bytes at which the buffer will be cleared. Default value — 1 MBerrorSendTimeout— interval in milliseconds between retry attempts to send events to the server if the previous attempt failed. Default value — 15000 ms (15 seconds)filterFn(event)— predicate function that allows excluding unnecessary events from collection. Events filtered out by this function will not be stored in the buffer for sendinginterceptors— custom configurations for automatic collection of custom eventscookieDisabled— disables duplication of the anonymous user identifier in cookies. Default value —false
Details on bufferLifetime, bufferLimit, and bufferMaxSize are available in the Batch Sending of Events section.
Details on interceptors can be found in Custom Event Collection.
Example of configuring the filterFn parameter to exclude events from collection:
filterFn: (data) => {
if (data.event.name === 'Page') {
return false;
}
return true;
}
Explanation:
data— the event to filterdata.event.name === 'Page'— filter condition
Additionally, the tracker file path can be modified via the snippet by specifying an array of strings. The file path is set in the second-to-last parameter of the snippet method call. The frequency of retry requests for the tracker file in case of an error is defined in the last parameter in milliseconds (default is 15000).
Snippet example:
(function(w,d,s,t,u,i,q){
q=[];w[t]=function(){q.push([arguments,new Date()]);};w[t].q=q;
(function l(j,e,n,c,o){e=d.createElement(s);e.async=1;e.src=u[j];
e.onerror=function(){e.parentNode.removeChild(e);o=(function(n){return n>=u.length?{n:0,c:i}:{n:n}})(j+1);n=o.n;c=o.c;c===undefined?l(n):setTimeout(function(){l(n)},c)};
b=d.getElementsByTagName(s)[0];b.parentNode.insertBefore(e,b);})(0);
w[t]({
app: "My site",
serverUrl: [
"https://example1.com",
"https://example2.com",
"https://example3.com",
"https://example4.com"
],
uuid: "qwertyuiopasdfghjklzxcvbnm123456",
});
})(window,document,"script","tracker",["tracker1.js","tracker2.js","tracker3.js"],15000);
Upon successful connection, the tracker build date will appear in the developer console (F12).
Batch Sending of Events
The tracker collects events and stores them in a buffer until one of the conditions for sending data to the server is met.
The logic for sending events is defined by the following parameters configured in the snippet:
bufferLifetime— the time interval in milliseconds between sending accumulated events. After the specified time elapses, the accumulated data is automatically sent to the server. Default value:InfinitybufferLimit— the maximum number of events that can be stored in the buffer. When this limit is reached, the accumulated data is sent to the server. Default value:1bufferMaxSize— the maximum buffer size in bytes. Once this value is reached, the buffer is automatically cleared. Default: 1 MB
How it works:
- Data is sent when one of the following conditions is met:
- The number of accumulated events reaches the limit set by
bufferLimit - The time defined by
bufferLifetimeexpires after the last successful data transmission
- The number of accumulated events reaches the limit set by
- Data is sent at the moment one of these conditions occurs, depending on which happens first
- After successful data transmission, the sent events are removed from the buffer and the timer is reset
- If the buffer reaches the maximum size defined by
bufferMaxSize, it is cleared. All accumulated data will be lost
Overriding the Data Sending Method
To implement custom logic for sending data, you can override the send method using the send parameter. The method passed as the value of this parameter takes two arguments:
- An array of events.
- The UUID passed in the tracker configuration.
The method passed via the send parameter can be used to implement custom logic for transforming tracker data or clearing the buffer.
Example:
(function(w,d,s,t,u,i,q){
q=[];w[t]=function(){q.push([arguments,new Date()]);};w[t].q=q;
(function l(j,e,n,c,o){e=d.createElement(s);e.async=1;e.src=u[j];
e.onerror=function(){e.parentNode.removeChild(e);o=(function(n){return n>=u.length?{n:0,c:i}:{n:n}})(j+1);n=o.n;c=o.c;c===undefined?l(n):setTimeout(function(){l(n)},c)};
b=d.getElementsByTagName(s)[0];b.parentNode.insertBefore(e,b);})(0);
w[t]({
app: "My site",
uuid: "qwertyuiopasdfghjklzxcvbnm123456",
send(data, url, headers) {
return fetch(url, {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
...headers,
},
body: JSON.stringify(data),
});
}
});
})(window,document,"script","tracker",["tracker.js"],15000);
Custom Buffer Cleanup
Automatic buffer data cleanup after successful transmission to the server can be implemented by overriding the tracker’s data sending method via the send parameter.
To clear the buffer:
- Ensure the data sending method passed via the
sendparameter returns aPromiseobject. - In the
Promisecallback, call theresolvemethod with the parameter{ ok: true }.
Example:
(function(w,d,s,t,u,i,q){
q=[];w[t]=function(){q.push([arguments,new Date()]);};w[t].q=q;
(function l(j,e,n,c,o){e=d.createElement(s);e.async=1;e.src=u[j];
e.onerror=function(){e.parentNode.removeChild(e);o=(function(n){return n>=u.length?{n:0,c:i}:{n:n}})(j+1);n=o.n;c=o.c;c===undefined?l(n):setTimeout(function(){l(n)},c)};
b=d.getElementsByTagName(s)[0];b.parentNode.insertBefore(e,b);})(0);
w[t]({
app: "My site",
uuid: "script",
send(data, url, headers) {
if (data.length > 10) {
return Promise.resolve({ok: true})
}
return fetch(url, {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
...headers
},
body: JSON.stringify(items),
});
}
});
})(window,document,"script","tracker",["tracker.js"],15000);
Progressive Retry Timing for Failed Requests
A progressive timeout for failed requests can be implemented by overriding the send method.
Example:
(function(w,d,s,t,u,i,q){
q=[];w[t]=function(){q.push([arguments,new Date()]);};w[t].q=q;
(function l(j,e,n,c,o){e=d.createElement(s);e.async=1;e.src=u[j];
e.onerror=function(){e.parentNode.removeChild(e);o=(function(n){return n>=u.length?{n:0,c:i}:{n:n}})(j+1);n=o.n;c=o.c;c===undefined?l(n):setTimeout(function(){l(n)},c)};
b=d.getElementsByTagName(s)[0];b.parentNode.insertBefore(e,b);})(0);
w[t]({
app: "My site",
uuid: "script",
send(data, url, headers) => {
function fetchWithRetry(url, options = {}, delays) {
let attempt = 0;
function makeRequest() {
return fetch(url, options)
.then(response => {
if (!response.ok) {
throw new Error(response.status);
}
return response;
})
.catch(error => {
if (attempt < delays.length) {
return new Promise(resolve =>
setTimeout(resolve, delays[attempt])
).then(() => {
attempt++;
return makeRequest();
});
} else {
throw error;
}
});
}
return makeRequest();
}
return fetchWithRetry(url, {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
...headers
},
body: JSON.stringify(items)
}, [1000, 2000, 3000, 4000, 5000, 7000]);
}
});
})(window,document,"script","tracker",["tracker.js"],15000);
Passing the Anonymous User ID Between Subdomains
To pass the anonymous user identifier person.anonymous_id between different parts of a web application located on subdomains of a main domain, duplication via cookie is used.
For example, if the login page is located at auth.domain.com and the main application is at main.domain.com, the anonymous ID is written into a cookie with the root domain domain=".domain.com" so that all subdomains have access to it.
For HTTPS websites, cookies are set with the parameters:
SameSite=NoneSecure
If storing data in cookies is disallowed by the site's security policy, duplication can be disabled using the cookieDisabled tracker configuration parameter.
X-Request-Id Header
When sending data to the server, each request is accompanied by an X-Request-Id header in the format f4c86d97-a74e-401b-b8ec-87ff04d33ca7_1. This header ensures the identification of each request and helps prevent duplication or data loss in case of network failures.
The header consists of two parts separated by an underscore _:
- A unique identifier generated once during tracker initialization and remains unchanged throughout the session.
- A sequential number that increments with each new data transmission attempt.
How it works:
- Each request transmits a batch of event packets, which receives its own unique
X-Request-Id. - If the request fails, the tracker retries sending the same batch with the same
X-Request-Id. New events added to the buffer during this time are not included in the retry. - Once the request is successfully sent, the accumulated data will be transmitted with a new
X-Request-Id. - If data is sent to multiple endpoints (for example, using a
serverUrlarray), theX-Request-Idfor a single batch remains unchanged.
Processing the Received Data
To process events delivered through the New events block, add other automation blocks to your script — for example, to automatically load the incoming data into a table for further analysis.
A detailed description of blocks used to process incoming data is provided on the Editing a Script page.
Test Event
If real data has not come from the website yet, you can test the New events block using a test event.
A test event is a validation of the block using mock data in JSON format. After processing the test event, the system creates fields which can then be used for mapping in subsequent script blocks. This allows you to work with fields of the New events block and configure the script before actual data is received.
To use a test event:
- Open the Test tab and click Test Event.
- In the panel that opens, enter a JSON example of the request body.
- Click Apply to generate a mapping from your example.
Custom Event Collection
The tracker provides two methods — trackEvent and identify. Both are available to application developers via the global window.tracker object or a tracker instance.
To track a custom event, you need to manually call the tracker's trackEvent method when the desired event occurs. This method allows you to send an event with a custom event.name and an optional key-value property object to the server. The method should be called at the moment the event happens, for example, inside an element's onClick handler.
Usage example usage when integrated via snippet:
tracker("trackEvent", "product catalog is opened");
tracker("trackEvent", "purchase", { product: "test item", price: "1$" });
If the optional key-value property object (event.props in the event structure) includes the key element with a value of type Element and/or the key elementName with a value of type String, these keys will be removed from the transmitted data and used to form elementData. The remaining fields will stay in the transmitted data.
Usage example usage when integrated via snippet:
tracker("trackEvent", "some event", {
element: document.getElementById("container"),
elementName: "name",
otherProp: "value",
});
To identify a user, the tracker provides the identify event. identify allows you to associate an anonymous user with any numeric or string identifier — such as a username, email, system ID, or similar. It is expected that this identifier will be provided by the application during user authentication.
The event should be triggered at the moment the identifier is confirmed.
If identify is called with an identifier that is already stored in the browser, the event will not be sent to the server.
Usage example usage when integrated via snippet:
tracker("identify", 1, { name: "John" });
This call adds the identifier "1" to the user's context, which is stored in localStorage and will be sent to the analytics server with every subsequent request.
Next, the call is automatically delegated to the trackEvent method with the event name identify, sending the event to the server. For the example above, the
tracker("trackEvent", "identify", { name: "John" });
Only the user's identifier will be saved to the context, not the entire object. For example, an object like { name: "John" } will not be stored.
Custom events are automatically collected using the interceptor parameter. The value of this parameter should be an array of objects.
To enable the JS tracker to collect custom events, extend the embedded code with a user configuration for interceptors:
interceptors: [
{
action: "Click" | "FieldChange",
interceptor: (target: EventTarget) => {
eventName: string,
elementName: string,
element: Element,
eventProps: object
} | null
}
]
Explanation:
action— the type of event that triggers theinterceptor. Possible values:Click— subscribes to theclickeventFieldChange— subscribes to thefocusinandfocusoutevents
interceptor— the event handler. It receives the element on which the event occurred and returns a structure describing the custom event. The handler code may returnnull, and in the implementation, you can specify which elements should be tracked. Only custom events tied to elements defined in the snippet will be recorded. If a custom event is associated with other elements,nullwill be returned
Depending on the data you want to collect, the parameter may include the following fields:
eventNameelementNameelementeventProps
Data collection is performed based on the specified fields.
Configuring CORS for Cross-Domain Data Exchange
If the JS tracker is located on a domain different from the one where Operavix is hosted, you need to configure a CORS policy that grants access to this domain. To do this, at the end of the Operavix configuration file com.operavix.subsystem.frontend.json, add the cors_policy parameter before the final curly brace. Set the parameter to * to enable cross-domain data exchange with any site.
"cors_policy": "*"
For cross-domain exchange only with specific sites, list their URLs in square brackets separated by commas.
"cors_policy": ["https://operavix.com", "https://operavix.org"]
The file is located on the Operavix server at C:\ProgramData\Operavix\config\com.operavix.subsystem.frontend.json.
If the system is installed on Linux OS, when starting the service, set the FE_CORS_POLICY variable to * to configure CORS for cross-domain data exchange with any sites. To restrict access, list the URLs with which cross-domain data exchange is required, separated by commas. For example: FE_CORS_POLICY="https://operavix.com, https://operavix.net". By default, the CORS policy is disabled.
If you need to allow cross-domain access from all subdomains of a specific domain (for example, *.operavix.com), use a mask with a regular expression. This will allow data exchange with all subdomains of operavix.com, such as https://app.operavix.com, https://dev.operavix.com, and others.
"cors_policy": ["https://(.*).operavix.com"]
Type Mismatch When Receiving Null in Activity
If a type mismatch occurs when receiving a Null value in an activity collected by the JS tracker, you need to change the target table columns in the script to the Nullable type. If the table was created earlier, you can use fix this issue as follows:
- Add an SQL query block to the target script.
- Enter the SQL query:
ALTER TABLE NAME_TABLE(table name) MODIFY COLUMN person_user_id Nullable(String) - Change the target column type.
- Delete the SQL Query block created in steps 1–2.
- Use the updated table (with columns of type Nullable) together with the existing script.
This method can be used to resolve any similar issues related to type mismatches.
Was the article helpful?