> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fingerprint.com/llms.txt
> Use this file to discover all available pages before exploring further.

# New Account Fraud

> Learn how to detect and prevent fraudulent account creation

## Overview

This tutorial walks through implementing Fingerprint to prevent new account fraud, where bad actors create multiple fake accounts to exploit sign-up bonuses, free trials, or other incentives.

You'll begin with a starter app that includes a mock sign-up page and a basic registration flow. From there, you'll add the JavaScript agent to identify each visitor and use server-side logic with Fingerprint data to detect and block multiple account creations from the same device. For simplicity, the sample will block new account sign-ups from devices that have already registered one account, but in practice, you might choose to add friction, require additional verification, or set higher thresholds instead.

By the end, you'll have a sample app that limits each visitor to one legitimate account and can be adapted to fit your own fraud prevention and business logic.

This tutorial uses just plain JavaScript and a Node server with SQLite on the backend. For language- or framework-specific setups, see the quickstarts.

> Estimated time: \< 15 minutes

<iframe className="w-full aspect-video rounded-md" src="https://www.youtube.com/embed/aWTI1cGCVNE" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen />

## Prerequisites

Before you begin, make sure you have the following:

* A copy of the [starter repository](https://github.com/fingerprintjs/use-case-tutorials) (clone with Git or download as a ZIP)
* [Node.js](https://nodejs.org/) (v20 or later) and npm installed
* Your favorite code editor
* Basic knowledge of JavaScript

## 1. Create a Fingerprint account and get your API keys

1. [Sign up](https://dashboard.fingerprint.com/signup) for a free Fingerprint trial, or log in if you already have an account.
2. After signing in, go to the [**API keys**](https://dashboard.fingerprint.com/api-keys) page in the dashboard.
3. Save your **public API key**, which you'll use to initialize the JavaScript agent.
4. Create and securely store a **secret API key** for your server. Never expose it on the client side. You'll use this key on the backend to retrieve full visitor information through the Fingerprint Server API.

## 2. Set up your project

1. Clone or download the [starter repository](https://github.com/fingerprintjs/use-case-tutorials) and open it in your editor.

```bash Terminal theme={"theme":"github-dark-dimmed"}
git clone https://github.com/fingerprintjs/use-case-tutorials.git
```

2. This tutorial will be using the `new-account-fraud` folder. The project is organized as follows:

<Tree>
  <Tree.Folder name="public" defaultOpen>
    <Tree.File name="index.html - Sign-up page" />

    <Tree.File name="index.js - Front-end logic to handle sign-up" />
  </Tree.Folder>

  <Tree.Folder name="server" defaultOpen>
    <Tree.File name="server.js - Serves static files and sign-up endpoint" />

    <Tree.File name="db.js - SQLite database connection" />

    <Tree.File name="accounts.js - New account fraud logic" />
  </Tree.Folder>

  <Tree.File name=".env.example - Example environment variables" />
</Tree>

3. Install dependencies:

```bash Terminal theme={"theme":"github-dark-dimmed"}
npm install
```

4. Copy or rename `.env.example` to `.env`, then add your Fingerprint API keys:

```bash Terminal theme={"theme":"github-dark-dimmed"}
FP_PUBLIC_API_KEY=your-public-key
FP_SECRET_API_KEY=your-secret-key
```

5. Start the server:

```bash Terminal theme={"theme":"github-dark-dimmed"}
npm run dev
```

6. Visit `http://localhost:3000` to view the mock sign-up page from the starter app. Create a few test accounts using different usernames and clicking **Sign up**. Then open the page in a different browser or incognito window and try creating another account from the same device. By default, the app allows unlimited account creations per device.

## 3. Add Fingerprint to the frontend

In this step, you'll load the JavaScript agent when the page loads and trigger identification when the user clicks **Sign up**. The JavaScript agent returns both a `visitorId` and a `requestId`. Instead of relying on the `visitorId` from the browser, you'll send the `requestId` to your server along with the signup payload. The server will then call the [Fingerprint Events API](/reference/v3/server-api-get-event) to securely retrieve the full identification details, including the verified `visitorId` and risk signals such as browser tampering or bot activity.

1. At the top of `public/index.js`, load the JavaScript agent:

```javascript public/index.js theme={"theme":"github-dark-dimmed"}
const fpPromise = import(`https://fpjscdn.net/v3/${window.FP_PUBLIC_API_KEY}`).then(
  (FingerprintJS) => FingerprintJS.load({ region: "us" }),
);
```

2. Make sure to change `region` to match your workspace region (e.g., `eu` for Europe, `ap` for Asia, `us` for Global (default)).
3. Near the bottom of `public/index.js`, the **Sign up** button already has an event handler for submitting the credentials. Inside this handler, request visitor identification from Fingerprint using the `get()` method and include the returned `requestId` when sending the signup request to the server:

```javascript public/index.js theme={"theme":"github-dark-dimmed"}
signupBtn.addEventListener("click", async () => {
  // ...

  const fp = await fpPromise;
  const { requestId } = await fp.get();

  try {
    const res = await fetch("/api/signup", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
	    body: JSON.stringify({ username, password, requestId }),
    });
    const data = await res.json();

    // ...
  }
});
```

The `get()` method sends signals collected from the browser to Fingerprint servers, where they are analyzed to identify the visitor. The returned `requestId` acts as a reference to this specific identification event, which your server can later use to fetch the full visitor details.

For lower latency in production, use [Sealed Client Results](/docs/v3/sealed-client-results) to return full identification details as an encrypted payload from the `get()` method.

## 4. Receive and use the request ID to get visitor insights

Next, pass the `requestId` through to your signup logic, initialize the [Fingerprint Node Server SDK](/reference/node-server-sdk), and fetch the full visitor identification event so you can access the trusted `visitorId` and [Smart Signals](https://fingerprint.com/products/smart-signals/).

1. In the backend, the `server/server.js` file defines the API routes for the app. Update the `/api/signup` route there to also extract `requestId` from the request body and pass it into the `attemptSignup` function.

```javascript server/server.js theme={"theme":"github-dark-dimmed"}
app.post("/api/signup", async (req, reply) => {
  const { username, password, requestId } = req.body || {};
  const result = await attemptSignup({ username, password, requestId });
  return reply.send(result);
});
```

2. The `server/accounts.js` file contains the logic for handling signups. Start by importing and initializing the Fingerprint Node Server SDK there, and load your environment variables with `dotenv`.

```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
import { db } from "./db.js";
import { config } from "dotenv";
import { FingerprintJsServerApiClient, Region } from "@fingerprintjs/fingerprintjs-pro-server-api";

config();

const fpServerApiClient = new FingerprintJsServerApiClient({
  apiKey: process.env.FP_SECRET_API_KEY,
  region: Region.Global,
});
```

3. Make sure to change `region` to match your workspace region (e.g., `EU` for Europe, `AP` for Asia, `Global` for Global (default)).
4. Update the `attemptSignup` function to accept `requestId` and use it to fetch the full identification event details from Fingerprint:

```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
export async function attemptSignup({ username, password, requestId }) {
  if (!username || !password || !requestId) {
    console.error("Missing one or more inputs.");
    return { success: false, error: "Sign up failed." };
  }

  const user = findAccountByUsername(username);
  if (user) {
    console.error("Account already exists");
    return { success: false, error: "Account already exists." };
  }

  const event = await fpServerApiClient.getEvent(requestId);

  // ...
}
```

Using the `requestId`, the getEvent will retrieve the full data for the visitor identification request. The returned object will contain the visitor ID, IP address, device, and browser details, and Smart Signals like bot detection, browser tampering detection, VPN detection, and more.

You can see a full example of the event structure and test it with your own device in the [demo playground](https://demo.fingerprint.com/playground).

For additional checks to ensure the validity of the data coming from your frontend, view [how to protect from client-side tampering and replay attacks](/docs/v3/protecting-from-client-side-tampering).

## 5. Block bots and suspicious devices

<Note>
  This optional step uses the Bot Detection and Suspect Score Smart Signals, which are only
  available on paid plans.
</Note>

A simple but powerful way to prevent automated abuse is to block bot signups. The `event` object includes the [Bot Detection Smart Signal](https://fingerprint.com/products/bot-detection/) that flags automated activity, making it easy to reject bot traffic.

This signal returns `good` for known bots like search engines, `bad` for automation tools, headless browsers, or other signs of automation, and `notDetected` when no bot activity is found.

1. Continuing in the `attemptSignup` function in `server/accounts.js`, check the bot signal returned in the `event` object:

```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
export async function attemptSignup({ username, password, requestId }) {
  // ...

  const event = await fpServerApiClient.getEvent(requestId);

  const botDetected = event.products?.botd?.data?.bot?.result !== "notDetected";

  if (botDetected) {
    console.error("Bot detected.");
    return { success: false, error: "Signup failed." };
  }

  // ...
}
```

You can also use Fingerprint's [Suspect Score](/docs/v3/suspect-score) to flag high-risk signups. The Suspect Score is a weighted representation of all Smart Signals present in the identification payload, helping to identify suspicious activity.

While it's not typical to block signups based solely on a high risk score, this example shows how you might incorporate it. In a real application, a better approach would be to flag the attempt for review or add additional friction during signup.

2. Below the bot detection check, add a condition that reads the Suspect Score from the `event` object and blocks the login if it exceeds a chosen threshold (for example, 20):

```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
export async function attemptSignup({ username, password, requestId }) {
  // ...

  const event = await fpServerApiClient.getEvent(requestId);

  const botDetected = event.products?.botd?.data?.bot?.result !== "notDetected";

  if (botDetected) {
    console.error("Bot detected.");
    return { success: false, error: "Signup failed." };
  }

  const suspectScore = event.products?.suspectScore?.data?.result || 0;

  if (suspectScore > 20) {
    console.error(`High Suspect Score detected: ${suspectScore}`);
    return { success: false, error: "Signup failed." };
  }

  // ...
}
```

## 6. Prevent multiple account sign-ups per device

Next, use the trusted `visitorId` from the `event` object to enforce a one-account-per-device rule. If the same device (`visitorId`) tries to create another account, reject the sign-up. In production, you may choose to allow a limited number of accounts per device, require additional verification, or flag the new account for review. *(This example simplifies account creation logic for demonstration purposes.)*

Note: The starter app includes a SQLite database with a table already created for you:

```text SQLite database tables theme={"theme":"github-dark-dimmed"}
accounts - Stores account details
	username TEXT PRIMARY KEY
	password TEXT NOT NULL
	visitorId TEXT
	createdAt INTEGER NOT NULL
```

1. Add a helper function to the bottom of the `server/accounts.js` file to check if the device has already been used to create an account:

```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
// Check if the device has already created an account
function findAccountByVisitorId(visitorId) {
  const row = db
    .prepare(`SELECT username FROM accounts WHERE visitorId = ? LIMIT 1`)
    .get(visitorId);
  return row;
}
```

2. Update `attemptSignup` to retrieve the `visitorId` and use it to enforce the one device per account rule and record successful logins:

```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
export async function attemptSignup({ username, password, requestId }) {
  // ...

  if (suspectScore > 20) {
    console.error(`High Suspect Score detected: ${suspectScore}`);
    return { success: false, error: "Signup failed." };
  }

  const visitorId = event.products.identification.data.visitorId;
  const account = findAccountByVisitorId(visitorId);
  if (account) {
    console.error("Account already exists for this device.");
    return { success: false, error: "Signup failed." };
  }

  // ...
}
```

3. Update the SQL statement to store the `visitorId` along with the account details so it can be used later for device verification checks:

```bash Terminal theme={"theme":"github-dark-dimmed"}
export async function attemptSignup({ username, password, requestId }) {
	// ...

  const visitorId = event.products.identification.data.visitorId;
  const account = findAccountByVisitorId(visitorId);
  if (account) {
    console.error("Account already exists for this device.");
    return { success: false, error: "Signup failed." };
  }

  db.prepare(
    `INSERT INTO accounts (username, password, visitorId, createdAt) VALUES (?, ?, ?, ?)`
  ).run(username, password, visitorId, Date.now());

  return { success: true };
}
```

This gives you a basic system to detect and block new account fraud. You can extend it by allowing a limited number of accounts per device, adding verification steps for suspicious sign-ups, setting time-based limits, or flagging high-risk activity for review.

<Info>
  This is a minimal example to show how to implement Fingerprint. In a real application, make sure
  to use proper security practices, input validation, password handling, and account management
  logic that align with your production standards.
</Info>

## 7. Test your implementation

Now that everything is wired up, you can test the full protected signup flow.

1. Start your server if it isn't already running and open [http://localhost:3000](http://localhost:3000):

```bash Terminal theme={"theme":"github-dark-dimmed"}
npm run dev
```

2. Reset the database by clicking on **Reset demo DB** at the bottom of the page.
3. Create a new account in your browser, you should see a success response. Then try to create another account and you'll see that the attempt fails.
4. Open an incognito/private window and try creating another account. The attempt will still be rejected because the device has already registered one account.
5. Bonus: Test the flow using a headless browser or automation tool to see bot detection in action. A sample script is available in `test-bot.js`. While your app is running, run the script with `node test-bot.js` in your terminal and observe that the automated signups are blocked.

## Next steps

You now have a working signup flow secured with Fingerprint. From here, you can expand the logic with more [Smart Signals](/docs/v3/smart-signals-reference), fine-tune rules based on your business policies, or layer in additional checks for suspicious visitors.

To dive deeper, explore the other use case tutorials for more step-by-step examples.

Check out these related resources:

* [Node SDK Reference](https://github.com/fingerprintjs/fingerprintjs-pro-server-api-node-sdk)
* [Vue frontend quickstart](/docs/v3/vue-quickstart)
* [React frontend quickstart](/docs/v3/react-quickstart)
* [API reference for the Events endpoint](/reference/v3/server-api-get-event)
* [Use case tutorial: Preventing account sharing](/docs/v3/account-sharing-use-case-tutorial)
* [Low-latency identification with Sealed Client Results](/docs/v3/sealed-client-results)
