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

# Ban Enforcement

> Learn how to enforce user bans and prevent banned users from accessing your platform

## Overview

This tutorial covers how to use Fingerprint to enforce bans on your platform by recognizing the same device even when a user changes accounts.

You'll begin with a starter app that shows a simple ticket resale marketplace: a page displaying existing listings and a form to post new ones. From there, you'll add the JavaScript agent to identify each visitor and use server-side logic to detect and block submissions from banned visitors.

By the end, you'll have a sample app that prevents banned users from re-posting listings, even if they change their email address or use a new account.

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/0DEQil-aGmw" 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 `ban-evasion` folder. The project is organized as follows:

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

    <Tree.File name="index.js - Front-end logic for listings and admin" />
  </Tree.Folder>

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

    <Tree.File name="listings.js - Ban enforcement logic" />

    <Tree.File name="server.js - Serves static files and listings endpoint" />
  </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](http://localhost:3000/) to view the mock ticket listing page from the starter app. Create a few listings using the form at the bottom and they'll appear in the list above. Click **Admin mode** in the top right to simulate admin access, then use **Ban seller** on any listing. This blocks that specific email address from posting again. Try creating another listing with the same email and you'll see it's rejected but simply changing the email will allow the submission to go through.

## 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 **Post listing**. 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 listing data. The server will then call the [Fingerprint Events API](/reference/v3/server-api-get-event) to securely retrieve the full identification details, including bot detection and other Smart Signals.

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 **Post listing** button already has an event handler for submitting the listing details. Inside this handler, request visitor identification from Fingerprint using the `get()` method and include the returned `requestId` when sending the request to the server:

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

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

  try {
    const res = await fetch("/api/listings", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        eventName,
        eventDate,
        venue,
        price,
        ticketCount,
        ticketDescription,
        sellerEmail,
        requestId,
      }),
    });

    const data = await res.json();
    showResult(data.success, data.message);
    if (data.success) getListings();
  } catch (err) {
    console.error("Listing posting failed:", err);
    showResult(false, "Something went wrong.");
  }
});
```

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 server-side listing 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 already defines API routes for the app. Notice that the `POST /api/listings` route simply passes the request body to a `postListing` function that is defined in the `server/listings.js` file:

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

2. The `server/listings.js` file contains the logic for handling listing submissions and ban enforcement. Start by importing and initializing the Fingerprint Node Server SDK there, and load your environment variables with `dotenv`:

```javascript server/listings.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 `postListing` function to extract `requestId` and use it to fetch the full identification event details from Fingerprint so you can log the `visitorId` alongside each listing:

```javascript server/listings.js theme={"theme":"github-dark-dimmed"}
export async function postListing(body) {
  const { sellerEmail, requestId } = body;

  const event = await fpServerApiClient.getEvent(requestId);
  const visitorId = event.products.identification.data.visitorId;

  if (isSellerBanned(sellerEmail)) {
    console.error("Seller is banned:", sellerEmail);
    return { success: false, message: "You are banned from posting listings." };
  }

  const saveResult = saveListing(body);
  return saveResult;
}
```

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 suspicious devices

<Note>This optional step uses Smart Signals, which are only available on paid plans.</Note>

A useful optional layer for ban enforcement is the Suspect Score signal. Suspect Score is a weighted representation of all Smart Signals detected during identification and can help surface visitors that show signs of risky or manipulated environments.

While you normally wouldn't block someone solely because they have a high Suspect Score, you might choose to treat these visitors differently — for example, requiring extra verification before allowing them to post a listing, or reviewing their activity more closely.

1. Continuing in the `postListing` function in `server/listings.js`, read the Suspect Score from the `event` object and apply any additional rules you want based on it:

```javascript server/listings.js theme={"theme":"github-dark-dimmed"}
export async function postListing(body) {
  const { sellerEmail, requestId } = body;

  const event = await fpServerApiClient.getEvent(requestId);
  const visitorId = event.products.identification.data.visitorId;

  const suspectScore = event.products?.suspectScore?.data?.result || 0;
  if (suspectScore > 20) {
    console.error(`High Suspect Score detected: ${suspectScore}`);
    return { success: false, message: "Listing posting failed." };
  }

  if (isSellerBanned(sellerEmail)) {
    console.error("Seller is banned:", sellerEmail);
    return { success: false, message: "You are banned from posting listings." };
  }

  const saveResult = saveListing(body);
  return saveResult;
}
```

## 6. Enforce bans using visitor IDs

Next, use the trusted `visitorId` from the `event` object to enforce your ban rules. If a visitor (`visitorId`) tries to create a new listing after being banned, reject the submission. In production, you may choose to apply different actions for banned visitors, such as blocking specific actions, requiring additional review, or flagging the account for your trust and safety team.

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

```text SQLite database tables theme={"theme":"github-dark-dimmed"}
listings – Stores ticket listings and the visitor that created them
  id INTEGER PRIMARY KEY AUTOINCREMENT
  eventName TEXT NOT NULL
  eventDate TEXT NOT NULL
  ticketCount INTEGER NOT NULL
  venue TEXT NOT NULL
  sellerEmail TEXT NOT NULL
  price REAL NOT NULL
  ticketDescription TEXT NOT NULL
  visitorId TEXT
  createdAt INTEGER NOT NULL

banned_visitors – Stores banned visitors and their identifiers
  id INTEGER PRIMARY KEY AUTOINCREMENT
  email TEXT
  visitorId TEXT
  bannedAt INTEGER NOT NULL
```

1. First update the existing `saveListing` helper function to store the `visitorId` alongside a new listing:

```javascript server/listings.js theme={"theme":"github-dark-dimmed"}
// Save listing to database
function saveListing(listing) {
  const createdAt = Date.now();
  try {
    db.prepare(
      "INSERT INTO listings (eventName, eventDate, ticketCount, venue, sellerEmail, price, ticketDescription, visitorId, createdAt) VALUES (@eventName, @eventDate, @ticketCount, @venue, @sellerEmail, @price, @ticketDescription, @visitorId, @createdAt)",
    ).run({ ...listing, createdAt });
    return { success: true, message: "Listing saved successfully." };
  } catch (err) {
    // ...
  }
}
```

2. Then update the existing `isSellerBanned` helper function to use the `visitorId` instead of email to check for banned visitors:

```javascript server/listings.js theme={"theme":"github-dark-dimmed"}
// Check if seller is banned
function isSellerBanned(visitorId) {
  const bannedAt = db
    .prepare("SELECT bannedAt FROM banned_visitors WHERE visitorId = ?")
    .get(visitorId);
  return bannedAt ? true : false;
}
```

3. Next update `banSeller` function so it looks up the listing's associated visitor ID instead of email address:

```javascript server/listings.js theme={"theme":"github-dark-dimmed"}
export function banSeller(listingId) {
  const bannedAt = Date.now();
  try {
    const listing = db.prepare("SELECT visitorId FROM listings WHERE id = ?").get(listingId);

    if (!listing) {
      console.error("Listing not found.");
      return { success: false, message: "Listing not found." };
    }

    const visitorId = listing.visitorId;

    db.prepare("INSERT INTO banned_visitors (visitorId, bannedAt) VALUES (?, ?)").run(
      visitorId,
      bannedAt,
    );

    return { success: true, message: "Seller banned successfully." };
  } catch (err) {
    console.error("Failed to ban seller:", err);
    return { success: false, message: "Failed to ban seller: " + err.message };
  }
}
```

4. Finally, update `postListing` to include the `visitorId` when checking if the visitor is banned and when saving new listings:

```javascript server/listings.js theme={"theme":"github-dark-dimmed"}
export async function postListing(body) {
  const { requestId } = body;

  const event = await fpServerApiClient.getEvent(requestId);
  const visitorId = event.products.identification.data.visitorId;

  const suspectScore = event.products?.suspectScore?.data?.result || 0;
  if (suspectScore > 20) {
    console.error(`High Suspect Score detected: ${suspectScore}`);
    return { success: false, message: "Listing posting failed." };
  }

  if (isSellerBanned(visitorId)) {
    console.error("Seller is banned.");
    return { success: false, message: "You are banned from posting listings." };
  }

  const saveResult = saveListing({ ...body, visitorId });
  return saveResult;
}
```

Together with Suspect Score, this gives you a way to filter out visitors you do not want interacting with your platform. More importantly, logging and enforcing bans by `visitorId` ensures that a banned visitor cannot return simply by changing their email address. You can expand the logic by adding more Smart Signals, adjusting thresholds, or customizing how bans are applied based on your business rules.

<Info>
  This is a minimal example to show how to implement Fingerprint for ban enforcement. In a real
  application, make sure to implement proper security practices, error checking, and access-control
  flows that align with your production standards.
</Info>

## 7. Test your implementation

Now that everything is wired up, you can test the full ban evasion 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. Try posting a listing by filling out the form at the bottom of the page and clicking **Post listing**. You should see a success response and the listing will appear in the list above.
3. Click **Admin mode** in the top right to toggle admin controls, then click **Ban seller** on one of the listings that *you* created.
4. Try to post another listing. The submission will be blocked because Fingerprint recognizes the banned visitor ID
5. Open the page in an incognito window and try posting a new listing. The request is still blocked, since Fingerprint continues to recognize the visitor.

## Next steps

You now have a working ban enforcement flow powered by Fingerprint. From here, you can expand the logic with more [Smart Signals](/docs/v3/smart-signals-reference), tailor ban rules to your platform's policies, or build additional protections like rate limits or step-up verification 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: Detecting new account fraud](/docs/v3/new-account-fraud-use-case-tutorial)
* [Low-latency identification with Sealed Client Results](/docs/v3/sealed-client-results)
