Skip to main content

Overview

This tutorial walks through implementing Fingerprint to prevent card testing and card cracking attacks, where fraudsters use bots to rapidly test stolen or generated credit card numbers on an online checkout form to find valid card details. You’ll begin with a starter app that includes a mock checkout page and a basic payment flow. From there, you’ll add the Fingerprint JavaScript agent to identify each visitor and use server-side logic with Fingerprint data to detect and block automated card submissions. By the end, you’ll have a sample app that rejects card-testing bots and can be customized to fit your use case and business rules. This tutorial uses just plain JavaScript and a Node server with SQLite on the back end. For language- or framework-specific setups, see our quickstarts.
Estimated time: < 15 minutes
This tutorial requires the Bot Detection Smart Signal, which is only available on paid plans.

Prerequisites

Before you begin, make sure you have the following:
  • A copy of the starter repository (clone with Git or download as a ZIP)
  • Node.js (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 for a free Fingerprint trial, or log in if you already have an account.
  2. After signing in, go to the API keys page in the dashboard.
  3. Save your public API key, which you’ll use to initialize the Fingerprint 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 and open it in your editor.
Terminal
git clone https://github.com/fingerprintjs/use-case-tutorials.git
  1. This tutorial will be using the card-testing folder. The project is organized as follows:
Project structure
.
├── public/
│   ├── index.html      # Checkout page
│   └── index.js        # Front-end logic to handle card submissions
├── server/
│   ├── db.js           # Initializes SQLite and exports a database connection
│   ├── orders.js       # Order submission and card testing prevention logic
│   └── server.js       # Serves static files and ordering endpoint
└── .env.example        # Example environment variables
  1. Install dependencies:
Terminal
npm install
  1. Copy or rename .env.example to .env, then add your Fingerprint API keys:
Terminal
FP_PUBLIC_API_KEY=your-public-key
FP_SECRET_API_KEY=your-secret-key
  1. Start the server:
Terminal
npm run dev
  1. Visit http://localhost:3000 to view the mock checkout page from the starter app. You can test the basic payment form by entering some fake card details and clicking Place order.
  2. Then try submitting an order using the included headless bot script test-bot.js. While the app is running, execute node test-bot.js and observe that the automated script successfully submits the order. By default, the server does not distinguish between bots and real users.
Terminal
node test-bot.js

3. Add Fingerprint to the front end

In this step, you’ll load the Fingerprint client when the page loads and trigger identification when the user clicks Place order. The client 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 checkout payload. The server will then call the Fingerprint Events API to securely retrieve the full identification details, including bot detection and other signals.
  1. At the top of public/index.js, load the Fingerprint JavaScript agent:
public/index.js
const fpPromise = import(
  `https://fpjscdn.net/v3/${window.FP_PUBLIC_API_KEY}`
).then((FingerprintJS) => FingerprintJS.load({ region: "us" }));
  1. Make sure to change region to match your workspace region (e.g., eu for Europe, ap for Asia, us for Global (default)).
  2. Near the bottom of public/index.js, the Place order button already has an event handler for submitting the payment details. Inside this handler, request visitor identification from Fingerprint using the get() method and include the returned requestId when sending the checkout request to the server:
public/index.js
placeOrderBtn.addEventListener("click", async () => {
	// ...

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

  try {
    const res = await fetch("/api/place-order", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        recipientEmail,
        amount,
        cardNumber,
        cardExp,
        cardCvv,
        requestId,
      }),
    });

	// ...
});
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, check out our documentation on using 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 order processing logic, initialize the Fingerprint Server API client, and fetch the full visitor identification event so you can access the trusted visitorId and Bot Detection Smart Signal.
  1. In the back end, the server/server.js file defines the API routes for the app. Update the /api/place-order route there to also extract requestId from the request body and pass it into the placeOrder function.
server/server.js
app.post("/api/place-order", async (req, reply) => {
  const result = await placeOrder(req.body);
  return reply.send(result);
});
  1. The server/orders.js file contains the logic for handling orders. Start by importing and initializing the Fingerprint Server API client there, and load your environment variables with dotenv.
server/orders.js
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,
});
  1. Make sure to change region to match your workspace region (e.g., EU for Europe, AP for Asia, Global for Global (default)).
  2. Update the placeOrder function to also extract requestId and use it to fetch the full identification event details from Fingerprint:
server/orders.js
export async function placeOrder(body) {
  const { recipientEmail, amount, cardNumber, cardExp, cardCvv, requestId } =
    body;

  const event = await fpServerApiClient.getEvent(requestId);

  // ...
}
Using the requestId, the Fingerprint server client 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 our demo playground. For additional checks to ensure the validity of the data coming from your front end, view how to protect from client-side tampering and replay attacks in our documentation.

5. Block card testing bots

Card testing and card cracking attacks rely heavily on automated checkout attempts, so rejecting bots outright can stop the abuse. Fingerprint returns notDetected if no bot activity is found, good for known bots, like search engines, and bad for other automation tools. Any visitor identification that does not return notDetected can be blocked from placing orders.
  1. Continuing in the placeOrder function in server/orders.js, check the bot signal returned in the event object and block bots:
server/orders.js
export async function placeOrder(body) {
  const { recipientEmail, amount, cardNumber, cardExp, cardCvv, requestId } =
    body;

  const event = await fpServerApiClient.getEvent(requestId);

  const botDetected = event.products?.botd?.data?.bot?.result !== "notDetected";
  if (botDetected) {
    console.error("Bot detected.");
    return { success: false, message: "Order failed." };
  }

  // ...
}
You can also add Suspect Score as a secondary layer. The Suspect Score is a weighted representation of all Smart Signals present in the identification payload, helping to identify suspicious activity. While you may not normally block checkout attempts based only on a high risk score, you could flag them for review, modify rate-limits, or require additional verification.
  1. Below the bot detection check, add a condition that reads the Suspect Score from the event object and blocks the order if it exceeds a chosen threshold (for example, 20):
server/orders.js
export async function placeOrder(body) {
  // ...

  const botDetected = event.products?.botd?.data?.bot?.result !== "notDetected";
  if (botDetected) {
    console.error("Bot detected.");
    return { success: false, message: "Order failed." };
  }

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

  // ...
}

6. Recognize repeat offenders by visitor ID

As a secondary measure, you can log the visitorId along with orders to spot suspicious activity. This lets you recognize and block the same device even if they clear cookies, change IPs, or change accounts. Note: The starter app includes a SQLite database with this table already created for you:
SQLite database tables
orders – Stores orders and associated visitor IDs
  orderNumber INTEGER PRIMARY KEY AUTOINCREMENT
  visitorId TEXT
  recipientEmail TEXT NOT NULL
  amount REAL NOT NULL
  createdAt INTEGER NOT NULL
  1. Add a new helper function to the bottom of the server/orders.js file to check the number of orders in the last 24 hours for a specific visitorId:
server/orders.js
// Count visitor's orders placed in the last 24 hours
function countRecentOrders(visitorId) {
  const since = Date.now() - 24 * 60 * 60 * 1000;

  const row = db
    .prepare(
      `SELECT COUNT(*) AS count
       FROM orders
       WHERE visitorId = ? AND createdAt >= ?`
    )
    .get(visitorId, since);

  return row.count;
}
  1. Also update the existing saveOrder helper function to accept and use the visitorId:
server/orders.js
// Save the order to the database
function saveOrder({ recipientEmail, amount, visitorId }) {
  db.prepare(
    "INSERT INTO orders (recipientEmail, amount, visitorId, createdAt) VALUES (?, ?, ?, ?)"
  ).run(recipientEmail, amount, visitorId, Date.now());
}
  1. Update placeOrder to retrieve the visitorId, and use it check for an unusually high volume of recent orders made by the visitor and when saving the order:
server/orders.js
export async function placeOrder(body) {
  // ...

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

  const visitorId = event.products.identification.data.visitorId;
  if (countRecentOrders(visitorId) >= 5) {
    console.error("Too many orders placed in the last 24 hours.");
    return { success: false, message: "Order failed." };
  }

  // ...

  saveOrder({ recipientEmail, amount, visitorId });

  // ...
}
Together with the bot detection Smart Signal, this allows you to protect your checkout and prevent card testing and card cracking attempts. No matter which account is used, you can monitor order velocity and tie activity back to the same browser or device. You can extend it by analyzing additional signals, changing rate limit thresholds, or varying your response based on risk.
This is a minimal example to show how to implement Fingerprint. In a real application, make sure to implement proper security practices, error checking, and payment and card data handling that align with your production standards.

7. Test your implementation

Now that everything is wired up, you can test the full checkout flow.
  1. Start your server if it isn’t already running and open http://localhost:3000:
Terminal
npm run dev
  1. Try placing an order by entering some fake payment details and clicking Place order. You should see a success response.
  2. If you make five orders, additional attempts from the same device will be blocked based on the recent-order check. Open the page in incognito mode to see that you are still blocked since Fingerprint still recognizes your browser with the same visitor ID.
  3. While your demo is running, run the included headless bot test script from the card-testing folder. This will attempt to place an order using a headless browser, which will be flagged by the Bot Detection signal and rejected:
Terminal
node test-bot.js
Note: If you encounter errors launching the automated browser, make sure you have the testing browser installed:
Terminal
npx puppeteer browsers install chrome

Next steps

You now have a working checkout flow that blocks card testing and card cracking attempts with Fingerprint. From here, you can expand the logic with more Smart Signals, fine-tune rules based on your business policies, or layer in additional defenses or step-up verification. To dive deeper, explore our other use case tutorials for more step-by-step examples. Check out these related resources: