Skip to Content
GuidesTracking

Tracking

This page is the technical reference for the Warm AI tracking script: how it loads, what it stores in the browser, which events it sends and what each field means. It is written for developers and for anyone checking what they are deploying.


Which script you are running

Every customer installs the same script, served per site from:

https://cdn.menoinfra.com/p/YOUR_TRACKING_ID.js

The file is built per tracking ID. The first line sets window.Pulse, which carries your tracking ID and the settings we hold for your site, and the tracker code follows it. You never edit this file; changes to your settings are picked up the next time the file is served.

If your account uses a custom tracker domain, the same file is served from your own subdomain instead of cdn.menoinfra.com.

Installed Warm AI before mid-2026? Some early customers run an older script, warm.js, from cdn.warmai.uk. It works the same way in outline (a tab-scoped session in sessionStorage, no cookies of its own, no persistent device identifier), but it uses a window.warmai command queue instead of window.pulse and sends events to a different endpoint. Newer features are only added to the standard script, so we recommend switching: replace your existing tag with the snippet in Installing the tracker. If you are not sure which one you have, search your site’s HTML for warm.js or menoinfra.


How the script loads

The install snippet does two things. It creates a small window.pulse function that queues any calls you make before the script has arrived, and it adds an async script tag for your per-site file.

<script> (function(id){ window.pulse=window.pulse||function(){(pulse.q=pulse.q||[]).push(arguments)}; var s=document.createElement('script');s.async=1; s.src='https://cdn.menoinfra.com/p/'+id+'.js'; document.head.appendChild(s); })('YOUR_TRACKING_ID'); </script>

When the script runs it:

  1. Reads your tracking ID from window.Pulse.id (set by the first line of the per-site file).
  2. Checks for a consent manager on the page and reads its current state.
  3. Starts or resumes the session and sends the first event.
  4. Replays any pulse(...) calls that were queued before it loaded.

Because the tracking ID is built into the file, the script also works as a plain <script src="https://cdn.menoinfra.com/p/YOUR_TRACKING_ID.js" async></script> tag. That form is useful when a consent manager needs to manage the tag directly; see Manual tagging.


What it stores in the browser

The tracking code stores one entry, in sessionStorage:

KeyContentsLifetime
_p_session{ "token": "<uuid>", "lastActivity": <timestamp> }Ends after 30 minutes without activity, and when the tab is closed

That is the only thing the tracking code itself writes. It sets no cookies and does not send any persistent device identifier. sessionStorage belongs to a single tab, so a second tab, or the same visitor tomorrow, starts a new session token.

The session token is a random UUID. It contains nothing about the visitor.


Events

The script sends three event types: session_start, page_view and session_end.

Common fields

Every event carries these fields:

FieldTypeDescription
tracking_script_idstringYour tracking ID
event_typestringsession_start, page_view or session_end
session_tokenstringThe UUID from _p_session
urlstringFull location.href when the event fired
pathstringlocation.pathname
titlestringdocument.title
referrerstring or nulldocument.referrer, or null when empty
user_agentstringnavigator.userAgent

The browser never sends an IP address. Our edge adds the visitor’s IP address and country to the event before it is stored (see Transport).

session_start

Sent when a page loads and there is no live session in _p_session (a new tab, or more than 30 minutes since the last activity). Also sent when a tab becomes visible again after being hidden, with a new session token (see Tab switching).

UTM parameters from the URL are added to session_start only, and only the ones present on the URL:

{ "tracking_script_id": "aeec6fcf-7138-4742-ac4e-0adc0221c737", "event_type": "session_start", "session_token": "3f1c9a52-6b0e-4d2a-9c1e-7a4b8e2d5f60", "url": "https://yoursite.com/pricing?utm_source=google&utm_campaign=brand", "path": "/pricing", "title": "Pricing | YourSite", "referrer": "https://www.google.com/", "user_agent": "Mozilla/5.0 ...", "utm_source": "google", "utm_campaign": "brand" }

The five UTM keys read are utm_source, utm_medium, utm_campaign, utm_term and utm_content. Ad click identifiers such as gclid are not sent as separate fields; they arrive as part of url and are read from it on our side.

page_view

Sent in two cases:

  1. A full page load inside a live session. For example, the visitor clicks a normal link to another page on your site. The session token is reused and the event carries only the common fields.
  2. A client-side navigation in a single-page app (history.pushState, history.replaceState or the back and forward buttons) that changes the path. Changes to the query string or hash alone are ignored. These events add timing fields for the page being left:
{ "event_type": "page_view", "duration_seconds": 47, "active_seconds": 32, "scroll_depth": 0 }
FieldMeaning
duration_secondsWall-clock seconds spent on the previous path
active_secondsActive seconds counted so far in this tab’s session (see Active time)
scroll_depthAlways 0 on single-page-app navigations at present. Scroll depth is reported on session_end

session_end

Sent when the tab is hidden (the visitor switches tab, minimises the window or locks the phone) or the page is unloaded.

{ "event_type": "session_end", "duration_seconds": 183, "active_seconds": 94, "scroll_depth": 100 }
FieldMeaning
duration_secondsWall-clock seconds on the current page
active_secondsActive seconds counted so far in this tab’s session
scroll_depthFurthest scroll position reached on the current page, as a percentage from 0 to 100 (100 if the page does not scroll)

Tab switching

Hiding a tab sends session_end. When the tab becomes visible again, the script creates a new session token and sends session_start. On our side, a session_start that arrives from the same IP address and browser within 30 minutes of an earlier session on the same site that is still open is added to that session rather than counted as a new visit. This is why one visitor’s tab switches do not normally show up as many separate sessions.


Active time and page time

The script measures two kinds of time:

  • Page time (duration_seconds): wall-clock time from arriving on a page until leaving it or hiding the tab. It includes time the tab sat in the background or the visitor was idle.
  • Active time (active_seconds): time during which the tab was visible and the visitor clicked, moved the mouse, pressed a key or touched the screen within the previous 30 seconds. It is checked every 5 seconds.

Active time is the better engagement signal. A pricing page left open in a background tab for 20 minutes has a large page time and almost no active time. A visitor reading every paragraph has the two close together.


The script reads consent from your consent manager if it finds one. Cookiebot, OneTrust, CookieYes, Transcend, Framer’s built-in banner, HubSpot, Klaro, Google Consent Mode v2 and IAB TCF v2 are detected automatically.

  • Consent denied: the script sends nothing.
  • Consent manager found, visitor has not chosen yet: the script holds up to 50 events and sends them if the visitor grants consent.
  • Consent granted: events are sent as normal.

How consent affects person-level identification, and what happens for visitors in different countries, is covered in Installing the tracker and Cookies & Consent.


The pulse command API

Once the script has loaded, window.pulse accepts commands. Calls made before that are queued by the install snippet and replayed in order, so you can call pulse(...) straight after the snippet.

CallWhat it does
pulse('consent', 'grant')Marks analytics consent as granted and sends any held events
pulse('consent', 'revoke')Marks consent as denied and discards any held events
pulse('consent', 'status')Returns { state, source, queued }. Only useful after the script has loaded, because a queued call has no return value
pulse('track', name, data)Sends a page_view carrying custom_event: name plus the fields in data

window.Pulse exposes the same consent controls as methods: Pulse.giveCookieConsent(), Pulse.revokeCookieConsent(), Pulse.consentStatus() and Pulse.track(name, data).

Use the manual consent calls only if you have no consent manager. If one is installed, let the tracker read it. A manual grant enables the session events above. Where person-level identification needs consent, it waits for a signal from a consent manager, and a manual grant does not unlock it.

pulse('track', ...) is not a custom events feature yet. The event is currently stored as an extra page view, and the name and properties you pass are not shown in the dashboard.


Transport

Events are sent with navigator.sendBeacon, which survives the page being closed. Browsers without it fall back to fetch with keepalive: true.

All events go to https://t.menoinfra.com/api/track. That edge proxy adds the visitor’s IP address and country and forwards the event to our ingestion service, which:

  • rejects events from a domain the tracking ID was not issued for (subdomains and localhost are allowed; see Installing the tracker);
  • rejects events while tracking for the site is paused;
  • runs company identification once a session has ended.

If your site sets a Content Security Policy, allow cdn.menoinfra.com in script-src and t.menoinfra.com in connect-src.


Troubleshooting

If sessions are not appearing in the dashboard a minute after you visit your own site:

  1. Is the script loading? Open devtools, go to Network and filter for menoinfra. p/YOUR_TRACKING_ID.js should return 200. No request at all means the snippet is not on the page or is not running.
  2. Are events being sent? Filter for api/track. If the script loaded but nothing is posted, check your Content Security Policy and whether a consent manager is holding the events.
  3. Is it the right tracking ID? The ID must be the one issued for this domain. Events from another domain are rejected.
  4. Check consent. In the console, run Pulse.consentStatus(). A state of denied means nothing is sent; pending means events are being held until the visitor accepts.

Platform-specific problems (smart quotes, WP Rocket, Cloudflare Rocket Loader, caching) are covered in install troubleshooting. Still stuck? Email support@getwarmai.com with your site URL and tracking ID.

Last updated on