Skip to content

7 min read

Analytics without cookie consent: what GDPR and ePrivacy require

ePrivacy and GDPR ask different questions, and cookieless isn't automatically exempt. A careful look at consent, the in-memory fallback and a consent API example.

Most founders looking for analytics without cookie consent under GDPR want one thing: to measure their site without a banner. Sometimes that's achievable. Often the reason it looks achievable is that two different sets of rules are being treated as one. This post separates them, explains why "cookieless" doesn't settle the question, and describes a pattern that keeps attribution working for visitors who decline.

This is not legal advice. It's a founder's reading of how the rules fit together, written to help you ask better questions. The rules are applied by national laws and national regulators, they vary by country, and they change. Before you rely on any of this for your site, check with a privacy professional who knows your jurisdiction.

Which rules apply to analytics in the EU?

Two, and they ask different questions.

ePrivacy. Article 5(3) of the ePrivacy Directive (2002/58/EC, amended in 2009) covers storing information on a user's device, or reading information already stored there. That needs the user's consent, with narrow exceptions: when it's needed purely to carry a communication, or when it's strictly necessary to provide a service the user explicitly asked for. It applies whether or not the information is personal data. Each country implements the directive in its own law, which is a big part of why the details differ from one country to the next.

GDPR. The GDPR applies when you process personal data, and an identifier tied to a browser or device generally counts. Every processing purpose needs a legal basis under Article 6. There are six; for analytics the realistic candidates are consent and legitimate interests, and legitimate interests requires a balancing test you should be able to show. On top of that come transparency, data minimization, retention limits, processor contracts and rules on transfers outside the EU.

ePrivacy, Article 5(3)GDPR
What triggers itStoring or reading information on the deviceProcessing personal data
Does it matter if the data is personalNoYes
What lets you proceedConsent, or a narrow strictly-necessary exceptionOne of six legal bases, plus the other principles
Where the detail livesNational law in each countryThe regulation itself, plus national guidance

Both can apply to the same script at once. Having a GDPR legal basis for your analytics doesn't cover the device storage, and consent for the storage doesn't settle everything you do with the data afterwards.

Why isn't "cookieless" automatically exempt?

Because Article 5(3) is about the device, not about cookies. localStorage, sessionStorage and IndexedDB are storage on the device. Reading device characteristics to build a fingerprint is access to information on the device. Swapping a cookie for a localStorage key changes the API, not the legal position.

The European Data Protection Board has published guidelines on the technical scope of Article 5(3) (Guidelines 2/2023) that read it broadly, covering techniques beyond cookies, including some uses of tracking pixels and URL parameters. How each national regulator applies that reading is up to them, which is one more reason to check locally.

Approaches that store nothing on the device, such as counting visitors with a server-side hash of IP address and user agent, may stay outside Article 5(3). They're still processing personal data under the GDPR, so they still need a legal basis, minimization and a privacy notice. Some regulators have also published conditions under which audience measurement can run without consent. France's CNIL is one example. Those conditions are strict about purpose, scope and retention, and they're specific to that regulator.

What can you usually do without consent?

Hedged, and subject to your local rules:

  • Keep server logs needed to run and secure the service.
  • Count aggregate visits without storing or reading anything on the device, with a legal basis for the processing.
  • Store the visitor's consent choice itself, which is commonly treated as necessary.

What you can't do honestly without consent is recognise the same browser over time. Knowing that today's signup first arrived two weeks ago from a particular post requires an identifier that survives between visits, which means storing something on the device. That's the part that needs consent, and there's no clever technical route around it that is also straightforward with visitors.

The in-memory-until-signup pattern

There's a narrower question that can often be answered without device storage: for a visitor who signs up in the same visit, where did that visit start?

The pattern:

  1. On page load, before any consent decision, work out the first touch: path, referrer hostname, UTM parameters. Keep it in a JavaScript variable. Write nothing to storage and send nothing.
  2. If the visitor accepts analytics, store an anonymous ID and send one session event as normal.
  3. If they decline or ignore the banner, keep the variable in memory only.
  4. If they create an account while it's still in memory, send it along with the signup.

The case for this being reasonable is that it uses only what the browser already provided to load the page, nothing persists on the device, and the data leaves the browser only as part of an account the person chose to create. Whether reading the page URL and referrer counts as "access" under your country's version of Article 5(3) is exactly the kind of question to put to your advisor, given how broadly some read the rule.

The GDPR side doesn't go away. Linking a landing page to an account is processing personal data, so it needs a legal basis (legitimate interests is the usual candidate), a line in your privacy policy, and minimization. Store the referrer's hostname rather than the full URL, which can carry personal data in its query string, and don't keep IP addresses.

It has real limits. A full page load on a multi-page site throws the variable away, so the pattern covers signups on the landing page and single-page apps, not someone who reads three posts and then signs up. Return visits are lost entirely. Your attributed share will fall, and it should be shown rather than hidden.

What does a consent API look like?

A minimal version in TypeScript. Your consent banner calls setAnalyticsConsent when the visitor decides, and on later page loads if a stored choice exists. Your signup handler calls onSignup.

type FirstTouch = { path: string; ref: string | null; utm: Record<string, string> };

const params = new URLSearchParams(location.search);
let ref: string | null = null;
try {
  ref = document.referrer ? new URL(document.referrer).hostname : null;
} catch {}

// Held in memory only. Nothing is stored or sent before consent.
const firstTouch: FirstTouch = {
  path: location.pathname,
  ref: ref === location.hostname ? null : ref,
  utm: Object.fromEntries(Array.from(params).filter(([k]) => k.startsWith("utm_"))),
};

let consented = false;

export function setAnalyticsConsent(granted: boolean) {
  consented = granted;
  if (granted) {
    const id = localStorage.getItem("aid") ?? crypto.randomUUID();
    localStorage.setItem("aid", id);
    navigator.sendBeacon("/api/session", JSON.stringify({ id, ...firstTouch }));
  } else {
    localStorage.removeItem("aid"); // withdrawal removes what was stored
  }
}

export function onSignup(userId: string) {
  navigator.sendBeacon(
    "/api/identify",
    JSON.stringify({
      userId,
      id: consented ? localStorage.getItem("aid") : null,
      firstTouch: consented ? null : firstTouch, // in-memory fallback
    }),
  );
}

A real version sends the session event once per session rather than on every page, and handles browsers where storage throws. The shape is the point: consent switches storage on and off, withdrawal deletes what was stored, and the fallback only travels with a signup.

Questions to take to your advisor

  • Which country's rules apply: where you're established, where your visitors are, or both.
  • Whether anything your analytics stores or reads fits a strictly-necessary exception, or a national exemption for audience measurement.
  • Whether the in-memory pattern counts as access to the device under your national rule.
  • Which legal basis covers linking a first touch to an account, and how you document it.
  • What your privacy policy says about it, how long you keep it, which processors see it, and whether any of it leaves the EU.

Joinsight's snippet follows the pattern above. Before consent it stores nothing on the device and keeps the first touch in memory, sending it with identify() if the visitor signs up. After joinsight("consent", true) it stores an anonymous ID, js_aid, in localStorage, and joinsight("consent", false) removes it. It doesn't store IP addresses. The details are on the privacy page, and the first-party analytics and ad blockers post covers the other half of why visitors go missing. None of that makes the legal call for your site, which stays with you and your advisor.

See what makes you money.

Connect Stripe or RevenueCat and Search Console, and you see combined MRR and 16 months of search history on day one. Page and keyword revenue follow as the snippet collects first touches.

No card required. The trial clock starts when your first revenue source connects.