How to Restrict Purchases by Country Using IP Geolocation?

Restricting purchases by country is a common requirement for modern e-commerce and SaaS platforms. Whether you are navigating complex tax jurisdictions like VAT/GST, complying with international trade sanctions, or mitigating high-risk fraud regions, a robust geographic filtering system is essential.

For backend engineers, this isn't just about a simple if statement. It involves managing IP intelligence, handling edge cases like VPNs/IPv6, and ensuring the restriction logic doesn't introduce significant latency into the checkout flow.

Restricting purchases by visitors country on your app or website

Why Restrict Purchases by Geography?

Before diving into the implementation, it is important to categorize why these restrictions are necessary, as the "strictness" of your implementation may vary based on the goal.

How IP-Based Restriction Works Internally

The process typically occurs at the application or middleware level. When a request hits your server, you extract the client's IP address and query a geolocation database or API to resolve that IP into a country code (ISO 3166-1 alpha-2).

The Logic Flow

Internally, IP geolocation providers maintain large datasets that map IP ranges to geographic regions. These datasets are continuously updated using data from regional internet registries (RIRs), routing tables, and network telemetry.

Practical Backend Implementation

1. The API Request

Using a specialized IP intelligence API is preferred over local MaxMind databases for most cloud-native apps to avoid the overhead of constant manual database updates.

Example Request (cURL):

curl -X GET "https://api.ip2geoapi.com/ip/8.8.8.8?key=YOUR_API_KEY"

2. The JSON Response

A reliable API should return a structured response containing the ISO country code and security metadata.

{
  "success": true,
  "ip": "8.8.8.8",
  "version": "ipv4",
  "geo": {
    "city": "Chicago",
    "country": "United States",
    "countryCode": "US",
    "region": null,
    "regionCode": null,
    "latitude": 37.751,
    "longitude": -97.822,
    "postalCode": null,
    "geonameId": 6252001,
    "accuracyRadius": 1000,
    "metroCode": null,
    "continentName": "North America",
    "continentCode": "NA",
    "isEuMember": false
  },
  "countryInfo": {
    "name": "United States of America",
    "alpha2Code": "US",
    "alpha3Code": "USA",
    "flag": "https://api.ip2geoapi.com/assets/flags/us.svg",
    "callingCodes": [
      "1"
    ],
    "currencies": [
      {
        "code": "USD",
        "name": "United States dollar",
        "symbol": "$"
      }
    ],
    "languages": [
      {
        "iso639_1": "en",
        "iso639_2": "eng",
        "name": "English",
        "nativeName": "English"
      }
    ]
  },
  "timezoneInfo": {
    "timezone": "America/Chicago",
    "utcOffsetSeconds": -21600,
    "utcOffsetText": "-06:00",
    "utcOffsetHours": -6,
    "isDst": false,
    "abbreviation": "CST",
    "localTime": "2026-01-18T21:44:25-06:00"
  },
  "network": {
    "cidr": "8.8.8.8/32",
    "prefixLen": 32,
    "asn": 15169,
    "asFormatted": "AS15169",
    "asName": "GOOGLE",
    "isp": "Google",
    "organization": "Google",
    "connectionType": "Corporate",
    "mobile": {
      "mcc": null,
      "mnc": null
    }
  },
  "asDetails": {
    "asn": 15169,
    "abuser_score": "0.001 (Low)",
    "descr": "GOOGLE, US",
    "country": "us",
    "active": true,
    "org": "Google LLC",
    "domain": "google.com",
    "abuse": "[email protected]",
    "type": "hosting",
    "created": "2000-03-30",
    "updated": "2012-02-24",
    "rir": "ARIN"
  },
  "security": {
    "isHosting": true,
    "isProxy": false,
    "proxyType": null,
    "isVpn": false,
    "vpnProvider": null,
    "vpnProviderUrl": null,
    "isTor": false,
    "isAnonymous": true,
    "trustScore": 65,
    "riskLevel": "medium"
  }
}

3. Backend Logic (Node.js/Express Example)

In a production environment, you should implement this as a middleware that runs specifically on your /api/v1/checkout or /api/v1/payment-intent endpoints.

const axios = require('axios');

const GEO_RESTRICTED_COUNTRIES = ['CN', 'RU', 'IR', 'KP'];

async function geoBlockMiddleware(req, res, next) {
    // Get client IP, accounting for proxies
    const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;

    try {
        const response = await axios.get(`https://api.ip2geoapi.com/ip/${ip}?key=${process.env.GEO_API_KEY}`);

        // Block if country is restricted OR if user is masking their location via VPN
        if (GEO_RESTRICTED_COUNTRIES.includes(response.geo.countryCode) || response.security.isVpn) {
            return res.status(403).json({
                error: "Service unavailable in your region.",
                code: "GEO_RESTRICT_BLOCK"
            });
        }

        next();
    } catch (error) {
        // Fallback: Decide whether to fail-open or fail-closed
        console.error("GeoIP Lookup failed", error);
        next(); 
    }
}

Implementing Geofencing in Python with FastAPI

import httpx
from fastapi import FastAPI, Request, HTTPException, Depends, status

app = FastAPI()

# Configuration
API_KEY = "YOUR_API_KEY"
RESTRICTED_COUNTRIES = ["CN", "RU", "IR", "KP"]
MIN_TRUST_SCORE = 70

async def get_geo_data(request: Request):
    # Retrieve the client IP (handling reverse proxies)
    client_ip = request.headers.get("x-forwarded-for") or request.client.host
    
    url = f"https://api.ip2geoapi.com/ip/{client_ip}?key={API_KEY}"
    
    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(url, timeout=2.0)
            if response.status_code != 200:
                # Fail-open: allow request if API is down, or log for manual review
                return None
            return response.json()
        except Exception as e:
            print(f"Geo-check error: {e}")
            return None

def verify_purchase_eligibility(geo_data: dict = Depends(get_geo_data)):
    if not geo_data or not geo_data.get("success"):
        return # Allow if data is missing or API failed (fail-open strategy)

    # Extract relevant fields from the nested structure
    country_code = geo_data["geo"]["countryCode"]
    security = geo_data["security"]
    
    # Check 1: Geographical Blocklist
    if country_code in RESTRICTED_COUNTRIES:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Service not available in your region."
        )

    # Check 2: Proxy/VPN detection for high-risk transactions
    if security["isVpn"] or security["isProxy"] or security["isTor"]:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="VPN or Proxy detected. Please use a direct connection."
        )

    # Check 3: Risk Level Check
    if security["trustScore"] < MIN_TRUST_SCORE:
         raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Transaction flagged as high risk."
        )

@app.post("/api/v1/purchase", dependencies=[Depends(verify_purchase_eligibility)])
async def process_purchase():
    return {"message": "Purchase authorized and processing."}

We have other flags to detect if the user is anonymous. Checkout our guide on blocking Tor, VPN or proxy traffic.

Utilizing ip2geoapi.com for Reliable Restrictions

For developers who need a production-ready solution without the high cost of enterprise-grade intelligence, ip2geoapi.com offers a specialized IP intelligence service.

It provides high-accuracy mapping for both IPv4 and IPv6 addresses. This is critical because as IPv4 address space becomes exhausted, more residential traffic moves to IPv6, which many legacy geo-databases handle poorly.

You can get a free API key here and be integrated in minutes.

Handling Edge Cases and Pitfalls

IPv4 vs. IPv6

Do not assume every request will be IPv4. If your server or load balancer is dual-stack, you will receive IPv6 addresses (e.g., 2001:4860:4860::8888). Ensure your backend logic and your API provider support both formats natively.

VPN, tor and Proxy Bypassing

A user in a restricted country can easily use a VPN to appear as if they are in the US.

The Fix: Use an API that provides a isVpn, isTor or isProxy flag. If the goal is strict legal compliance (e.g., online gambling or sanctioned goods), you should block all non-residential IP types. We offer all these 3 flags, in addition to the isHosting flag. You can see this article on blocking traffic from high risk countries.

False Positives (The "Mobile" Problem)

Users on mobile data (CGNAT) may sometimes have their IP registered in a different city or neighboring small country depending on how their ISP routes traffic.

Best Practice: Always allow users to manually select their country in the UI for shipping purposes, but use the IP data as a "hard gate" for digital-only services.

Common Implementation Mistakes

Mistake Why It’s a Problem
Enforcing rules only on frontend Easily bypassed
Trusting unvalidated headers Can be spoofed
Blocking entire countries without logging Difficult to debug false positives
Assuming 100% accuracy Leads to user friction
Ignoring IPv6 Incomplete enforcement

Performance and Security Considerations

Conclusion

Restricting purchases by country is a vital component of a secure and compliant backend architecture. By implementing a server-side check using a high-fidelity intelligence service like ip2geoapi.com, you can automate compliance, reduce fraud, and ensure your business operates within its legal boundaries.

Vijay Prajapati
About the Author

Vijay Prajapati

I am a backend developer and founder of IP2GeoAPI, specializing in IP geolocation, network intelligence, and API architecture. I focus on building fast, accurate and scalable APIs for developers.

Connect on LinkedIn

Ready to Integrate?

Start using our IP geolocation & intelligence API in minutes. Sign up now and get your FREE API key with 100,000 monthly quota every month — no credit card required.

Join developers worldwide who rely on IP2GeoAPI for speed, accuracy, and reliability.