Capture the network country when the candidate clicks Continue, then ask them to declare their country later in the application, and compare the two on submission. The browser Geolocation API and GPS are never used, so there is no permission popup — yet the comparison still separates honest answers from convenient ones.
Each email carries a candidate-specific signed link that binds the eventual submission to the intended recipient. No check happens on link open — email security scanners and mail proxies automatically open links from locations unrelated to the recipient.
The page shows the role, the neutral disclosure, and a single Continue button.
The click fires a POST to the backend. The server resolves the connection's IP to a country, opens an application session, and reveals nothing to the candidate — not the detected country, not the whitelist, not the scoring rule. Avoiding the initial question also keeps their later answer unprimed.
Later in the flow the form asks: “Which country are you currently located in?” followed by a written attestation. The candidate answers freely, with no hint that a network check is waiting.
Declared country vs. captured country vs. whitelist → one of five statuses is recorded. The raw IP is discarded as soon as the country is derived.
POST, automated link-openers never pollute the signal.
The comparison is a plain table — no triangulation, no scoring model. Only the country pair and the whitelist matter.
| Resolved (network) | Declared | Outcome | Action |
|---|---|---|---|
| Whitelisted | Same country | Verified | Continue normally |
| Whitelisted | Different whitelisted country | Review | Ask candidate to confirm; never auto-reject |
| Outside whitelist | Whitelisted country | Possible mismatch | Review trigger |
| Outside whitelist | Outside whitelist | Ineligible | Fails regional eligibility |
| Unknown / VPN / proxy | Any | Unverified | Retry or manual review |
Do not reveal which country was detected, and never accuse or reject automatically on a mismatch — VPNs, travel, corporate networks and mobile roaming all produce legitimate discrepancies.
One enum recorded per submission, alongside both country codes:
type LocationCheck = | "MATCH" | "MATCH_LOW_CONFIDENCE" | "MISMATCH" | "UNVERIFIABLE" | "INCOMPLETE";
XX, T1). Retry or request confirmation.The core check is a few lines. Server-side only — no browser Geolocation API, no GPS, no client-side geo API:
// Cloudflare Worker — swap the first line for your host's header if not on CF const resolvedCountry = typeof request.cf?.country === "string" ? request.cf.country.toUpperCase() : null; const claimedCountry = form.claimedCountry?.trim().toUpperCase() || null; const locationCheck = !claimedCountry ? "INCOMPLETE" : !resolvedCountry || resolvedCountry === "XX" || resolvedCountry === "T1" ? "UNVERIFIABLE" : claimedCountry === resolvedCountry ? "MATCH" : "MISMATCH";
| Source | When to use |
|---|---|
request.cf.country (Cloudflare) |
Easiest if the site is already behind Cloudflare — country-level IP geolocation on all plans. It is an estimate and should not be the sole signal for compliance-critical decisions. |
| Platform header | Vercel, Netlify or CloudFront already supply the request country as a header on their platforms — read it instead. |
| MaxMind GeoLite2, self-hosted | If the host supplies nothing, resolve locally. |
| Third-party geo API | Avoid — do not ship every applicant's IP to an additional processor unless necessary. |
Data minimisation: country is all that is needed, so country is all that is kept. The raw IP is discarded once the country is derived.
{
"applicationSessionId": "sess_456",
"candidateToken": "cand_123",
"resolvedCountry": "IN",
"claimedCountry": "IN",
"countryStatus": "MATCH",
"capturedAt": "2026-09-10T12:30:00Z"
}The disclosure stays general. It must be transparent enough for the privacy notice to hold up, but must not reveal the whitelist, the matching rule, or the stage at which comparison happens — and it must not nudge the candidate toward any particular answer.
The privacy notice should identify the purpose, the data used, the retention period, the processors, and the review / correction process. Whether consent or another lawful basis is required depends on the jurisdictions involved — have employment and privacy counsel approve the final wording, especially where the result can influence applicant eligibility.
Continue POST
and compare only at final submission.That asymmetry is the whole design: a useful inconsistency signal, captured without invasive permission prompts, honest in what it claims, and paired with a human review step instead of an automated gate.