Saltar al contenido
Guías de la API

API de detección de fraude por IP: puntúa registros, inicios de sesión y checkouts en Node y Python

Añade una API de detección de fraude por IP al registro, el inicio de sesión y el checkout: código en Node y Python que ejecutamos sobre seis IP públicas, con salida real, caché y los límites de tasa.

PorEquipo de GeoIPHubPublicadoActualizadoTiempo de lectura16 min de lectura
Una ventana de aplicación con un cable luminoso conectado desde un nodo brillante, que representa añadir detección de fraude por IP con una sola llamada a la API

Llegan dos registros con un segundo de diferencia, y ambos ocultan la dirección doméstica de la persona. Uno viene de una salida de Tor en Hamburgo. El otro viene de iCloud Private Relay, que Apple describe como "parte de una suscripción a iCloud+" que te protege "cuando navegas por la web en Safari" (Soporte de Apple). Una regla que bloquea "todo lo oculto" rechaza a ambos, y uno de ellos es un cliente de Apple que paga.

Distinguirlos es el trabajo. Es una parte de lo que abarca la inteligencia de IP, y cabe en un solo archivo de menos de 50 líneas. A continuación tienes la petición, una respuesta real, una versión en Node y otra en Python con caché y gestión de fallos, lo que imprimieron para seis direcciones y dónde se queda corta la comprobación.

Un cronómetro junto a una ventana de aplicación que se llena con una tarjeta de respuesta, que representa una integración en minutos
Una petición, una respuesta, todos los campos.

La única petición que necesita una API de detección de fraude por IP

Toda la integración es un único GET con la IP en la ruta y tu clave en la cabecera x-api-key. La referencia de la API documenta la misma forma:

GeoIPHub API
curl https://api.geoiphub.com/v1/lookup/45.83.220.7 \ -H "x-api-key: YOUR_API_KEY"

Tres detalles pillan a la gente en el primer intento:

  • La IP va en la ruta. /v1/lookup/{ip} es la única forma. Una versión con query string redirige en lugar de devolver datos.
  • No envíes el marcador de posición. Enviamos la cadena literal YOUR_API_KEY y recibimos 401 con {"message":"Invalid API key"}. Omite la cabecera hasta que tengas una clave real; las llamadas anónimas funcionan a 30 por minuto.
  • Una IP por llamada. Cada petición admite una dirección, así que un script que revisa una lista hace una petición por dirección y cada una cuenta para tu límite.

Crea una clave gratuita en el panel cuando pases de las pruebas. El plan gratuito da a esa clave 1.500 consultas al día, con todos los campos incluidos.

Qué devolvió una dirección de Mullvad y sobre qué campos ramificar

La respuesta ocupa unos 2,7 KB de JSON anidado en diez grupos, y una comprobación de fraude necesita dos de ellos: detection y scoring. Esta es la consulta de 45.83.220.7, recortada pero con los valores intactos:

GeoIPHub API
{ "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" } }

Esta dirección está en 31173 Services, la red en la que Mullvad VPN tiene sus servidores, en Malmö. Cuando lo comprobamos el 13 de octubre de 2026, no figuraba en la lista de servidores que publica la propia Mullvad. Se activaron dos señales. known_vpn_server procede de nuestra tabla de redes de hosting de VPN conocidas, y por eso vpn_confidence es 85 y no el 95 que da una coincidencia con la lista de servidores de la propia Mullvad. datacenter_ip indica que es hosting, no una conexión doméstica. Juntas suman 46, que cae en review: una VPN por sí sola no es fraude.

Tres campos sostienen la decisión:

CampoQué contieneCómo lo usa el código
scoring.recommended_actionallow (puntuación hasta 25), review (hasta 50), step_up (hasta 75), block (más de 75)La rama
scoring.fraud_scoreLa puntuación compuesta de 0 a 100 que hay detrás de esa acciónRegistro y ajuste posterior
scoring.detection_methodsLas señales que se activaron, como tor_exit u openvpn_openEl motivo que guardas y muestras a los revisores

Los indicadores de detection (is_vpn, is_proxy, is_tor, is_hosting, is_relay) son los hechos en bruto que hay detrás de la puntuación. Úsalos para las pocas reglas en las que tu negocio se aparta de las bandas predeterminadas. La puntuación también tiene señales exculpatorias: un ISP residencial resta 20 puntos y un rastreador verificado 30, así que un indicador por sí solo no empuja una dirección a block. La guía de la puntuación de fraude de IP explica qué mueve la cifra.

La versión en Node: fetch, una caché de una hora y cuatro ramas

Este es el archivo exacto que ejecutamos, en Node 24 y sin dependencias. Necesita Node 18 o posterior para el fetch integrado:

GeoIPHub API
// 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); }

Cuatro decisiones del código son deliberadas:

  • Parte de recommended_action y solo lo sobrescribe en dos sitios. Una salida de Tor siempre se bloquea, y en el checkout review pasa a ser step_up, porque es ahí donde sale el dinero.
  • Falla en suave. Un tiempo de espera agotado, un 5xx o un 429 devuelven review, no block. Si nuestra API no está disponible, tus registros siguen funcionando y quedan registrados para una segunda revisión.
  • El tiempo de espera es de 3 segundos. Por nuestra parte, una IP conocida se responde desde una base de datos mapeada en memoria en menos de un milisegundo, así que el viaje de ida y vuelta por la red es casi toda la espera. Una IP que no hemos visto nunca se clasifica en vivo en menos de 2,5 segundos, y 3 segundos cubren ese caso.
  • La clave se queda en tu servidor. Llama a la API desde tu backend, nunca desde código del navegador, donde cualquiera puede leer la clave.

La versión en Python necesita un User-Agent o recibe un 403

Nuestra API responde con 403 Forbidden al User-Agent predeterminado de urllib en Python, Python-urllib/3.x. Lo reprodujimos el 16 de octubre de 2026 a las 05:36 UTC con una llamada simple a urllib.request.urlopen: HTTP Error 403: Forbidden. Curl y requests no tienen este problema, porque envían User-Agents distintos. Así que el archivo de Python establece el suyo, con un comentario que explica por qué:

GeoIPHub API
# 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 la misma lógica que el archivo de Node, usando solo la biblioteca estándar, probada en Python 3.9.6. urllib lanza HTTPError para cualquier estado que no sea 2xx, así que el 429 y su cabecera Retry-After se leen de la excepción.

Lo que imprimieron ambos scripts para seis direcciones públicas

Ambos scripts dieron el mismo veredicto para todas las direcciones. Ejecutamos el archivo de Node a las 07:39 UTC y el de Python a las 07:40 UTC del 16 de octubre de 2026, sin clave, contra seis direcciones públicas. El último comando vuelve a ejecutar la dirección de Mullvad como checkout:

GeoIPHub API
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
GeoIPHub API
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 con los mismos argumentos imprimió las mismas siete líneas, carácter por carácter. Esto es cada dirección y por qué acabó donde acabó:

DirecciónQué esAcciónSeñales que se activaron
45.33.32.156scanme.nmap.org, el host público de pruebas de Nmap en Linodeallow, 10Solo datacenter_ip. El hosting por sí solo no es fraude
104.28.46.99Salida de iCloud Private Relay en Cloudflareallow, 9relay_ip. La puntuación tiene un tope de 60 para las direcciones de relé, así que nunca pueden llegar a block
45.83.220.7La red de Mullvad en Malmöreview, 46; step_up en el checkoutknown_vpn_server, datacenter_ip
91.90.122.25Servidor de PIA VPN en Schaan, Liechtenstein, en M247step_up, 64datacenter_blocklisted, no_ptr_datacenter, known_spammer. No se marca como VPN
102.129.235.17Servidor de VPN comercial en Washington D. C.block, 92known_vpn_server, más openvpn_open e ipsec_open: nuestras sondas completaron handshakes de OpenVPN e IPsec
185.220.101.1Salida de Tor en Hamburgoblock, 100tor_exit, datacenter_blocklisted, no_ptr_datacenter, known_spammer

Los dos registros del principio acabaron en extremos opuestos: la salida de Tor con 100 y el usuario de Private Relay con 9. Esa dispersión es la razón para puntuar en lugar de bloquear toda dirección oculta.

Los resultados cambian a medida que cambian nuestros datos. Hasta esta reescritura, este artículo mostraba 45.83.220.7 como hosting normal con una puntuación de 10 y allow. Para el 8 de octubre de 2026 nuestro escáner la había vinculado a Mullvad, y ahora obtiene 46. Registra la acción y los métodos con cada decisión, para que un cambio así aparezca en tus datos en lugar de sorprenderte.

Dónde llamarla: en cada registro, inicio de sesión y checkout

Puntúa cada registro, cada inicio de sesión y cada checkout, en el servidor, antes de confirmar la acción. Son los tres momentos en los que un estafador obtiene algo, y son lo bastante poco frecuentes por usuario como para caber en un presupuesto diario. No la llames en cada vista de página.

Lo que significa cada acción en la práctica:

AcciónRegistroInicio de sesiónCheckout
allowCrear la cuentaIniciar sesiónCobrar el pago
reviewCrearla y marcarla para revisiónIniciar sesión y registrar el motivoNuestro código la sube a step_up
step_upVerificar antes el email o el teléfonoPedir el segundo factorEjecutar 3-D Secure
blockRechazar con un mensaje neutroRechazar y contarlo para el bloqueo de la cuentaRechazar el pedido

Las bandas predeterminadas son un punto de partida, y tus propios datos de fraude deberían moverlas. Nuestros umbrales de puntuación de fraude para inicio de sesión, registro y checkout dan puntos de corte iniciales por acción y el razonamiento de cada uno.

Puntúa también la dirección correcta. Detrás de un balanceador de carga o una CDN, la dirección del socket es tu proxy, y la IP del cliente está en una cabecera de reenvío que cualquier cliente también puede falsificar. Tómala del salto que añadió tu propio proxy de confianza, nunca del valor más a la izquierda que envió un cliente.

Mantenerse por debajo de 1.500 consultas al día: caché y límite de tasa

El plan gratuito permite 1.500 consultas al día. Guarda en caché por IP en tu propio código y el presupuesto da para mucho más, porque una persona normalmente se registra, inicia sesión y paga desde la misma dirección en menos de una hora.

La API en sí no ayuda con la caché: cada respuesta lleva Cache-Control: private, no-store, así que ninguna caché HTTP compartida la guardará. Ambos scripts conservan un veredicto durante una hora en la memoria del proceso. Usa Redis o tu caché existente si tienes más de un servidor. No conserves un veredicto más de un día, porque nuestras fuentes de amenazas y la base de datos de consultas se actualizan a diario.

Los dos límites se comportan de forma distinta, y llegamos a uno de ellos mientras probábamos este artículo:

Llamada comoLímiteQué pasa al superarlo
Sin clave30 consultas por minuto por IP de origenLa petición número 30 dentro de un minuto devuelve 429 y bloquea esa IP durante dos horas
Clave gratuita1.500 consultas al día429 con una cabecera Retry-After hasta que se reinicia el contador diario

A las 05:37 UTC del 16 de octubre de 2026 lanzamos una ráfaga de peticiones anónimas para ver el 429. En veinte segundos la API devolvió 429 con Retry-After: 7199, y todas las llamadas anónimas desde nuestra IP quedaron bloqueadas durante dos horas, incluidos nuestros propios scripts. Su ruta de fallo suave es la primera salida real que obtuvimos:

GeoIPHub API
45.33.32.156 review - - lookup failed: rate limited, retry after 6971s 45.33.32.156 review - - HTTP 429, retry after 6970s

Así que trata el límite anónimo como un margen para pruebas y usa una clave en producción. Ramifica según el código de estado y Retry-After, no según el texto del mensaje del 429.

Dónde se queda corta esta comprobación

Un veredicto de IP es una señal, y se le escapan cosas. Mientras escribíamos este artículo surgieron cuatro casos:

  • Un servidor de VPN que figura en una lista puede volver limpio. El servidor de PIA 91.90.122.25 estaba en la lista que publica la propia PIA el 4 de octubre de 2026, pero nuestra API devuelve is_vpn: false. Llegó a step_up solo porque está en una lista de bloqueo DNS. Sin ese historial habría pasado como hosting normal.
  • Los proxies residenciales son en su mayoría invisibles para los datos de IP. El tráfico enrutado a través de conexiones domésticas reales parece de un usuario doméstico. El campo is_residential_proxy es una inferencia aproximada a partir de la red, no una detección, así que no construyas una regla sobre él.
  • Las direcciones compartidas arrastran el historial de otras personas. Los operadores móviles y el CGNAT colocan a muchos usuarios detrás de una IP, y por eso la puntuación tiene un tope para los rangos de CGNAT y block debería seguir siendo poco frecuente ahí.
  • Las ubicaciones de los relés son aproximadas. Para 104.28.46.99, nuestra geolocalización dice Luanda, Angola, mientras que la lista de salidas de la propia Apple sitúa ese rango en Lusaka, Zambia. Usa el indicador de relé, no la ciudad, para tomar decisiones, como explica nuestra guía para dejar pasar Private Relay.

Combina la comprobación de IP con lo que sabes de la cuenta: antigüedad del email, dispositivo, historial de pagos y velocidad. La guía del stack de detección de fraude en pagos muestra dónde encajan las señales de IP entre ellas.

Pruébala primero con tu propio tráfico

Antes de integrarla en un flujo en producción, ejecuta cualquiera de los dos scripts sobre un día de IP de registros pasados con una clave gratuita y compara sus acciones con lo que ya sabes de esas cuentas. Si block habría detectado cuentas que luego vetaste, y allow cubre a los clientes que conservaste, ponla primero delante del registro, después del inicio de sesión y después del checkout. Para comprobar una dirección a mano, el comprobador de puntuación de fraude de IP muestra la misma puntuación y los mismos motivos sin escribir código.

Preguntas frecuentes

¿La API de GeoIPHub es gratuita para empezar?

Sí. El plan gratuito incluye 1.500 consultas al día con todos los campos de datos, una clave de API y sin tarjeta de crédito. Sin clave puedes hacer 30 consultas por minuto para pruebas.

¿Sobre qué campo debe ramificar mi código?

scoring.recommended_action. Es allow hasta una puntuación de 25, review hasta 50, step_up hasta 75 y block por encima de 75. Lee scoring.detection_methods para registrar el motivo.

¿Por qué mi script de Python recibe un 403?

Nuestra API responde con 403 Forbidden al User-Agent predeterminado de urllib, Python-urllib/3.x. Establece tu propia cabecera User-Agent y la misma petición devuelve 200.

¿Puedo guardar la respuesta en caché?

Sí, en tu propio código. La API envía Cache-Control: private, no-store, así que las cachés HTTP compartidas no la guardarán. Nosotros reutilizamos un veredicto durante una hora por IP.

¿Bloqueará a mis clientes reales?

No si usas las cuatro acciones tal como están diseñadas. Una dirección de iCloud Private Relay volvió como allow con una puntuación de 9 en nuestra prueba, y un servidor en la nube corriente volvió como allow con 10. Solo la salida de Tor y una VPN verificada por sondeo llegaron a block.

¿Tengo que enviar datos personales?

No. Envías una dirección IP y obtienes inteligencia sobre esa dirección. Los nombres, los emails y los datos de tarjeta se quedan en tu sistema.

Escrito por

Equipo de GeoIPHub

El equipo detrás de la API de GeoIPHub

Escrito por el equipo de GeoIPHub, las personas que crean la API de inteligencia IP de GeoIPHub y su escáner. Los borradores se preparan con ayuda de IA y el equipo los edita; los ejemplos de la API son consultas reales contra la API en producción, salvo que se indique que son ilustrativos.

Comparte este artículo
Más artículos

Artículos relacionados