Most GeoComply tutorials stop at the browser. They show you how to drop the JavaScript plugin onto a web page, request a location token, and read back a pass/fail flag. That is enough to demo a login flow – but it is not what a licensed sportsbook or online casino actually runs in production. A real operator has a web client, a native iOS app, a native Android app, withdrawal flows that touch money, seven different state regulators with seven different technical standards, and an audit team that will ask you to reproduce any single allow-or-deny decision from eighteen months ago.
This guide is the production build. It treats geolocation as a dedicated compliance microservice that sits behind every player-facing surface, fetches verdicts from GeoComply server-to-server rather than trusting the client, and reconciles every transaction so nothing is ever lost. GeoComply processes roughly 2 billion geolocation checks per month and reports a 99% transaction pass rate, and during Super Bowl LIX weekend its customers logged 724,400+ new wagering accounts and 535,000+ geolocation transactions in a single event window. The vendor behind that volume is no longer a niche player: GetLatka estimated GeoComply’s revenue at $56 million and its valuation at $168 million as of December 2025, SourceForge’s GeoComply Core integration listings were still being updated as of May 2026 and continue to catalog 20+ live integrations across major platforms, and the operator roster keeps deepening – in June 2026 Caesars expanded its IDComply deployment across 8+ jurisdictions, per G3 Newswire, with T-Net and Techcouver separately reporting that same month on the 14-year GeoComply-Caesars relationship deepening further, with IDComply now embedded across multiple jurisdictions’ compliance workflows, while DraftKings renewed its GeoComply agreement that same month to cover every regulated state it operates in, per Gaming.net. That is the scale you are designing for. Over fourteen steps you will integrate the native mobile SDKs, build the License Server result fetch, add idempotency and reconciliation, drive a multi-state rules engine from config, and ship with canary rollout and regulator sign-off.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
How GeoComply Core Powers Multi-Surface Geolocation
Before writing any code, you need an accurate mental model of which GeoComply product does what, because the names are easy to conflate and the integration shape changes depending on which surface you are serving. The flagship product is GeoComply Core, marketed as “compliance-grade geolocation that also helps stop fraud.” Its published capability stack includes a historical risk engine, device integrity and intelligence, user integrity and intelligence, location intelligence, and data integrity – all surfaced through a single operator dashboard. Payment providers are adopting the same stack at signup, not just at login: Sightline Payments began running GeoComply’s digital identity platform at account creation in January 2026, combining three signal types – location, device integrity, and behavioral analysis – to screen new accounts before the first transaction ever clears, and crypto exchange Luno’s own case study, published in March 2025 directly by GeoComply, documents the same platform automating regulatory checks and user verification well beyond traditional sports betting. The biggest operators are going further than payments-only rollouts: FanDuel’s renewed GeoComply partnership, announced in August 2026 after 13 years together, now covers 17 million customers and expands the deployment footprint from a single state to 20+ U.S. states, per Biometric Update, while adding dedicated GeoComply engineers embedded directly in FanDuel’s product teams, per Gaming.net – a sign that geolocation is being treated as core product infrastructure, not a bolted-on vendor call.
The Product Map: Core, GeoGuard, and IDComply
Three GeoComply products commonly appear in an iGaming stack, and they solve different problems. Keeping them straight prevents the classic mistake of trying to do KYC inside the geolocation call. The product line keeps expanding internationally, too – in March 2026 GeoComply launched an integrated identity platform for Brazil’s newly regulated iGaming market that folds KYC, fraud, and geolocation into a single endpoint with a managed KYC waterfall built to target 100% compliant onboarding, per Business Wire and separately confirmed by Techcouver, which previews where IDComply is headed even if your footprint is U.S.-only today.
| Product | Job | Where it runs | You consume it via |
|---|---|---|---|
| GeoComply Core | Compliance-grade location + device/user risk | Web plugin, native mobile SDK, desktop | Encrypted packet → server-to-server result fetch |
| GeoGuard | VPN, proxy, DNS, emulator and spoof detection | Library / data feed inside the location check | Risk flags returned with the geolocation verdict |
| IDComply | KYC / identity orchestration | Server-side verification workflow | Separate API – runs alongside, not inside, geo |
GeoComply also publishes the GeoGuard Location Validator Android helper app (package com.geocomply.oobeeapp) that browsers and apps invoke when device-side location services are required. You do not build that – but your support team needs to know it exists, because a large share of “I can’t bet” tickets trace back to a missing or stale validator install.
Why a Dedicated Microservice Instead of Inline Web SDK
The introductory approach wires the GeoComply web SDK directly into one front end. That couples your geolocation logic to a single surface and scatters license credentials across clients. In production you want exactly one service that owns the GeoComply relationship: it holds the License Server credentials, normalizes verdicts from web and mobile into one schema, enforces per-state rules in one place, and produces one audit trail. Every game server, wallet service, and login flow calls this microservice and never touches GeoComply directly.
The Trust Boundary: Never Trust the Client Verdict
The single most important rule in this entire guide: the client never decides whether a player is in bounds. The mobile SDK and web plugin only collect an encrypted geolocation packet and hand back a transaction reference. The actual allow/deny decision is fetched by your backend from GeoComply’s License Server using your operator credentials. A spoofed or patched client can lie about its own verdict; it cannot forge a server-side decision keyed to your license.
Architecture of a Production Geolocation Microservice
The service is small in surface area but strict in contract. It exposes three internal endpoints – issue a session, submit a transaction reference, and read the current verdict – and it talks to GeoComply, Postgres, and Redis. Everything else (game server, wallet, KYC, the front ends) integrates through those three endpoints.
Request Flow: Packet → Server-to-Server Fetch → Verdict
The end-to-end flow has six hops, and step four is the one casual tutorials skip:
- Client requests a geolocation session from your microservice; you return a license token / session id.
- The GeoComply SDK on the client collects signals (GPS, Wi-Fi, cell, IP, device integrity) and produces an encrypted geolocation packet – GeoComply Core’s own technical specs, updated in August 2025, describe this as device data pulled from four distinct sources: GPS, GSM, WiFi, and IP.
- The packet is submitted (directly to GeoComply or proxied through you) and a transaction id is returned to the client.
- The client posts only that transaction id to your microservice; your backend then performs a server-to-server result fetch from the GeoComply License Server using operator credentials.
- You normalize, persist, and cache the verdict (in-bounds, troubleshooting messages, risk flags) keyed to the player session.
- Game server / wallet asks your service “can this session play / withdraw in state X?” and gets a cached, authoritative yes/no.
Components and Data Stores
| Component | Technology | Responsibility |
|---|---|---|
| API layer | Node 20 LTS + Fastify 4 | Session issue, transaction submit, verdict read |
| Result fetcher | GeoComply License Server client | Server-to-server verdict retrieval + retry |
| Verdict cache | Redis 7 | Sub-millisecond reads for gameplay gating |
| System of record | PostgreSQL 16 | Idempotent transaction log + audit evidence |
| Rules engine | In-process, config-driven | Per-state login/periodic/withdrawal policy |
| Observability | OpenTelemetry + Prometheus | SLOs, latency, deny-rate alerting |
Keeping the verdict cache and the system of record separate is deliberate: gameplay gating reads from Redis on the hot path, while the immutable Postgres log is what your compliance team queries during an audit. They serve different masters and have different durability requirements.
Prerequisites and Pinned Versions
Pin everything. Geolocation is a compliance control, and “it worked on my machine” is not a defense a regulator accepts. The versions below are a known-good baseline for a 2026 build.
Accounts, Licenses, and Credentials
- A signed GeoComply operator agreement and access to the License Manager dashboard.
- Per-environment license credentials (a distinct license for
stagingandproduction– never share one). - Native SDK packages for iOS and Android from your GeoComply onboarding pack.
- Allow-listed server egress IPs registered with GeoComply for the License Server fetch.
- Active regulator approval (or sandbox) for every state you intend to serve.
Runtime and Library Versions
| Layer | Component | Pinned version |
|---|---|---|
| Backend runtime | Node.js | 20.x LTS |
| Web framework | Fastify | 4.28 |
| Language | TypeScript | 5.4 |
| Database | PostgreSQL | 16.x |
| Cache | Redis | 7.2 |
| iOS | Swift / Xcode | 5.9 / 15.x, iOS 15+ target |
| Android | Kotlin / Gradle | 1.9 / 8.x, minSdk 24 |
| Container | Docker | 26.x |
If you are still deciding whether GeoComply is even the right vendor for your footprint, settle that first – it is worth evaluating GeoComply against Xpoint and LocationSmart before you commit an integration sprint, because the SDK shapes differ and migrating later is painful.
Step 1 – Provision Licenses, Environments, and Secrets
Start with credential hygiene, because every later step assumes secrets are loaded safely. Create two fully isolated environments. The staging license must point at GeoComply’s test endpoints so your QA spoof tests never pollute production analytics or trip a regulator’s monitoring.
Secret Layout
Never bake license credentials into a client bundle or a container image. Load them at runtime from your secrets manager (AWS Secrets Manager, HashiCorp Vault, or equivalent). A minimal environment contract:
// config.ts – fail fast if any secret is missing
import { z } from "zod";
const Env = z.object({
GEOCOMPLY_LICENSE_ID: z.string().min(1),
GEOCOMPLY_LICENSE_SECRET: z.string().min(1),
GEOCOMPLY_LICENSE_SERVER_URL: z.string().url(),
GEOCOMPLY_ENV: z.enum(["staging", "production"]),
DATABASE_URL: z.string().url(),
REDIS_URL: z.string().url(),
});
export const env = Env.parse(process.env);
if (env.GEOCOMPLY_ENV === "production" &&
env.GEOCOMPLY_LICENSE_SERVER_URL.includes("staging")) {
throw new Error("Refusing to boot: prod env pointed at staging License Server");
}
That last guard has saved more than one launch night. A misrouted production deployment that quietly talks to the staging License Server will return verdicts that look fine in load tests and fail the moment a real player in a live state tries to bet.
Step 2 – Scaffold the Geolocation Microservice
Create the service skeleton. The goal is a thin, well-instrumented Fastify app with three routes and clear separation between transport, the GeoComply client, and persistence.
Project Bootstrap
mkdir geo-compliance-svc && cd geo-compliance-svc
npm init -y
npm i [email protected] [email protected] [email protected] [email protected] undici@6
npm i -D [email protected] @types/node tsx
npx tsc --init --target ES2022 --module NodeNext --strict
The Three-Route Contract
// server.ts
import Fastify from "fastify";
import { issueSession } from "./routes/session.js";
import { submitTransaction } from "./routes/submit.js";
import { readVerdict } from "./routes/verdict.js";
const app = Fastify({ logger: true, requestIdHeader: "x-request-id" });
// 1) client asks for a geo session before invoking the SDK
app.post("/v1/geo/session", issueSession);
// 2) client returns the GeoComply transaction id; we fetch + persist
app.post("/v1/geo/transactions", submitTransaction);
// 3) game server / wallet reads the authoritative cached verdict
app.get("/v1/geo/verdict/:sessionId", readVerdict);
app.listen({ port: 8080, host: "0.0.0.0" });
Notice there is no route that accepts a verdict from the client. The client can submit a transaction id; it can never assert a result. This is the trust boundary expressed in routing.
Step 3 – Integrate the Native Mobile SDK
This is where production diverges hardest from the web-only walkthroughs. A native sportsbook or casino app must use GeoComply’s native mobile SDK, not a webview wrapper, because the SDK needs OS-level access to GPS, Wi-Fi scan results, cellular signals, and device-integrity attestation that a browser cannot reach. The SDK’s only job is to produce the encrypted packet and a transaction id; it never decides the verdict.
iOS (Swift)
Add the GeoComply SDK via your dependency manager, request location permission, then request a geolocation. Wire the callbacks to ship the transaction id to your microservice:
import GeoComplySDK
import CoreLocation
final class GeoService: NSObject, GeoComplyClientDelegate {
private let client = GeoComplyClient.shared
func start(sessionToken: String, license: String) {
client.delegate = self
client.setLicense(license)
client.setUserId(sessionToken) // your opaque session id
client.setGeolocationReason("LOGIN") // LOGIN | PERIODIC | WITHDRAWAL
client.requestGeolocation()
}
// SDK finished collecting + encrypting the packet
func geoComply(_ c: GeoComplyClient, didReceiveTransactionId txId: String) {
Task { await Backend.submitTransaction(txId: txId) } // POST /v1/geo/transactions
}
func geoComply(_ c: GeoComplyClient, didFailWithError error: GCError) {
Telemetry.log("geo_sdk_error", error.code) // surface to support, do NOT block silently
}
}
Android (Kotlin)
import com.geocomply.client.GeoComplyClient
import com.geocomply.client.GeoComplyClientListener
class GeoService(ctx: Context) : GeoComplyClientListener {
private val client = GeoComplyClient.getInstance(ctx)
fun start(sessionToken: String, license: String) {
client.setLicense(license)
client.setUserId(sessionToken)
client.setReason("LOGIN")
client.setListener(this)
client.requestGeolocation()
}
override fun onGeolocationSuccess(transactionId: String) {
Backend.submitTransaction(transactionId) // POST /v1/geo/transactions
}
override fun onGeolocationFailed(code: Int, message: String) {
Telemetry.log("geo_sdk_error", code, message)
}
}
Permission and Lifecycle Reality
On both platforms the player must grant precise location permission; “approximate” is not acceptable for a compliance check and the SDK will report it as insufficient. Request permission in context (right before the first geolocation), handle the denial path with a clear in-app explanation, and re-collect on app foreground if your session policy has expired. Treat an SDK error as “verdict unknown,” never as “allow.”
Step 4 – Capture the Web PLC Plugin Packet
Your web surface still matters, but in this architecture it feeds the same microservice. The web flow uses the GeoComply Player Location Check (PLC) plugin / JavaScript SDK to collect the packet, and again returns only a transaction id to your backend.
Browser-Side Collection
// web client – collect packet, hand backend only the transaction id
async function runGeoCheck(sessionToken) {
const geo = new GcHtml5(); // GeoComply web SDK handle
geo.setUserId(sessionToken);
geo.setReason("LOGIN");
geo.setLicense(await getLicenseToken()); // short-lived token from your service
geo.events.on("hint", (code, msg) => showTroubleshooting(code, msg));
geo.events.on("engine.success", async (txId) => {
await fetch("/v1/geo/transactions", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ sessionId: sessionToken, transactionId: txId }),
});
});
geo.connect();
}
The hint events are gold for support: they carry the exact troubleshooting code (location services off, VPN detected, validator app missing) that you should surface to the player verbatim rather than a generic “check failed.”
Step 5 – Build the Server-to-Server Result Fetch
This is the heart of the integration and the part that makes it tamper-resistant. When your microservice receives a transaction id, it calls the GeoComply License Server with your operator credentials, signs the request, and pulls the authoritative verdict. The client is never in the trust path.
Signed License Server Call
// geocomply.ts – server-to-server verdict fetch
import { request } from "undici";
import crypto from "node:crypto";
import { env } from "./config.js";
function sign(payload: string, ts: string): string {
return crypto.createHmac("sha256", env.GEOCOMPLY_LICENSE_SECRET)
.update(ts + payload).digest("hex");
}
export async function fetchVerdict(transactionId: string) {
const ts = Math.floor(Date.now() / 1000).toString();
const body = JSON.stringify({ licenseId: env.GEOCOMPLY_LICENSE_ID, transactionId });
const res = await request(`${env.GEOCOMPLY_LICENSE_SERVER_URL}/transactions/result`, {
method: "POST",
headers: {
"content-type": "application/json",
"x-gc-license": env.GEOCOMPLY_LICENSE_ID,
"x-gc-timestamp": ts,
"x-gc-signature": sign(body, ts),
},
body,
bodyTimeout: 4000,
headersTimeout: 4000,
});
if (res.statusCode !== 200) throw new GeoFetchError(res.statusCode, transactionId);
return normalize(await res.body.json());
}
Normalizing the Verdict
GeoComply returns rich data – coordinates, the resolved state/region, troubleshooting messages, and GeoGuard risk flags. Collapse it into one internal shape so the rest of your platform never parses vendor JSON:
function normalize(raw: any) {
return {
transactionId: raw.transaction_id,
inBounds: raw.geolocation?.allowed === true,
resolvedState: raw.geolocation?.region ?? null,
troubleshooter: raw.troubleshooter ?? [], // codes to show the player
risk: {
vpn: !!raw.risk?.vpn,
proxy: !!raw.risk?.proxy,
spoof: !!raw.risk?.location_spoofing,
emulator: !!raw.risk?.emulator,
},
checkedAt: new Date().toISOString(),
};
}
Exact field names come from your GeoComply onboarding spec; map them once, here, and nowhere else. For broader market context, see our coverage of best sweepstakes casino sites.
Step 6 – Add Idempotency and Transaction Reconciliation
At Super Bowl scale you will see retries, duplicate submissions, and dropped responses. Without idempotency you double-charge your GeoComply transaction quota and corrupt your audit log. This step is the most common gap in DIY integrations.
Idempotent Write
Key every transaction on the GeoComply transaction id and write once. Postgres makes this a one-liner with a unique constraint and ON CONFLICT:
-- schema
CREATE TABLE geo_transactions (
transaction_id text PRIMARY KEY,
session_id text NOT NULL,
reason text NOT NULL, -- LOGIN | PERIODIC | WITHDRAWAL
in_bounds boolean,
resolved_state text,
risk jsonb NOT NULL DEFAULT '{}',
raw jsonb NOT NULL, -- full vendor payload for audit
status text NOT NULL, -- FETCHED | PENDING | FAILED
created_at timestamptz NOT NULL DEFAULT now()
);
// idempotent persist
await pg.query(
`INSERT INTO geo_transactions
(transaction_id, session_id, reason, in_bounds, resolved_state, risk, raw, status)
VALUES ($1,$2,$3,$4,$5,$6,$7,'FETCHED')
ON CONFLICT (transaction_id) DO NOTHING`,
[v.transactionId, sessionId, reason, v.inBounds, v.resolvedState,
v.risk, rawPayload]
);
The Reconciliation Sweeper
Some transactions will be submitted but never resolved (client crashed mid-fetch, License Server timeout). A background sweeper re-fetches anything stuck in PENDING so your records and GeoComply’s always agree:
// runs every 60s
const stuck = await pg.query(
`SELECT transaction_id FROM geo_transactions
WHERE status = 'PENDING' AND created_at < now() - interval '2 minutes' LIMIT 200`);
for (const row of stuck.rows) {
try {
const v = await fetchVerdict(row.transaction_id);
await persist(v); // flips status to FETCHED
} catch { /* leave PENDING; alert if age > 30m */ }
}
Reconciliation is also what lets you answer the auditor’s question “show me every check for player X on March 3” with confidence that nothing fell through a crack.
Step 7 – The Multi-State Rules Engine
Hardcoding “re-check every 30 minutes” anywhere in your codebase is a liability. As of 2026, online sports betting is live in roughly 38 U.S. states plus D.C., while real-money online casino (iGaming) operates in just seven states – and each regulator sets its own technical standard for when a player must be re-located. Drive all of it from a config table.
State Policy as Data
| State | Regulator | Login check | Periodic re-check | Pre-withdrawal |
|---|---|---|---|---|
| New Jersey | NJ DGE | Required | Periodic (per technical standard) | Required |
| Pennsylvania | PGCB | Required | Periodic (per technical standard) | Required |
| Michigan | MGCB | Required | Periodic (per technical standard) | Required |
| Massachusetts | MGC (205 CMR) | Required | Periodic (per technical standard) | Required |
| Ohio | Casino Control Commission | Required | Periodic (per technical standard) | Required |
Re-check intervals are defined by each jurisdiction’s current technical standard and change over time – load the exact value from config and confirm it against the regulator’s published standard, never from a blog. Verify the source directly with the New Jersey Division of Gaming Enforcement, the Pennsylvania Gaming Control Board, and the Michigan Gaming Control Board.
Evaluating a Session Against State Rules
// rules.json (hot-reloadable, version-controlled, audited)
{
"NJ": { "periodicSeconds": 1800, "vertical": ["sports","casino"], "blockVpn": true },
"MI": { "periodicSeconds": 1800, "vertical": ["sports","casino"], "blockVpn": true },
"PA": { "periodicSeconds": 1800, "vertical": ["sports","casino"], "blockVpn": true }
}
export function evaluate(state: string, verdict: Verdict, lastCheck: number) {
const rule = RULES[state];
if (!rule) return { allow: false, reason: "STATE_NOT_LICENSED" };
if (!verdict.inBounds) return { allow: false, reason: "OUT_OF_BOUNDS" };
if (rule.blockVpn && (verdict.risk.vpn || verdict.risk.proxy))
return { allow: false, reason: "VPN_DETECTED" };
const age = (Date.now() - lastCheck) / 1000;
if (age > rule.periodicSeconds) return { allow: false, reason: "RECHECK_REQUIRED" };
return { allow: true };
}
Step 8 – Enforce Session Lifecycle Re-Checks
A geolocation verdict is perishable. A player who passed at login can drive across a state line in twenty minutes. Your service must enforce three lifecycle moments: the initial login check, periodic re-checks while the session is active, and a fresh check immediately before any withdrawal.
The Verdict Cache With TTL
Cache each verdict in Redis with a TTL equal to the state’s periodic interval. When the cache key expires, the gameplay gate naturally returns RECHECK_REQUIRED and the client is prompted to re-collect – no cron needed for the happy path:
const ttl = RULES[state].periodicSeconds;
await redis.set(`verdict:${sessionId}`, JSON.stringify(verdict), "EX", ttl);
// gameplay gate (hot path, sub-ms)
export async function canPlay(sessionId: string, state: string) {
const cached = await redis.get(`verdict:${sessionId}`);
if (!cached) return { allow: false, reason: "RECHECK_REQUIRED" };
return evaluate(state, JSON.parse(cached), Date.now()); // age handled by TTL
}
The Pre-Withdrawal Hard Gate
Withdrawals are special. Never serve a cached verdict for a cash-out; force a brand-new check with reason WITHDRAWAL and block the payout until it resolves in-bounds. This is also where pairing geolocation with identity pays off – operators that run a KYC provider alongside the location check can gate the payout on both signals at once, and those moving real money should confirm their licensed gambling card processor is wired to honor that combined gate before funds release.
Step 9 – Degraded Mode and Failover
GeoComply is highly available, but your network, your DNS, or a regional outage can still interrupt the License Server fetch. The compliance-safe default is brutal and correct: fail closed. If you cannot obtain a fresh verdict, you cannot let the player place a bet. The engineering work is in making fail-closed humane.
Timeouts, Retries, and Circuit Breaking
import CircuitBreaker from "opossum";
const breaker = new CircuitBreaker(fetchVerdict, {
timeout: 4000, // abandon a slow fetch
errorThresholdPercentage: 40,
resetTimeout: 15000, // probe again after 15s
});
breaker.fallback(() => ({ status: "DEGRADED", allow: false }));
breaker.on("open", () => alert("geo_license_server_circuit_open"));
What “Humane Fail-Closed” Looks Like
When degraded, show the player an honest, branded message (“We’re confirming your location – betting is paused for a moment”), keep their session and balance intact, retry in the background, and auto-resume the instant a fresh verdict arrives. Never silently allow play, and never log them out and lose their slip. Track degraded minutes as an SLI; if it exceeds your error budget you escalate to GeoComply with transaction-level evidence.
Step 10 – Compliance Audit Logging and the Evidence Warehouse
Regulators do not ask “is your system compliant?” They ask “prove that this specific bet, at this timestamp, was placed by a player you had located in-state.” Your Postgres geo_transactions table is the live system of record, but you also need an append-only evidence warehouse that survives schema changes and supports multi-year retention.
The Append-Only Evidence Record
Every verdict fetch writes one immutable evidence row capturing the full vendor payload, the rule version applied, and the decision served. Store the raw GeoComply JSON – during an audit you must be able to show the original data, not just your interpretation of it. For broader market context, see our coverage of leading online casino sites.
CREATE TABLE geo_evidence (
id bigserial PRIMARY KEY,
transaction_id text NOT NULL,
session_id text NOT NULL,
player_id text NOT NULL,
decision text NOT NULL, -- ALLOW | DENY | DEGRADED
decision_reason text,
rule_version text NOT NULL, -- git sha of rules.json
vendor_payload jsonb NOT NULL, -- verbatim GeoComply result
occurred_at timestamptz NOT NULL DEFAULT now()
);
-- ship to object storage / warehouse nightly; retain per state (often 5+ years)
Make Audits a Query, Not a Project
Index on player_id and occurred_at so “every check for this player in this window” is one query. The difference between a one-hour audit response and a two-week fire drill is whether you designed for this on day one. GeoComply’s own value proposition leans on this: operators report chargeback win rates up to 90% precisely because the geolocation evidence proves the player placed the bet.
Step 11 – Observability, SLOs, and Alerting
Geolocation sits on the critical path of every bet, so it deserves first-class observability. Instrument the License Server fetch latency, the deny rate by reason, the degraded-minute counter, and the reconciliation backlog. A sudden spike in OUT_OF_BOUNDS denies usually means a GeoGuard data update or a regional ISP routing change – not mass fraud – and you want to know within minutes.
Service Level Objectives
| SLI | Target SLO | Why it matters |
|---|---|---|
| Verdict fetch latency (p95) | < 1.5 s | Login feels instant; players don’t bounce |
| Verdict fetch success rate | > 99.9% | Fewer degraded-mode pauses |
| Reconciliation backlog age | < 5 min | Audit completeness |
| Cache read latency (p99) | < 5 ms | Gameplay gate stays off the hot path |
Alert on Patterns, Not Single Failures
// Prometheus-style alert intent
- alert: GeoDenyRateSpike
expr: rate(geo_decision_total{decision="DENY"}[5m])
/ rate(geo_decision_total[5m]) > 0.15
for: 10m
labels: { severity: page }
annotations: { summary: "Deny rate >15% – check GeoGuard feed / ISP routing" }
Step 12 – Spoof, VPN, and Emulator Defense with GeoGuard
GeoComply’s anti-fraud edge is the layered detection bundled into the verdict. The published model is a multi-layer verification stack – IP intelligence, GPS/Wi-Fi/cell fusion, device location services, geofencing, behavioral analysis, and compliance checks – engineered so that defeating one layer still leaves the others active. Sightline Payments, describing its January 2026 integration, called GeoComply the only digital identity platform “purpose-built to shut down spoofing” – which is exactly the design goal behind stacking six independent layers instead of relying on any single signal. The results show up in KYC metrics, too: GeoComply’s integration with sports-betting platform Dabble lifted KYC pass rates above 90%, Crowdfund Insider reported in January 2026, noting that GeoComply’s underlying digital identity platform was by then processing data from over 200 million devices, and Hard Rock Bet disclosed a similar multi-year expansion of GeoComply’s digital identity tools across onboarding and fraud screening in May 2026, per Business Wire. Your job is to act on the risk flags, not re-implement detection.
Acting on Risk Flags
The normalized risk object from Step 5 carries vpn, proxy, spoof, and emulator. Your rules engine already blocks on VPN/proxy where the state requires it; for spoof and emulator flags, the safe policy is deny-and-log, because a patched client reporting a fake GPS coordinate is the canonical attack this whole system exists to stop.
Don’t Punish False Positives Blindly
Corporate VPNs, carrier-grade NAT, and privacy DNS will occasionally light up a flag for a legitimate player. Route those to a clear troubleshooting screen (“disable your VPN to continue”) rather than a permanent ban, and feed confirmed false positives back to GeoComply support. The 99% pass rate exists because the layered model is tuned to keep legitimate players flowing.
Step 13 – Load Testing for Peak Events
Plan for your worst Sunday, not your average Tuesday. GeoComply customers processed 535,000+ geolocation transactions over Super Bowl LIX weekend and saw a 24% jump in geolocation transactions during the NFL playoffs. Your microservice, cache, and database must absorb that without queueing.
A Realistic Load Profile
# k6 – simulate login spike + periodic re-checks
import http from "k6/http";
import { check } from "k6";
export const options = {
scenarios: {
login_spike: { executor: "ramping-arrival-rate",
startRate: 200, timeUnit: "1s",
stages: [{ target: 2000, duration: "2m" }, { target: 2000, duration: "5m" }],
preAllocatedVUs: 500 },
},
};
export default function () {
const res = http.post("https://geo.internal/v1/geo/transactions",
JSON.stringify({ sessionId: `s-${__VU}-${__ITER}`, transactionId: `tx-${__VU}-${__ITER}` }),
{ headers: { "content-type": "application/json" } });
check(res, { "200": (r) => r.status === 200, "p95 < 1.5s": (r) => r.timings.duration < 1500 });
}
Watch connection-pool exhaustion to the License Server and to Postgres first – those, not CPU, are what fall over under a real spike.
Step 14 – Production Rollout, Canary, and Regulator Sign-off
Do not flip geolocation on for 100% of traffic at once. Roll out behind a flag, canary to a small percentage in one state, compare deny rates against your staging baseline, and only then widen. Geolocation changes are compliance changes, which means several states require notice or re-certification before a material change goes live.
The Canary Gate
- Deploy to 1% of sessions in your most mature state; hold for a full peak cycle.
- Compare allow/deny/degraded distributions to baseline; investigate any >2% drift.
- Confirm evidence rows are landing with correct
rule_version. - Widen to 10% → 50% → 100% only as each gate passes.
Regulator Sign-off Checklist
Keep a living document mapping each state’s technical-standard requirement to the exact code, config, and evidence query that satisfies it. When the regulator’s lab asks how you enforce pre-withdrawal location, you point at Step 8’s hard gate and a sample evidence row. This is also the moment to confirm your independent test-lab certification (GLI, BMM, or equivalent) covers the integration as built.
Beyond the Standard Coverage: What Existing GeoComply Guides Miss
The competitor inventory for this keyword is dominated by two genres: GeoComply’s own marketing pages and player-facing troubleshooting threads (Reddit, FanDuel support, Action Network). The closest operator-grade competitor, a 2026 sportsbook-geolocation comparison running ~4,160 words, covers vendor selection, a state mandate map, spoof vectors, and a TCO section – but it is a buyer’s decision framework, not a build guide. None of these address the engineering reality this guide is built around.
Three Gaps This Guide Closes
- Native mobile, not just web. Every introductory tutorial wires the JavaScript web SDK. None show the iOS/Android native SDK flow that a real sportsbook app requires, including the precise-location permission and foreground re-collection lifecycle.
- Server-to-server reconciliation. The marketing pages describe the verdict; they never explain idempotent persistence, the reconciliation sweeper, or how to guarantee your records and GeoComply’s never diverge under retry storms.
- Multi-state policy as data. Comparison articles list which states are legal; none give you a hot-reloadable rules engine that turns “38 sports-betting states, 7 iGaming states, all with different re-check standards” into config rather than scattered conditionals.
The Cost Conversation Nobody Publishes
GeoComply Core itself does not publish public MSRP – pricing is enterprise and quote-based, typically structured per-check with monthly minimums. IDComply is the exception: as of November 2025 its pricing page advertised a fixed price with no setup fees and no monthly or annual volume commitment, so if your KYC spend is the harder line item to forecast, that product is worth pricing out on its own. What is public for the core geolocation service is the value side that justifies the quote-based model: a reported $550,000 in daily fraud savings for a top-five U.S. operator, chargeback win rates up to 90%, and the 99% pass rate that protects revenue by not blocking legitimate players. Model your TCO as (per-check volume × negotiated rate + integration/maintenance) against fraud-loss avoidance and chargeback recovery, and you will find the geolocation line item is rarely the expensive part of compliance.
5 Production Pitfalls to Avoid
- Trusting a client-reported verdict. If any code path lets the mobile app or browser assert “in bounds,” you have a spoofable hole. Only the server-to-server fetch is authoritative.
- Caching the pre-withdrawal check. Serving a cached verdict for a cash-out is a direct compliance failure. Withdrawals always force a fresh check.
- Failing open under degradation. When the License Server is unreachable, the safe state is paused-not-allowed. Failing open to “keep players happy” can void your license.
- Hardcoding state intervals. Re-check intervals and licensed verticals change. Anything not in config will eventually be wrong in at least one state.
- Discarding the raw vendor payload. If you only store your interpreted boolean, you cannot reconstruct the original evidence an auditor demands.
8 Troubleshooting Scenarios
- “Location services are off.” The SDK hint fires before any verdict. Surface the exact OS path to enable precise location; do not show a generic error.
- VPN false positive for a real player. GeoGuard flagged a corporate VPN or privacy DNS. Route to a “disable VPN” screen and report confirmed false positives to support.
- Verdict fetch times out at peak. Almost always connection-pool exhaustion to the License Server or Postgres. Raise pool sizes, enable the circuit breaker, and watch the degraded-minute SLI.
- Border-region player blocked. Near a state line GeoComply tightens checks; this is correct behavior. Explain it in-app rather than treating it as a bug.
- GeoGuard Location Validator missing on Android. A share of web denials trace to the absent
com.geocomply.oobeeapphelper. Detect and prompt installation. - Transactions stuck in PENDING. The reconciliation sweeper should clear these within minutes; if the backlog grows, the License Server fetch is failing – check credentials and egress allow-listing.
- Staging verdicts in production dashboards. Your production deploy is pointed at the staging License Server. The Step 1 boot guard prevents this; if you see it, that guard was bypassed.
- Sudden deny-rate spike across a region. Usually a GeoGuard data refresh or ISP routing change, not fraud. Confirm against the alert annotation before paging the fraud team.
Advanced Production Tips
Once the core flow is live, a few refinements separate a working integration from a resilient one. First, version your rules.json with the git SHA and stamp it into every evidence row – when a regulator asks “what policy was in effect on this date,” it is a lookup, not an archaeology dig. Second, pre-warm the verdict cache on login completion so the first gameplay gate is a guaranteed cache hit. Third, run a synthetic geolocation canary from a known-good location every minute and alert if it ever denies; this catches credential expiry and License Server issues before real players do.
Pair Geolocation With the Wider Compliance Stack
Geolocation is one control among several. The strongest operators fuse the location verdict with KYC status, AML transaction monitoring, and payment risk into a single decision at sensitive moments like first deposit and withdrawal. Treat your microservice as one signal provider to a broader compliance orchestrator rather than an island.
Plan for SDK Upgrades
GeoComply ships SDK updates that track new spoofing techniques and OS changes. Build the mobile SDK behind a thin adapter (as in Step 3) so a version bump touches one file, and gate every SDK upgrade through the same canary process you use for rule changes.
Complete Project Example: End-to-End Verdict Flow
Here is the full submitTransaction handler tying the pieces together – fetch, idempotent persist, evidence write, and cache – the exact code path that runs millions of times during a peak event.
// routes/submit.ts
import { fetchVerdict } from "../geocomply.js";
import { pg } from "../db.js";
import { redis } from "../cache.js";
import { RULES, RULE_VERSION } from "../rules.js";
export async function submitTransaction(req, reply) {
const { sessionId, transactionId, reason = "LOGIN" } = req.body;
// 1) authoritative server-to-server fetch (with circuit breaker upstream)
let verdict;
try {
verdict = await fetchVerdict(transactionId);
} catch {
await markPending(transactionId, sessionId, reason); // sweeper will retry
return reply.code(202).send({ status: "PENDING" });
}
// 2) idempotent system-of-record write
await pg.query(
`INSERT INTO geo_transactions
(transaction_id, session_id, reason, in_bounds, resolved_state, risk, raw, status)
VALUES ($1,$2,$3,$4,$5,$6,$7,'FETCHED')
ON CONFLICT (transaction_id) DO NOTHING`,
[verdict.transactionId, sessionId, reason, verdict.inBounds,
verdict.resolvedState, verdict.risk, verdict.raw ?? {}]);
// 3) immutable evidence row for audit
const state = verdict.resolvedState;
const decision = decide(state, verdict);
await pg.query(
`INSERT INTO geo_evidence
(transaction_id, session_id, player_id, decision, decision_reason,
rule_version, vendor_payload)
VALUES ($1,$2,$3,$4,$5,$6,$7)`,
[verdict.transactionId, sessionId, req.user?.id ?? "anon",
decision.allow ? "ALLOW" : "DENY", decision.reason ?? null,
RULE_VERSION, verdict.raw ?? {}]);
// 4) cache the verdict with state-specific TTL for the gameplay gate
if (decision.allow && state && RULES[state]) {
await redis.set(`verdict:${sessionId}`,
JSON.stringify(verdict), "EX", RULES[state].periodicSeconds);
}
return reply.send({ allow: decision.allow, reason: decision.reason ?? null });
}
function decide(state, v) {
const rule = RULES[state];
if (!rule) return { allow: false, reason: "STATE_NOT_LICENSED" };
if (!v.inBounds) return { allow: false, reason: "OUT_OF_BOUNDS" };
if (rule.blockVpn && (v.risk.vpn || v.risk.proxy))
return { allow: false, reason: "VPN_DETECTED" };
if (v.risk.spoof || v.risk.emulator)
return { allow: false, reason: "SPOOF_DETECTED" };
return { allow: true };
}
Wrap this service in a container, run two replicas behind a load balancer, point your iOS, Android, and web clients at the three routes from Step 2, and you have a geolocation control that satisfies multi-state requirements, survives Super Bowl traffic, and produces audit evidence on demand. For the technical background on why IP-plus-device geolocation is treated as authoritative, GeoComply’s own contribution to the IETF/IAB workshop on IP geolocation is worth reading, and the American Gaming Association tracks the state-by-state legal landscape your rules engine must mirror.
Frequently Asked Questions
Do I need the native mobile SDK, or can I use a webview?
For a native iOS or Android app you need the native GeoComply SDK. A webview cannot reach the OS-level GPS, Wi-Fi scan, and device-integrity signals the compliance check requires, and regulators expect native collection for native apps.
Why fetch the verdict server-to-server instead of reading it on the client?
Because a patched or spoofed client can lie about its own result. The server-to-server fetch from the GeoComply License Server, signed with your operator credentials, is the only verdict you can trust and the only one an auditor will accept.
How often must I re-check a player’s location?
At login, periodically while the session is active, and again immediately before any withdrawal. The exact periodic interval is set by each state’s technical standard, so load it from config and confirm it against the current regulator standard – do not hardcode it.
What should happen when GeoComply is unreachable?
Fail closed: pause betting, keep the session and balance intact, retry in the background, and resume automatically when a fresh verdict arrives. Failing open to allow play during an outage can jeopardize your license.
How much does GeoComply cost?
GeoComply does not publish public MSRP; pricing is enterprise and quote-based, generally structured per-check with monthly minimums. The reported value side includes up to $550,000 in daily fraud savings for a top-five operator and chargeback win rates up to 90%.
How many states does this need to support?
As of 2026, online sports betting is live in roughly 38 states plus D.C., while real-money online casino runs in about seven states. Each sets its own geolocation technical standard, which is exactly why the rules engine in Step 7 is config-driven.
How do I prove compliance during an audit?
Query your append-only evidence table by player and time window. Each row stores the verbatim GeoComply payload, the decision served, and the rule version in effect, so any single allow-or-deny decision is fully reconstructable years later.
Does GeoComply handle VPN and spoof detection for me?
Yes – GeoGuard’s layered detection returns VPN, proxy, spoof, and emulator risk flags with the verdict. Your job is to act on those flags in the rules engine, route legitimate false positives to a troubleshooting screen, and deny on confirmed spoofing.







