Device and IP verification for signups and free trials
Know which device and network every signup comes from. One script on your frontend, one call from your backend. We return the data; you decide what counts as a duplicate account.
How it works
- 01
Browser collects
verify.js runs in your page, collects the browser fingerprint and sends it to us. The same request gives us the visitor’s real IP.
- 02
You get a token
We combine device and network signals into a result and return a single-use, encrypted token that expires in minutes.
- 03
Your backend exchanges
Your server sends the token with its secret key and receives the full result. Store it and apply your own rules.
The fingerprint and result never pass through the browser in readable form, so users can’t tamper with them. Nothing about your end users is stored on our side.
Integrate in two steps
<!-- 1. Load once on the page that starts the trial -->
<script src="https://ipcheckhq.com/verify.js" async></script>
<script>
async function startTrial(form) {
const verifier = Verify.init({
publicKey: "pk_live_YOUR_PUBLIC_KEY",
// persistentIdentifier: true, // optional: stores a random ID on your site to recognise returning browsers (disclose it)
});
const { token } = await verifier.run({ reference: "trial-signup" });
// 2. Send the token to YOUR backend with the signup request
await fetch("/api/start-trial", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: form.email.value, verificationToken: token }),
});
}
</script>- Create a project in the console, add your app’s origin and create a secret key.
- Call
run()when the user submits the signup or trial form. - Tokens are single-use and expire after 2 minute(s). Exchange them before creating the account.
- If verify.js is blocked or fails, decide your fallback (allow, require a card, or manual review).
What your backend receives
- device.id
- Stable per project: the same browser gets the same ID in your project, and a different ID in other projects.
- device.visitor_id
- Raw browser fingerprint from the open-source library, with its confidence.
- device.*_hash
- Canvas, WebGL, audio and composite hashes for similarity matching.
- device.randomized_signals
- Signals the browser deliberately randomises (privacy browsers, some anti-detect tools).
- network.*
- IP, ASN, ISP, location, network type, and hosting / VPN / proxy / Tor flags with certainty. Observed server-side.
- consistency.*
- Browser timezone and language compared with the IP location.
- risk.*
- A transparent, rule-based score and level (low/medium/high) with the exact signals behind it — a starting point, not a fraud verdict.
- reference
- Whatever you passed to run(), returned unchanged.
{
"success": true,
"verification": {
"object": "verification",
"schema_version": 1,
"id": "ver_3kP9sQ2mX7vB1nR8tY4wZ6aC",
"project_id": "prj_Hx82kLq0Pz4mN7vT",
"reference": "trial-signup",
"origin": "https://app.example.com",
"created_at": "2026-01-01T12:00:00.000Z",
"expires_at": "2026-01-01T12:02:00.000Z",
"device": {
"id": "dev_7c1e9a0b4f2d8e6a5b3c9d1e0f7a2b4c",
"visitor_id": "8c37d02f891aa0b1c2d3e4f5a6b7c8d9",
"visitor_id_confidence": 0.6,
"composite_hash": "f1e2d3c4b5a6978877665544332211009988776655443322110099887766554433",
"component_hashes": {
"userAgent": "a1b2c3d4e5f60718",
"canvas": "0f1e2d3c4b5a6978"
},
"canvas": {
"status": "exposed",
"hash": "304ab52fef01d…"
},
"webgl": {
"status": "exposed",
"hash": "827189d231a…",
"vendor": "Google Inc. (Apple)",
"renderer": "ANGLE (Apple, Apple M2)"
},
"audio": {
"status": "exposed",
"hash": "9284c22a8f…",
"sample_rate": 48000
},
"fonts": {
"status": "exposed",
"detected_count": 22,
"detected": [
"Arial",
"Helvetica Neue",
"…"
]
},
"randomized_signals": [],
"browser": {
"name": "Chrome",
"version": "140",
"engine": "Blink",
"os": "macOS",
"os_version": "15.1",
"device_type": "desktop"
},
"environment": {
"timezone": "America/New_York",
"languages": [
"en-US",
"en"
],
"locale": "en-US",
"screen": {
"width": 1512,
"height": 982
},
"device_pixel_ratio": 2,
"color_depth": 30,
"cpu_threads": 10,
"device_memory_gb": 8,
"touch_points": 0,
"webdriver": false,
"cookies_enabled": true
}
},
"network": {
"ip": "203.0.113.42",
"ip_version": 4,
"asn": 7922,
"asn_name": "Comcast Cable Communications, LLC",
"isp": "Comcast Cable Communications, LLC",
"organization": "Comcast Cable Communications, LLC",
"network_type": "residential",
"hosting": false,
"proxy": false,
"vpn": false,
"tor": false,
"location": {
"country": "United States",
"country_code": "US",
"region": "New York",
"region_code": null,
"city": "New York",
"postal_code": null,
"latitude": 40.71,
"longitude": -74.01,
"timezone": "America/New_York"
},
"certainty": {
"network_type": "inferred",
"hosting": "inferred",
"proxy": "inferred",
"vpn": "inferred",
"tor": "likely"
}
},
"consistency": {
"timezone": {
"status": "matches",
"network": "America/New_York",
"browser": "America/New_York"
},
"language_region": {
"status": "matches"
},
"locale_region": {
"status": "matches"
}
},
"risk": {
"score": 0,
"level": "low",
"signals": []
},
"client": {
"user_agent": "Mozilla/5.0 …",
"accept_language": "en-US,en;q=0.9",
"client_type": "browser"
},
"trust": {
"server_observed": [
"network",
"client",
"origin",
"created_at"
],
"client_reported": [
"device"
],
"note": "Network data is observed by the server…"
}
}
}Building your duplicate-account rules
Common, effective patterns (implemented in your backend):
- Same
device.idalready used for a trial → require a card or deny the second trial. network.hostingorvpnis true plus a timezone mismatch → add friction (phone or card).- Many signups from one IP or /24 in a short window → rate-limit or review.
- Combine with your own signals: normalised email, disposable domains, card fingerprint.
Device signals are reported by the browser and can be spoofed by determined attackers. Treat them as risk signals, not proof, and avoid hard blocks on a single match (shared computers, offices and mobile carriers exist).
API reference
| Endpoint | Called from | Auth | Purpose |
|---|---|---|---|
| GET /verify.js | Browser | — | The SDK |
| POST /api/verify/v1/collect?key=pk_… | Browser (via SDK) | Public key + allowed origin | Returns a token |
| POST /api/verify/v1/exchange | Your server | Bearer secret key | Returns the result |
| POST /api/verify/v1/lookup | Your server | Bearer secret key · Growth/Business | Seen-before facts only — no verdict |
Errors use { success: false, error: { code, message } }. Exchange codes: UNAUTHORIZED, INVALID_TOKEN, TOKEN_EXPIRED, TOKEN_ALREADY_USED, RATE_LIMITED. SDK errors expose the same code on VerifyError. Machine-readable spec: /openapi.json.
Pricing
A verification is one completed backend exchange. Collect attempts (retries, abandoned page loads) have a separate, more generous limit so they never count against your verification quota.
Free
Free
- 250 verifications/mo
- 1 project
- 3 origins/project
- 30/min collect rate limit
- No stored verification log
- No identity lookup API
Most popular
Growth
$49/mo
- 10,000 verifications/mo
- 5 projects
- 25 origins/project
- 120/min collect rate limit
- 6-month verification log
- Identity lookup API included
Business
$249/mo
- 100,000 verifications/mo
- 50 projects
- 50 origins/project
- 600/min collect rate limit
- 2-year verification log
- Identity lookup API included
Self-serve billing is coming soon. Growth and Business are activated manually today — email plans@ipcheckhq.com after signing up for a free account. Need more than Business offers? That's Enterprise — same email, we'll talk volume pricing.
Going over your plan's verification limit returns 429 PLAN_LIMIT_EXCEEDED from /exchange — decide server-side whether that blocks the signup or lets it through, then upgrade when you're ready.
Privacy and your obligations
- We don’t store your end users’ fingerprints, IPs or results. Only aggregate daily counts per project are kept.
- You are the controller of the data you receive. Disclose device fingerprinting in your privacy policy.
- In the EU/UK, fingerprinting generally requires consent unless strictly necessary; fraud prevention may qualify under legitimate interests, which you should document.
- Keep secret keys on your server. Rotate or revoke them in the console at any time.