Web Analyzer App
/ Docs

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 localStorage with a cookie fallback. IP addresses are used for geolocation only and never stored.
  • Non-blocking — loaded asynchronously with the async attribute. 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
Tip: The tracker is fully GDPR-friendly. Since it uses only first-party storage and never stores IP addresses, most privacy frameworks do not require a cookie consent banner for analytics-only tracking.

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>
Tip: Use the two-script approach (the first example) when you need to set configuration options like 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>
How it works: The tracker checks for 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

Tip: To disable autotrack entirely, simply remove the 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.

Warning: If you are testing your tracker integration with Selenium, Playwright, Cypress, or Puppeteer, events and page views will not appear in your dashboard. Use a regular (non-automated) browser window to verify that tracking is working correctly.

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.
Tip: The tracker will never negatively impact your Core Web Vitals scores. In fact, it measures them — LCP, CLS, INP, and TTFB are collected via the PerformanceObserver API and sent to your dashboard with each page view.

11. Troubleshooting

Common issues and their solutions:

Script is not loading

  • Check that the <script> tag has the correct src URL 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_key is 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 = true is 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/session directly rather than checking the dashboard UI.

SPA page views not tracked

  • The tracker patches history.pushState and history.replaceState. If your framework uses a custom router that bypasses the History API, you may need to call Tracker.page() manually after each navigation.
  • Hash-based routing (e.g. /#/page) is supported via the hashchange listener.

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 fetch with keepalive and sendBeacon to maximise delivery of exit data, but some edge cases (e.g. mobile browser kill, airplane mode) may still result in missing close events.

Help & FAQ

Find answers instantly