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

# Chargeback Dispute

> Learn how to reduce chargebacks and win disputes with device identification data

## Overview

This tutorial covers how to use Fingerprint to strengthen chargeback dispute evidence by linking purchases to a visitor over time, even when a customer claims an unauthorized transaction.

You'll begin with a starter app that includes a simple event ticket storefront where you can make purchases, a personal order history view where you can simulate a chargeback, and a merchant/admin view where you can view purchase details. From there, you'll add the JavaScript agent to identify each visitor and use server-side logic to link purchases together with a visitor identifier to prove a consistent pattern of legitimate activity tied to the same visitor.

By the end, you'll have a sample app that creates a browser-linked purchase history you can reference when a customer disputes a charge, claiming the purchase wasn't made by them (AKA friendly fraud).

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/zeg404AQxJo" 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 `chargeback-dispute` folder. The project is organized as follows:

<Tree>
  <Tree.Folder name="public" defaultOpen>
    <Tree.File name="admin.html - Admin dashboard" />

    <Tree.File name="admin.js - Front-end logic for admin view" />

    <Tree.File name="history.html - Order history and dispute simulator" />

    <Tree.File name="history.js - Front-end logic for history view" />

    <Tree.File name="index.html - Event ticket storefront" />

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

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

    <Tree.File name="purchases.js - Purchase and evidence logic" />

    <Tree.File name="server.js - Serves static files and purchase endpoints" />
  </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 events storefront and make a few test purchases, then open your order history page to simulate a chargeback on one of them, and finally switch to the merchant view to inspect the disputed order where you'll see only limited evidence to help you prove the purchase was actually appropriately authorized.

## 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 **Complete purchase**. Fingerprint 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 purchase data. The server will then call the [Fingerprint Events API](/reference/v3/server-api-get-event) to securely retrieve the full identification details, including Smart Signals you can use as evidence during chargeback disputes.

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

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

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

  try {
    const res = await fetch("/api/purchases", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        eventName: currentEvent.name,
        ticketQuantity: parseInt(ticketQuantity),
        price: parseFloat(currentEvent.price.replace("$", "")),
        creditCard,
        deliveryEmail,
        requestId,
      }),
    });

    const data = await res.json();
    if (data.success) {
      showResult(true, "Purchase completed successfully!");
    } else {
      showResult(false, data.message || "Failed to complete purchase.");
    }
  } catch (err) {
    console.error("Purchase failed:", err);
    showResult(false, "Something went wrong. Please try again.");
  }
});
```

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 purchase 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 any additional Smart Signals you want to store for dispute evidence.

1. In the backend, the `server/server.js` file already defines API routes for the app. Notice that the `POST /api/purchases` route simply passes the request body to a `postPurchase` function that is defined in the `server/purchases.js` file:

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

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

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

```javascript server/purchases.js theme={"theme":"github-dark-dimmed"}
export async function postPurchase(body) {
  const createdAt = Date.now();

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

  // ...
}
```

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. Link purchase history with visitor IDs

Next, use the trusted `visitorId` from the `event` object to link purchases together. Instead of relying only on an account email or IP address, you'll associate each purchase with the `visitorId`, so you can later show that the same visitor has a history of legitimate purchases when a chargeback happens.

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

```text SQLite database tables theme={"theme":"github-dark-dimmed"}
purchases – Stores completed ticket purchases and the visitor who made them
  id INTEGER PRIMARY KEY AUTOINCREMENT
  eventName TEXT NOT NULL
  ticketQuantity INTEGER NOT NULL
  price REAL NOT NULL
  creditCard TEXT NOT NULL
  deliveryEmail TEXT NOT NULL
  visitorId TEXT
  chargeback INTEGER DEFAULT 0
  createdAt INTEGER NOT NULL
```

1. First update the existing `postPurchase` function so it stores the `visitorId` alongside each new row in the `purchases` table.

```javascript server/purchases.js theme={"theme":"github-dark-dimmed"}
export async function postPurchase(body) {
  const createdAt = Date.now();

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

  try {
    db.prepare(
      `INSERT INTO purchases (eventName, ticketQuantity, price, creditCard, deliveryEmail, visitorId, createdAt)
      VALUES (@eventName, @ticketQuantity, @price, @creditCard, @deliveryEmail, @visitorId, @createdAt)`,
    ).run({ ...body, visitorId, createdAt });
    return { success: true, message: "Purchase completed successfully." };
  } catch (err) {
    console.error("Failed to save purchase:", err);
    return {
      success: false,
      message: "Failed to save purchase: " + err.message,
    };
  }
}
```

2. Update the `getRelatedPurchases` function so it looks up related orders based on both email and visitorId. This lets you see the full history for a visitor even if they change accounts.

```javascript server/purchases.js theme={"theme":"github-dark-dimmed"}
export function getRelatedPurchases(orderId) {
  try {
    const purchase = db
      .prepare("SELECT deliveryEmail, visitorId FROM purchases WHERE id = ?")
      .get(orderId);

    if (!purchase) {
      return {
        success: false,
        message: "No purchase found for the given order ID.",
      };
    }

    const { deliveryEmail, visitorId } = purchase;

    const purchases = db
      .prepare(
        `SELECT id, eventName, ticketQuantity, price, creditCard, deliveryEmail, chargeback, visitorId, createdAt
        FROM purchases
        WHERE deliveryEmail = ? OR visitorId = ?
        ORDER BY createdAt DESC`,
      )
      .all(deliveryEmail, visitorId);

    return { success: true, purchases };
  } catch (err) {
    console.error("Failed to get purchases:", err);
    return {
      success: false,
      message: "Failed to get purchases: " + err.message,
    };
  }
}
```

3. In the merchant/admin view (`public/admin.js`), update the following functions to include the `visitorId` when displaying and exporting the history related to a purchase:

```javascript public/admin.js theme={"theme":"github-dark-dimmed"}
function createHistoryRow(purchase) {
  // ...
  row.querySelector("[data-email]").textContent = purchase.deliveryEmail || "";
  row.querySelector("[data-visitor-id]").textContent = purchase.visitorId || "";
  row.querySelector("[data-ticket-quantity]").textContent = purchase.ticketQuantity;
  row.querySelector("[data-total]").textContent = `$${total.toFixed(2)}`;
  row.querySelector("[data-credit-card]").textContent = purchase.creditCard;

  // ...
}

function exportToCsv() {
  // ...

  // CSV headers
  const headers = ["Event", "Date", "Email", "Visitor ID", "Tickets", "Total", "Card", "Status"];

  // Convert purchases to CSV rows
  const rows = currentPurchaseHistory.map((purchase) => {
    const total = purchase.price * purchase.ticketQuantity;
    const purchaseDate = formatDate(purchase.createdAt);
    const status = purchase.chargeback === 1 ? "Chargeback" : "Completed";

    return [
      purchase.eventName,
      purchaseDate,
      purchase.deliveryEmail || "",
      purchase.visitorId || "",
      purchase.ticketQuantity,
      `$${total.toFixed(2)}`,
      purchase.creditCard,
      status,
    ];
  });

  // ...
}
```

4. Then in the `admin.html` file, uncomment the visitor ID header and template column:

```html public/admin.html theme={"theme":"github-dark-dimmed"}
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500" data-visitor-id>Visitor ID</td>

<!-- ... -->

<th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
  Visitor ID
</th>
```

This gives the simulated evidence package additional information to help dispute friendly fraud chargebacks. Browser and device identifiers aren't the only types of evidence a merchant can submit, but they provide a strong additional signal of continuity that [card networks ask for during chargeback dispute reviews](https://fingerprint.com/blog/chargeback-dispute-process/). You can expand the logic by including more factors, depending on the level of detail your business needs.

<Info>
  This is a minimal example showing how to use Fingerprint data to support chargeback disputes. In a
  real application, make sure to implement proper security practices, perform robust server-side
  validation, and follow your organization's standards for storing and transmitting evidence related
  to payments and disputes.
</Info>

## 6. Test your implementation

Now that everything is wired up, you can see how the visitor ID provides additional evidence for chargeback disputes.

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. Make a few purchases from the main **Events** page. Each one will be logged with both your account email (you are logged in as `jamie@example.com`) and your visitor ID.
3. Open your personal **Order history** page and simulate a chargeback on one of them.
4. Switch to the **Merchant view** and view the purchase history related to the purchase with the chargeback. You'll see your full purchase history including visitor ID, showing that the disputed purchase and the earlier successful purchases all originated from the same browser.

## Next steps

You now have a basic chargeback dispute helper powered by Fingerprint. From here, you can enrich the dispute evidence even more with [Smart Signals](/docs/v3/smart-signals-reference). 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)
