Tracking
Tracker Reference
Complete technical reference for the Web Analyzer App tracking script — configuration, API, autotrack events, session lifecycle, and more.
1. Overview
The Web Analyzer App tracker (t.js) is a lightweight, dependency-free JavaScript snippet that collects analytics data from your website and sends it to the Web Analyzer App ingestion API. It is designed with three core principles:
- Lightweight — approximately 8.3 KB minified (~3 KB gzipped). No external dependencies.
- Privacy-first — no third-party cookies, no fingerprinting. Visitor identity is stored in first-party
localStoragewith a cookie fallback. IP addresses are used for geolocation only and never stored. - Non-blocking — loaded asynchronously with the
asyncattribute. It never delays your page render or interferes with other scripts.
What the tracker collects
- Page views (URL, path, referrer, title)
- Session start and end (inferred from inactivity)
- Time on page — active seconds, idle seconds, total duration
- Scroll depth (0–100%)
- Device type, browser, and OS (from User-Agent)
- Country (geo-resolved server-side from IP — the raw IP is never stored)
- UTM parameters and ad-platform click IDs (gclid, fbclid, msclkid, etc.)
- Core Web Vitals — LCP, CLS, INP, TTFB
- Custom events with optional JSON payloads
2. Installation
Add the following snippet to every page you want to track, just before the closing </body> tag:
HTML — Basic installation
<script>
window.wa_key = 'YOUR_TRACKING_KEY';
window.wa_auto = true;
</script>
<script src="https://webanalyzerapp.com/t.js" async defer></script>
Replace YOUR_TRACKING_KEY with the tracking key shown on your website's detail page in the dashboard. The key is a unique identifier for your website and is required for the tracker to function.
Alternative: data-key attribute
You can also pass the tracking key via a data-key attribute on the script tag. This keeps your HTML cleaner if you prefer a single-line embed:
HTML — Single-tag installation
<script async src="https://webanalyzerapp.com/t.js"
data-key="YOUR_TRACKING_KEY"></script>
wa_auto. Use the single-tag approach when you want the simplest possible embed.
3. Configuration Options
All configuration is done via window.* variables set before the tracker script loads. The tracker reads these values at initialization time.
| Variable | Type | Required | Default | Description |
|---|---|---|---|---|
| window.wa_key | string | Yes | — | Your website's unique tracking key. Found on the website detail page in your dashboard. The tracker will silently exit if no key is provided. |
| window.wa_auto | boolean | No | false | When true, enables automatic tracking of error pages, site searches, outbound links, and file downloads. See Section 5. |
| window.onustrack_key | string | No | — | Legacy alias for wa_key. Provided for backwards compatibility with older installations. If both are set, wa_key takes precedence. |
Example — Full configuration
<script>
window.wa_key = 'abc123-your-tracking-key';
window.wa_auto = true;
</script>
<script src="https://webanalyzerapp.com/t.js" async defer></script>
4. JavaScript API
Once the tracker script loads, a global window.Tracker object is available with the following methods:
| Method | Description |
|---|---|
| Tracker.track(name, payload?) | Fire a custom event. name is a required string (e.g. signup, purchase). payload is an optional plain object with additional key/value data. |
| Tracker.page(path?) | Manually trigger a page view. Defaults to window.location.pathname. Useful in SPAs after a client-side route change. Also closes the current page view and records its metrics (time on page, scroll depth, Web Vitals). |
Signatures
Tracker.track(name: string, payload?: Record<string, any>): void
Tracker.page(path?: string): void
Tracker.track() examples
JavaScript — Basic event
// Simple event with no payload
Tracker.track('signup');
// Event with a payload object
Tracker.track('purchase', {
product_id: 'sku-42',
value: 49.99,
currency: 'USD'
});
// Button click tracking
document.getElementById('cta-btn').addEventListener('click', function() {
Tracker.track('cta_click', { button: 'hero', page: location.pathname });
});
Tracker.page() examples
JavaScript — Manual page view
// Record a page view for the current URL
Tracker.page();
// Record a page view for a specific path
Tracker.page('/virtual/thank-you');
Pre-stub queue pattern
Since the tracker loads asynchronously, there is a brief window where Tracker.track() is not yet available. Use the pre-stub queue pattern to fire events immediately without waiting for the script to load. Queued events are flushed automatically once the tracker initialises.
JavaScript — Pre-stub queue
<script>
window.wa_key = 'YOUR_TRACKING_KEY';
window.wa_auto = true;
// Create a stub Tracker with a queue before the real script loads
window.Tracker = window.Tracker || { _q: [] };
window.Tracker.track = window.Tracker.track || function(name, payload) {
window.Tracker._q.push([name, payload]);
};
// These events will be queued and sent once the tracker is ready
Tracker.track('page_intent', { source: 'landing' });
</script>
<script src="https://webanalyzerapp.com/t.js" async defer></script>
window.Tracker._q at boot. If a queue exists, all entries are replayed as real events once the session is established. The stub Tracker object is then replaced with the full API.
5. Autotrack Events
When window.wa_auto = true is set, the tracker automatically fires the following events without any additional code. These appear in your Events dashboard alongside custom events.
| Event Name | Trigger | Payload |
|---|---|---|
| error_page | Page title contains a recognized HTTP error code (404, 403, 419, 500, 503) or phrases like "not found", "forbidden", "server error". Also checks <meta name="http-status"> tag. |
{ path: "/missing", status_code_hint: 404 } |
| site_search | URL contains a recognized search query parameter: q, s, query, search, keyword, term, and 12 more variants. Customisable via data-search-params. |
{ query: "user search term" } |
| outbound_link | Click on any <a> link pointing to an external domain (different from the current hostname, ignoring www. prefix). |
{ url: "https://...", text: "Link text", domain: "example.com" } |
| file_download | Click on a link to a file with a recognized extension: .pdf, .zip, .docx, .xlsx, .csv, .mp4, .exe, .dmg, and 15 more. |
{ url: "/report.pdf", filename: "report.pdf", extension: "pdf" } |
Supported file extensions
.pdf,
.zip,
.docx,
.doc,
.xlsx,
.xls,
.pptx,
.ppt,
.csv,
.mp4,
.mp3,
.dmg,
.exe,
.pkg,
.deb,
.rpm,
.tar,
.gz,
.rar,
.7z,
.epub,
.apk
Supported search query parameters
q,
s,
query,
search,
keyword,
keywords,
term,
searchword,
srch,
live-search,
search_query,
text,
find,
k,
kw,
search_term,
sq,
wp_search,
dgSearchQuery,
ia_search,
bbp_search
window.wa_auto = true line from your snippet. You can still fire custom events manually via Tracker.track().
6. Session Management
The tracker manages visitor identity and session lifecycle using browser storage. No server-side cookies or third-party identifiers are involved.
Visitor identity
Each visitor is assigned a UUID v4 identifier, stored in localStorage under the key ot_vid. If localStorage is unavailable (e.g. in private browsing), a first-party cookie named ot_vid is used as a fallback (365-day expiry, SameSite=Lax). This UUID is sent to the server to associate page views and events with a returning visitor.
Session lifecycle
A new session is created on the first page load in a browser tab. The session ID is stored in sessionStorage under the key ot_sid, so it persists across MPA (multi-page application) navigations within the same tab but is automatically discarded when the tab is closed.
| Storage Key | Storage Type | Purpose | Lifetime |
|---|---|---|---|
| ot_vid | localStorage (cookie fallback) | Visitor UUID — identifies returning visitors | Persistent (localStorage) or 365 days (cookie) |
| ot_sid | sessionStorage | Session ID — ties page views in the same tab | Until the browser tab is closed |
| ot_vdb | sessionStorage | Database visitor ID — server-assigned numeric ID | Until the browser tab is closed |
| ot_pvc | sessionStorage | Cumulative page view count for the session | Until the browser tab is closed |
| ot_dur | sessionStorage | Cumulative session duration in seconds | Until the browser tab is closed |
| ot_acq | localStorage | Acquisition source (referrer + UTMs) — prevents false "direct" attribution | 30-minute TTL (refreshed on activity) |
Activity and idle detection
The tracker monitors user activity via mousemove, keydown, scroll, click, and touchstart events. If no activity is detected for 30 seconds, the user is marked as idle. Time is split into active seconds and idle seconds, giving you accurate engagement metrics.
Heartbeat mechanism
While the user is active, the tracker sends a heartbeat request to the server every 60 seconds. This keeps the session alive and updates the session duration on the server. Heartbeats are skipped when the user is idle to avoid inflating session duration. When the user leaves the page (tab close or navigation), a final beacon is sent with the exit page, total duration, and page view count.
7. SPA Support
The tracker has built-in support for Single Page Applications (SPAs). It automatically detects client-side route changes by patching history.pushState and history.replaceState, and by listening for popstate and hashchange events. In most cases, no additional code is needed.
How it works
- When a route change is detected, the tracker automatically closes the current page view (recording time on page, scroll depth, and Web Vitals) and opens a new one.
- The session is maintained across all route changes within the same tab.
- UTM parameters and referrer data are stripped from the recorded path to keep URLs clean.
React / Next.js
React Router and Next.js use history.pushState internally, so the tracker will detect route changes automatically. Simply install the snippet once in your index.html, _app.js, or layout.tsx:
Next.js — app/layout.tsx
import Script from 'next/script';
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Script id="wa-config" strategy="beforeInteractive">
{`window.wa_key = 'YOUR_KEY';
window.wa_auto = true;`}
</Script>
<Script
src="https://webanalyzerapp.com/t.js"
strategy="afterInteractive"
/>
</body>
</html>
);
}
Vue / Nuxt
Vue Router also uses history.pushState. Add the snippet to your index.html or Nuxt config:
Nuxt 3 — nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
script: [
{
children: `window.wa_key = 'YOUR_KEY';
window.wa_auto = true;`
},
{
src: 'https://webanalyzerapp.com/t.js',
async: true, defer: true
}
]
}
}
})
Svelte / SvelteKit
SvelteKit — src/app.html
<body data-sveltekit-preload-data="hover">
%sveltekit.body%
<script>
window.wa_key = 'YOUR_KEY';
window.wa_auto = true;
</script>
<script src="https://webanalyzerapp.com/t.js" async defer></script>
</body>
Manual page view (fallback)
If automatic detection does not work for your framework (e.g. a custom routing library), you can manually trigger page views:
JavaScript — Manual SPA tracking
// Call after each route change
Tracker.page();
// Or with an explicit path
Tracker.page('/dashboard/settings');
8. Bot Detection
The tracker includes client-side bot detection that silently prevents tracking in automated and headless browsers. This ensures your analytics data is not polluted by crawlers, testing frameworks, or scraping tools. Bot detection runs as a two-layer system:
Client-side detection (t.js)
The tracker checks for the following signals before initialising. If any match, the tracker exits silently — no session, page views, or events are recorded:
| Detection Signal | Catches | How it works |
|---|---|---|
| navigator.webdriver | Selenium, Playwright, Puppeteer, ChromeDriver, WebDriver | Set to true by all WebDriver-based automation |
| window.phantom / window._phantom / window.callPhantom | PhantomJS | PhantomJS exposes these globals automatically |
| navigator.languages | Headless Chrome (older versions) | Empty or missing languages array indicates headless mode |
| navigator.plugins | Headless Chrome (non-Firefox) | Zero plugins in a non-Firefox browser strongly suggests headless mode |
Server-side detection
The server also inspects the User-Agent string on session creation. If a known bot pattern is detected (e.g. Googlebot, bingbot, AhrefsBot), the server returns { bot: true } and the tracker halts all further requests for that session.
9. Data Attributes
The tracker script tag supports optional data-* attributes for configuration that does not require a separate <script> block.
| Attribute | Description | Example |
|---|---|---|
| data-key | Alternative way to pass the tracking key. Equivalent to setting window.wa_key. |
data-key="abc123" |
| data-search-params | Comma-separated list of additional URL query parameter names to detect as site searches. Added to the built-in list of 20+ parameters. | data-search-params="product_query,search_input" |
HTML — Using data attributes
<script>
window.wa_key = 'YOUR_KEY';
window.wa_auto = true;
</script>
<script async src="https://webanalyzerapp.com/t.js"
data-search-params="product_query,filter_term"></script>
10. Performance
The tracker is designed to have zero measurable impact on your website's performance. Here is how:
| Technique | How it helps |
|---|---|
| Async loading | The script is loaded with async defer attributes. It never blocks HTML parsing, CSS rendering, or other scripts. |
| Tiny payload | ~8.3 KB minified, ~3 KB gzipped. Smaller than most images. No external dependencies to load. |
| sendBeacon for exit events | Page exit and session close data is sent using fetch with keepalive: true (preferred) or navigator.sendBeacon (fallback). Both are non-blocking and survive page unload without delaying navigation. |
| Passive event listeners | All activity listeners (mousemove, scroll, keydown, click, touchstart) are registered with { passive: true } so they never block scrolling or interactions. |
| Minimal network requests | One session request + one page view request per page load. Heartbeats every 60s (only when active). No polling, no WebSocket connections. |
| No DOM manipulation | The tracker never modifies the DOM, injects elements, or changes page styling. It is invisible to your users. |
11. Troubleshooting
Common issues and their solutions:
Script is not loading
- Check that the
<script>tag has the correctsrcURL pointing to your t.js file. - Open your browser DevTools → Network tab and look for
t.js. It should return a 200 status. - If you see a CORS error, ensure your Web Analyzer App instance is configured to accept requests from your domain.
- Check that no ad blocker or privacy extension is blocking the request. Some blockers filter requests to third-party analytics domains.
No data appearing in the dashboard
- Verify that
window.wa_keyis set before the tracker script loads. The configuration script block must appear first. - Confirm the tracking key matches the one shown on your website detail page. Keys are case-sensitive.
- Check the browser Console for errors. The tracker fails silently by design, but network errors may appear.
- In DevTools → Network, look for requests to
/api/t/session. The response should be{ "ok": true, ... }. - Ensure the queue worker is running on your server (
php artisan queue:work) — geolocation and session processing are handled by background jobs.
Events are not appearing
- Make sure you are calling
Tracker.track()after the tracker has loaded. Use the pre-stub queue pattern if you need to fire events immediately. - Check that
window.wa_auto = trueis set if you expect autotrack events (error_page, site_search, outbound_link, file_download). - Verify you have not exceeded your monthly event quota. The Free plan has a limit of 1,000,000 combined page views and events per month. Check usage on the Billing page.
Bot detection is blocking test browsers
- Automated testing tools (Selenium, Playwright, Cypress, Puppeteer) set
navigator.webdriver = true. This is a standard WebDriver flag that cannot be disabled. The tracker will not track these sessions by design. - Use a regular browser window (Chrome, Firefox, Safari, Edge) to verify that tracking works.
- For integration tests, assert against the API response from
/api/t/sessiondirectly rather than checking the dashboard UI.
SPA page views not tracked
- The tracker patches
history.pushStateandhistory.replaceState. If your framework uses a custom router that bypasses the History API, you may need to callTracker.page()manually after each navigation. - Hash-based routing (e.g.
/#/page) is supported via thehashchangelistener.
Session duration shows 0 or is inaccurate
- Session duration is calculated from active and idle seconds. A single-page visit with an immediate bounce may show 0 seconds if the exit beacon does not fire (e.g. the browser kills the tab instantly).
- The tracker uses
fetchwithkeepaliveandsendBeaconto maximise delivery of exit data, but some edge cases (e.g. mobile browser kill, airplane mode) may still result in missing close events.