Skip to main content
Regional pricing rewards buyers in lower-cost markets with a cheaper rate. The catch is that anyone can sit behind a VPN, a proxy or iCloud Private Relay, point their session at a discount region, and claim the lower price from anywhere. This tutorial reads the account’s own country history, the countries on the identification and its risk signals, so your pricing endpoint can check whether the claimed region is trustworthy enough to honor.

What is regional pricing abuse?

Regional pricing abuse is when a buyer fakes their location, usually with a VPN, proxy, or relay that exits in a low-cost country, to claim a discounted price they are not eligible for. The geolocation looks local, but the network is masking where the person actually sits and pays.

How ShieldLabs surfaces it

ShieldLabs ties a region claim to the buyer’s account and to the network behind the moment of the claim.
  • The account. Every identification of the account carries the country of its public IP, so its history shows where the buyer has been. ShieldLabs also detects Impossible travel on the account, at Medium or High confidence, when it appears in locations it could not reach in the time between them. High-Risk Events are available in the analytics dashboard, the API and webhooks.
  • The moment. Each identification returns two ISO countries plus its risk signals:
    • public_ip.country: the country of the public IP, which a VPN can set to any region.
    • local_ip.country: the country of the Local IP, the address the browser itself reports, which can differ from the public IP behind a VPN or proxy. Empty when it was not captured; the handler then compares the claim with public_ip.country and relies on the masking signals.
Compare the claimed region against local_ip.country when it is present, falling back to public_ip.country. detection_flags.ip_mismatch marks two different addresses; it adds nothing to the Risk Score (0-100) and can be ordinary on mobile networks, so the country comparison is the check that matters for a price. The Device ID holds through cleared cookies, incognito mode and IP changes, so a region-shopper who re-rolls the session resolves to the same device. You choose the price for each case.

Prevent region-shopping

Read the account’s country history, both countries on the identification and its risk signals when the buyer confirms a region. The rule: honor the discount when the claimed region matches the network country, no masking signal fires, and the account has used that region before (or has no history yet). Otherwise (the account has only been seen elsewhere, the two countries on the identification disagree, the session carries a VPN, Proxy, Tor, Privacy Relay, Datacenter IP or Browser VPN/Proxy signal, or the network country does not match the claim) fall back to the standard price or ask for a billing-country check. You gate a price, not a person. The countries an account has used are on its user card in the analytics dashboard, under Linked countries (public IP) and Linked local countries (Local IP), each with its identification count. A VPN can set the public IP country anywhere, so trust a region the account has used on its Local IP.
The user card for User HID a91f3c7e5b2d4086 in the analytics dashboard with Linked countries open: US with 10 identifications and DE with 2.The user card for User HID a91f3c7e5b2d4086 in the analytics dashboard in the dark theme with Linked countries open: US with 10 identifications and DE with 2.

The countries one account has used, in the analytics dashboard.

Build it

1

Create a ShieldLabs account and get your keys

Start Free with 5,000 identifications, one time, no credit card, or log in. In the analytics dashboard, add the domain you want to protect under Integration > Domains, then open Integration > API keys and copy its keys with the copy button next to each. The Public Key loads the snippet in the browser. Keep the server credentials on your backend: the Private API Key reads the History API, and each webhook endpoint has its own whsec_… signing secret. See API keys and Integration.
2

Identify when the region is set

Load the snippet on the checkout or plan-selection page. When the buyer selects or confirms a region, call forceCheckAuthenticatedUser: it runs an identification every time, keeps the current Session ID and restarts the five-minute window, so the countries and risk signals reflect the session at decision time. A plain checkAuthenticatedUser inside five minutes of the last identification in the same visit posts nothing and calls back with { status: "not_initialized" }. Identify when the region is set rather than at submit, so the webhook has time to arrive before the buyer continues. Pass a hashed or pseudonymous user id, never a raw email or account id.
pricing.html
For a buyer who is not signed in, call forceCheckAnonymous instead; the account read below then has no account to read, so rely on the countries and risk signals.
3

Receive the webhook and cache both countries

ShieldLabs POSTs the webhook about 300 ms after the check, or at most about 10 seconds later when follow-up network checks run. Verify X-Shield-Signature on the raw body, then cache the result keyed by request_id. The shared waitForScore helper does this and returns the webhook data, so public_ip, local_ip and detection_flags are available to the pricing handler below. When it falls back to the History API, the row carries the public IP country and the History flags but no local_ip, so the handler prices that claim as unverified.A data excerpt where the buyer claims a Brazil price, the public IP exits in BR, but the Local IP puts the network in the US:
The two countries disagree and a VPN signal is present: the claimed region is masked. (ip_mismatch is also true, because the two addresses differ.) Gate on the country comparison and the risk signals, not on the Risk Score alone.The same comparison is on the identification’s card in the analytics dashboard, under Device and network: the public IP and its country next to the local IP and its country.
The Device and network section of one identification in the analytics dashboard: desktop, Windows, Chrome, public IP 198.51.100.34 with Country DE, local IP 192.0.2.16 with Local country US, and connection type vpn.The Device and network section of one identification in the analytics dashboard in the dark theme: desktop, Windows, Chrome, public IP 198.51.100.34 with Country DE, local IP 192.0.2.16 with Local country US, and connection type vpn.

One identification with its public IP country and local IP country, in the analytics dashboard.

4

Gate the price on country, account and risk signals

Wait briefly for the identification, then weigh four things: does the claim match a country the account used in earlier sessions, is the session masked, do the two countries on the identification disagree, and does the network country match the claimed region. Any masking signal or a country mismatch is enough to stop honoring the discount. Branch on the detection_flags booleans and the country fields, not on label text.
price.js
The account check reads the account’s countries with the shared accountView helper from the Use Case Tutorials and leaves out the current Session ID; if the History read fails, the buyer gets the standard price and a verification step. The identification for the preselected region and every signed-in page earlier in this session already exit from the country being claimed, so only earlier sessions can show that the account has been there. History reads never count against your included identifications.ShieldLabs returns the two countries, the Risk Score, every named risk signal and the detection_flags on each identification, and detects Impossible travel on your users. You choose the price for each case and act on it in your backend.

Reading the signals for a price decision

For a checkout decision you weigh the Risk Score and its bands. For a regional-price claim the country comparison carries most of the weight, because a masked session can score in the Trusted band yet still be hiding its location. The three bands are defined in Risk Scoring; here is how to read the inputs together. Each entry in signals carries a stable slug in name and the weight it added in weight; the table gives the slug, the label and the weight. Branch on the matching detection_flags boolean. The full table lives in Risk Scoring.
A legitimate buyer can trip this. A traveler abroad, a corporate VPN, or a privacy browser all detach the network country from where the customer actually lives and pays. This is why the play gates a price decision, not a ban: fall back to the standard price, ask for a billing-country or payment check, or hold for review. Decide on the two countries, the risk signals, the account’s history and your own verification step together, never on one input alone, and let the buyer prove their region rather than locking them out.

Test it

Open your pricing page behind a VPN whose exit is in a discount region, then select that region. The webhook should carry the masking signal in signals and a public_ip.country that does not match the claim, so your endpoint falls back to the standard price. Now clear cookies or reopen the page in incognito: the device_id returns the same, so a buyer re-rolling the session to retry the discount still resolves to one device. A different browser is a different Device ID, which is why the account’s country history matters here. ShieldLabs detects Impossible travel on signed-in accounts: an account appearing in locations it could not reach in the time between them, at Medium or High confidence. High-Risk Events are available in the analytics dashboard, the API and webhooks, and need the hashed User HID, which the forceCheckAuthenticatedUser call above passes. The countries and risk signals of the identification remain your check at the moment of the claim. When an Impossible travel event arrives for an account through the API or webhooks, or when you review it in the analytics dashboard, act on the account. You choose the action for each case, for example asking for verification before its next regional price.

Next steps

Risk signals

Every risk signal that can appear in signals, in plain language, with its weight.

The Risk Score

How the Risk Score from 0 to 100 is built, what the signals array carries, and the band definitions.

Checkout protection

The fresh-check pattern at the payment step, where the same signals and the buyer’s account gate the charge.

Acting on results

Turn the Risk Score, the country fields, and the signals into allow, verify, and hold logic in your app.