IP Geolocation for E-commerce — Tax Routing, Carding Defence, PPP Pricing, and Shipping-Zone Steering

Why e-commerce is its own axis: every checkout that crosses a tax border is a five-jurisdiction decision in <40 ms — EU OSS / IOSS, UK VAT, US state-level Wayfair nexus, AU/SG/IN GST, and a sanctions hard-stop. Get the country wrong on first paint and you under-collect VAT (booked as merchant liability under EU Directive 2006/112/EC Art. 33), over-charge a domestic buyer 19 % they don’t owe (chargeback risk), or ship a dual-use item into a sanctioned destination (EU 2021/821 fine = up to €5 M + criminal exposure for the DGA). IP-layer signals are upstream of every checkout-side decision — tax engine, currency display, fulfilment centre, BIN-vs-IP carding score, and OFAC/EU/BIS gating. A wrong country resolve on the storefront’s first paint is a five-system breach within the same render frame.

The country an IP resolves to, the ASN it belongs to, and whether it’s a known datacenter, VPN, residential-proxy, or Tor exit are inputs to four separate e-commerce control surfaces:

  1. Tax-jurisdiction routing at checkout — EU OSS distance-sales over the €10 K pan-EU threshold per merchant; IOSS for ≤€150 imports from outside EU; UK VAT MOSS post-Brexit; US state-level Wayfair-nexus (50 states, Alaska/Oregon excepted); AU GST low-value imports; Singapore GST OVR; India GST IGST; Brazil ICMS state-by-state. The wrong country on first paint mis-prices tax for the entire cart and is a merchant-liability event.
  2. Carding, refund-fraud, and billing-shipping-mismatch defence — BIN-country (from the first 6-8 digits of the card) cross-checked against IP-country flags stolen-card attempts; residential-proxy ASNs (Bright Data, Oxylabs, Smartproxy, IPRoyal) signal trial-abuse and gift-card fraud; velocity-on-IP rules catch coordinated bot fleets. Stripe Radar, Adyen RevenueProtect, Forter, and Signifyd all consume IP-country + ASN + threat-flags as primary features.
  3. Currency display + PPP-adjusted pricing on first paint — €99 in DE/NL/FR, £79 in UK, $109 in US, R$199 in BR, ₹3 999 in IN, ¥14 800 in JP — the storefront has < 100 ms to pick the right currency, the right tier, and the right PPP markdown before exit-before-add-to-cart spikes. The VPN/proxy fall-back must route to billing-country (from the BIN) before commit.
  4. Shipping-zone routing + fulfillment-centre selection + customs-duty pre-calc — multi-region 3PLs (Amazon FBA, ShipBob, Bringg, Cubyn, byrd) host inventory across DE-FRA + NL-AMS + UK-DOV + IT-MIL + ES-MAD + US-East/West + IN-DEL + SG-SIN; ship-from-nearest-warehouse needs IP-country + region; cross-border carts need DDP/DDU customs-duty pre-calc shown before the place-order button; restricted-item gating (lithium batteries, aerosols, prescription items) is per-destination.

A single REST call to IP Geo API returns all four signal classes — country/region/city/lat-lon, ASN, threat-flags (VPN/proxy/Tor/hosting/relay), risk score — on every plan, no add-on SKU, ≤40 ms median in EU.

What e-commerce buyers care about (in order)

  1. Checkout-page first-paint latency budget ≤ 40 ms. Baymard / Shopify 2025 data — every +100 ms to Largest-Contentful-Paint on the cart/checkout flow correlates with 7 % drop in checkout-complete rate. The IP-resolve must finish in ≤ 40 ms inside the storefront’s first-paint path or currency, tax estimate, and PPP tier render late and the cart is abandoned before the place-order button. IP Geo API runs on EU edges (Hetzner Frankfurt) for ≤ 30-40 ms median across DE/NL/FR/IE/ES/IT/UK, ≤ 60 ms US-EU round-trip.
  2. DAC7 / GDPR / EU OSS audit-trail for the platform-liability bundle. Marketplaces (Bol.com, Allegro, Etsy, eBay, Vinted) operating under DAC7 (EU Directive 2021/514) must retain seller + buyer geo-resolution logs for 5 years; merchants under EU OSS must keep VAT-jurisdiction evidence for 10 years; merchants under MiFID II affiliate-flows for 5. IP Geo API ships a deterministic-replay log-format on every paid tier — same country_code + region + is_vpn + is_proxy + risk_score at lookup-time gets persisted in the response envelope for audit-grade reconstruction.
  3. Threat fields on every plan, not a paid add-on. Most US incumbents (MaxMind, ipinfo.io, ipstack) split datacenter/VPN/proxy classification into a paid Security Module or Privacy add-on. IP Geo API ships is_vpn, is_proxy, is_tor, is_hosting, is_relay, and a numeric risk_score on the free tier — critical for catching trial-abuse on coupon-codes, gift-card resale fraud, and BIN-vs-IP geo-mismatch attacks that dominate carding loss > €1 B/year EU-wide.
  4. ASN-level granularity for residential-proxy block-listing on checkout. Country-only checks pass residential-proxy traffic through; ASN + is_proxy flag catches them at the place-order step before the card auth. We expose asn, asn_org, and is_proxy as first-class fields so your checkout filters can reject at ASN granularity (Bright Data 212238 / 401116, Oxylabs 396982 / 60068, Smartproxy 62240 / 16276, IPRoyal 35916 / 174, Tier3/Choopa 21859 / 32475) without maintaining a list yourself.
  5. EU residency + GDPR + DAC7 posture. Storefront IPs cannot be transferred to a US vendor without GDPR §28 DPA + SCCs + TIA; DAC7 marketplace-reporting requires seller-and-buyer geo-residency evidence; the entire chain (IP-resolve, tax-jurisdiction routing, fraud-score persistence) must be EU-data-residency end-to-end. IP Geo API is EU-only data-flow, signed DPA in 24h, no SCCs required, DAC7-audit-ready.

The four e-commerce control surfaces, in code

1. Tax-jurisdiction routing at checkout (hard-fail-closed on sanctions)

// /api/checkout/quote.js — Node 20 / cart-quote path
// Called on every cart-quote refresh BEFORE the place-order button enables.
// Fail-closed: sanctioned destinations return HTTP 451; ambiguous resolves default to merchant-VAT-country.
const fetch = require('undici').fetch;

const TAX_ROUTES = {
  // EU OSS — distance-sales destination-country VAT for B2C
  'EU': { engine: 'oss', rates: { DE:0.19, NL:0.21, FR:0.20, IE:0.23, ES:0.21, IT:0.22, PL:0.23, PT:0.23, BE:0.21, AT:0.20, DK:0.25, FI:0.255, SE:0.25, CZ:0.21, GR:0.24, HU:0.27, LU:0.17, RO:0.19, SK:0.23, SI:0.22, BG:0.20, HR:0.25, CY:0.19, EE:0.22, LV:0.21, LT:0.21, MT:0.18 }},
  'GB': { engine: 'uk-vat',  rate: 0.20 },
  'CH': { engine: 'ch-vat',  rate: 0.077 },
  'NO': { engine: 'no-mva',  rate: 0.25 },
  'US': { engine: 'us-wayfair', nexus_states: ['CA','NY','TX','FL','WA','IL','NJ','PA','GA','MA','NC','VA','OH'] },
  'CA': { engine: 'ca-gst-hst', province_rates: { ON:0.13, QC:0.14975, BC:0.12, AB:0.05, MB:0.13, SK:0.11 }},
  'AU': { engine: 'au-gst', rate: 0.10 },
  'SG': { engine: 'sg-gst', rate: 0.09 },
  'IN': { engine: 'in-igst', rate: 0.18 },
  'BR': { engine: 'br-icms', state_rates: { SP:0.18, RJ:0.20, MG:0.18 }},
  'JP': { engine: 'jp-jct', rate: 0.10 },
};

// EU CFSP + OFAC SDN + UK OFSI restrictive-measures + BIS Entity-List destinations — hard-stop
const SANCTIONED = new Set(['IR','KP','SY','CU','BY','RU','MM','VE']);

async function quoteCart(req, res) {
  const ip = req.headers['cf-connecting-ip'] || req.ip;
  let geo;
  try {
    geo = await (await fetch(`https://ipgeo.10b.app/v1/${ip}?fields=country_code,region,city,is_vpn,is_proxy,is_hosting,asn,risk_score`, {
      headers: { 'Authorization': `Bearer ${process.env.IPGEO_KEY}` },
      signal: AbortSignal.timeout(40)   // ≤ 40 ms hard budget
    })).json();
  } catch {
    // Soft-degrade to merchant-VAT-country to keep checkout responsive
    return res.json({ tax_country: 'NL', tax_rate: 0.21, currency: 'EUR', degraded: true });
  }

  // Hard sanctions stop — no quote, no DDP/DDU, no order accepted
  if (SANCTIONED.has(geo.country_code)) return res.status(451).end();

  // EU OSS distance-sales destination-country VAT
  const eu = TAX_ROUTES.EU.rates;
  if (geo.country_code in eu) {
    return res.json({ tax_country: geo.country_code, tax_rate: eu[geo.country_code], currency: 'EUR' });
  }

  const route = TAX_ROUTES[geo.country_code];
  if (!route) return res.json({ tax_country: geo.country_code, tax_rate: 0, currency: 'EUR', export_only: true });

  // US Wayfair state-nexus check
  if (route.engine === 'us-wayfair') {
    const collect = route.nexus_states.includes(geo.region);
    return res.json({ tax_country: 'US', region: geo.region, tax_rate: collect ? 0.0625 : 0, currency: 'USD', engine: 'us-wayfair' });
  }
  return res.json({ tax_country: geo.country_code, tax_rate: route.rate, currency: 'USD', engine: route.engine });
}

2. Carding / BIN-vs-IP / refund-fraud scoring (place-order gate)

# /api/checkout/risk.py — FastAPI / place-order risk gate
# Called immediately before the card auth-only request; declines on score >= 70 before charging.

from fastapi import FastAPI, HTTPException
import httpx
from binlist import bin_country   # vendor-shipped first-6 BIN → ISO-2 map

RESIDENTIAL_PROXY_ASN = {
    212238, 401116,  # Bright Data
    396982,  60068,  # Oxylabs
     62240,  16276,  # Smartproxy
     35916,    174,  # IPRoyal
     21859,  32475,  # Tier3 / Choopa
}

async def score_order(order: dict) -> dict:
    ip = order["client_ip"]
    bin6 = order["card_bin"]                       # first 6 digits, never the PAN
    ship_country = order["shipping_address"]["country"]
    bill_country = bin_country(bin6) or order["billing_address"]["country"]

    async with httpx.AsyncClient(timeout=0.04) as cx:
        r = await cx.get(f"https://ipgeo.10b.app/v1/{ip}",
            params={"fields": "country_code,asn,is_vpn,is_proxy,is_tor,is_hosting,risk_score"},
            headers={"Authorization": f"Bearer {IPGEO_KEY}"})
    g = r.json()

    score = 0
    # BIN-country vs IP-country mismatch — strongest signal in carding
    if bill_country and g["country_code"] and bill_country != g["country_code"]:
        score += 35
    # Shipping vs billing mismatch
    if ship_country and bill_country and ship_country != bill_country:
        score += 20
    # Circumvention / anonymisation
    if g["is_vpn"] or g["is_proxy"] or g["is_tor"]:
        score += 25
    # Residential-proxy ASN — gift-card / trial-abuse / coupon-stuff
    if g["asn"] in RESIDENTIAL_PROXY_ASN:
        score += 30
    # Datacenter origin on consumer purchase
    if g["is_hosting"] and order["channel"] == "web":
        score += 15

    if score >= 70:
        raise HTTPException(402, "Order declined: risk threshold")
    return {"risk_score": score, "geo_country": g["country_code"], "bill_country": bill_country}

3. Currency display + PPP-adjusted pricing on first paint

// /api/storefront/price.js — Edge function on the storefront's first paint
// Returns { currency, price_tier, pricebook_id } in ≤ 40 ms so the hero renders pre-hydration.
const PPP_TIERS = {
  // Tier 1 — high-PPP markets, full price
  T1: { currency: 'EUR', price: 99,  countries: ['DE','NL','FR','BE','LU','AT','DK','SE','FI','IE','IS','NO','CH'] },
  T2: { currency: 'GBP', price: 79,  countries: ['GB'] },
  T3: { currency: 'USD', price: 109, countries: ['US','CA','AU','NZ','SG','HK','JP','KR','TW','IL','AE'] },
  // Tier 4 — PPP-adjusted EU periphery + LATAM upper
  T4: { currency: 'EUR', price: 79,  countries: ['ES','IT','PT','GR','CZ','SK','SI','PL','HU','EE','LT','LV','HR','RO','BG','CY','MT'] },
  T5: { currency: 'BRL', price: 199, countries: ['BR','MX','AR','CL','UY','CR','PE','CO'] },
  // Tier 6 — emerging
  T6: { currency: 'INR', price: 3999, countries: ['IN','PH','ID','TH','VN','MY','EG','PK','BD','LK','KE','NG','ZA','MA'] },
  T7: { currency: 'TRY', price: 1990, countries: ['TR'] },
};

async function pickPriceBook(ip, billCountryHint) {
  const r = await fetch(`https://ipgeo.10b.app/v1/${ip}?fields=country_code,is_vpn,is_proxy`, {
    headers: { 'Authorization': `Bearer ${process.env.IPGEO_KEY}` },
    signal: AbortSignal.timeout(40),
  });
  const g = await r.json();
  // VPN/proxy fall-back — use BIN-derived billing-country if present, otherwise T1 EUR
  const country = (g.is_vpn || g.is_proxy) ? (billCountryHint || 'NL') : g.country_code;
  for (const [tier, def] of Object.entries(PPP_TIERS)) {
    if (def.countries.includes(country)) return { tier, ...def, country };
  }
  return { tier: 'T1', ...PPP_TIERS.T1, country };
}

4. Shipping-zone routing + fulfillment-centre selection + customs-duty pre-calc

# /api/cart/fulfillment.py — picks 3PL warehouse + DDP/DDU duty estimate for the cart
# Restricted-item gating runs before the warehouse-pick (lithium, aerosol, perfume, prescription).

from fastapi import FastAPI, HTTPException
import httpx

WAREHOUSES = {
    'FRA': { 'region': 'EU', 'serves': ['DE','AT','CH','CZ','SK','HU','PL'] },
    'AMS': { 'region': 'EU', 'serves': ['NL','BE','LU','DK','NO','SE','FI'] },
    'MAD': { 'region': 'EU', 'serves': ['ES','PT'] },
    'MIL': { 'region': 'EU', 'serves': ['IT','SM','VA','MT','GR','CY','HR','SI'] },
    'DOV': { 'region': 'UK', 'serves': ['GB','IE','JE','GG','IM'] },
    'IAD': { 'region': 'US', 'serves': ['US-EAST','US-CENTRAL','CA-EAST'] },
    'LAX': { 'region': 'US', 'serves': ['US-WEST','MX','CA-WEST'] },
    'DEL': { 'region': 'IN', 'serves': ['IN','BD','LK','NP'] },
    'SIN': { 'region': 'APAC', 'serves': ['SG','MY','TH','VN','PH','ID'] },
}

# DDP duty rates (importer pays at checkout). Simplified — real systems consult HTS code per SKU.
DUTY_RATES = {
    ('US','EU'): 0.025,  ('EU','US'): 0.030,
    ('GB','EU'): 0.020,  ('EU','GB'): 0.000,   # UK→EU still under EU-UK TCA
    ('CH','EU'): 0.035,  ('EU','CH'): 0.020,
    ('IN','EU'): 0.075,  ('EU','IN'): 0.100,
    ('BR','EU'): 0.180,  ('EU','BR'): 0.120,
}

RESTRICTED_ITEMS_BY_DEST = {
    'lithium-battery': ['CN','RU','BY','SA'],
    'aerosol':         ['AE','SA','OM','QA'],
    'prescription':    ['US','CA','AU','GB','DE','FR','NL'],   # OTC vs Rx differ by destination
}

async def pick_fulfillment(cart: dict) -> dict:
    ip = cart["client_ip"]
    async with httpx.AsyncClient(timeout=0.04) as cx:
        r = await cx.get(f"https://ipgeo.10b.app/v1/{ip}",
            params={"fields": "country_code,region,is_vpn,is_proxy"},
            headers={"Authorization": f"Bearer {IPGEO_KEY}"})
    g = r.json()
    if g["is_vpn"] or g["is_proxy"]:
        raise HTTPException(451, "Anonymised origin not supported for shipping")

    dest = g["country_code"]
    # Restricted-item gate
    for sku in cart["items"]:
        for category, blocked_dests in RESTRICTED_ITEMS_BY_DEST.items():
            if sku["category"] == category and dest in blocked_dests:
                raise HTTPException(451, f"{category} restricted to {dest}")

    # Pick nearest warehouse with inventory
    warehouse = next((w for w, def_ in WAREHOUSES.items() if dest in def_["serves"]), 'IAD')
    cross_border = WAREHOUSES[warehouse]["region"] != _region_of(dest)
    duty_rate = 0
    if cross_border:
        duty_rate = DUTY_RATES.get((_region_of(dest), WAREHOUSES[warehouse]["region"]), 0.05)
    return {
        "warehouse": warehouse,
        "dest_country": dest,
        "cross_border": cross_border,
        "duty_rate_estimate": duty_rate,
        "ddp_recommended": cross_border and duty_rate <= 0.075,
    }

def _region_of(cc: str) -> str:
    if cc in ('US','CA','MX'): return 'US'
    if cc in ('GB','IE','JE','GG','IM'): return 'UK'
    if cc in ('IN','BD','LK','NP','PK'): return 'IN'
    if cc in ('SG','MY','TH','VN','PH','ID'): return 'APAC'
    return 'EU'

Pricing math — when does IP Geo API pay for itself for an e-commerce operator?

Plan Lookups / month Median checkout-first-paint latency Threat fields Equivalent fraud-loss / VAT-mis-collection reduction
Free (1K/day) 30 K ≤ 40 ms bundled Sandbox / staging — covers checkout-funnel QA
Starter €29 ~1 M ≤ 40 ms bundled Single-region D2C ≤ 50 K monthly orders
Business €99 ~15 M ≤ 40 ms bundled, SLA Multi-region D2C / marketplace ≤ 1 M monthly orders, audit-grade
Enterprise (POA) ≥ 50 M dedicated edges full + custom Tier-1 marketplace (Bol/Allegro-class), DAC7 + EU OSS audit-bundle

At Business €99/mo: a single blocked BIN-vs-IP carding attempt that would have charged €600 in stolen-card volume saves the merchant chargeback fee (~€25), card-network arbitration (~€100), and the average dispute-loss (~€450). Most e-commerce operators recoup the €99/mo Business tier on < 1 blocked carding session per month.

Honest limits — when IP geolocation is not the right signal

  1. CG-NAT regions — in MENA, parts of LATAM, and rural India, CG-NAT collapses thousands of subscribers behind one egress IP. Country-resolve stays accurate but city-resolve degrades; never block a cart on city + ASN alone in CG-NAT-heavy markets. We expose city_confidence so the storefront can fall back to country-only tax/currency in CG-NAT regions.
  2. iCloud Private Relay + Proxy-on-by-default OS-modes — Apple’s iCloud Private Relay shifts the egress IP to a Cloudflare/Akamai relay-block; we classify these as is_relay=true so the storefront can either trust-but-verify (preferred for repeat buyers with order-history) or fall back to a postcode/zip prompt before tax calc.
  3. Mobile-data IP rotation — 4G/5G subscribers can hop between cell-tower egress IPs mid-cart; do not invalidate a logged-in cart on IP-change alone. Pin the cart-session’s tax-country to the at-add-to-cart snapshot for ≤ 6 h.
  4. MaxMind is_anonymous_proxy deprecation — MaxMind sunset is_anonymous_proxy in 2024 Q3; if your existing fraud-team’s Sigma rules reference it, migrate to is_proxy + is_vpn + is_relay + is_hosting composite. We expose all four for forward-compatibility on the audit-trail.
  5. HS-code-level tariff precision — DDP/DDU duty-rate above is a single-rate estimate per country-pair; real customs duty is per HS-6 code × destination × origin × free-trade-agreement (RCEP, USMCA, EU-Japan EPA). For >€500 cart-value cross-border, integrate a customs-rate API (Avalara, TaxJar, Zonos) on top of the IP-derived country pair.

Use-cases that compose with e-commerce

Every e-commerce stack composes 3-5 IP-layer use-cases. The relevant primary deep-dives:

Compare IP Geo API to the providers e-commerce teams evaluate

If you’re shortlisting vendors for a tax-engine refactor, a fraud-stack rebuild, or a marketplace DAC7 audit, these head-to-heads cover the providers most often shortlisted in the IP-geolocation market:

Read also — narrative deep-dives

Seven 2026-dated comparison articles with code-level migration sketches and latency / pricing math at 100K / 1M / 10M req/mo:

Migration walkthroughs — drop-in code-level guides

Already on an incumbent? These step-by-step migration guides ship with field-by-field maps, code diffs, shadow-mode validation, and rollback notes:

Industry deep-dives

Other vertical-specific surfaces using the same IP Geo API primitives:


Get started — e-commerce-friendly procurement

Sign up at https://ipgeo.10b.app/pricing and start with a sandbox key today.


Get early access — 50% off for 12 months

First 100 signups lock in 50% off any paid plan for the first year. No credit card required — we’ll email you at launch.