惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
C
CXSECURITY Database RSS Feed - CXSecurity.com
D
Docker
有赞技术团队
有赞技术团队
WordPress大学
WordPress大学
Jina AI
Jina AI
小众软件
小众软件
Last Week in AI
Last Week in AI
Hugging Face - Blog
Hugging Face - Blog
博客园 - 三生石上(FineUI控件)
宝玉的分享
宝玉的分享
美团技术团队
爱范儿
爱范儿
V
V2EX
大猫的无限游戏
大猫的无限游戏
人人都是产品经理
人人都是产品经理
J
Java Code Geeks
博客园 - 司徒正美
博客园 - 叶小钗
S
SegmentFault 最新的问题
量子位
S
Secure Thoughts
月光博客
月光博客
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
O
OpenAI News
L
LINUX DO - 最新话题
罗磊的独立博客
SecWiki News
SecWiki News
雷峰网
雷峰网
Recent Announcements
Recent Announcements
V2EX - 技术
V2EX - 技术
T
Tailwind CSS Blog
H
Hacker News: Front Page
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
云风的 BLOG
云风的 BLOG
Schneier on Security
Schneier on Security
T
The Blog of Author Tim Ferriss
IT之家
IT之家
博客园 - 聂微东
腾讯CDC
N
News | PayPal Newsroom
P
Proofpoint News Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The GitHub Blog
The GitHub Blog
Hacker News: Ask HN
Hacker News: Ask HN
aimingoo的专栏
aimingoo的专栏
Webroot Blog
Webroot Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Google DeepMind News
Google DeepMind News
K
Kaspersky official blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Show the Cookie Consent Banner Only to Visitors Who Legally Need It
ABDULLAH AFZAL · 2026-06-24 · via DEV Community

A cookie consent banner is a legal requirement in some places and optional in others, yet most sites show the same one to every visitor on the planet. The EU and UK want opt-in before any non-essential cookie fires. The US wants notice and an opt-out, not a wall. A lot of traffic needs no banner at all.

This is a guide to detecting a visitor's jurisdiction from their IP and showing the consent experience that actually applies: a strict opt-in banner for Europe, an opt-out notice for US states, nothing where it isn't required. It is not legal advice. It is the engineering around a line your lawyer should draw.

TL;DR

  • The EEA and the UK require opt-in consent before non-essential cookies run. The US requires notice and an opt-out. Much of the rest of the world requires neither.
  • One IP field, is_eu, does most of the routing. It covers EU member states only, so add the UK and the EEA states (Iceland, Liechtenstein, Norway) yourself.
  • Geo-targeting the banner is a UX and conversion decision, not a compliance shortcut. The law still applies wherever the visitor actually is.
  • IP geolocation is probabilistic. VPNs, proxies, and travelers break it, so fail toward showing consent when you are not sure.
  • The EU's Digital Omnibus may simplify all of this, but as of June 2026 it is a proposal, not law. In short: read the visitor's country and an EU flag from their IP, route them to one of three consent experiences, and gate your non-essential scripts behind that decision. When the lookup is uncertain, show the opt-in banner anyway. The code below is the same classifier wired into curl, the browser, and an Express server.

Who actually needs a cookie consent banner?

If you have wondered whether you need a cookie banner at all, the honest answer is that it depends on where your visitor is sitting. The cookie banner requirements come down to three regimes that cover most of your traffic, and the cookies that trip the rules are always the non-essential ones: analytics, advertising, A/B testing, heatmaps, cross-site pixels. Session cookies, CSRF tokens, the load-balancer cookie, and the cookie that remembers a consent choice are strictly necessary and never need permission.

The EU and EEA: opt-in before non-essential cookies

In Europe, the rule is consent first. Article 5(3) of the ePrivacy Directive says you can only store or read information on someone's device once they have given consent, with a narrow exemption for what is strictly necessary to deliver the service they asked for. The GDPR then sets the bar for what counts as consent: a freely given, specific, informed, unambiguous opt-in. No pre-ticked boxes, no "by continuing you agree."

This applies across the whole European Economic Area, not just the 27 EU member states. The EEA adds three more countries through the EEA Agreement: Iceland, Liechtenstein, and Norway. They enforce the same opt-in standard. That detail matters for the code, because the obvious is_eu check misses all three.

The UK: still opt-in, on its own track

The UK left the EU but kept the rule. Consent for non-essential cookies comes from the Privacy and Electronic Communications Regulations (PECR), with the UK GDPR supplying the consent standard. The ICO finalized its guidance on storage and access technologies in April 2026, and it expects an "Accept all" and a "Reject all" option with equal prominence, with non-essential scripts blocked until the user opts in.

The practical catch for developers: the UK is not in the EU, so a naive "is this visitor in the EU?" check returns false for British traffic and silently skips a banner the UK still requires. The Data (Use and Access) Act 2025 carved out a few narrow low-risk exceptions, but analytics and advertising are not among them.

The US: notice and opt-out, not a banner

No US state law mandates a cookie banner. The model is the opposite of Europe's: collect first, give notice, and let people opt out. Under the CCPA and CPRA, enforced by California's Attorney General, a business that sells or shares personal information has to post a clear "Do Not Sell or Share My Personal Information" link (or a "Your Privacy Choices" control) and stop on request.

There is one piece you do have to honor automatically. A Global Privacy Control signal, sent as the Sec-GPC: 1 request header, counts as a valid opt-out under California law, and businesses must respect it. So the US branch of your logic is not "show a banner," it is "show a privacy-choices link and read the GPC header."

Roughly a dozen states now have their own consumer privacy laws (California, Virginia, Colorado, Connecticut, Utah, Texas, Florida, and more), and the stricter California regulations took effect on January 1, 2026, with explicit bans on dark patterns in the opt-out flow. They differ in detail, but none of them turns into a European-style consent wall.

Here is the whole decision in one table:

Region Consent model What to show IP signal
EEA (EU-27 + Iceland, Liechtenstein, Norway) Opt-in before non-essential cookies Opt-in banner; block scripts until consent is_eu === true, plus IS/LI/NO by country code
United Kingdom Opt-in (UK GDPR + PECR) Opt-in banner, equal-prominence reject country_code2 === 'GB'
United States Notice + opt-out "Your Privacy Choices" link; honor GPC country_code2 === 'US' (use state_code for nuance)
Most other countries Usually none No banner (confirm per market) anything else

If your site runs on WordPress or Shopify, a consent platform like Cookiebot or CookieYes will geo-target the banner for you as a paid feature, and that is a reasonable buy. The rest of this guide is for everyone building it themselves.

Why one global banner is the wrong default

A single global banner is the lazy default that manages to annoy the people who do not need it and under-serve the ones who do. Show a GDPR cookie banner to US visitors and you cost yourself conversions on traffic that never required a click, while training those users to slap "Accept" on every banner out of habit. Skip the banner in Europe and you are setting tracking cookies without consent, which is what the ICO's letter campaigns and the larger CNIL fines have been about.

The defensible move is to match the experience to the jurisdiction, and when you genuinely cannot tell where someone is, default to the stricter option. That is the whole strategy. The rest is reading an IP and being honest about how much you can trust it.

Detecting the visitor's jurisdiction from their IP

You need three things from an IP: the country, the US state (for later nuance), and a flag for whether the country is in the EU. Any IP geolocation API gives you this. IPGeolocation, ipinfo, ip-api, MaxMind GeoIP2, IPLocate, and IP2Location all return country and region. I'll use ipgeolocation.io for the examples because its free tier returns an is_eu boolean alongside the country and state code in one call, which keeps the routing logic short. You can grab a free API key and follow along; the free tier covers 1,000 lookups a day.

The one field that does most of the work: is_eu

A single lookup against the /v3/ipgeo endpoint returns everything the free tier offers. Here is the real response for 8.8.8.8 (Google's public DNS, which geolocates to the US), so you can see what one call gives you:

curl "https://api.ipgeolocation.io/v3/ipgeo?apiKey=$IPGEO_API_KEY&ip=8.8.8.8"

{
  "ip": "8.8.8.8",
  "location": {
    "continent_code": "NA",
    "continent_name": "North America",
    "country_code2": "US",
    "country_code3": "USA",
    "country_name": "United States",
    "state_prov": "California",
    "state_code": "US-CA",
    "city": "Mountain View",
    "zipcode": "94043-1351",
    "latitude": "37.42240",
    "longitude": "-122.08421",
    "is_eu": false
  },
  "country_metadata": {
    "calling_code": "+1",
    "tld": ".us",
    "languages": ["en-US", "es-US", "haw", "fr"]
  },
  "currency": { "code": "USD", "name": "US Dollar", "symbol": "$" },
  "asn": { "as_number": "AS15169", "country": "US", "organization": "Google LLC" },
  "time_zone": { "name": "America/Los_Angeles", "current_time": "2026-03-03 03:22:55.618-0800" }
}

That is the full free-tier shape (location, country metadata, currency, ASN, and timezone in one response), but the consent logic only needs the location object. Trim the payload with fields and pull the confidence rating with include=geo_accuracy:

curl "https://api.ipgeolocation.io/v3/ipgeo?apiKey=$IPGEO_API_KEY&ip=91.128.103.196&fields=location&include=geo_accuracy"

{
  "ip": "91.128.103.196",
  "location": {
    "country_code2": "SE",
    "country_name": "Sweden",
    "state_code": "SE-AB",
    "city": "Stockholm",
    "accuracy_radius": "9.148",
    "confidence": "high",
    "is_eu": true
  }
}

Sweden comes back with is_eu: true; the US came back false. That boolean is the cheapest signal you have. The trap is treating it as the whole answer. is_eu reflects EU membership, so it is false for the UK and for the EEA states Norway, Iceland, and Liechtenstein, all of which still require opt-in. So the rule is: trust is_eu for the EU, and add the other four countries by hand.

One value to watch: confidence comes back as a string, and the casing is inconsistent across responses (high, High, low). Lowercase it before you compare, or a === 'low' check will quietly miss half the cases.

A jurisdiction classifier

Here is the function that turns a location object into a decision. It is pure (no network, no globals), which is why the same code drops into the browser and the server unchanged.

// UK + the EEA-EFTA states. Same opt-in rule as the EU, but is_eu doesn't flag them.
const GDPR_OPTIN_NON_EU = new Set(['GB', 'IS', 'LI', 'NO']);

// Returns 'gdpr-optin' | 'us-optout' | 'none', or null when we can't tell.
function classifyJurisdiction(location) {
  // No location object means the lookup gave us nothing usable.
  // Hand back null and let the caller fail toward consent.
  if (!location || typeof location !== 'object') return null;

  const country = location.country_code2 ?? null;

  // is_eu covers EU member states. The EEA (Norway, Iceland, Liechtenstein)
  // and the post-Brexit UK enforce the same opt-in rule, so add them explicitly.
  if (location.is_eu === true || (country && GDPR_OPTIN_NON_EU.has(country))) {
    return 'gdpr-optin';
  }

  // US: notice + opt-out, not a banner. state_code (e.g. "US-CA") is here
  // if you later need to special-case a state.
  if (country === 'US') return 'us-optout';

  // Everywhere else: usually no banner. Confirm against the markets you serve.
  return 'none';
}

The null return is deliberate. A missing country is not the same as "no banner needed," and the difference is the whole point of the next sections.

Showing the banner conditionally

Two places to make the call: in the browser after the page loads, or on the server before it renders. The server-side version is better because the banner ships in the initial HTML with no flash, but the client-side version is fewer moving parts. Both reuse the classifier above.

Client-side: gate the banner in the browser

On load, look up the caller's IP (omit the ip parameter and the API uses the request's own address), classify, and show the right UI:

async function detectConsentRegime() {
  // This key ships to the browser. Restrict it to your domain with the API's
  // request-origin (CORS) allowlist, or proxy the call through your own backend.
  const KEY = window.IPGEO_PUBLIC_KEY;

  try {
    const res = await fetch(
      `https://api.ipgeolocation.io/v3/ipgeo?apiKey=${KEY}&fields=location&include=geo_accuracy`,
      { signal: AbortSignal.timeout(1500) } // don't hang the page on a slow lookup
    );

    // 423 (private/bogon IP), 429 (rate limit), 5xx: don't guess, fail toward consent.
    if (!res.ok) return 'gdpr-optin';

    const data = await res.json();
    const location = data?.location;
    const confidence = (location?.confidence ?? '').toLowerCase();
    const regime = classifyJurisdiction(location);

    // Unknown country or low-confidence geo: show the stricter banner.
    if (regime === null || confidence === 'low') return 'gdpr-optin';
    return regime;
  } catch (err) {
    console.error('Geo lookup failed, defaulting to consent:', err);
    return 'gdpr-optin';
  }
}

detectConsentRegime().then((regime) => {
  if (regime === 'gdpr-optin') {
    showOptInBanner();        // load analytics/ads ONLY after the user accepts
  } else if (regime === 'us-optout') {
    showPrivacyChoices();     // "Your Privacy Choices" link; also honor GPC (below)
  }
  // 'none': load nothing extra, show no banner
});

Showing the banner is the easy half. The non-essential scripts (analytics, ads, pixels) must not run until you have consent in opt-in regions. A geo-gate that hides the banner but still fires Google Analytics on page load is the kind of mistake that looks fine in the UI and silently protects nothing. Keep your tracking calls inside the accept handler, not in the page's initial load.

Server-side: decide before the page renders

The same logic as Express middleware. The one thing people get wrong here is the client IP: behind a proxy, load balancer, or CDN, req.ip is the proxy's address unless you tell Express to trust it.

const express = require('express'); // Node 18+ for built-in fetch; else import undici/node-fetch
const app = express();

// Trust your proxy/CDN so req.ip is the real client, not the edge.
// Set this to your actual infrastructure; never blindly trust X-Forwarded-For.
app.set('trust proxy', 1);

async function lookupRegime(ip) {
  const KEY = process.env.IPGEO_API_KEY; // never hardcode the key server-side
  const url =
    `https://api.ipgeolocation.io/v3/ipgeo?apiKey=${KEY}&ip=${ip}` +
    `&fields=location&include=geo_accuracy`;

  const res = await fetch(url, { signal: AbortSignal.timeout(1500) });
  if (!res.ok) return 'gdpr-optin'; // 423 for private/localhost IPs in dev, 429, 5xx

  const data = await res.json();
  const location = data?.location;
  const confidence = (location?.confidence ?? '').toLowerCase();
  const regime = classifyJurisdiction(location);
  return (regime === null || confidence === 'low') ? 'gdpr-optin' : regime;
}

async function consentRegime(req, res, next) {
  try {
    req.consentRegime = await lookupRegime(req.ip);
  } catch (err) {
    console.error('Consent lookup failed, defaulting to consent:', err);
    req.consentRegime = 'gdpr-optin'; // fail toward consent, never away from it
  }
  // A GPC signal is a valid opt-out request under US state law. Honor it.
  req.gpcOptOut = req.headers['sec-gpc'] === '1';
  next();
}

app.use(consentRegime);

app.get('/', (req, res) => {
  res.send(renderPage({ regime: req.consentRegime, gpcOptOut: req.gpcOptOut }));
});

Now the banner is baked into the first byte of HTML, and US visitors who arrive with Sec-GPC: 1 get treated as already opted out before a single tracking script loads.

Make it production-safe

The naive version calls the API on every request and trusts the answer completely. Two changes fix the cost and the correctness.

Cache the lookup

The mapping from an IP to a jurisdiction barely changes, so caching the decision (not the whole response) for hours is free accuracy. A 6-hour TTL turns a busy site's thousands of requests into a handful of lookups, and your 1,000-a-day free tier stops being a constraint.

const TTL_MS = 6 * 60 * 60 * 1000; // IP-to-jurisdiction is stable for hours
const cache = new Map();            // swap for Redis across multiple instances

function getCachedRegime(ip) {
  const hit = cache.get(ip);
  if (hit && hit.expires > Date.now()) return hit.regime;
  cache.delete(ip); // expired
  return null;
}

async function regimeForIp(ip) {
  const cached = getCachedRegime(ip);
  if (cached) return cached;
  const regime = await lookupRegime(ip);
  cache.set(ip, { regime, expires: Date.now() + TTL_MS });
  return regime;
}

Point the middleware at regimeForIp(req.ip) instead of lookupRegime(req.ip) and you are done. On more than one server instance, use Redis with the same key and TTL.

Fail toward consent, not away from it

Every error path in the code above returns gdpr-optin, and that is intentional. When you cannot reach the API, the request times out, the geo confidence is low, or the country is unknown, the safe default is to show the stricter banner.

The reason is that the two failure modes are not symmetric. Showing an opt-in banner to someone who did not strictly need one costs a click. Hiding it from a European visitor and setting tracking cookies without consent is the violation regulators actually fine. When in doubt, eat the click.

VPNs, proxies, and travelers

This is where the title earns its asterisk. Country-level IP geolocation is strong (ipgeolocation.io reports 95%+ country-level accuracy), but it is a probability, not a fact. A German on a US corporate VPN looks American. A Londoner on holiday in Spain looks Spanish. iCloud Private Relay, datacenter proxies, and carrier-grade NAT all blur the picture.

So "show the banner only to visitors who legally need it" is really "show it to the visitors who appear to need it, and fail toward showing it when the signal is weak." For logged-in users you have a better signal than their current IP: the account's country or billing region. Use that to override the IP guess for known users, and let the confidence field downgrade you to the opt-in banner for everyone else. None of this makes IP a legal determination of jurisdiction. It makes the consent experience usually right and safe when it is wrong.

What's changing: the EU Digital Omnibus

If you have read that cookie banners are going away, that comes from the European Commission's Digital Omnibus proposal, published on November 19, 2025. It would fold the cookie rules out of the ePrivacy Directive and into the GDPR (new Articles 88a and 88b), require a single-click reject with equal prominence, stop sites from re-asking for six months after a refusal, push consent toward browser-level machine-readable signals that sites must honor, and exempt plain first-party audience measurement so basic visitor counting would not need a banner at all.

It is a proposal, not law. As of June 2026 the GDPR and the ePrivacy Directive are fully in force, unchanged. The Parliament and Council are still working through it, adoption is expected mid-to-late 2026 at the earliest, the cookie changes would only apply six months after that, and the browser-signal mechanism is years out. The EDPB and EDPS have issued a joint opinion backing the goal while flagging concerns about weakening protections. The UK, on its own track, will not move in lockstep regardless.

What that means today: build the routing now, because the divergence between regimes is widening, not shrinking. If the low-risk exemption does land, plain analytics may drop out of the banner inside the EU, and the classifier is exactly where you would encode that, one branch at a time.

Drop the classifier into your stack, gate your non-essential scripts behind it, and default to the opt-in banner whenever the lookup is unsure. When you outgrow the free tier's 1,000 lookups a day, cache harder before you reach for a paid plan. And keep a lawyer in the loop: the routing is sound engineering, but the line between "required" and "optional" is theirs to draw, not your IP database's.