Visitor Geolocation for Analytics — Without the Cookie Banner
Get country, region, city, ISP, and device context from the server — before any JS runs, before any cookie is set.
Privacy-first analytics is the new default. Plausible, Umami, Fathom, PostHog self-hosted — they all avoid third-party cookies. But they still need to answer one question your product manager asks every Monday: “Where are our users?” IP geolocation answers that at the server, with no tracking pixels and no banner.
The business problem
Traditional analytics used third-party cookies + GeoIP databases shipped with the client. GDPR, PECR, and ITP killed that pattern:
- Client-side GeoIP DBs (Maxmind-in-browser) leak the full user IP into your page source.
- Cookie-based analytics need a consent banner, which 15–40% of users reject.
- Self-hosted analytics tools often claim no-cookie but can’t tell you where the visitor is.
IP geolocation at the server returns country / region / city / ISP / timezone from the IP alone — no fingerprinting, no cookie, no consent banner needed (recital-free processing under GDPR Art. 6(1)(f) legitimate interest, because the IP is a transient network identifier, not stored with personal data).
Implementation
Server-side logging (append to every page-view event)
// Express / Fastify middleware
import { ipgeoLookup } from "./lib/ipgeo.js";
app.use(async (req, res, next) => {
const ip = req.ip;
const geo = await ipgeoLookup(ip);
// Log the event; drop IP after lookup to stay consent-free
analytics.track("pageview", {
path: req.path,
country: geo.country_code,
region: geo.region,
city: geo.city,
asn: geo.asn,
is_mobile_network: geo.connection_type === "mobile",
timestamp: Date.now()
// NOTE: no raw IP stored → no personal data under most GDPR interpretations
});
next();
});
Plausible / PostHog enrichment
If you self-host Plausible or PostHog, swap their bundled MaxMind DB for a lookup call:
// Plausible plugin (or any custom ingestion endpoint)
export async function enrichEvent(event) {
const geo = await ipgeoLookup(event.ip);
return {
...event,
country_code: geo.country_code,
region: geo.region,
city: geo.city,
isp: geo.org,
is_eu: geo.is_eu,
timezone: geo.timezone
};
}
Batch enrichment (historic log backfill)
# Pipe a CSV of IPs into our batch endpoint (100 per call)
jq -c '{ips: .}' ips.json \
| curl -X POST https://api.ipgeo.10b.app/v1/lookup/batch \
-H "Authorization: Bearer $IPGEO_API_KEY" \
-H "Content-Type: application/json" \
-d @-
Useful for enriching archived server logs, S3 access logs, or Cloudflare logpush output.
Why IP Geo API for this use case
- One call = 20+ analytic dimensions. Country, country-code, region, region-code, city, zip, lat, lon, timezone, currency, calling-code, languages, is_eu, continent, asn, org, connection_type.
- No bundled MaxMind DB = no ops. No weekly download, no database deploys, no version drift between your staging and prod.
- EU-processed lookups. All requests handled on Hetzner Nürnberg; no US processor, no SCC paperwork.
- Batch endpoint (100 IPs per call) — ~100× cheaper than individual calls for log-enrichment jobs.
- Under 40 ms median latency — doesn’t add tail-latency to page-view logging.
Pricing math
For most analytics deployments, you cache lookups for 1+ hours per IP, so the cost scales with unique IPs, not page-views.
| Monthly unique IPs | Tier | Cost/mo |
|---|---|---|
| < 30 K | Free | € 0 |
| < 1 M | Starter | € 29 |
| < 10 M | Business | € 99 |
A blog with 300 K monthly unique visitors → € 0 (free tier, assuming aggressive caching). A high-traffic media site with 5 M uniques → € 99.
Honest trade-offs
- City accuracy is 60–85%, not 100%. IP-to-city maps are derived from public registries and traceroute data — they’re excellent at country, good at region, okay at city, and never at street-level. Don’t make product decisions that rely on city precision.
- Mobile carriers often geolocate to a central POP. Dutch KPN mobile IPs often report “Amsterdam” regardless of where the user actually is. Treat mobile IPs as “country-only accurate”.
- Corporate proxies collapse all employees to one location. If you’re a B2B SaaS and ACME Corp’s office VPN is in London, all ACME users look like London. That’s either a feature (company-level segmentation) or a bug (personal location) — know which you need.
Privacy note
IP geolocation without storing the raw IP past the lookup is widely considered pseudonymous processing under GDPR. Best practice:
- Look up the IP on request.
- Store only the aggregated dimensions (country, region, city).
- Drop the raw IP before the event reaches your warehouse.
This removes the “personal data” classification for the stored event and simplifies your privacy policy.
Related use cases
- Geo-personalization —
./geo-personalization.md - Geo-pricing —
./geo-pricing.md - Compliance —
./geoblocking-compliance.md
Get started
Free tier: 1 000 lookups / day → /pricing. Sign up at https://ipgeo.10b.app/pricing.
Get early access — 50% off for 12 months
First 100 signups lock in 50% off any paid plan for the first year. No credit card required — we’ll email you at launch.