Skip to content

Half the EU sites we measured tracked us before we could answer the banner

On 29 July 2026 we crawled 1,845 European domains and watched what left the browser before we touched anything. A tracker fired before any click, keypress or banner choice on 641 of them. What share that is depends on what you divide by: 48.8% of the 1,314 rows that loaded at all, or 54.0% of the 1,186 rows that produced any detection. Both bases are explained below, and every breakdown in this post is computed on the narrower one. The median site made its first third-party request 419 ms after the scan started (n=1,062), which is quicker than most people can find the cookie dialog, let alone read it.

We measured it with two instruments rather than one: a Playwright crawler for the corpus, and Statable GDPR Checker, our free extension, which runs the same 101 signatures in a real browser on a real tab. They agree on most sites and disagree in ways worth reading, which is the second half of this post.

What we found

Six numbers carry the whole scan. Every percentage below has its own denominator, because they are not all measured on the same set of sites.

  • Pre-consent tracking is the normal state of the EU web. 641 of 1,186 sites (54.0%) contacted a tracker before interaction. The verdict split on the same 1,186: 531 red (44.8%), 345 amber (29.1%), 178 unknown (15.0%), 132 green (11.1%).
  • It happens faster than a banner can render. Against that 419 ms median the mean was 945.6 ms (n=1,062), dragged up by a slow tail, and four out of five sites made third-party contact inside one second.
  • A consent platform does not stop it. We recognised a CMP on 351 of 1,186 sites (29.6%). Of those 351, 265 (75.5%) still sent data to trackers before any interaction. Most vendors land between 66% and 92%, though Complianz reaches 49.1% on the same sample size that gives OneTrust 81.8%.
  • Genuinely quiet sites are rare. Only 124 of 1,186 sites (10.5%) made zero third-party contact of any kind before interaction, fonts and maps and embeds included.
  • It is mostly Google. Pre-consent signatures on the 1,186: google-fonts 542 (45.7%), GA4 443 (37.4%), GTM 330 (27.8%). Counted by host, googletagmanager.com led on 544 domains, and the biggest non-Google host was connect.facebook.net.
  • The country spread is wide. Poland sits at one end, with a pre-consent tracker on 64.9% of its 134 domains, and Germany at the other on 40.0% of 100. Both operate under the same regulation.

Two things about how we got there, up front. The scanner never clicks anything and never touches a consent banner, so "before interaction" here is a property of how the crawl is built rather than a stopwatch reading. It also waits 15 seconds after load on every domain, which covers 96.3% of all third-party requests we recorded. The next two sections spell out both.

These are behavioural findings about what browsers loaded on 29 July 2026, not legal verdicts about any site.

Methodology

Runpilot-fresh-20260729, 29 July 2026, single day
Corpus1,845 domains attempted, 1,761 rows after HAR replay, 1,186 with a detection
Instrument APlaywright headless Chromium, requests captured at initiation
Instrument BStatable GDPR Checker v0.9.1, chrome.webRequest, observational only
Observation window15 s after load (crawler), first interaction or 30 s (extension)
Classifier101 signatures, ruleset 1.3.0, byte-identical across both instruments
Engine0.2.0, commit 2badab8
SourcesMajestic Million (CC BY 3.0), OpenStreetMap (ODbL)
Data licenceCC BY 4.0

Two instruments answer the same question in different ways. This section covers all of it:

  1. What "before consent" means here, the operational definition both instruments are held to.
  2. Why every percentage is out of 1,186, and what the other 659 rows were.
  3. Layer one: the crawler, its budgets, the three input events it does perform, and how it treats robots.txt.
  4. Layer two: the extension, which measures the window rather than assuming it.
  5. How the sample was drawn, deterministically, so it can be rebuilt.
  6. Does it come out the same twice, the repeat scans and how far they agreed.

Everything we know to be wrong with this method is in Where this method is weak, further down. It is not a short list.

The claim in this post is narrow. A request left the browser for a third-party host, or a cookie got written, before the visitor did anything that could count as a choice. No click on Accept, no click on Reject, no dismissal of a banner. That is the whole definition, and the two layers arrive at it differently.

The crawler (repo statable.com-crawler, Playwright, engine 0.2.0, ruleset 1.3.0, commit 2badab8) opens a fresh BrowserContext per domain and intercepts requests at initiation via page.on('request'). It never clicks and never presses a key, so every network detection carries preConsent: true by construction. That is a constructed condition, not a measured one, and we would rather say it plainly than dress it up. There is no second "after consent" pass at all. One deliberate exception lives on the input side, and the next section spells it out.

The extension (Statable GDPR Checker v0.9.1, separate repo, Chrome/Firefox/Edge) measures rather than constructs. Its window opens at navigationStart and closes at the first real interaction (a content script listens for pointerdown, keydown, touchstart) or at a fixed timeout, whichever comes first. chrome.webRequest is observational only and never blocks, and a chrome.cookies stream catches cookies written by JavaScript. In the validation run a harness drove it without interacting, so window_status stayed observing on every row, which means pre-consent was constructive there too.

The two share a classifier, not a measuring layer. The extension uses the same signature set and the same verdict logic, but its own instrumentation, in its own repository, synced by hand.

Why every percentage is out of 1,186

The 29 July 2026 EU run (pilot-fresh-20260729) attempted 1,845 domains. HAR replay produced 1,761 rows. Of those, 1,186 carried at least one detection, and 1,186 is the denominator for every share in this post.

StageRows
Domains attempted1,845
Rows after HAR replay1,761
Rows with detections1,186

Funnel from 1,845 domains attempted down to the 641 that contacted a tracker before interaction, showing both denominators

Most of that gap is fetch failure, and the crawler section below breaks those failures down. A domain that returned HTTP 403 to our scanner tells us nothing about its tag behaviour, so counting it as clean would flatter the web. Counting it as dirty would be worse. It leaves the denominator instead.

Some of the gap is not failure, and that cuts against us. 128 of the 575 excluded rows returned ok, scanned one page and produced no detection at all; 87 of those made no third-party request of any kind. They stay out of the main denominator because a zero-detection row cannot be told apart from a ruleset gap by inspection. The boundary is imperfect in the other direction too: 227 of the 1,186 rows we do count came back error, timeout or blocked and still produced a detection. So the base is not "sites that loaded". It is rows that produced at least one detection.

On the wider base of 1,314 rows that either loaded cleanly or produced a detection, the headline is 641 of 1,314 (48.8%), the verdict split is 531 red, 345 amber, 260 green, 178 unknown, and zero third-party contact rises to 211 of 1,314 (16.1%). Germany moves furthest, from 40.0% to 27.2% on 147 rows. We report the narrower base throughout because it is the one every signature-level breakdown is computed on, and we give the wider one here so nobody has to reconstruct it.

What follows is the two measuring layers, then the results, then everything we know is wrong with them.

Layer one: the crawler

The scan runs from statable.com-crawler, a Playwright script driving headless Chromium. Every domain gets a fresh BrowserContext, so nothing carries over between sites: no cookies, no storage, no warm cache. The context is set to locale nl-NL, timezone Europe/Amsterdam, viewport 1366x900, service workers blocked. The user agent is StatableScanner/0.1 with a contact email in the string, so anyone who finds us in their logs can write back.

Requests are captured at initiation, through page.on('request'), not from the response side. A request that gets blocked by CSP, or that never resolves, still counts as an attempt to contact a third party, which is the thing we care about.

The budgets

SettingValue
PER_DOMAIN_DELAY_MS2,000
NAV_TIMEOUT_MS45,000
POST_LOAD_WAIT_MS15,000
DOMAIN_DEADLINE_MS90,000
concurrency6

Six domains in parallel, a 2 second delay per domain, 15 seconds of watching after load, and a hard 90 second deadline per domain. The 15 second window is not a guess. Of the third-party requests we logged across the 1,761 scanned rows, weighted by request count, 95.61% arrived within 10 seconds of the scan's first request and 96.29% within 15 seconds; counted once per host, the same curve reads 94.07% and 95.46%. Waiting longer buys very little and costs a lot of wall clock. The budget itself was set earlier, from the arrival curve of a 10-second run recorded in config.ts; the figures here are recomputed on this corpus.

The budgets show up in the failures. Of the 1,761 rows, 1,087 were ok, 423 error, 145 timeout and 106 blocked. The three most common error strings were domain deadline exceeded (110), anti-bot response: HTTP 403 (78) and main document HTTP 404 (16).

Yes, we move the mouse

This is the part people should know about before they read any of our numbers. The scanner performs three input events on every page: mouse.move(10,10), mouse.move(400,300), mouse.wheel(0,300). Never a click. Never a keypress. Never any contact with a consent banner control.

The reason is deferred loading. LiteSpeed Delay JS, WP Rocket, Perfmatters and NitroPack all attach the site's real tags to the first mouseover, wheel or touch event, and several of those setups ship no timer fallback at all. A scanner that sits perfectly still records a clean page on a site that fires Google Tag Manager the moment a human twitches. The nudge is there to reach the state a visitor reaches, not to trigger anything a visitor would not.

The consequence is the one named above: "before interaction" in the crawler dataset is constructed, not measured, and time is relative to the first request of that domain's scan rather than to navigationStart.

Wording matters here. "Before consent" in this dataset means before any click, keypress or banner choice, not before any pointer event at all - a mouse.move is a pointer event, and that is precisely why it is in the script. Summaries of this run that describe it as strictly before any pointer interaction state it more tightly than the crawler warrants.

robots.txt comes first

We fetch and parse robots.txt before touching a domain. Disallowed domains get the status blocked_robots and are never loaded, and we identify as StatableScanner/0.1 with a contact address throughout, so an operator who wants us gone can say so in the file. The parser is our own minimal one and it has real holes, which the limitations section goes through.

Layer two: an extension that actually watches you

The crawler constructs "before interaction". The extension measures it. That gap is why we built a second thing instead of one bigger scanner.

Statable GDPR Checker is a free extension, published in the Chrome Web Store, Firefox Add-ons and Microsoft Edge Add-ons. Version 0.9.1, the one used here, hooks chrome.webRequest in observational mode only. It never blocks a request, never rewrites a header, never touches a consent banner. Alongside it runs a chrome.cookies stream, which is the part the network view cannot give us: cookies written by document.cookie never appear in a request header until the next hit, so a network-only tool undercounts JS-set storage. Watching the cookie store directly closes that hole.

The pre-consent window here is real, not assumed. It opens at navigationStart and closes at the first genuine interaction or at 30,000 ms, whichever comes first. A content script listens for pointerdown, keydown, and touchstart, and the first of those stamps the window shut. Third-party classification uses tldts with a real Public Suffix List, which is exactly where the crawler's homemade regex heuristic leaks in both directions.

What 1,997 domains looked like

The mass run wrote extension-pass1.jsonl: 1,997 unique domains, of which 1,682 produced a verdict.

VerdictDomains (of 1,682)
red695
amber621
green333
unknown33

One caveat we will not bury. That run went through a harness with no human at the keyboard, so window_status stayed observing on all 1,682 rows. The interaction listener never fired, the window closed on the 30 s cap, and pre-consent in that dataset is therefore constructive too, same as the crawler's. The difference is that the mechanism for measuring it exists and fires when a person drives the browser.

The two layers also see different webs. On the 1,761 domains this run shares with the extension's pass 1, the crawler finished ok on 1,087 and the extension on 1,364. Crawler-blind (extension saw it, crawler did not) was 304 of 1,761 (17.3%). Extension-blind was 27 of 1,761 (1.5%). A real browser profile walks past anti-bot walls that a headless Playwright context runs into.

What the two share, precisely

They share a classifier, not an engine. signatures.yaml is byte-for-byte identical (101 signatures, version 1.3.0), and matcher.ts, verdict.ts, third-parties.ts, and cookies.ts match line for line apart from a comment about vendoring. But these are separate repositories, the extension keeps its own copy of core, and syncing is manual. The measurement layers have nothing in common at all. So: the extension uses the same signature set and the same verdict logic, with its own measurement layer. Not the same engine.

That is the instrumentation. Here is what it saw.

How the sample was drawn

The sample is deterministic. Every candidate domain name is hashed, and the selection comes from that hash, so the same source list plus the same seed yields the same domains on any machine. Nothing is drawn at random, which means anyone can rebuild the exact domain list before running a single request.

The lists are public and stay under their own licences. Ranked sites come from Majestic Million (CC BY 3.0). Location-derived sets, like the Dutch clinic scan of 21 July 2026, come from OpenStreetMap (ODbL).

Does it come out the same twice

We ran the same check on the same domains twice with each layer.

ComparisonGap between runsOverlapBoolean "pre-consent tracker" agreed
Crawler: pilot-fresh (29 July 2026) vs mass-validation (28 July 2026)one day1,580 domains in both runs, 1,040 with detections in both1,018 of 1,040 (97.9%)
Extension: pass 1 vs pass 2, both on 28 July 20262 hours 20 minutes1,037 domains1,033 of 1,037 (99.6%)

The extension's full verdict, not just the boolean, matched on 1,024 of 1,037 domains (98.7%). The crawler figure is the weaker of the two, and it should be: a full day passed, and sites deploy tags on their own schedule. We would not read the 22 of 1,040 crawler domains (2.1%) that flipped as measurement noise alone.

Results

Three cuts of the same 1,186 rows: by country, by consent platform, and by how quickly the first request left the browser.

Nine countries, one regulation

We split the 1,186 rows that produced detections by ccTLD and kept every country with at least 77 domains in the set. The column below is the metric we have used throughout: at least one third-party tracker request fired before any interaction, measured against the domains from that country where the scan returned detections.

CountryDomains with detectionsPre-consent tracker
PL13464.9%
ES10060.0%
DK7757.1%
BE13255.3%
IT13653.7%
FR10652.8%
SE15250.0%
AT11243.8%
DE10040.0%

Pre-consent tracking by country, from Poland at 64.9 percent of 134 domains down to Germany at 40.0 percent of 100

Poland leads at 64.9% of 134 domains, Germany sits last at 40.0% of 100, and the two ends are close to 25 points apart.

The stricter number, and why it gets confused

There is a second metric in the dataset and it measures something else entirely: the share of sites that contacted no third-party host at all before interaction. Not "no tracker". No third party of any kind.

ScopeZero third parties before interaction
EU corpus124 of 1,186 (10.5%)
DE21 of 100 (21.0%)
IT21 of 136 (15.4%)
AT17 of 112 (15.2%)
ES10 of 100 (10.0%)
SE15 of 152 (9.9%)
BE12 of 132 (9.1%)
PL10 of 134 (7.5%)
DK5 of 77 (6.5%)
FR5 of 106 (4.7%)

Germany reads well on both, and the distance between the two readings is the interesting part. 40.0% of the 100 German domains fired a pre-consent tracker, so 60.0% did not, but only 21 of those 100 (21.0%) kept the first load free of third-party contact altogether. Belgium is starker: 55.3% of 132 domains fired a pre-consent tracker, and 12 of 132 (9.1%) had a genuinely clean first load. These two are the numbers most often misquoted, including once by us - see the note on figures quoted elsewhere, below.

Most of that gap is fonts. google-fonts was the single most common pre-consent signature in the corpus, ahead of GA4, and the full breakdown is a few sections below. A site can pass the tracker test and still hand an IP address to a third party before the visitor has done anything.

We keep both numbers because they answer different questions, and we label them separately every time. Mixing them up moves a country further than the whole 25-point spread between Poland and Germany.

Same statute, different plumbing

A spread of close to 25 points across countries running the same law says the variance is not in the statute. It is in the local stack: which CMS themes the local agencies ship, whether those themes hotlink Google Fonts or self-host them, which CMP vendor is the regional default, whether the popular hosting bundle already includes a tag manager. Ruleset 1.3.0 sees the output of those choices, never the choices themselves.

We did not test that explanation. We have no per-country data on CMS or hosting share, only on what the pages did on 29 July 2026. What we can say is that the results cluster by country in a way a shared legal text does not explain, and that reading this corpus per country is mostly reading local engineering habit.

A banner on the page does not mean the page is waiting

We recognised a consent platform on 351 of the 1,186 rows with detections (29.6%). On 265 of those 351 sites (75.5%), trackers still fired before any interaction. Three out of four sites that had a consent tool deployed were running that tool next to the traffic instead of in front of it.

The vendor table

Leak rate by consent platform, from Google Funding Choices at 100 percent of 13 sites down to Complianz at 49.1 percent of 55

CMPSites detectedSent data pre-interactionShare
Google Funding Choices1313100.0%
Didomi252392.0%
Axeptio9888.9%
consentmanager141285.7%
CookieFirst7685.7%
OneTrust554581.8%
CookieYes181477.8%
Cookiebot987677.6%
iubenda231773.9%
Cookie Information181372.2%
Usercentrics151066.7%
Complianz552749.1%

(Quantcast Choice appears on one site and is left out.)

Read the n column before the percentage column. Google Funding Choices at 13 of 13 (100.0%), Axeptio at 8 of 9 (88.9%) and CookieFirst at 6 of 7 (85.7%) are samples too small to rank vendors by. A single site moving in or out of those buckets swings the percentage by several points. Three rows carry enough sites to be worth something: Cookiebot at 76 of 98 (77.6%), OneTrust at 45 of 55 (81.8%) and Complianz at 27 of 55 (49.1%). Two of those have exactly 55 deployments each and land 33 points apart. Across all 351 sites with a recognised CMP the rate is 265 of 351 (75.5%).

Most vendors cluster between 66% and 92%, a band narrow enough that picking a name off that list mostly does not change the outcome. Complianz is the exception worth staring at: the same sample size as OneTrust, a little over half the leak rate. The floor is not fixed by the tool, but the tool is not usually what sets it either. What predicts the outcome is how the site wired its tags: a banner that renders while Google Tag Manager has already been loaded from the head is a banner reporting on a decision that was made before it appeared.

Google Consent Mode has two failure shapes, and we count them separately because they mean different things.

Tier A is the hard one: before any interaction, a cookie went out. An identifier left the browser and can be joined to the visitor later. We saw tier A on 305 of 1,186 sites (25.7%).

Tier B is the soft one: before any interaction, the page fired a cookieless ping carrying gcs=G100, the signal that says consent has not been granted. No identifier, no cookie, a request that exists so Google can model the gap later. We saw tier B on 177 of 1,186 sites (14.9%). Tier B is roughly what Consent Mode is designed to look like in the pre-consent window. It is still a network request to a third party, which is why we log it, but it is not the same event as tier A.

Then there are 44 of the 1,186 sites that did both. That combination usually means the tags on one page disagree with each other: something is running in Consent Mode with the denied default while something else, often an older hardcoded snippet or an ad tag added by a different team, never got the memo and set its cookie anyway.

Half a second, and the page has already talked to Google

Timing is the other half of the story, and it is where the corpus gets uncomfortable. The median time to the first third-party request was 419 ms across the 1,062 rows where we could time it, against a mean of 945.6 ms. Of those 1,062 rows, 79.8% made third-party contact inside one second and 57.8% inside 500 ms. Zero on that clock is the first request of the domain's scan, not navigationStart.

What shows up in that window is remarkably boring. Shares are out of the 1,186 rows with detections.

Histogram of time to the first third-party request, peaking in the 250 to 500 millisecond bin at 31.8 percent of 1,062 sites

SignatureSitesShare of 1,186
google-fonts54245.7%
ga444337.4%
gtm33027.8%
google-ads32527.4%
google-maps17114.4%

By host, the same picture from the network side, counted as the number of domains that contacted each host:

Third-party hostDomains contacting it
googletagmanager.com544
fonts.gstatic.com490
fonts.googleapis.com476
connect.facebook.net139

So the fast half of the web is not doing anything exotic. It is loading a tag manager and a font file, and the biggest non-Google host in the corpus does not appear until a quarter of the way down. The long tail is where the variety lives: 2,609 unique third-party hosts appeared across the corpus, most of them on a handful of sites each.

The tail also holds the one result we still find hard to look at. A single European retailer (we are not naming the domain) set 511 cookies in one page load, and one of those cookies carried a maxAge of roughly 1,083 years. That is one site, not a pattern, and we report it as a curiosity rather than a trend.

Sector context matters more than the outlier. In our Dutch clinic sample (21 July 2026), 146 of 186 rows with detections (78.5%) fired a tracker before interaction, against 54.0% across the EU corpus, and the median time to first third-party request was 741 ms (n=180), about 1.77 times slower than the EU median. Slower, but not safer: the GA family alone fired before interaction on 141 of those 186 clinic rows (75.8%). Late still counts.

If you want to check your own timing rather than take ours, the Statable GDPR Checker extension runs the same classifier described above, with its own measurement layer: it observes from navigationStart until the first real pointerdown, keydown or touchstart, or a fixed timeout, whichever comes first.

Where this method is weak

The crawler constructs "before interaction" rather than measuring it

This is the caveat that sits under every number above, so it belongs in one place at full strength. In the crawler, preConsent: true is true by construction: the scanner never clicks and never presses a key, so every network detection it records is pre-consent by definition of the run, not because we watched a user do nothing. The extension is the layer that actually measures the window, but in the mass validation we drove it with a harness that never interacted either, so pre-consent came out constructive on both sides. The two mouse moves and the wheel scroll are the deliberate exception, and they mean "no interaction" is an approximation rather than a vacuum.

The 15 second window cuts off a tail

POST_LOAD_WAIT_MS is 15,000, inside a 90,000 ms per-domain deadline and a 45,000 ms navigation timeout. That leaves roughly 3.7% of every third-party request we recorded outside the window: lazy pixels, scroll-triggered tags, late chat widgets. A site whose only tracker fires at 20 s reads as clean here.

Our eTLD+1 is homemade

The crawler classifies first-party versus third-party with a hand-rolled regex heuristic, not the Public Suffix List. That errs in both directions: it can split one organisation across two "parties" on multi-label suffixes, and it can fold a genuinely separate host into the first party. The difference is baked into every crawler-side third-party count in this post. The signature ruleset is the part the two engines do share, so they disagree about party boundaries while calling the same tag by the same name.

robots.txt fails open, silently

We fetch robots.txt over https only. If the fetch returns 4xx, 5xx or times out, we set allowed: true and leave no trace in the dataset, so a reader cannot tell a real allow from a failed lookup. For an http-only site, robots is not checked at all. Two known deviations from RFC 9309: no Allow priority at equal match length, and the query string ignored during path matching. An earlier substring-based group selection was fixed before this run - at commit 2badab8 group selection is prefix-matched as the RFC requires.

Being honest costs us a sixth of the web

The 17.3% of shared domains that the crawler could not open and the extension could is not a rounding error, it is the shape of the sample. This is the price of a declared user agent, a datacenter IP and a 2,000 ms per-domain delay: WAFs and bot walls turn away the scanner that identifies itself, while a real browser with a real person behind it walks in. Any corpus built this way is skewed toward sites without aggressive bot defence, and we have no way to say whether those 578 blind spots track more or less than the rest. It is also worth being clear about what the agreement between the two layers proves: both run the same ruleset off the same signature file, so where they agree, that is evidence about the measurement, not an independent check of the signatures.

There is no post-consent pass at all

We never accept, never reject, never open a preference centre. So the obvious follow-up question, what changes after consent, is one this work does not answer. When we report that three out of four sites with a recognised CMP send data to trackers before interaction, that is a statement about the pre-interaction window only. It says nothing about whether the banner honours a later "reject all".

Home pages only

Every measurement in this post comes from a single page load of the domain root. Article pages, checkout flows, embedded video and account areas typically carry more third parties than a home page, so treat these numbers as a floor for each site, not a site-wide average.

What you need to repeat it

Four things, and they are all pinned:

The simplest version of this needs no code at all. Install the extension, open any site you are responsible for, and read what fired before you touched anything. It reports the same verdict on one page that the crawler reported across 1,186, because both run the same 101 signatures.

  • Engine 0.2.0, ruleset 1.3.0 (101 signatures), crawler commit 2badab8.
  • Browser context: headless Chromium via Playwright, locale nl-NL, timezone Europe/Amsterdam, viewport 1366x900, user agent StatableScanner/0.1 with a contact address in it, service workers blocked.
  • Budgets: PER_DOMAIN_DELAY_MS 2,000, NAV_TIMEOUT_MS 45,000, POST_LOAD_WAIT_MS 15,000, DOMAIN_DEADLINE_MS 90,000, concurrency 6.
  • The two repos. The crawler and the Statable GDPR Checker extension (v0.9.1) are separate codebases sharing one classifier and no measurement code, with the extension's copy of core synced by hand.

A note on figures quoted elsewhere

A press release issued on 21 August 2026 quotes this dataset. A few figures there do not match this post, and this post is the version to cite.

  • German sites with no third-party contact at all before interaction: 21 of 100 (21.0%), not 30%. The comparison in that release still holds - Polish sites read 10 of 134 (7.5%), so German sites are about three times as likely to be clean.
  • Belgian sites with no third-party contact at all: 12 of 132 (9.1%), not 3 of 132.
  • The repeat crawler comparison spans one day (28 to 29 July 2026), not two. 82 of the 1,658 mass-validation rows were scanned on 27 July, so a small slice of it spans two.
  • The extension validation covered 1,997 unique domains, 1,682 of which produced a verdict: red 695 (41.3%), amber 621 (36.9%), green 333 (19.8%), unknown 33 (2.0%). That release rounds those shares, drops the unknown bucket and cites a smaller corpus of 1,000.
  • Complianz at 49.1% is correct and now appears in the vendor table above.

The underlying run (pilot-fresh-20260729) has not been re-scanned or reprocessed. Only the way it is summarised has changed.

Citing it

The dataset is available under CC BY 4.0. It is not posted for download yet, because the rows carry domain names and we would rather decide how to handle that than publish first and think later. Write to support@statable.com and we will send it. If you want to cite the study itself, use this string:

Statable, EU pre-consent tracking scan, run pilot-fresh-20260729, scanned 29 July 2026, engine 0.2.0, ruleset 1.3.0, commit 2badab8. CC BY 4.0.

Everything here is a behavioural finding about what a browser observed on a given date. It is not a legal verdict about any site.


Ready to take control of your web analytics? Try Statable free for 30 days. No credit card required, full feature access, built for GDPR. Start your free trial or view a live demo.