How to Route Users to the Nearest Region by IP
Pick the closest backend region from one lookup — using the latitude/longitude in the response — for lower latency.
You can send a user to your closest backend region — for lower latency — from a single IP lookup. The response includes latitude and longitude; this guide computes the nearest region with a simple great-circle distance, with a country fallback for IPs that return no coordinates.
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"]2Compute the nearest region from latitude/longitude:
The response carries geoLocation.latitude and geoLocation.longitude. Define your regions once, then pick the closest with a great-circle (haversine) distance. Always keep a default for IPs that return no coordinates:
import math
REGIONS = {
"eu-west (Frankfurt)": (50.11, 8.68),
"us-east (Virginia)": (38.0, -78.5),
"us-west (Oregon)": (45.8, -119.7),
"ap-northeast (Tokyo)": (35.69, 139.69),
}
def haversine(a, b):
R = 6371 # km
la1, lo1, la2, lo2 = map(math.radians, [a[0], a[1], b[0], b[1]])
h = math.sin((la2-la1)/2)**2 + math.cos(la1)*math.cos(la2)*math.sin((lo2-lo1)/2)**2
return 2 * R * math.asin(math.sqrt(h))
def nearest_region(data):
geo = data.get("geoLocation") or {}
lat, lon = geo.get("latitude"), geo.get("longitude")
if lat is None or lon is None:
return {"region": "default", "reason": "no coordinates"}
pt = (lat, lon)
region = min(REGIONS, key=lambda r: haversine(pt, REGIONS[r]))
return {"country": geo.get("countryCode"), "region": region,
"km": round(haversine(pt, REGIONS[region]))}
print(nearest_region(lookup("91.0.0.1")))3Route the request — the full script:
Everything together. Swap the REGIONS map for your real backend coordinates. Note that coordinates are approximate (city-level or coarser) — this is ideal for region selection, not for anything needing pinpoint accuracy:
#!/usr/bin/env python3
"""Route to the nearest backend region by IP (IPWho IP geolocation API)."""
import os, sys, math, urllib.request, json
API_KEY=*** REGIONS = {
"eu-west (Frankfurt)": (50.11, 8.68),
"us-east (Virginia)": (38.0, -78.5),
"us-west (Oregon)": (45.8, -119.7),
"ap-northeast (Tokyo)": (35.69, 139.69),
}
def lookup(ip):
req = urllib.request.Request(
f"https://api.ipwho.org/ip/{ip}",
headers={"Authorization": f"Bearer {API_KEY}", "User-Agent": "IPWho-Routing/1.0"},
)
with urllib.request.urlopen(req, timeout=10) as resp:
return json.loads(resp.read())["data"]
def haversine(a, b):
R = 6371
la1, lo1, la2, lo2 = map(math.radians, [a[0], a[1], b[0], b[1]])
h = math.sin((la2-la1)/2)**2 + math.cos(la1)*math.cos(la2)*math.sin((lo2-lo1)/2)**2
return 2 * R * math.asin(math.sqrt(h))
def nearest_region(data):
geo = data.get("geoLocation") or {}
lat, lon = geo.get("latitude"), geo.get("longitude")
if lat is None or lon is None:
return {"region": "default", "reason": "no coordinates"}
pt = (lat, lon)
region = min(REGIONS, key=lambda r: haversine(pt, REGIONS[r]))
return {"country": geo.get("countryCode"), "region": region, "km": round(haversine(pt, REGIONS[region]))}
if __name__ == "__main__":
ip = sys.argv[1] if len(sys.argv) > 1 else "91.0.0.1"
print(f"{ip}: {nearest_region(lookup(ip))}")$ python3 route.py 91.0.0.1 # Germany
91.0.0.1: {'country': 'DE', 'region': 'eu-west (Frankfurt)', 'km': 183}
$ python3 route.py 76.102.11.203 # California
76.102.11.203: {'country': 'US', 'region': 'us-west (Oregon)', 'km': 958}
$ python3 route.py 133.0.0.1 # Japan
133.0.0.1: {'country': 'JP', 'region': 'ap-northeast (Tokyo)', 'km': 0}