Skip to content

Analytics ​

Every Events Stream has an Analytics view: unique visitors, visits, pageviews, bounce rate and visit duration, where visitors came from, what they read, where they are, what they browse with, which goals they hit, how fast your pages load for them, and which JavaScript errors they ran into.

Websites add one script. Apps send the same events over the HTTP API. Open it under Telemetry: pick an Events Stream and choose Analytics (next to Explore).

Add the script ​

Issue an ingest key for an Events Stream (Manage Streams → Keys & Setup), then add this to every page's <head>:

html
<script defer data-key="lit_..." src="https://latency.app/js/script.min.js"></script>

That's it. Pageviews start arriving within seconds, including navigation in single-page apps (React, Vue, Svelte, Next.js and the like). The key only writes to its one Stream, so it's safe in page code; give it a key limit so a copied key can only send a little. The readable source is at latency.app/js/script.js if you want to review it or host your own copy.

AttributeMeaning
data-keyThe ingest key (required)
data-visitor-keyKeep a visitor id in localStorage under this name (see Visitors)
data-hashCount #hash changes as pages
data-localAlso track localhost, which is ignored by default
data-apiSend somewhere other than https://ws.latency.app, such as a proxy on your own domain

What's tracked ​

How
PageviewsEvery page load and single-page app navigation
SourcesThe referrer, or utm_source / ref / source in the URL. Known sites show by name (Google, Hacker News, X)
Campaignsutm_medium, utm_campaign, utm_content, utm_term
PagesTop pages, plus entry and exit pages of each visit
LocationsCountry, region and city from the visitor's IP address. The address itself is never stored
DevicesBrowser, OS and screen size (mobile, tablet, laptop, desktop)
Time on page and scroll depthSent when the visitor leaves or switches tabs. This makes visit duration accurate, not a guess from the gap between pageviews
GoalsCustom events, outbound link clicks (outbound) and file downloads (download)
Page speedCore Web Vitals from real visits (below)
JavaScript errorsUncaught errors and unhandled promise rejections, the first 10 distinct ones per page load

A visit is one visitor's activity with no gap longer than 30 minutes. A bounce is a visit with a single pageview.

Crawlers, headless browsers and traffic from cloud data centers are acknowledged and dropped, so they don't count as visitors or toward your plan.

Visitors ​

By default the script is cookieless: it stores nothing in the browser. A visitor is a hash of the site's hostname, the visitor's IP address and browser, and a random salt that changes every day and is deleted after two days. The IP address is never stored, and a hash can't be traced back once its day is over. The trade-off: someone who comes back tomorrow counts as a new visitor, and people sharing one network and the same browser version count as one.

If your site already keeps an anonymous id in the browser, name it with data-visitor-key and the script uses it (creating one if it's missing). Visitors are then counted across days and devices can't collide. Storing an id in the browser may need your visitors' consent where you operate.

Goals ​

Track anything that matters, with optional properties:

js
latency.track('Signup', { plan: 'pro' });

Or tag an element, no code needed. Clicks send the event with every data-latency-prop-* attribute as a property:

html
<a href="/signup" data-latency-event="Signup" data-latency-prop-plan="pro">Start free trial</a>

To track before the script has loaded, add this line above it; calls queue up and are sent once it loads:

html
<script>window.latency = window.latency || { q: [], track: function () { this.q.push(arguments); } };</script>

For a 404 page, send latency.track('404', { path: location.pathname }).

In Analytics, Goals shows visitors, events and conversion rate for each. Click one to filter the whole view by it and break it down by a property. Funnel shows how many visitors did a series of steps in order (a page, then another page, then a goal), with at most 30 minutes between steps. End a page with * to match everything under it, like /docs/*.

Page speed ​

For each page load the script measures the Core Web Vitals and sends them once. Analytics shows the 75th percentile, overall and per page, rated the way Google rates them:

GoodPoor
LCP, Largest Contentful Paintup to 2.5sover 4s
INP, Interaction to Next Paintup to 200msover 500ms
CLS, Cumulative Layout Shiftup to 0.1over 0.25
FCP, First Contentful Paintup to 1.8sover 3s
TTFB, Time to First Byteup to 800msover 1.8s

Pages loaded in a background tab don't report paint timings, which would otherwise be meaningless.

Filters ​

Click any row (a source, page, country, browser, goal) to filter every number on the page by it, and remove filters from the chips at the top. Explore has the raw events for anything the view doesn't answer: they're ordinary events named pageview, engagement, js_error and your goals.

From an app ​

Apps send the same events to POST /v1/ingest. Name the app with an X-Latency-Client header, use an app:// URL for the screen, and send your app's own device id as visitor_id:

bash
curl -X POST https://ws.latency.app/v1/ingest \
  -H "Authorization: Bearer $LATENCY_KEY" \
  -H "X-Latency-Client: ios.example.com" \
  -H "Content-Type: application/json" \
  -d '{"name": "pageview", "url": "app://ios.example.com/cart", "visitor_id": "6f9619ff-8b86-d011-b42d-00c04fc964ff"}'

Location and OS come from the request. Send goals the same way.

Plans and billing ​

Pageviews, goals and errors count toward your plan's monthly events. The time-on-page pings the script sends on its own don't.

© 2026 Latency Labs LLC