IP-Betrugserkennungs-API: Registrierungen, Logins und Checkouts in Node und Python bewerten
Bauen Sie eine IP-Betrugserkennungs-API in Registrierung, Login und Checkout ein: Node- und Python-Code, getestet an sechs öffentlichen IPs, mit echter Ausgabe, Caching und Rate-Limits.

Zwei Registrierungen kommen im Abstand von einer Sekunde an, und beide verbergen die Heimadresse der Person. Eine kommt von einem Tor-Exit in Hamburg. Die andere kommt von iCloud Private Relay, das Apple als „part of an iCloud+ subscription“ (Teil eines iCloud+-Abonnements) beschreibt, der Sie schützt, „when you browse the web in Safari“ (wenn Sie in Safari im Web surfen) (Apple Support). Eine Regel, die „alles Verborgene“ blockiert, lehnt beide ab, und eine davon ist eine zahlende Apple-Kundin.
Diese beiden auseinanderzuhalten ist die Aufgabe. Sie ist ein Ausschnitt dessen, was IP-Intelligence abdeckt, und sie passt in eine einzige Datei mit weniger als 50 Zeilen. Unten finden Sie die Anfrage, eine echte Antwort, eine Node- und eine Python-Version mit Caching und Fehlerbehandlung, was beide für sechs Adressen ausgegeben haben und wo die Prüfung an Grenzen stößt.

Die eine Anfrage, die eine IP-Betrugserkennungs-API braucht
Die gesamte Integration ist ein einziges GET mit der IP im Pfad und Ihrem Schlüssel im Header x-api-key. Die API-Referenz dokumentiert dieselbe Form:
curl https://api.geoiphub.com/v1/lookup/45.83.220.7 \
-H "x-api-key: YOUR_API_KEY"
Drei Details erwischen viele beim ersten Versuch:
- Die IP gehört in den Pfad.
/v1/lookup/{ip}ist die einzige Form. Eine Variante mit Query-String leitet weiter, statt Daten zu liefern. - Senden Sie nicht den Platzhalter. Wir haben den wörtlichen String
YOUR_API_KEYgesendet und401mit{"message":"Invalid API key"}erhalten. Lassen Sie den Header weg, bis Sie einen echten Schlüssel haben; anonyme Aufrufe funktionieren mit 30 pro Minute. - Eine IP pro Aufruf. Jede Anfrage nimmt eine Adresse entgegen, ein Skript, das eine Liste prüft, stellt also pro Adresse eine Anfrage, und jede zählt gegen Ihr Limit.
Erstellen Sie im Dashboard einen kostenlosen Schlüssel, sobald Sie über das Testen hinaus sind. Der kostenlose Tarif gibt diesem Schlüssel 1.500 Abfragen pro Tag, mit allen Feldern.
Was für eine Mullvad-Adresse zurückkam und nach welchen Feldern Sie verzweigen
Die Antwort umfasst etwa 2,7 KB verschachteltes JSON in zehn Gruppen, und eine Betrugsprüfung braucht zwei davon: detection und scoring. Hier ist die Abfrage für 45.83.220.7, gekürzt, aber mit unveränderten Werten:
{
"ip": "45.83.220.7",
"asn": {
"asn": 39351,
"org": "ESAB-AS - 31173 Services AB",
"asn_type": "hosting",
"connection_type": "datacenter"
},
"geo": {
"country_code": "SE",
"city": "Malmö"
},
"detection": {
"is_vpn": true,
"is_proxy": false,
"is_tor": false,
"is_hosting": true,
"is_relay": false,
"vpn_provider": "Mullvad (31173 Services)",
"vpn_confidence": 85
},
"scoring": {
"fraud_score": 46,
"confidence": 0.56,
"recommended_action": "review",
"detection_methods": [
"known_vpn_server",
"datacenter_ip"
]
},
"meta": {
"last_classified_at": "2026-10-16T07:40:17.524Z"
}
}
Diese Adresse liegt bei 31173 Services in Malmö, dem Netzwerk, in dem Mullvad VPN seine Server betreibt. Als wir am 13. Oktober nachsahen, stand sie nicht auf der von Mullvad selbst veröffentlichten Serverliste. Zwei Signale schlugen an. known_vpn_server stammt aus unserer Tabelle bekannter VPN-Hosting-Netzwerke, deshalb liegt vpn_confidence bei 85 und nicht bei den 95, die ein Treffer auf Mullvads eigener Serverliste ergibt. datacenter_ip besagt, dass es sich um Hosting handelt, nicht um einen Heimanschluss. Zusammen ergeben sie einen Score von 46, der in review landet: Ein VPN allein ist kein Betrug.
Drei Felder tragen die Entscheidung:
| Feld | Was es enthält | Wie der Code es nutzt |
|---|---|---|
scoring.recommended_action | allow (Score bis 25), review (bis 50), step_up (bis 75), block (über 75) | Die Verzweigung |
scoring.fraud_score | Der zusammengesetzte Wert von 0 bis 100 hinter dieser Aktion | Protokollierung und spätere Feinabstimmung |
scoring.detection_methods | Die Signale, die angeschlagen haben, etwa tor_exit oder openvpn_open | Der Grund, den Sie speichern und Prüfern anzeigen |
Die detection-Flags (is_vpn, is_proxy, is_tor, is_hosting, is_relay) sind die Rohfakten hinter dem Score. Nutzen Sie sie für die wenigen Regeln, bei denen Ihr Geschäft von den Standardbereichen abweicht. Der Score kennt auch entlastende Signale: Ein Privatkunden-ISP zieht 20 Punkte ab und ein verifizierter Crawler 30, ein Flag allein schiebt eine Adresse also nicht in block. Der Leitfaden zum IP-Betrugsscore erklärt, was die Zahl bewegt.
Die Node-Version: fetch, ein Cache für eine Stunde und vier Zweige
Dies ist genau die Datei, die wir ausgeführt haben, auf Node 24 ohne Abhängigkeiten. Sie benötigt Node 18 oder neuer für das eingebaute fetch:
// screen.mjs: score an IP before a signup, login or checkout. Node 18+, no dependencies.
const API = "https://api.geoiphub.com/v1/lookup/";
const KEY = process.env.GEOIPHUB_API_KEY; // without a key: 30 lookups a minute, for testing only
const TTL_MS = 60 * 60 * 1000; // reuse a verdict for an hour
const cache = new Map(); // ip -> { at, data }
async function lookup(ip) {
const hit = cache.get(ip);
if (hit && Date.now() - hit.at < TTL_MS) return hit.data;
const headers = KEY ? { "x-api-key": KEY } : {};
const res = await fetch(API + encodeURIComponent(ip), {
headers,
signal: AbortSignal.timeout(3000),
});
if (res.status === 429) throw new Error(`rate limited, retry after ${res.headers.get("retry-after")}s`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
cache.set(ip, { at: Date.now(), data });
return data;
}
export async function screen(ip, flow) {
let data;
try {
data = await lookup(ip);
} catch (err) {
return { action: "review", reason: `lookup failed: ${err.message}` }; // fail soft, never block on our outage
}
const { scoring, detection } = data;
let action = scoring.recommended_action; // "allow" | "review" | "step_up" | "block"
if (detection.is_tor) action = "block"; // no step-up through a Tor exit
if (flow === "checkout" && action === "review") action = "step_up"; // stricter where money moves
const flags = ["is_vpn", "is_proxy", "is_tor", "is_hosting", "is_relay"].filter((f) => detection[f]);
return { action, score: scoring.fraud_score, flags, methods: scoring.detection_methods };
}
// Try it: node screen.mjs signup 45.33.32.156 104.28.46.99
const [flow, ...ips] = process.argv.slice(2);
for (const ip of ips) {
const v = await screen(ip, flow);
console.log(ip.padEnd(15), v.action.padEnd(8), v.score ?? "-", v.flags?.join(",") || "-", v.methods?.join(",") || v.reason);
}
Vier Entscheidungen darin sind bewusst getroffen:
- Sie geht von
recommended_actionaus und überschreibt den Wert nur an zwei Stellen. Ein Tor-Exit wird immer blockiert, und beim Checkout wird ausrevieweinstep_up, weil dort Geld fließt. - Sie scheitert weich. Ein Timeout, ein 5xx oder ein 429 liefert
review, nichtblock. Ist unsere API nicht erreichbar, funktionieren Ihre Registrierungen weiter und werden für einen zweiten Blick protokolliert. - Das Timeout beträgt 3 Sekunden. Auf unserer Seite wird eine bekannte IP aus einer per Memory-Mapping eingebundenen Datenbank in unter einer Millisekunde beantwortet, den größten Teil der Wartezeit macht also der Netzwerk-Roundtrip aus. Eine IP, die wir noch nie gesehen haben, wird live in unter 2,5 Sekunden klassifiziert, und 3 Sekunden decken diesen Fall ab.
- Der Schlüssel bleibt auf Ihrem Server. Rufen Sie die API aus Ihrem Backend auf, niemals aus Browser-Code, in dem jeder den Schlüssel lesen kann.
Die Python-Version braucht einen User-Agent, sonst gibt es einen 403
Unsere API beantwortet den Standard-User-Agent von Python urllib, Python-urllib/3.x, mit 403 Forbidden. Wir haben das am 16. Oktober um 05:36 UTC mit einem nackten Aufruf von urllib.request.urlopen nachgestellt: HTTP Error 403: Forbidden. Curl und requests sind nicht betroffen, weil sie andere User-Agents senden. Die Python-Datei setzt deshalb einen eigenen, mit einem Kommentar, der den Grund nennt:
# screen.py: score an IP before a signup, login or checkout. Python 3.8+, standard library only.
import json, os, sys, time, urllib.error, urllib.request
API = "https://api.geoiphub.com/v1/lookup/"
KEY = os.environ.get("GEOIPHUB_API_KEY") # without a key: 30 lookups a minute, for testing only
TTL = 3600 # reuse a verdict for an hour
_cache = {} # ip -> (time, data)
def lookup(ip):
hit = _cache.get(ip)
if hit and time.time() - hit[0] < TTL:
return hit[1]
# Set a User-Agent: our API answers urllib's default "Python-urllib/3.x" with 403 Forbidden.
headers = {"User-Agent": "signup-screen/1.0"}
if KEY:
headers["x-api-key"] = KEY
req = urllib.request.Request(API + ip, headers=headers)
with urllib.request.urlopen(req, timeout=3) as resp:
data = json.load(resp)
_cache[ip] = (time.time(), data)
return data
def screen(ip, flow):
try:
data = lookup(ip)
except urllib.error.HTTPError as e:
wait = e.headers.get("Retry-After") if e.code == 429 else None
return {"action": "review", "reason": f"HTTP {e.code}" + (f", retry after {wait}s" if wait else "")}
except (OSError, ValueError) as e: # network error, timeout, bad JSON
return {"action": "review", "reason": f"lookup failed: {e}"} # fail soft
scoring, detection = data["scoring"], data["detection"]
action = scoring["recommended_action"] # "allow" | "review" | "step_up" | "block"
if detection["is_tor"]:
action = "block" # no step-up through a Tor exit
if flow == "checkout" and action == "review":
action = "step_up" # stricter where money moves
flags = [f for f in ("is_vpn", "is_proxy", "is_tor", "is_hosting", "is_relay") if detection[f]]
return {"action": action, "score": scoring["fraud_score"], "flags": flags,
"methods": scoring["detection_methods"]}
if __name__ == "__main__":
flow, ips = sys.argv[1], sys.argv[2:]
for ip in ips:
v = screen(ip, flow)
print(f"{ip:<15} {v['action']:<8}", v.get("score", "-"), ",".join(v.get("flags", [])) or "-",
",".join(v.get("methods", [])) or v.get("reason", ""))
Es ist dieselbe Logik wie in der Node-Datei, nur mit der Standardbibliothek, getestet auf Python 3.9.6. urllib löst bei jedem Status außerhalb von 2xx einen HTTPError aus, daher werden der 429 und sein Retry-After-Header aus der Exception gelesen.
Was beide Skripte für sechs öffentliche Adressen ausgegeben haben
Beide Skripte lieferten für jede Adresse dasselbe Urteil. Wir haben die Node-Datei am 16. Oktober 2026 um 07:39 UTC und die Python-Datei um 07:40 UTC ausgeführt, ohne Schlüssel, gegen sechs öffentliche Adressen. Der letzte Befehl führt die Mullvad-Adresse erneut als Checkout aus:
node screen.mjs signup 45.33.32.156 104.28.46.99 45.83.220.7 91.90.122.25 102.129.235.17 185.220.101.1
node screen.mjs checkout 45.83.220.7
45.33.32.156 allow 10 is_hosting datacenter_ip
104.28.46.99 allow 9 is_relay relay_ip
45.83.220.7 review 46 is_vpn,is_hosting known_vpn_server,datacenter_ip
91.90.122.25 step_up 64 is_hosting datacenter_blocklisted,no_ptr_datacenter,known_spammer
102.129.235.17 block 92 is_vpn known_vpn_server,openvpn_open,ipsec_open
185.220.101.1 block 100 is_tor,is_hosting tor_exit,datacenter_blocklisted,no_ptr_datacenter,known_spammer
45.83.220.7 step_up 46 is_vpn,is_hosting known_vpn_server,datacenter_ip
python3 screen.py mit denselben Argumenten gab dieselben sieben Zeilen aus, Zeichen für Zeichen. Hier sehen Sie, was jede Adresse ist und warum sie dort gelandet ist, wo sie gelandet ist:
| Adresse | Was sie ist | Aktion | Angeschlagene Signale |
|---|---|---|---|
45.33.32.156 | scanme.nmap.org, der öffentliche Testhost von Nmap bei Linode | allow, 10 | Nur datacenter_ip. Hosting allein ist kein Betrug |
104.28.46.99 | Egress von iCloud Private Relay bei Cloudflare | allow, 9 | relay_ip. Der Score deckelt Relay-Adressen bei 60, sie können block also nie erreichen |
45.83.220.7 | Mullvads Netzwerk in Malmö | review, 46; step_up beim Checkout | known_vpn_server, datacenter_ip |
91.90.122.25 | VPN-Server von PIA in Schaan, Liechtenstein, bei M247 | step_up, 64 | datacenter_blocklisted, no_ptr_datacenter, known_spammer. Nicht als VPN markiert |
102.129.235.17 | Kommerzieller VPN-Server in Washington, D.C. | block, 92 | known_vpn_server, dazu openvpn_open und ipsec_open: Unsere Probes haben OpenVPN- und IPsec-Handshakes abgeschlossen |
185.220.101.1 | Tor-Exit in Hamburg | block, 100 | tor_exit, datacenter_blocklisted, no_ptr_datacenter, known_spammer |
Die beiden Registrierungen vom Anfang landeten an entgegengesetzten Enden: der Tor-Exit bei 100 und die Private-Relay-Nutzerin bei 9. Diese Spannweite ist der Grund, zu bewerten, statt jede verborgene Adresse zu blockieren.
Die Ergebnisse ändern sich, wenn sich unsere Daten ändern. Bis zu dieser Überarbeitung zeigte dieser Beitrag 45.83.220.7 als reines Hosting mit einem Score von 10 und allow. Bis zum 8. Oktober hatte unser Scanner die Adresse Mullvad zugeordnet, und jetzt liegt ihr Score bei 46. Protokollieren Sie zu jeder Entscheidung die Aktion und die Methoden, damit eine solche Änderung in Ihren Daten sichtbar wird, statt Sie zu überraschen.
Wo Sie die API aufrufen: bei jeder Registrierung, jedem Login und jedem Checkout
Bewerten Sie jede Registrierung, jeden Login und jeden Checkout, auf dem Server, bevor Sie die Aktion ausführen. Das sind die drei Momente, in denen ein Betrüger etwas gewinnt, und sie kommen pro Nutzer selten genug vor, um in ein Tagesbudget zu passen. Rufen Sie die API nicht bei jedem Seitenaufruf auf.
Was jede Aktion in der Praxis bedeutet:
| Aktion | Registrierung | Login | Checkout |
|---|---|---|---|
allow | Konto anlegen | Anmelden | Zahlung annehmen |
review | Anlegen, zur Prüfung markieren | Anmelden, Grund protokollieren | Unser Code stuft es zu step_up hoch |
step_up | Zuerst E-Mail oder Telefon verifizieren | Zweiten Faktor abfragen | 3-D Secure ausführen |
block | Mit neutraler Meldung ablehnen | Ablehnen und für die Sperre mitzählen | Bestellung ablehnen |
Die Standardbereiche sind ein Ausgangspunkt, und Ihre eigenen Betrugsdaten sollten sie verschieben. Unser Beitrag zu Schwellenwerten des Betrugsscores für Login, Registrierung und Checkout nennt Startwerte pro Aktion und die Begründung für jeden.
Bewerten Sie auch die richtige Adresse. Hinter einem Load Balancer oder CDN ist die Socket-Adresse Ihr Proxy, und die IP des Clients steht in einem Forwarding-Header, den jeder Client auch fälschen kann. Nehmen Sie sie aus dem Hop, den Ihr eigener vertrauenswürdiger Proxy hinzugefügt hat, niemals aus dem Wert ganz links, den ein Client gesendet hat.
Unter 1.500 Abfragen pro Tag bleiben: Caching und das Rate-Limit
Der kostenlose Tarif erlaubt 1.500 Abfragen pro Tag. Cachen Sie in Ihrem eigenen Code pro IP, und das Budget reicht viel weiter, denn eine Person registriert sich, meldet sich an und bezahlt meist innerhalb einer Stunde von derselben Adresse aus.
Die API selbst hilft beim Caching nicht: Jede Antwort trägt Cache-Control: private, no-store, kein gemeinsam genutzter HTTP-Cache wird sie also speichern. Beide Skripte halten ein Urteil eine Stunde lang im Prozessspeicher. Verwenden Sie Redis oder Ihren vorhandenen Cache, wenn Sie mehr als einen Server betreiben. Behalten Sie ein Urteil nicht länger als einen Tag, denn unsere Threat-Feeds und die Lookup-Datenbank werden täglich aktualisiert.
Die beiden Limits verhalten sich unterschiedlich, und eines davon haben wir beim Testen für diesen Beitrag ausgelöst:
| Aufruf als | Limit | Was danach passiert |
|---|---|---|
| Ohne Schlüssel | 30 Abfragen pro Minute pro aufrufender IP | Die 30. Anfrage innerhalb einer Minute liefert 429 und sperrt diese IP für zwei Stunden |
| Kostenloser Schlüssel | 1.500 Abfragen pro Tag | 429 mit einem Retry-After-Header, bis der Tageszähler zurückgesetzt wird |
Am 16. Oktober um 05:37 UTC haben wir eine Salve anonymer Anfragen abgefeuert, um den 429 zu sehen. Innerhalb von zwanzig Sekunden lieferte die API 429 mit Retry-After: 7199, und jeder anonyme Aufruf von unserer IP blieb zwei Stunden lang gesperrt, auch unsere eigenen Skripte. Ihr Fail-soft-Pfad war die erste echte Ausgabe, die wir bekamen:
45.33.32.156 review - - lookup failed: rate limited, retry after 6971s
45.33.32.156 review - - HTTP 429, retry after 6970s
Betrachten Sie das anonyme Limit also als Kontingent zum Testen und verwenden Sie in der Produktion einen Schlüssel. Verzweigen Sie nach dem Statuscode und Retry-After, nicht nach dem Meldungstext des 429.
Wo diese Prüfung an Grenzen stößt
Ein IP-Urteil ist ein Signal, und es übersieht Dinge. Vier Fälle sind uns beim Schreiben dieses Beitrags begegnet:
- Ein gelisteter VPN-Server kann sauber zurückkommen. Der PIA-Server
91.90.122.25stand am 4. Oktober auf PIAs eigener veröffentlichter Liste, trotzdem liefert unsere APIis_vpn: false. Er erreichtestep_upnur, weil er auf einer DNS-Blocklist steht. Ohne diese Vorgeschichte wäre er als reines Hosting durchgegangen. - Residential Proxys sind für IP-Daten größtenteils unsichtbar. Traffic, der über echte Heimanschlüsse geleitet wird, sieht aus wie ein Heimnutzer. Das Feld
is_residential_proxyist eine grobe Ableitung aus dem Netzwerk, keine Erkennung, bauen Sie also keine Regel darauf. - Geteilte Adressen tragen die Vorgeschichte anderer. Mobilfunkanbieter und CGNAT setzen viele Nutzer hinter eine IP, deshalb deckelt der Score CGNAT-Bereiche, und deshalb sollte
blockdort selten bleiben. - Relay-Standorte sind ungefähr. Für
104.28.46.99nennt unsere Geolokalisierung Luanda, Angola, während Apples eigene Egress-Liste diesen Bereich in Lusaka, Sambia, verortet. Nutzen Sie für Entscheidungen das Relay-Flag, nicht die Stadt, wie unser Leitfaden zum Durchlassen von Private Relay erklärt.
Kombinieren Sie die IP-Prüfung mit dem, was Sie über das Konto wissen: Alter der E-Mail-Adresse, Gerät, Zahlungshistorie und Velocity. Der Leitfaden zum Payment-Fraud-Stack zeigt, wo IP-Signale darin ihren Platz haben.
Testen Sie es zuerst mit Ihrem eigenen Traffic
Bevor Sie die Prüfung in einen Live-Ablauf einbauen, lassen Sie eines der Skripte mit einem kostenlosen Schlüssel über die Registrierungs-IPs eines vergangenen Tages laufen und vergleichen Sie die Aktionen mit dem, was Sie über diese Konten bereits wissen. Wenn block Konten erwischt hätte, die Sie später gesperrt haben, und allow die Kunden abdeckt, die Sie behalten haben, setzen Sie die Prüfung zuerst vor die Registrierung, dann vor den Login, dann vor den Checkout. Um eine einzelne Adresse von Hand zu prüfen, zeigt der IP-Betrugsscore-Checker denselben Score und dieselben Gründe ganz ohne Code.
Häufig gestellte Fragen
Ist die GeoIPHub-API für den Einstieg kostenlos?
Ja. Der kostenlose Tarif umfasst 1.500 Abfragen pro Tag mit allen Datenfeldern, einen API-Schlüssel und keine Kreditkarte. Ohne Schlüssel können Sie zum Testen 30 Abfragen pro Minute ausführen.
Nach welchem Feld sollte mein Code verzweigen?
Nach scoring.recommended_action. Der Wert ist allow bis zu einem Score von 25, review bis 50, step_up bis 75 und block über 75. Lesen Sie scoring.detection_methods, um den Grund zu protokollieren.
Warum bekommt mein Python-Skript einen 403?
Unsere API beantwortet den Standard-User-Agent von urllib, Python-urllib/3.x, mit 403 Forbidden. Setzen Sie einen eigenen User-Agent-Header, und dieselbe Anfrage liefert 200.
Kann ich die Antwort cachen?
Ja, in Ihrem eigenen Code. Die API sendet Cache-Control: private, no-store, gemeinsam genutzte HTTP-Caches speichern sie also nicht. Wir verwenden ein Urteil pro IP eine Stunde lang wieder.
Blockiert das meine echten Kunden?
Nicht, wenn Sie die vier Aktionen wie vorgesehen nutzen. Eine Adresse von iCloud Private Relay kam in unserem Test mit allow und einem Score von 9 zurück, ein gewöhnlicher Cloud-Server mit allow und 10. Nur der Tor-Exit und ein per Probing verifiziertes VPN erreichten block.
Muss ich personenbezogene Daten senden?
Nein. Sie senden eine IP-Adresse und erhalten Informationen über diese Adresse. Namen, E-Mail-Adressen und Kartendaten bleiben in Ihrem System.
Das Team hinter der GeoIPHub-API
Verfasst vom GeoIPHub-Team, den Menschen, die die GeoIPHub-API für IP-Intelligence und ihren Scanner entwickeln. Entwürfe entstehen mit KI-Unterstützung und werden vom Team redigiert; API-Beispiele sind echte Abfragen gegen die Live-API, sofern nicht als illustrativ gekennzeichnet.


