VisitTrack
Start free
☰ Browse the docs

Tracking

Cookieless mode

Track without storing anything in your visitors' browsers — how it works, what you give up, and when you still need a consent banner.

Snippets use YOUR_SITE_ID as a placeholder. Sign in and they're filled in with your real site id.

VisitTrack has three tracking modes. Standard (the default) keeps an anonymous visitor id in a first-party cookie, so the same person is recognised across days. Cookieless stores nothing on the visitor's device at all. Cookieless in the EU (hybrid) is cookieless for visitors in the EU/EEA, UK and Switzerland and standard for everyone else.

Turn it on

  • For the whole site: Settings → General → Tracking mode, or tick Use cookieless tracking script during install. This is enforced on our servers, so it applies to every script tag on the site — even ones installed without the attribute.
  • Per script tag: add data-cookieless. Useful if only some of your pages need it.
code
<script defer data-site="YOUR_SITE_ID" data-cookieless src="https://visitrack.app/tracker.js"></script>

How it works

In cookieless mode the script never writes a cookie, localStorage or sessionStorage entry for a visitor id (and clears the standard-mode ones if they exist). Instead, our server derives a visitor id from a one-way hash of the site, the visitor's IP address and user agent, and a secret salt that changes every day (UTC). The IP and user agent only feed the hash and are never stored, and because the salt changes daily, the same browser gets a new, unrelated id each day — visits on different days aren't linked.

What you give up

StandardCookieless
Pageviews, sources, pages, countries, devicesFullFull
Unique visitors within a dayFullFull (people sharing an IP and browser can merge)
Returning visitors, retentionFullLimited — anyone returning after 24h counts as new
Users and Journeys tabsFull historyLimited to a single day
Revenue attributionWorks across daysSame 24h window — or across days if your app sends the first touch back (below)
Cross-domain trackingSupportedNot available
Custom events, goals, funnels, Web VitalsFullFull

Users, Journeys and Retention show a notice while a site is cookieless, so nobody reads a shortened history as a real drop.

Hybrid: cookieless in the EU only

The third mode, Cookieless in the EU (hybrid), decides per visitor. On each visit our server looks up the country from the IP (the IP is not stored). Visitors in the EU/EEA, the UK or Switzerland — and anyone whose location can't be determined — are counted exactly like cookieless mode. Everyone else is told by the server that the script may store a first-party _vt_vid id, so you keep multi-day journeys and revenue attribution for them.

  • Turn it on in Settings → General → Tracking mode, and add data-hybrid to the script tag so it stores nothing until the server has answered. Without the attribute the script briefly writes an id on the first page load and deletes it again for EU visitors.
  • Visitors are counted consistently: the first hit is counted under the server-side hash, and a non-EU visitor then keeps that same id, so the first page and every later visit are one visitor.
  • Hybrid still sets a first-party identifier for visitors outside the EU. Consent rules there vary (some US states, Brazil, Canada, and others have their own) — check what applies to your audience. This is general information, not legal advice.
code
<script defer data-site="YOUR_SITE_ID" data-hybrid src="https://visitrack.app/tracker.js"></script>

Keep first-touch attribution across days

Without a stored id, someone who reads your blog on Monday and signs up on Thursday is a new visitor on Thursday, and the signup is credited to Thursday's source. You can fix that without any cookie from us: the script tells your page where the current visit came from, your own app keeps it (in the signup form, its own session or its database), and sends it back when the person signs up. We then set that visitor's first touch — only if it's earlier than what we already have — and payments later attributed to them are credited to the original source.

window.visitrack.attribution() returns { referrer, utmSource, utmMedium, utmCampaign, landingPath, landedAt } for the current visit, or null before the script knows. It works on any page of the visit, not just the landing page. Keeping it is your app's decision and your app's storage — treat it like any other data your signup form collects.

Plain HTML form

Add a hidden input with data-vt-first-touch; the script fills it with the visit's attribution as JSON (only if empty, so a value your app saved earlier wins). Send it back on the thank-you page, or from your server (below).

code
<form action="/signup" method="post">
  <input name="email" type="email" required />
  <input type="hidden" name="first_touch" data-vt-first-touch />
  <button>Create account</button>
</form>

<!-- after signup, in the browser -->
<script>
  window.visitrack.identify("usr_8f2a1", { plan: "free" }, {
    firstTouch: JSON.parse(savedFirstTouchJson), // what your app stored at signup
  });
</script>

Next.js server action

code
// app/signup/actions.ts
"use server";

export async function signup(formData: FormData) {
  const user = await createUser(String(formData.get("email")));

  // The hidden data-vt-first-touch field, kept on your own user row so
  // it survives until the first payment.
  const raw = formData.get("first_touch");
  const firstTouch = typeof raw === "string" && raw ? JSON.parse(raw) : undefined;
  await saveFirstTouch(user.id, firstTouch);

  await fetch("https://visitrack.app/api/track", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.VISITRACK_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      type: "event",
      visitorId: user.id, // use this same id as the checkout metadata visitor id
      name: "signup",
      firstTouch,
    }),
  });
}

In cookieless mode there's no browser visitor id to pass through checkout, so use your own stable user id as visitorId both here and in your payment provider's metadata. The payment is then attributed to this visitor, and credited to the first touch you sent. The server-side call is documented in full at /docs/server-events.

  • landedAt is required (an ISO date or epoch milliseconds). A value in the future or more than two years old is ignored.
  • referrer is capped at 500 characters, landingPath at 500 (a full URL is reduced to its path), each UTM value at 200. Anything else in the object is dropped.
  • A first touch only ever moves back in time: if we already hold an earlier one for this visitor, yours is ignored.

This is general information, not legal advice — check with your own counsel for your situation.

  • Standard mode stores a persistent identifier on the visitor's device. In the EU and UK, the ePrivacy Directive (and national laws implementing it, like PECR) generally requires consent before storing or reading such an identifier unless it's strictly necessary for a service the user asked for — and analytics usually isn't. Some regulators (e.g. France's CNIL) allow narrow audience-measurement exemptions, but you shouldn't assume one applies. Plan on a consent banner, or load the script only after consent.
  • Cookieless mode stores and reads nothing on the device, which is what the ePrivacy consent rule is about, so it's designed to run without a cookie banner. GDPR still applies to the processing itself (the IP address is personal data while it's being hashed), so mention VisitTrack in your privacy policy.
  • Outside the EU/UK, rules differ; standard mode is often fine with a privacy-policy disclosure, but some jurisdictions have their own consent or opt-out rules. Hybrid mode applies the cookieless behaviour in the EU/EEA, UK and Switzerland only — it still stores a first-party id everywhere else.

A good default

If you already show a consent banner, keep standard mode and load the script after consent — you get the most accurate data. If you don't want a banner at all, use cookieless mode.

Something missing? Tell us.

AI agent or LLM? Read this page as markdown.