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 returns two two-letter ISO countries plus the anonymity signals on the visit, so your code can tell a real local buyer from a masked one:public_ip.country— resolved from the public IP, which a VPN can set to any region.local_ip.country— resolved from the real network IP, which can reveal the underlying network behind that VPN. Empty when the follow-up network check could not complete; treat absent real-network data as unverified, not clean.
detection_flags.ip_mismatch becomes true — a strong sign the visible exit country is not the buyer’s real one. ShieldLabs surfaces this comparison for your code; it is informational and does not by itself change the Risk Score (0–100). So a public-IP country check by itself is easy to defeat; compare the claimed region against local_ip.country when it is present, falling back to public_ip.country. The durable DeviceID survives cleared cookies, incognito, and IP rotation, so a repeat region-shopper who re-rolls the session resolves to the same device. ShieldLabs surfaces the countries and the signals; your code owns the price.
Prevent region-shopping
Read both countries, theip_mismatch flag, the anonymity signals, and the durable DeviceID when the buyer confirms a region. The rule your code applies: honor the discount only when the network country matches the claimed region and no masking signal fires; otherwise — if the two countries disagree (ip_mismatch), the session carries a VPN, Proxy, Tor, Privacy Relay, Datacenter IP, or Browser VPN/Proxy signal, or the exit 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.
Build it
Create a ShieldLabs account and get your keys
Sign up for free and get 5,000 identifications, or log in if you already have an account. Register the domain you want to identify visitors on, then open the Keys page. Use the Public Key to initialize the snippet in the browser, and keep your server-side credentials on your backend: the Private API Key authenticates the History API, and each webhook endpoint has its own
whsec_… signing secret. See Keys.Identify when the region is set
Load the snippet on the checkout or plan-selection page. When the buyer selects or confirms a region, force a current read with the
forceCheck* variant: it clears the session and runs a new identify call immediately, so the country and signals reflect the session at decision time. Pass a hashed or pseudonymous user id to the authenticated call, never a raw email or account id.pricing.html
Receive the webhook and cache both countries
Within about 1 second of the check, ShieldLabs POSTs a signed risk event to your endpoint. Verify The two countries disagree, so
X-Shield-Signature on the raw body, then cache the result keyed by request_id — the shared waitForScore helper does this and returns the raw webhook payload, so public_ip, local_ip, and detection_flags are all available to the pricing handler below without any extra mapping.A geo-pricing body where the buyer claims a Brazil price, the public IP exits in BR, but the real network IP puts the underlying network in the US:ip_mismatch is true, and a VPN signal is present: the claimed region is masked. Gate on the country comparison and the flag, not on the score alone.Gate the price on country and anonymity
Wait briefly for the score, then weigh four things: is the session anonymized, do the two countries disagree (The fallback, hold, and verification steps run in your application. ShieldLabs returns the two countries, the
ip_mismatch), does the network country match the claimed region, and how strong is the Score. Any anonymity signal, a country mismatch, or ip_mismatch is enough to stop honoring the discount. Branch on the detection_flags booleans, not the label text, which can change.price.js
score, the signals, and the detection_flags; your pricing code owns the decision.Reading the signals for a price decision
For a checkout decision you weigh the Score and its bands. For a regional-price claim the country comparison carries most of the weight, because a masked session can score Low yet still be hiding its location. The four bands are defined in Risk Scoring; here is how to read the inputs together.| Country vs claimed region | Anonymity signal in signals | Reasonable action |
|---|---|---|
| Match | None | Honor the regional price |
| Match | VPN / Proxy / Privacy Relay present | Verify before discount; a corporate VPN can match by coincidence |
ip_mismatch flag set (countries disagree) | Any | Standard price, ask for verification |
Mismatch (public_ip.country ≠ claim) | None | Standard price, ask for verification |
| Mismatch | Any present | Standard price, or hold for review |
| Unknown (no webhook yet) | Unknown | Standard price until verified |
signals for visibility and logging; branch on the matching detection_flags boolean, not the label text. Each adds to the Score by the weight shown; the exact weights live in the Risk Scoring table.
Signal in signals | Weight | Why it breaks a region claim |
|---|---|---|
| Tor | 99 | Connection exits through the Tor network, so the country is the exit node, not the buyer. |
| Browser VPN/Proxy | 30 | An in-browser VPN or proxy extension routes the session, so the exit country was toggled, not lived in. |
| VPN | 15 | Traffic routes through a VPN, so the exit country is chosen, not where the buyer sits. |
| Privacy Relay | 15 | iCloud Private Relay relays the connection, so the visible country can differ from the real one. |
| Proxy | 10 | IP flagged as a proxy. The geolocated country reflects the proxy, not the person. |
| Datacenter IP | 10 | IP is in a hosting range. A real shopper on a personal device rarely exits from a datacenter. |
| Timezone Mismatch | 10 | The browser timezone disagrees with the IP timezone, a corroborating tell that the geolocated country is not where the device actually is. |
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 insignals and an public_ip.country that does not match the claim, so your code falls back to the standard price. Now clear cookies, reopen the page in incognito, or switch to a second browser on the same machine — the durable DeviceID returns the same, so a buyer re-rolling the session to retry the discount still resolves to one device. Across visits, a signed-in account that claims several discount regions in a day also surfaces on the dashboard as the Multiple Countries on One Account pattern.
Next steps
Signals
Every anonymity signal that can appear in
signals, in plain language, with its weight.The Risk Score
How the 0 to 100 score 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 gate the charge.
Acting on the Risk Score
Turn the Score, the country fields, and the
signals into allow, verify, and hold logic in your app.