Whotrack

Docs

How to connect your website, send events from your server, record conversions and tag your ads. Whotrack labels every visit human, bot or unknown, inferred from the evidence it records, and shows that evidence next to the label.

Install the snippet

Add this tag to the <head> of every page on your site:

<script defer src="https://c.whotrack.com/s.js" data-site="YOUR_SITE_KEY"></script>

Your site key is in Setup, which shows this tag with the key already filled in.

Whotrack accepts events only from the origin you entered when you added the website, and the match is exact: scheme, subdomain and port. https://example.com, https://www.example.com and http://example.com are three different origins, so enter the one your pages are served from.

The script respects Do Not Track: in a browser that sends it, the script records nothing. Do Not Track does not cover visits a website reports from its own server: those are recorded whatever the browser sends. The script sets no cookies and never reads what people type into forms. As evidence for each verdict, it counts input events: clicks and taps, key presses, mouse or pen movement, and scrolling with a wheel or touch. It does not record which key was pressed or what a field contains. Single-page apps need nothing extra: a page change through the browser’s history is recorded as a new page view.

To check the install, open a page of your site, then Overview in the dashboard. The visit appears within 10 seconds.

First-party proxy

An option, not the default install. Your own web server forwards two paths to Whotrack, so the script and the events it sends come from your domain instead of c.whotrack.com.

It has costs today. Through a proxy, Whotrack sees your server’s address instead of the visitor’s: countries show your server’s location, and the limit of 600 events per minute per address applies to your whole site.

The tracker sends events to /collect at the root of the origin it was loaded from, wherever the script itself sits. So forward /collect at the root of your site. Forwarded under a sub-path instead, it is never called, and those events are lost.

For collection that ad blockers cannot affect, use server ingest: it runs on your server, not in the browser, and it passes the visitor’s own address.

What to forward

  • /s.js, the script
  • /collect, where the script sends events

Set the Host header to the upstream’s, c.whotrack.com. Pass Origin and User-Agent through unchanged, as Caddy and Nginx do by default: Whotrack accepts an event only when its Origin matches your site’s origin, and the user agent is part of the evidence for each verdict.

Caddy

In your site’s block, move your existing directives into a final handle, so a rewrite such as try_files cannot catch /collect first:

example.com {
	@whotrack path /s.js /collect
	handle @whotrack {
		reverse_proxy https://c.whotrack.com {
			header_up Host {upstream_hostport}
		}
	}
	handle {
		# Your site’s existing directives, unchanged
	}
}

Nginx

Add both locations inside your site’s server block. proxy_ssl_server_name on sends the collector’s name in the TLS handshake. The last two lines make Nginx check the collector’s certificate, which it otherwise skips; the bundle path shown is Debian’s and Ubuntu’s, and other systems keep it elsewhere.

location = /s.js {
    proxy_pass https://c.whotrack.com;
    proxy_set_header Host c.whotrack.com;
    proxy_ssl_server_name on;
    proxy_ssl_verify on;
    proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
}
location = /collect {
    proxy_pass https://c.whotrack.com;
    proxy_set_header Host c.whotrack.com;
    proxy_ssl_server_name on;
    proxy_ssl_verify on;
    proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
}

The snippet

Then load the script from your own domain:

<script defer src="/s.js" data-site="YOUR_SITE_KEY"></script>

Server ingest

The script only sees browsers that run it. Your server sees every HTML request, including crawlers and other clients that never run JavaScript, and ad blockers cannot affect it. Server events go to https://c.whotrack.com/ingest/server, sent with your site’s server secret.

The server secret is shown once, when you add the website, and Setup can issue a new one. Keep it on your server: Whotrack treats anything sent with it as coming from your server.

Express

The reference middleware, whotrackMiddleware in integrations/express.ts, reports each HTML page your app serves. Setup shows the middleware file to copy into your app, and the mount code with your site’s values filled in. Mount it before your page routes:

import { whotrackMiddleware } from './integrations/express.js';

app.use(whotrackMiddleware({
  collectorUrl: process.env.WHOTRACK_COLLECTOR_URL!,
  siteKey: process.env.WHOTRACK_SITE_KEY!,
  serverSecret: process.env.WHOTRACK_SERVER_SECRET!,
  siteOrigin: 'https://example.com',
}));
OptionValue
collectorUrlhttps://c.whotrack.com, kept in WHOTRACK_COLLECTOR_URL
siteKeyYour site key from Setup, kept in WHOTRACK_SITE_KEY
serverSecretYour server secret, kept in WHOTRACK_SERVER_SECRET
siteOriginYour site’s origin, exactly as you entered it in Whotrack

The middleware gives each HTML response a request ID. Render it in the page’s <head>, before the snippet, so the server’s record of the request and the browser’s page view join one visit. In EJS:

<meta name="whotrack-request-id" content="<%= whotrackRequestId %>">

Whotrack does not link server and browser records by address or timing alone.

For each completed GET or HEAD request that returns HTML, including error pages with their status code, the middleware sends:

  • the page URL with only its attribution parameters (utm_source, utm_medium, utm_campaign, utm_content, utm_term and ref), and for an ad click ID such as gclid, only that one was present
  • the referrer, without its query string
  • the user agent and the visitor’s IP address
  • a short list of navigation headers: Accept, Accept-Language, the Sec-Fetch-* headers, Sec-CH-UA, Sec-Purpose and Purpose

It does not send redirects, assets or other responses that are not HTML, and never sends cookies, authorization headers, request bodies or other query parameters. Whotrack reduces the navigation headers to facts such as “sent Accept-Language” and does not store their values. Only server ingest with these headers lets Whotrack check whether a request’s headers fit the browser it claims to be; the snippet alone does not make that check. It uses the IP address to look up the country and to group server requests into visits, and does not store it.

Pages served from a CDN cache never reach your app, so the middleware cannot see them. Delivery is best effort: one attempt with a 3-second timeout, and a failure is logged, not retried.

HTTP API

From any other server, send JSON to https://c.whotrack.com/ingest/server with the header Authorization: Bearer YOUR_SERVER_SECRET:

{
  "siteKey": "YOUR_SITE_KEY",
  "eventId": "unique-retry-stable-id",
  "type": "request",
  "requestId": "id-embedded-in-html",
  "url": "https://example.com/pricing",
  "userAgent": "the request's User-Agent header",
  "ip": "the visitor's IP address"
}

type is request, event or conversion, and event and conversion also need a name. Other optional fields are referrer, status (the page’s HTTP status), headers (the navigation headers listed above) and visitorId.

Send userAgent and ip whenever you have them: they feed the verdict and the country. Leave userAgent out if you did not capture it, and send an empty string only when the request really had none. An event ID Whotrack has already received is ignored, so a retry never counts twice.

Conversions and goals

Record a conversion in the browser after a form has been submitted successfully, or on the thank-you page it leads to:

window.whotrack?.convert('lead');

The ?. skips the call when the script is not running, for example in a browser that sends Do Not Track. A second argument can carry details such as { form: 'quote' }; keep names, email addresses and other personal details out of it.

Then create a goal on the Conversions page with the same event name, lead. The goal counts the visits that reach it, and each of those visits keeps its human, bot or unknown verdict.

Browser conversions are unverified: anyone who knows your public site key can send one. For a conversion your application has confirmed, such as a paid order, send it from your server with a stable eventId, so a retry counts once:

await sendServerEvent(options, {
  type: 'conversion',
  eventId: `paid-order-${order.id}`, // the same ID on every retry
  name: 'paid_order',
  url: 'https://example.com/checkout',
  visitorId: checkout.whotrackVisitorId, // optional: window.whotrack.visitorId, sent from the browser
});

sendServerEvent comes with the reference middleware. From another server, send the same fields to the HTTP API. A goal shows these as Server-confirmed. Whotrack does not check the order itself: server-confirmed means sent with your server secret.

URL templates

Whotrack reads UTM parameters from the address a visit lands on. Add them to your ads so each visit shows where it came from.

Meta

Paste this into the URL parameters field of each ad:

utm_source=facebook&utm_medium=paid&utm_campaign={{campaign.name}}&utm_content={{ad.name}}&utm_id={{campaign.id}}

Google Ads

Keep auto-tagging on, and set the account’s final URL suffix to:

utm_campaign={campaignid}&utm_content={creative}

Auto-tagging adds a Google click ID, which marks the visit as a paid click from Google. Google fills in {campaignid} with the campaign’s number, so Google campaigns appear by that number.

What reports use today

Reports use utm_source and utm_campaign, in the source and campaign breakdowns, each split by verdict; utm_medium also helps decide the channel. Whotrack stores utm_content and utm_term with each visit but does not keep utm_id yet. For click IDs such as gclid and fbclid, it keeps only which one was present, not its value.

Whotrack will add a breakdown per ad, from utm_content, and human conversions per campaign. Coming soon

Whotrack sees visits to your own website. Meta Instant Forms are filled in inside Facebook and Instagram and never load your site, so Whotrack cannot see or label those leads.

Email tracking

Whotrack can record when an image in an email loads. On the Emails page, create a tracking image for one message to one recipient, and paste the HTML it gives you into that email before you send it. Whotrack does not send the email. Use a new image for every recipient and every message.

Copy the HTML when it is created: Whotrack keeps only a hash of the secret part of the image’s address, so it cannot show the address again. Emails then lists each load with its time, the declared user agent of whatever requested the image, and an approximate country.

A load does not prove that a person opened or read the email. Mail apps, privacy proxies, security scanners and forwarded copies all load images, and Apple Mail can load them in the background without the message being opened. No load does not mean unread either: many mail apps block images, and a plain-text email cannot carry one. So Whotrack never labels an email load as human: each load is marked as an image proxy, a declared bot or unknown.

Email loads are kept apart from website visits, conversions and CSV exports.

Ad integrations Coming soon

None of these is available yet. Setup steps will be here when they ship.

Meta Conversions API forwarding (Pro)

You will paste a Pixel ID and a Conversions API token from Meta Events Manager into a site’s settings. Whotrack will then send Meta the conversions from visits labelled human, and leave out bot and unknown visits unless you choose to include unknown ones.

Google Ads conversion file (Pro)

Each site will get a private address for a file of the last 90 days of human conversions that carry a Google click ID, in Google Ads’ offline conversion import format, for a scheduled upload in Google Ads.

Ad spend import

Whotrack will read daily spend per campaign and ad from Meta and Google once both platforms have reviewed its access. With spend in place, it will estimate the spend on visits labelled bot and show how the estimate is made.