Web analytics that show their working
Paste one script into your pages and this records every page load, then tells you where the traffic came from, which pages people read, what they clicked, and which hours of the week your visitors actually show up. Every figure links back to the events it was computed from, and every definition is written down.
The demo above is a real site on this installation with real traffic. No signup, nothing hidden.
What you get on every site
Each panel below exists in the product. The links open the public demo so you can see the real thing rather than a screenshot.
Traffic over time
Page views, unique visitors, sessions, average time on page and bounce rate, plotted by minute, hour, day, week, month, quarter or year, with any previous period overlaid for comparison.
See the graphsWhere visitors are and where they came from
A map of current visitors plus breakdowns by country, region, city, referring site, language, source, campaign and the exact pages they loaded.
See the map and tablesWhat people used the page for
Click heatmaps on a 24×24 grid, the elements visitors clicked most, and how far down the page they scrolled. Filter by page and by day.
See behaviour analyticsWhen your audience is awake
A 7×24 grid of visits by weekday and hour, in your own timezone, so you can tell a Tuesday-morning audience from a Sunday-evening one.
See the hourly gridGoals and conversions
Define a goal as a page URL or a clicked element, then watch sessions, visitors and conversion rate against it over any period.
See goalsCampaigns
Traffic and conversions grouped by the campaign parameters your links carry, so you can tell which campaign produced a signup rather than a click.
See campaignsHow it works
Add the site
Give it a name and the address it will appear on. Each site gets its own tracking script and its own set of pages.
Paste one block
Add the snippet at the bottom of every page you want counted. It loads asynchronously and makes no third-party requests.
Read the numbers
Visits arrive within a second or two and appear in the live counter. The hourly and daily rollups are recomputed just after midnight.
What the words mean
Analytics products rarely agree on these, so here is exactly what each one counts here. The same list is on the dashboard itself.
- View
- One page load. Counted when the page is first seen. The "time on page" report a browser sends when a tab is hidden updates that same view rather than adding another, so reloading a page does count twice.
- Session
- One browser tab, tracked with a per-tab cookie. Navigation inside the tab stays in the same session. Because it is a tab and not a person, three open tabs count as three sessions, and "online now" is measured in sessions rather than visitors.
- Visitor
- A distinct long-lived cookie. Clearing cookies, switching browser or a private window each start a new visitor, so visitor counts are a floor, not a headcount.
- Bounce rate
- Share of sessions that produced a single view. A session that spans midnight is counted on the day it started.
- Bots
- Matched from the user agent string. Bot traffic is counted separately and can be excluded from any chart with the humans/bots filter.
- Where the time comes from
- The server timestamps each event when it receives it, not the visitor's clock. Charts are drawn in the timezone of whoever is looking at them.
Tracking snippet
This is the entire integration. Replace the host with the address
this installation is reachable on and 41 with the id
of your own site. Put it before the closing
</body> tag on every page you want counted.
<script>
(function () {
var s = document.createElement('script');
s.async = true;
s.src = 'https://your-host/track.js';
s.addEventListener('load', function () { addWebsiteId(41); });
var first = document.getElementsByTagName('script')[0];
first.parentNode.insertBefore(s, first);
})();
</script>
The script is about 10 KB uncompressed, 3.4 KB over gzip, served with a one-hour cache header. It loads asynchronously and makes no third-party requests — the only network calls it makes are the beacons back to this host.
What the script sends
Everything below is sent by the snippet itself. Nothing is fetched from a third party and no cross-site identifier is read.
On every page load
One page view, sent immediately. It carries the page URL, the
referring URL, viewport size, browser language, platform and
user agent string, plus campaign values from
utm_source, utm_medium and
utm_campaign (or the shorter
source, medium,
campaign).
When the tab is hidden
A duration update carrying how long the page was actually visible. The server applies it to the page view already stored rather than creating a second one, so closing a tab never inflates the count.
Behavioural events
Clicks (with the element path, tag, id or class, up to four levels deep), scroll depth as a percentage, sampled cursor positions and viewport resizes. These are what the heatmaps and scroll figures are built from.
Identifiers
Two values are stored in the visitor's own browser and sent with each event. Neither is a third-party cookie and neither is shared between sites:
localStorage afw — a long-lived
visitor id. This is what "unique visitors" counts.
sessionStorage afw_sid — a per-tab
session id, discarded when the tab closes. This is what "online
now" counts, which is why it measures tabs rather than people.
The server also receives the request IP and resolves it to a country, region and city at ingest; the stored row keeps that resolution, not the address.
Turning behaviour tracking off
Click maps, scroll depth and cursor sampling are the only part that watches interaction rather than page loads. Disable them before the script loads, either on the script tag:
<script data-behavior="off" src="https://your-host/track.js"></script>
or in your own code, which also lets you tune the sampling rate:
window.AFW_CONFIG = { behavior: false };
// or: { behavior: { moveSampleMs: 2000, flushIntervalMs: 5000, maxBatchSize: 50 } }
Page views, sessions and campaign attribution keep working; the behaviour panels simply stay empty.