Ir al contenido
Integration

Cómo añadir geolocalización IP a tu SaaS en 20 minutos

| 7 min read

Cuatro funciones de SaaS que necesitan geolocalización de IP: valores predeterminados de moneda, banners de GDPR, detección de fraude y paneles de análisis. Código de trabajo para cada uno, no se requiere Google Maps.

World map with location pins showing IP geolocation data
Photo by NASA on Unsplash

Su SaaS muestra USD a un usuario en Berlín. Su banner de cookies aparece para los visitantes en Texas. Su sistema antifraude no puede detectar cuando una IP nigeriana utiliza una dirección de facturación alemana. Cuatro características Necesita geolocalización de IP y puede agregar los cuatro en 20 minutos con una API.

La llamada API

Cada característica de esta publicación comienza con el mismo punto final. Aquí está el rizo crudo:

curl -X POST https://api.botoi.com/v1/ip/lookup \\
  -H "Content-Type: application/json" \\
  -d '{"ip": "8.8.8.8"}'

Respuesta:

{
  "success": true,
  "data": {
    "ip": "8.8.8.8",
    "city": "Mountain View",
    "region": "California",
    "country": "US",
    "countryName": "United States",
    "latitude": 37.386,
    "longitude": -122.0838,
    "timezone": "America/Los_Angeles",
    "isp": "Google LLC",
    "org": "Google Public DNS",
    "as": "AS15169 Google LLC"
  }
}

One POST le brinda ciudad, región, código de país, nombre completo del país, coordenadas, zona horaria, ISP, organización y número de AS. Son datos suficientes para potenciar las cuatro funciones siguientes.

Característica 1: selección automática de moneda al finalizar la compra

Mostrar la moneda incorrecta al finalizar la compra elimina las tasas de conversión. Un visitante de Alemania ve "$49,99" y tiene que convertir mentalmente a euros antes de decidir. Peor aún, podrían suponga que no sirve a su región.

Solucione este problema con middleware que asigne el país IP del visitante a una moneda predeterminada:

const COUNTRY_CURRENCY = {
  US: "USD", GB: "GBP", DE: "EUR", FR: "EUR", JP: "JPY",
  IN: "INR", BR: "BRL", AU: "AUD", CA: "CAD", CN: "CNY",
  KR: "KRW", MX: "MXN", SE: "SEK", CH: "CHF", SG: "SGD",
};

async function currencyMiddleware(req, res, next) {
  const ip = req.headers["x-forwarded-for"]?.split(",")[0] || req.ip;

  try {
    const response = await fetch("https://api.botoi.com/v1/ip/lookup", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Api-Key": process.env.BOTOI_API_KEY,
      },
      body: JSON.stringify({ ip }),
    });

    const { data } = await response.json();
    req.defaultCurrency = COUNTRY_CURRENCY[data.country] || "USD";
  } catch {
    req.defaultCurrency = "USD";
  }

  next();
}

// Usage in Express
app.get("/checkout", currencyMiddleware, (req, res) => {
  res.render("checkout", { currency: req.defaultCurrency });
});

El mapa de país a moneda cubre los 15 principales mercados de SaaS. Amplíelo para su audiencia. La alternativa al USD maneja las fallas de API con gracia; ningún visitante ve nunca una caja rota porque se agotó el tiempo de espera de una llamada de geolocalización.

Característica 2: banner de cookies GDPR solo para visitantes de la UE

Mostrar un banner de consentimiento de cookies a cada visitante es innecesario y molesto. El RGPD se aplica a los visitantes de la Unión Europea. Todos los demás pueden omitirlo.

Este middleware compara el país IP del visitante con la lista de estados miembros de la UE. y establece una cookie en la interfaz que dice:

const EU_COUNTRIES = new Set([
  "AT", "BE", "BG", "HR", "CY", "CZ", "DK", "EE", "FI", "FR",
  "DE", "GR", "HU", "IE", "IT", "LV", "LT", "LU", "MT", "NL",
  "PL", "PT", "RO", "SK", "SI", "ES", "SE",
]);

async function gdprMiddleware(req, res, next) {
  const ip = req.headers["x-forwarded-for"]?.split(",")[0] || req.ip;

  try {
    const response = await fetch("https://api.botoi.com/v1/ip/lookup", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Api-Key": process.env.BOTOI_API_KEY,
      },
      body: JSON.stringify({ ip }),
    });

    const { data } = await response.json();
    req.isEU = EU_COUNTRIES.has(data.country);
  } catch {
    // Default to showing the banner when the lookup fails
    req.isEU = true;
  }

  next();
}

// Set a cookie so the frontend knows whether to show the banner
app.use(gdprMiddleware, (req, res, next) => {
  res.cookie("gdpr_applies", req.isEU ? "1" : "0", {
    httpOnly: false,
    maxAge: 86400 * 1000,
  });
  next();
});

En la interfaz, lea la cookie y active el banner:

// Frontend: read the cookie and conditionally show the banner
function shouldShowCookieBanner() {
  const match = document.cookie.match(/gdpr_applies=(\d)/);
  return match ? match[1] === "1" : true; // default to showing
}

if (shouldShowCookieBanner()) {
  document.getElementById("cookie-banner").style.display = "block";
}

El comportamiento predeterminado en caso de error es mostrar el banner. Esto peca del lado del cumplimiento; Si la búsqueda geográfica falla, aún cumple con los requisitos del RGPD. La galleta dura 24 horas, por lo que solo llamas a la API una vez por visitante al día.

Característica 3: Detección de fraude con discrepancia geográfica

Cuando alguien realiza el pago con una dirección de facturación en Alemania pero su IP se geolocaliza en Nigeria, esa es una señal que vale la pena investigar. Esto no significa que la transacción sea fraudulento; la gente viaja, usa VPN y compra regalos para amigos en el extranjero. pero es un dato punto que su equipo de revisión de fraude necesita.

async function checkGeoMismatch(ip, billingCountry) {
  const response = await fetch("https://api.botoi.com/v1/ip/lookup", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Api-Key": process.env.BOTOI_API_KEY,
    },
    body: JSON.stringify({ ip }),
  });

  const { data } = await response.json();

  const mismatch = data.country !== billingCountry;

  return {
    mismatch,
    ipCountry: data.country,
    ipCity: data.city,
    billingCountry,
    riskNote: mismatch
      ? \`IP located in \${data.countryName} but billing address is \${billingCountry}\`
      : null,
  };
}

// Usage during checkout
app.post("/checkout", async (req, res) => {
  const ip = req.headers["x-forwarded-for"]?.split(",")[0] || req.ip;
  const { billingCountry } = req.body;

  const geo = await checkGeoMismatch(ip, billingCountry);

  if (geo.mismatch) {
    // Flag for manual review instead of blocking
    await flagOrder(req.body.orderId, geo.riskNote);
  }

  // Continue processing the order
  await processOrder(req.body);
  res.json({ success: true });
});

La función devuelve un objeto estructurado con el indicador de no coincidencia y un texto legible por humanos. nota de riesgo que su equipo de soporte puede revisar. Marcar el pedido para revisión manual en lugar de bloqueándolo por completo. Combine esto con otras señales (antigüedad del dominio de correo electrónico, pago velocidad, huella digital del dispositivo) para obtener una imagen más completa.

Característica 4: Panel de análisis con distribución de usuarios

Saber dónde están sus usuarios le ayuda a decidir qué idiomas admitir y qué regiones. a los que dirigirse con marketing y dónde colocar los servidores perimetrales. Este script procesa un lote de IP de visitantes y produce una distribución de países ordenada:

async function buildCountryDistribution(ips) {
  const counts = {};

  // Process in batches to respect rate limits
  for (const ip of ips) {
    try {
      const response = await fetch("https://api.botoi.com/v1/ip/lookup", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-Api-Key": process.env.BOTOI_API_KEY,
        },
        body: JSON.stringify({ ip }),
      });

      const { data } = await response.json();
      const country = data.countryName || "Unknown";
      counts[country] = (counts[country] || 0) + 1;
    } catch {
      counts["Unknown"] = (counts["Unknown"] || 0) + 1;
    }
  }

  // Sort by count descending
  return Object.entries(counts)
    .sort(([, a], [, b]) => b - a)
    .map(([country, count]) => ({
      country,
      count,
      percentage: ((count / ips.length) * 100).toFixed(1) + "%",
    }));
}

// Example output:
// [
//   { country: "United States", count: 4521, percentage: "34.2%" },
//   { country: "Germany", count: 1893, percentage: "14.3%" },
//   { country: "United Kingdom", count: 1247, percentage: "9.4%" },
//   ...
// ]

Ejecute esto como un trabajo nocturno en sus registros de acceso. La salida le dice exactamente qué Los países que generan la mayor parte del tráfico. Si el 14% de tus usuarios están en Alemania pero solo tu aplicación admite inglés, esa es una oportunidad de localización que puedes cuantificar.

estrategia de almacenamiento en caché

Las asignaciones de IP a ubicación no cambian con frecuencia. No hay motivo para volver a llamar a la API para la misma IP dentro de una sesión. Este caché utiliza un mapa simple con un TTL de 1 hora:

class GeoCache {
  constructor(ttlMs = 60 * 60 * 1000) {
    this.cache = new Map();
    this.ttlMs = ttlMs;
  }

  get(ip) {
    const entry = this.cache.get(ip);
    if (!entry) return null;
    if (Date.now() - entry.timestamp > this.ttlMs) {
      this.cache.delete(ip);
      return null;
    }
    return entry.data;
  }

  set(ip, data) {
    this.cache.set(ip, { data, timestamp: Date.now() });
  }
}

const geoCache = new GeoCache();

async function lookupWithCache(ip) {
  const cached = geoCache.get(ip);
  if (cached) return cached;

  const response = await fetch("https://api.botoi.com/v1/ip/lookup", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Api-Key": process.env.BOTOI_API_KEY,
    },
    body: JSON.stringify({ ip }),
  });

  const { data } = await response.json();
  geoCache.set(ip, data);
  return data;
}

Para servidores Node.js de instancia única, el mapa en memoria funciona bien. Si ejecutas varios instancias detrás de un balanceador de carga, cambie el mapa por Redis. Se aplica la misma lógica TTL; almacene los datos geográficos como una cadena JSON con una caducidad de 3600 segundos.

Extrayendo la IP del cliente

La parte más complicada de la geolocalización de IP no es la llamada a la API; está obteniendo la IP correcta en primer lugar. Si su aplicación se encuentra detrás de un proxy inverso, un equilibrador de carga o una CDN, req.connection.remoteAddress devuelve la IP del proxy, no la del visitante.

A continuación se explica cómo obtener la IP del cliente real en cada entorno:

// Express
const ip = req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.ip;

// Next.js (App Router)
import { headers } from "next/headers";
const headerList = await headers();
const ip = headerList.get("x-forwarded-for")?.split(",")[0]?.trim();

// Cloudflare Workers
const ip = request.headers.get("cf-connecting-ip");

// Vercel (edge or serverless)
const ip = request.headers.get("x-real-ip")
  || request.headers.get("x-forwarded-for")?.split(",")[0]?.trim();

Siempre dividido en la primera coma en x-forwarded-for. Este encabezado puede contener una cadena de IP cuando el tráfico pasa a través de múltiples servidores proxy. La primera entrada es la IP del cliente original.

Si estás en Cloudflare, usa cf-connecting-ip. Cloudflare establece este encabezado en cada solicitud y es más difícil falsificar que x-forwarded-for.

FAQ

¿Cómo agrego geolocalización IP a mi aplicación SaaS?
Envíe la IP de su visitante a una API de geolocalización de IP (como POST /v1/ip/lookup) y utilice los datos devueltos de país, ciudad y zona horaria para personalizar su experiencia. Los casos de uso comunes incluyen la selección automática de moneda al momento de pagar, mostrar pancartas de GDPR a los visitantes de la UE, señalar fraudes por discrepancias geográficas y crear paneles de análisis. Puede agregar las cuatro funciones con una única API.
¿Cuál es la mejor API de ubicación de IP para productos SaaS?
Busque una API que devuelva datos de país, ciudad, región, coordenadas, zona horaria y ISP en una sola llamada. /v1/ip/lookup de Botoi devuelve todos estos campos sin necesidad de registrarse para el acceso anónimo (5 solicitudes/min). Para uso en producción, una clave API gratuita proporciona 1000 solicitudes por día. Los planes pagos comienzan en $9/mes.
¿Puedo geolocalizar usuarios por IP sin Google Maps?
Sí. Las API de geolocalización de IP devuelven datos de latitud, longitud, ciudad y país sin necesidad de Google Maps ni de ningún servicio de mapas. Solo necesita una biblioteca de mapas si desea mostrar la ubicación en un mapa visual. Para funciones como valores predeterminados de moneda, cumplimiento del RGPD y detección de fraude, todo lo que necesita son los datos de geolocalización sin procesar de la API.
¿Qué tan precisa es la geolocalización IP para detectar la ubicación del usuario?
La geolocalización IP es precisa a nivel de país aproximadamente el 99% del tiempo y a nivel de ciudad aproximadamente el 80-90% del tiempo. La precisión disminuye para los operadores de telefonía móvil y los usuarios de VPN. Para funciones de SaaS como la selección de moneda y el cumplimiento del RGPD, la precisión a nivel de país es suficiente. Para la detección de fraude, combine la geolocalización de IP con los datos de la dirección de facturación en lugar de depender de la precisión a nivel de ciudad.
¿Debo almacenar en caché los resultados de geolocalización de IP en mi SaaS?
Sí. Las asignaciones de IP a ubicación cambian con poca frecuencia, por lo que el almacenamiento en caché de los resultados durante 1 hora por IP reduce significativamente las llamadas a la API. Utilice un mapa en memoria para implementaciones de instancia única o Redis para configuraciones de instancias múltiples. La mayoría de las aplicaciones SaaS tienen tasas de aciertos de caché del 60 al 80 % porque los visitantes que regresan acceden a la misma IP dentro de una sesión.

Empieza a construir con botoi

150+ endpoints de API para consultas, procesamiento de texto, generacion de imagenes y utilidades para desarrolladores. Plan gratuito, sin tarjeta de credito.