Docs menuAll pages, quickstarts and this page’s contents
- Androidsoon
- iOSsoon
- React Nativesoon
- Fluttersoon
JavaScript quickstart
Add Kaidn to a browser app and give every visitor a stable device id you can send to your backend. Scaffolded with Vite, and the example is the one most people start with: stopping the same person opening account after account.
Estimated time: under 10 minutes
Before you start
- Node 20+ and npm, to run Vite.
- A free Kaidn account. Register: 10,000 events a month, no card.
This is the frontend half, and on its own it blocks nothing. By the end you will have a device id in the console. That id only becomes fraud prevention when your server sends it to /v1/score and acts on the verdict, so finish with a backend quickstart: Python, PHP or Node.js. A page that stopped here would leave you believing you were protected while nothing was being scored.
Get your publishable key
The browser needs a different key from your server, and the distinction matters.
- Create an account if you do not have one.
- Go to Fraud Scoring API → Device trackers and create a tracker, listing the domains it may run on (include
localhostwhile you build). - Copy the publishable key. It starts
pk_live_.
This key is meant to be visible in page source. It is domain-locked and can only send fingerprints: it cannot score anything, read your data, or be used from a site you did not authorise. Your kdn_live_ secret key is the opposite and must never reach a browser.
Set up your project
Scaffold a vanilla JavaScript app. Skip to step 3 if you have a project already.
npm create vite@latest kaidn-js-quickstart -- --template vanilla cd kaidn-js-quickstart npm install
Run it and open http://localhost:5173, Vite's default. You should see the Vite welcome page.
npm run dev
Build the signup form
Something to attach to. Replace the body of index.html:
<body> <main class="wrap"> <h1>Create an account</h1> <form id="signup"> <label for="email">Email</label> <input id="email" name="email" type="email" placeholder="you@example.com" required /> <label for="password">Password</label> <input id="password" name="password" type="password" required /> <!-- @kaidn/fp fills this in before the form submits --> <input id="kaidn_device_id" name="kaidn_device_id" type="hidden" /> <button type="submit">Create account</button> </form> <pre id="out"></pre> </main> <script type="module" src="/src/main.js"></script> </body>
And enough CSS to see it. Overwrite src/style.css:
html, body { margin: 0; font-family: system-ui, sans-serif; }
.wrap {
max-width: 380px; min-height: 100vh; margin: 0 auto;
display: flex; flex-direction: column; justify-content: center; gap: .75rem;
padding: 1rem;
}
form { display: flex; flex-direction: column; gap: .5rem; }
input { padding: .6rem; border: 1px solid #ccc; border-radius: 6px; font: inherit; }
label { font-size: .85rem; color: #555; }
button {
margin-top: .5rem; padding: .7rem 1.2rem; font: inherit; cursor: pointer;
background: #111; color: #fff; border: 0; border-radius: 6px;
}
pre { font-size: .78rem; color: #444; white-space: pre-wrap; word-break: break-all; }Install and initialise
npm install @kaidn/fp
Then open src/main.js and replace it. beacon()does two things in one call: it fingerprints the browser, and it posts that to the Kaidn edge so your connection's TLS handshake is captured against this device. Your server never has to forward either.
import "./style.css"; import { beacon } from "@kaidn/fp"; // Domain-locked and safe in page source. Yours is in the dashboard under // Fraud Scoring API -> Device trackers. const PUBLISHABLE_KEY = "pk_live_your_key_here"; const ENDPOINT = "https://api.kaidn.io/v1/fp";
For production, put the key in an environment variable. Vite exposes anything prefixed VITE_, so import.meta.env.VITE_KAIDN_PK keeps it out of your repository. It is not a secret, but a key you can rotate without a code change is worth having.
Collect on submit
Fingerprint the visitor at the moment they act, rather than on every page load. It is one fewer thing running on pages that do not need it, and the signal is freshest exactly when you are about to use it.
const form = document.getElementById("signup"); const out = document.getElementById("out"); form.addEventListener("submit", async (e) => { e.preventDefault(); let deviceId = null; try { // Fingerprints the browser AND beacons it to the edge, so Kaidn can // capture the TLS handshake for this device. Returns in ~200ms. const fp = await beacon(ENDPOINT, PUBLISHABLE_KEY); deviceId = fp.device_id; } catch { // FAIL OPEN. A blocked script, an offline visitor or an ad blocker must // never stop somebody signing up. Your backend scores the event either // way; it just has one signal fewer. } document.getElementById("kaidn_device_id").value = deviceId ?? ""; out.textContent = "device_id: " + (deviceId ?? "(unavailable)"); // Send it to YOUR server, which scores it and decides. // await fetch("/api/signup", { // method: "POST", // headers: { "content-type": "application/json" }, // body: JSON.stringify({ // email: form.email.value, // kaidn_device_id: deviceId, // }), // }); });
Note what is not here: no verdict, and no decision. The browser is an untrusted place to make one, so it never sees a score. It produces an id, your server produces the judgement.
Score it on your server
This is the step that turns a device id into fraud prevention. Everything before it collects; nothing before it decides.
npm install @kaidn/sdk
import { Kaidn } from "@kaidn/sdk"; // Your SECRET key. Server only, never the browser. const kaidn = new Kaidn({ apiKey: process.env.KAIDN_API_KEY }); // Express, Fastify, Hono, a Next.js route handler: the call is the same. app.post("/api/signup", async (req, res) => { let r; try { r = await kaidn.score({ event: "signup", ip: req.ip, email: req.body.email, device_id: req.body.kaidn_device_id, // the id the browser collected }); } catch { // FAIL OPEN. An outage in your fraud vendor must never become an // outage in your signup form. return res.json({ ok: true }); } if (r.verdict === "block") { // Do not name the signal. It teaches the next attempt. return res.status(403).json({ error: "We could not create that account." }); } const user = await createUser(req.body); if (r.verdict === "review") await flagForReview(user.id, r.event_id, r.reason_text); res.json({ ok: true }); });
Three answers, and you decide what each one means. Kaidn never blocks anybody on your behalf.
Whichever you pick, the client covers every endpoint an API key can reach, not just scoring: check.email, batch.score, lists.add, config.set, label, forget, suppressions, events, stats. None of them belong in a browser, because all of them need your secret key. Full reference.
Test it
npm run dev
Open http://localhost:5173, fill the form, submit. You should see the id on the page and in the hidden field:
device_id: 8f1c2ae9d4b7c3e05a1f6b28d9074e3c
Turn ad blockers off on localhost while you test. Any fingerprinting script can be blocked by an extension, and when that happens you get nothing rather than a cautious answer. We measured what that does to a competitor and published the numbers, including the part where our own endpoint is only unblocked because nobody has listed it yet. This is why the code above fails open, and why the device id is one signal in a score rather than the whole thing.
Next steps
Nothing is being scored yet. Send that device_id to your backend and pass it to /v1/score, which is where a verdict and a decision actually happen. Pick your server:
Two things worth reading once the loop is closed: the drop-in tracker if you would rather not build (one script tag, no npm), and the device token, which upgrades a guessed identity to a remembered one and is the single biggest accuracy win available to you.