Skip to content
IP Checker

IP Checker Verify

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

  1. 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.

  2. 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.

  3. 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

Frontend
<!-- 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" });
    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.
reference
Whatever you passed to run(), returned unchanged.
POST /api/verify/v1/exchange
{
  "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"
      }
    },
    "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.id already used for a trial → require a card or deny the second trial.
  • network.hosting or vpn is 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

EndpointCalled fromAuthPurpose
GET /verify.jsBrowserThe SDK
POST /api/verify/v1/collect?key=pk_…Browser (via SDK)Public key + allowed originReturns a token
POST /api/verify/v1/exchangeYour serverBearer secret keyReturns the result

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.

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.