How to Detect Jurisdiction & Compliance Context from an IP
Resolve country, state/province and EU membership from one lookup — to drive GDPR notices, VAT, sales tax and regional policy before the page renders.
You can resolve a visitor's jurisdiction context — country, state or province, and EU membership — from a single IP lookup, and use it to pre-apply the right GDPR notice, VAT or sales tax, and regional policy before the page renders. The response carries countryCode, regionCode and is_in_eu directly. These are signals, not legal determinations — always pair IP with stated billing or residency for anything binding.
The examples here are Python, standard library only — but the same logic maps directly onto our official SDKs for TypeScript, Go, Ruby, Java and 7 more. Field paths and the worked outputs below are all verified against the live API.
1Get a key and call the API securely
Create a free account here — 2,000 lookups/day, no card. Send the key as an Authorization: Bearer header from an environment variable, keeping it out of URLs and logs:
import os, urllib.request, json
API_KEY=*** # export it; never hardcode
def lookup(ip):
req = urllib.request.Request(
f"https://api.ipwho.org/ip/{ip}",
headers={"Authorization": f"Bearer {API_KEY}", "User-Agent": "MyApp/1.0"},
)
with urllib.request.urlopen(req, timeout=10) as resp:
return json.loads(resp.read())["data"]2Read country, state/province and EU status:
The response carries geoLocation.countryCode, geoLocation.regionCode (state or province), and geoLocation.is_in_eu. Country and EU status cover GDPR and country-level VAT — but a lot of law is sub-national: US sales tax, Australian state rules, Canadian provincial tax all turn on the region. Read regionCode too, and remember it can be null on some IPs, so keep a country-level fallback:
data = lookup("76.102.11.203")
geo = data.get("geoLocation") or {}
country = geo.get("countryCode") # "US"
region = geo.get("regionCode") # "CA" -> state/province (can be None)
in_eu = bool(geo.get("is_in_eu")) # False
print(country, region, in_eu) # US CA False3Map jurisdiction to policy — signal, not verdict:
Use country + region + EU status to pre-apply a GDPR banner, the right VAT or state sales tax, and regional rules. Treat IP as a hint: for anything binding — tax invoices, KYC, age-gating — confirm against the user's stated billing or residency, because IP location is approximate, region can be missing, and VPNs exist.
#!/usr/bin/env python3
"""Detect jurisdiction/compliance context from an IP (IPWho IP geolocation API).
Signals only — not legal advice; confirm with billing/residency for binding use."""
import os, sys, urllib.request, json
API_KEY=*** EU_VAT = {"DE": 0.19, "ES": 0.21, "FR": 0.20} # illustrative country VAT
US_STATE_TAX = {"CA": 0.0725, "NY": 0.04} # illustrative state sales tax
def lookup(ip):
req = urllib.request.Request(
f"https://api.ipwho.org/ip/{ip}",
headers={"Authorization": f"Bearer {API_KEY}", "User-Agent": "IPWho-Compliance/1.0"},
)
with urllib.request.urlopen(req, timeout=10) as resp:
return json.loads(resp.read())["data"]
def compliance_context(data):
geo = data.get("geoLocation") or {}
cc = geo.get("countryCode")
region = geo.get("regionCode") # state / province — many laws are sub-national
in_eu = bool(geo.get("is_in_eu"))
return {
"country": cc,
"region": region,
"gdpr_applies": in_eu,
"vat_rate": EU_VAT.get(cc),
"us_state_tax": US_STATE_TAX.get(region) if cc == "US" else None,
}
if __name__ == "__main__":
ip = sys.argv[1] if len(sys.argv) > 1 else "76.102.11.203"
print(f"{ip}: {compliance_context(lookup(ip))}")$ python3 compliance.py 91.0.0.1 # Germany (EU)
91.0.0.1: {'country': 'DE', 'region': 'NW', 'gdpr_applies': True, 'vat_rate': 0.19, 'us_state_tax': None}
$ python3 compliance.py 76.102.11.203 # California, USA
76.102.11.203: {'country': 'US', 'region': 'CA', 'gdpr_applies': False, 'vat_rate': None, 'us_state_tax': 0.0725}
$ python3 compliance.py 24.48.0.1 # Quebec, Canada
24.48.0.1: {'country': 'CA', 'region': 'QC', 'gdpr_applies': False, 'vat_rate': None, 'us_state_tax': None}