Guide

How to Harden Signup & Login with an IP Geolocation API

Score new sign-ups and logins from one lookup — network type, anonymiser and threat signals — and step up verification only when risk warrants.

You can risk-score a sign-up or login inline — from a single IP lookup — and step up verification only when the signals warrant it. The response tells you whether a request comes from a datacentre network, hides behind VPN or Tor, or carries a known-threat flag. This guide turns that into an allow / email-verify / require-MFA decision, keeping friction off the 95% of genuine users.

Python

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:

Python
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"]

2Score the sign-up from the network and anonymiser signals:

Give a Corporate (cloud/hosting) network some weight — a lot of bulk registration runs through datacentres — but don't over-trust it: the same Corporate label also covers office networks, universities and corporate VPNs that plenty of genuine users sign up from. So it should raise the score toward a step-up check, never auto-reject. Keep it additive so no single signal is a verdict:

Python
def signup_risk(data):
    conn = data.get("connection") or {}
    sec  = data.get("security") or {}
    score, reasons = 0, []

    # A Corporate network (cloud/hosting, but also offices & corporate VPNs) on a
    # consumer signup is worth review weight — not proof. Step up, don't auto-reject.
    if conn.get("connection_type") == "Corporate":
        score += 40; reasons.append("datacentre/hosting network")
    if sec.get("isTor"): score += 35; reasons.append("Tor")
    if sec.get("isVpn"): score += 20; reasons.append("VPN")

    threat = (sec.get("isThreat") or "low").lower()
    if threat == "high":   score += 30; reasons.append("high threat")
    elif threat == "medium": score += 15; reasons.append("medium threat")

    if score >= 60:   action = "require email + MFA"
    elif score >= 30: action = "email verify"
    else:             action = "allow"
    return {"score": score, "action": action, "reasons": reasons}

print(signup_risk(lookup("185.220.101.1")))

3Map the score to a step-up action — not a block:

Turn the score into allow, email verify, or require MFA. The goal is to add friction only where risk is real, never to hard-block a would-be customer. Feed this alongside your device and behavioural signals:

Python
#!/usr/bin/env python3
"""Risk-score a signup/login from an IP (IPWho IP geolocation API).
Step up verification only when risk warrants — keep friction off genuine users."""
import os, sys, urllib.request, json

API_KEY=*** lookup(ip):
    req = urllib.request.Request(
        f"https://api.ipwho.org/ip/{ip}",
        headers={"Authorization": f"Bearer {API_KEY}", "User-Agent": "IPWho-Signup/1.0"},
    )
    with urllib.request.urlopen(req, timeout=10) as resp:
        return json.loads(resp.read())["data"]

def signup_risk(data):
    conn = data.get("connection") or {}
    sec  = data.get("security") or {}
    score, reasons = 0, []
    if conn.get("connection_type") == "Corporate":
        score += 40; reasons.append("datacentre/hosting network")
    if sec.get("isTor"): score += 35; reasons.append("Tor")
    if sec.get("isVpn"): score += 20; reasons.append("VPN")
    threat = (sec.get("isThreat") or "low").lower()
    if threat == "high":   score += 30; reasons.append("high threat")
    elif threat == "medium": score += 15; reasons.append("medium threat")
    if score >= 60:   action = "require email + MFA"
    elif score >= 30: action = "email verify"
    else:             action = "allow"
    return {"score": score, "action": action, "reasons": reasons}

if __name__ == "__main__":
    ip = sys.argv[1] if len(sys.argv) > 1 else "185.220.101.1"
    print(f"{ip}: {signup_risk(lookup(ip))}")
Output
$ python3 signup.py 185.220.101.1   # Tor exit node
185.220.101.1: {'score': 90, 'action': 'require email + MFA', 'reasons': ['datacentre/hosting network', 'Tor', 'medium threat']}

$ python3 signup.py 52.95.110.1     # AWS datacentre
52.95.110.1: {'score': 40, 'action': 'email verify', 'reasons': ['datacentre/hosting network']}

$ python3 signup.py 76.102.11.203   # residential broadband
76.102.11.203: {'score': 0, 'action': 'allow', 'reasons': []}

Start with a free API key

Free tier: 2,000 lookups/day, no card. Paid plans from $2.50/month. Full field reference here, or see the account-security use case here.