# Account Sharing
Source: https://docs.fingerprint.com/docs/account-sharing-use-case-tutorial
Learn how to detect and prevent unauthorized account sharing
## Overview
This tutorial walks through implementing Fingerprint to prevent account sharing, where multiple people use the same account across different devices.
You'll begin with a starter app that includes a mock login page and a basic login flow. From there, you'll add the JavaScript agent to identify each visitor and use server-side logic with Fingerprint data to detect when an account is accessed from a new device. For simplicity, the sample will block logins from new devices for an account, but in practice, you would likely choose to add friction, log out other devices, or flag for review instead.
By the end, you'll have a sample app that limits an account to a single device 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 backend. For language- or framework-specific setups, see the quickstarts.
> Estimated time: \< 15 minutes
## 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 `account-sharing` folder. The project is organized as follows:
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 login page from the starter app. You can test the basic login form using the included test account (`demo@example.com` / `password123`) and clicking **Log in**. Then open the page in a completely different browser and log in with the same account. By default, the same account can be logged into from any 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 **Log in**. The JavaScript agent returns both a `visitor_id` and an `event_id`. Instead of relying on the `visitor_id` from the browser, you'll send the `event_id` to your server along with the login payload. The server will then call the [Fingerprint Events API](/reference/server-api-get-event) to securely retrieve the full identification details, including the verified `visitor_id` 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/v4/${window.FP_PUBLIC_API_KEY}`).then((Fingerprint) =>
Fingerprint.start({ 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 **Log in** 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 `event_id` when sending the login request to the server:
```javascript public/index.js theme={"theme":"github-dark-dimmed"}
loginBtn.addEventListener("click", async () => {
// ...
const fp = await fpPromise;
const { event_id: eventId } = await fp.get();
try {
const res = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password, eventId }),
});
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 `event_id` 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/sealed-client-results) to return full identification details as an encrypted payload from the `get()` method.
## 4. Receive and use the event ID to get visitor insights
Next, pass the `eventId` through to your login logic, initialize the [Fingerprint Node Server SDK](/reference/node-server-sdk), and fetch the full visitor identification event so you can access the trusted visitor ID 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/login` route there to also extract `eventId` from the request body and pass it into the `attemptLogin` function:
```javascript server/server.js theme={"theme":"github-dark-dimmed"}
app.post("/api/login", async (req, reply) => {
const { email, password, eventId } = req.body || {};
const result = await attemptLogin({ email, password, eventId });
return reply.send(result);
});
```
2. The `server/accounts.js` file contains the logic for handling logins. 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 { FingerprintServerApiClient, Region } from "@fingerprint/node-sdk";
config();
const fpServerApiClient = new FingerprintServerApiClient({
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 `attemptLogin` function to accept `eventId` and use it to fetch the full identification event details from Fingerprint:
```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
export async function attemptLogin({ email, password, eventId }) {
if (!email || !password) {
console.error("Missing credentials.");
return { success: false, error: "Login failed." };
}
if (!eventId) {
console.error("Missing eventId.");
return { success: false, error: "Login failed." };
}
const user = findAccountByEmail(email);
if (!user || user.password !== password) {
console.error("Invalid credentials");
return { success: false, error: "Login failed." };
}
const event = await fpServerApiClient.getEvent(eventId);
// ...
}
```
Using the `eventId`, the getEvent method will retrieve the full data for the visitor identification event. 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/protecting-from-client-side-tampering).
## 5. Block bots and suspicious devices
This optional step uses the Bot Detection and Suspect Score Smart Signals, which are only
available on paid plans.
A simple but powerful way to prevent automated abuse is to block bot logins. 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 `not_detected` when no bot activity is found.
1. Continuing in the `attemptLogin` 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 attemptLogin({ email, password, eventId }) {
// ...
const event = await fpServerApiClient.getEvent(eventId);
const botDetected = event.bot !== "not_detected";
if (botDetected) {
console.error("Bot detected.");
return { success: false, error: "Login failed." };
}
// ...
}
```
You can also use Fingerprint's [Suspect Score](/docs/suspect-score) to flag high-risk logins. 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 actions 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 some additional step-up authentication.
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 attemptLogin({ email, password, eventId }) {
// ...
const suspectScore = event.suspect_score || 0;
if (suspectScore > 20) {
console.error(`High Suspect Score detected: ${suspectScore}`);
return { success: false, error: "Login failed." };
}
// ...
}
```
## 6. Prevent account sharing
Next, use the trusted `visitor_id` from the `event` object to enforce a one-device-per-account rule. If the same account logs in from a different device (`visitor_id`), reject the login. In production you may choose to allow a certain number of devices, add step-up verification, or alert the user. *(This example oversimplifies login logic for demonstration purposes.)*
Note: The starter app includes a SQLite database with these tables already created for you:
```text SQLite database tables theme={"theme":"github-dark-dimmed"}
accounts - Stores login credentials for test accounts
email TEXT PRIMARY KEY
password TEXT NOT NULL
account_devices - Logs which device (visitorId) is associated with each account
email TEXT PRIMARY KEY
visitorId TEXT NOT NULL
createdAt INTEGER NOT NULL
```
1. Add some helper functions to the bottom of the `server/accounts.js` file to read and write device details for an account:
```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
// Get the last seen device for the user
function getLastDeviceFor(email) {
return db.prepare(`SELECT visitorId FROM account_devices WHERE email = ?`).get(email);
}
// Record the visitor's device on logging in
function saveDeviceFor(email, visitorId) {
db.prepare(
`INSERT OR IGNORE INTO account_devices (email, visitorId, createdAt)
VALUES (?, ?, ?)`,
).run(email, visitorId, Date.now());
}
```
2. Update `attemptLogin` to retrieve the `visitor_id` 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 attemptLogin({ email, password, eventId }) {
// ...
if (botDetected) {
console.error("Bot detected.");
return { success: false, error: "Login failed." };
}
const visitorId = event.identification.visitor_id;
const last = getLastDeviceFor(email);
if (last && last.visitorId !== visitorId) {
console.error("Access from new device detected.");
return {
success: false,
error:
"You can only access this account from one device. (Reset the demo to simulate logging out on the other device.)",
};
}
if (!last) saveDeviceFor(email, visitorId);
return { success: true };
}
```
This gives you a basic system to detect and block account sharing. You can extend it by allowing a small number of devices per account, expiring devices after some time, adding step-up verification on change, notifying the account owner, etc.
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 password handling that align with your
production standards.
## 7. Test your implementation
Now that everything is wired up, you can test the full protected login 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. Log in (`demo@example.com` / `password123`) in one browser. You should see a success response.
3. In a different browser or device, try logging in with the same account. The second attempt will be rejected because the account is tied to a different device.
4. 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 logins are blocked.
## Next steps
You now have a working login flow secured with Fingerprint. From here, you can expand the logic with more [Smart Signals](/docs/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/node-sdk)
* [Vue frontend quickstart](/docs/vue-quickstart)
* [React frontend quickstart](/docs/react-quickstart)
* [API reference for the Events endpoint](/reference/server-api-get-event)
* [Use case tutorial: Detecting new account fraud](/docs/new-account-fraud-use-case-tutorial)
* [Low-latency identification with Sealed Client Results](/docs/sealed-client-results)
# Account Takeover
Source: https://docs.fingerprint.com/docs/account-takeover-use-case-tutorial
Learn how to protect your application from account takeover attacks
## Overview
This tutorial walks through implementing Fingerprint to prevent account takeover. Account takeover (ATO) occurs when an attacker gains access to a real user's account, often through stolen credentials, automated login abuse at scale, phishing, or other attacks aimed at your sign-in flow.
You'll begin with a starter app that includes a mock login page and a basic login 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 automated login attempts.
By the end, you'll have a sample app that rejects high-risk traffic 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 backend. For language- or framework-specific setups, see the 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](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
cd use-case-tutorials/account-takeover
```
2. This tutorial uses the `account-takeover` folder. The project is organized as follows:
3. From the `account-takeover` directory, 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 login page from the starter app. You can test the basic login form using the included test account (`demo@example.com` / `password123`) and clicking **Log in**.
7. Then try to log in using the included headless bot test script `test-bot.js`. While the app is running, execute `node test-bot.js` and observe that the automated script logs in successfully. By default, the server does not distinguish between bots and real users.
```bash Terminal theme={"theme":"github-dark-dimmed"}
node test-bot.js
```
## 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 **Log in**. The JavaScript agent returns both a `visitor_id` and an `event_id`. Instead of relying on the `visitor_id` from the browser, you'll send the `event_id` to your server along with the login payload. The server will then call the [Fingerprint Events API](/reference/server-api-get-event) to securely retrieve the full identification details, including bot detection and other 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/v4/${window.FP_PUBLIC_API_KEY}`
).then((Fingerprint) => Fingerprint.start({ 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 **Log in** 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 `event_id` when sending the login to the server:
```javascript public/index.js theme={"theme":"github-dark-dimmed"}
loginBtn.addEventListener("click", async () => {
// ...
const fp = await fpPromise;
const { event_id: eventId } = await fp.get();
try {
const res = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password, eventId }),
});
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 `event_id` 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/sealed-client-results) to return full identification details as an encrypted payload from the `get()` method.
## 4. Receive and use the event ID to get visitor insights
Next, pass the `eventId` through to your login logic, initialize the [Fingerprint Node Server SDK](/reference/node-server-sdk), and fetch the full visitor identification event so you can access the trusted visitor ID, Bot Detection, and other [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/login` route there to also extract `eventId` from the request body and pass it into the `attemptLogin` function:
```javascript server/server.js theme={"theme":"github-dark-dimmed"}
app.post("/api/login", async (req, reply) => {
const { email, password, eventId } = req.body || {};
const result = await attemptLogin({ email, password, eventId });
return reply.send(result);
});
```
2. The `server/accounts.js` file contains the logic for handling logins. 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 { FingerprintServerApiClient, Region } from "@fingerprint/node-sdk";
config();
const fpServerApiClient = new FingerprintServerApiClient({
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 `attemptLogin` function to accept `eventId` and use it to fetch the full identification event details from Fingerprint:
```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
export async function attemptLogin({ email, password, eventId }) {
if (!email || !password) {
console.error("Missing credentials.");
return { success: false, error: "Login failed." };
}
if (!eventId) {
console.error("Missing eventId.");
return { success: false, error: "Login failed." };
}
const event = await fpServerApiClient.getEvent(eventId);
const user = findAccountByEmail(email);
if (!user || user.password !== password) {
console.error("Invalid credentials");
return { success: false, error: "Login failed." };
}
return { success: true };
}
```
Using the `eventId`, the `getEvent` method will retrieve the full data for the visitor identification event. 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'll use those fields in the next steps to decide whether a sign-in attempt looks like account takeover or other abuse.
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/protecting-from-client-side-tampering).
## 5. Block logins after too many recent failed attempts
Takeover attempts and password guessing often produce many failures from the same device, even when the attacker rotates usernames. Once you can reliably associate attempts with a Fingerprint visitor ID, you can persist outcomes and rate-limit or block visitors that exceed a threshold of recent failures—before or alongside other checks.
The starter app includes a SQLite `login_attempts` table for this demo. Each row records the visitor ID, account email, whether the attempt succeeded, and a timestamp:
```text SQLite database tables theme={"theme":"github-dark-dimmed"}
accounts - Stores login credentials for test accounts
email TEXT PRIMARY KEY
password TEXT NOT NULL
login_attempts - Records each login attempt for rate limits and device history
id INTEGER PRIMARY KEY AUTOINCREMENT
visitorId TEXT NOT NULL
email TEXT NOT NULL
success INTEGER NOT NULL (0 = failed, 1 = succeeded)
createdAt INTEGER NOT NULL
```
1. Add helper functions at the bottom of `server/accounts.js` to record failures and count how many failed attempts a visitor had in the last 24 hours:
```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
function logFailedAttempt(visitorId, email) {
db.prepare(
`INSERT INTO login_attempts (visitorId, email, success, createdAt) VALUES (?, ?, 0, ?)`
).run(visitorId, email, Date.now());
}
function getRecentFailedAttempts(visitorId) {
const since = Date.now() - 24 * 60 * 60 * 1000;
const row = db
.prepare(
`SELECT COUNT(*) as count
FROM login_attempts
WHERE visitorId = ? AND success = 0 AND createdAt >= ?`
)
.get(visitorId, since);
return row.count;
}
```
2. In `attemptLogin`, after you call `getEvent`, read `visitor_id` from `event.identification`. Then before validating the password, block visitors with too many recent failures. On any failed outcome in this flow, record a row with `logFailedAttempt` so later attempts stay counted.
At this stage, `attemptLogin` should look like this:
```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
export async function attemptLogin({ email, password, eventId }) {
if (!email || !password) {
console.error("Missing email or password.");
return { success: false, error: "Login failed." };
}
if (!eventId) {
console.error("Missing eventId.");
return { success: false, error: "Login failed." };
}
const event = await fpServerApiClient.getEvent(eventId);
const visitorId = event.identification.visitor_id;
if (getRecentFailedAttempts(visitorId) >= 3) {
logFailedAttempt(visitorId, email);
console.error("Too many failed login attempts.");
return { success: false, error: "Login failed." };
}
const user = findAccountByEmail(email);
if (!user || user.password !== password) {
logFailedAttempt(visitorId, email);
console.error("Invalid credentials");
return { success: false, error: "Login failed." };
}
return { success: true };
}
```
## 6. Block unrecognized devices from logging in
Valid passwords are often used from new devices during account takeover. A practical pattern is to require a prior successful login from the same visitor ID for that user before treating the session as routine, or to step up authentication (MFA, OTP, etc.) when that history is missing.
This tutorial is simplified and will block unrecognized devices from logging in for demonstration purposes. For the first successful login for an account there is no history yet, so allow it and establish a baseline. For later logins, if the account already has at least one successful login stored, but this visitor ID has never succeeded for that email, block as an unrecognized device.
1. Add helpers to query and record successful attempts by email and visitor ID:
```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
function hasAnySuccessfulLogin(email) {
return !!db
.prepare(
`SELECT 1 FROM login_attempts WHERE email = ? AND success = 1 LIMIT 1`
)
.get(email);
}
function hasSuccessfulLoginForVisitor(email, visitorId) {
return !!db
.prepare(
`SELECT 1 FROM login_attempts WHERE email = ? AND visitorId = ? AND success = 1 LIMIT 1`
)
.get(email, visitorId);
}
function logSuccessfulAttempt(visitorId, email) {
db.prepare(
`INSERT INTO login_attempts (visitorId, email, success, createdAt) VALUES (?, ?, 1, ?)`
).run(visitorId, email, Date.now());
}
```
2. Within `attemptLogin`, after the password matches, add the check before returning and logging a successful attempt. If the account already has successful logins but this visitor ID has not, log a failed attempt and return an error:
```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
const accountHasSuccessfulLogins = hasAnySuccessfulLogin(email);
const visitorHasLoggedInBefore = hasSuccessfulLoginForVisitor(email, visitorId);
if (accountHasSuccessfulLogins && !visitorHasLoggedInBefore) {
logFailedAttempt(visitorId, email);
console.error("Unrecognized device.");
return {
success: false,
error: "Login failed.",
};
}
logSuccessfulAttempt(visitorId, email);
return { success: true };
```
## 7. Block bots
Automated scripts and headless browsers often show up in account takeover workflows. Fingerprint Bot Detection helps differentiate real users from abusive automation. Fingerprint returns `not_detected` if no bot activity is found, `good` for known bots (for example known search engines, verified AI agents, etc.), and `bad` for other automation.
1. Continuing in `attemptLogin`, add a bot check on the same `event` you already fetched, then a [Suspect Score](/docs/suspect-score) check (optional but useful as a secondary layer for suspicious traffic):
```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
const botDetected = event.bot !== "not_detected";
if (botDetected) {
logFailedAttempt(visitorId, email);
console.error("Bot detected.");
return { success: false, error: "Login failed." };
}
const suspectScore = event.suspect_score || 0;
if (suspectScore > 20) {
logFailedAttempt(visitorId, email);
console.error(`High Suspect Score detected: ${suspectScore}`);
return { success: false, error: "Login failed." };
}
logSuccessfulAttempt(visitorId, email);
return { success: true };
```
Suspect Score is a weighted rollup of Smart Signals. In production you might use it to step up authentication rather than hard-block, depending on your business rules.
Together, rate limits by visitor ID, device history, and bot detection give you a layered defense against account takeover. You can extend this with more [Smart Signals](/docs/smart-signals-reference), different thresholds, or alternate responses.
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 password handling that align with your production standards.
## 8. Test your implementation
Now that everything is wired up, you can test the full protected login 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. Log in once with a valid test account (`demo@example.com` / `password123`). You will see a success response, and that browser’s visitor ID will be recorded as a known trusted device for that account.
3. Try several failed login attempts using the wrong password from the same browser. After three failures within the rolling time window, the recent failed attempts check blocks that visitor ID, including with the correct password, until the window rolls forward or you reset the demo database.
4. Reset the demo database, then log in successfully again from your browser so there is a known trusted device on file for `demo@example.com`.
5. With that successful browser session in place, open the demo in a completely different browser. Attempt to log in with the correct credentials. Since the new browser has no prior successful login, the login will be rejected as an unrecognized device
6. Next, run the headless test script from the `account-takeover` directory. It uses the correct credentials but because it is a headless browser, the login will be rejected as a bot:
```bash Terminal theme={"theme":"github-dark-dimmed"}
node test-bot.js
```
*Note: If you encounter errors launching the automated browser, make sure you have the testing browser installed:*
```bash Terminal theme={"theme":"github-dark-dimmed"}
npx puppeteer browsers install chrome
```
## Next steps
You now have a working login flow that layers visitor-based rate limiting, device history, and bot detection with Fingerprint for account takeover protection. From here, you can expand the logic with more [Smart Signals](/docs/smart-signals-reference), fine-tune rules based on your business policies, or layer in additional defenses such as multi-factor authentication.
To dive deeper, explore the other use case tutorials for more step-by-step examples.
Check out these related resources:
* [Node SDK Reference](/reference/node-server-sdk)
* [Vue frontend quickstart](/docs/vue-quickstart)
* [React frontend quickstart](/docs/react-quickstart)
* [API reference for the Events endpoint](/reference/server-api-get-event)
* [Use case tutorial: Detecting new account fraud](/docs/new-account-fraud-use-case-tutorial)
* [Low-latency identification with Sealed Client Results](/docs/sealed-client-results)
# AI agent detection
Source: https://docs.fingerprint.com/docs/ai-agents
Detect and verify AI agents visiting your web properties, and distinguish them from malicious bots.
AI agent detection lets you identify authorized AI agents visiting your website and distinguish them from malicious bots.
It uses [Web Bot Auth](/docs/bot-detection/web-bot-auth-implementation), a cryptographic signing protocol, to verify agent identity. When Fingerprint detects an agent, it returns structured metadata, including the agent's provider, name, and the identity verification status in the `bot_info` field of the Server API response.
This lets you make precise access decisions: allow authorized agents, block unverified automation, or apply different logic based on agent category and provider.
## How verification works
1. The agent signs its HTTP requests using a private key.
2. Fingerprint retrieves the agent's public key from a directory hosted at a known URL.
3. Fingerprint verifies the signature and returns an `identity` value in `bot_info`.
The `identity` value reflects the outcome of the verification:
| Value | Meaning |
| ---------- | -------------------------------------------------------------------------------------------------------------- |
| `signed` | Signature verified against the agent's public key directory |
| `verified` | Signature verified, and the agent is operated exclusively by its vendor (not configurable by vendor customers) |
| `unknown` | Agent recognized, but no verifiable identity was presented |
| `spoofed` | Agent presented an identity that failed verification |
## API response
When Fingerprint detects a bot or agent, the [Server API](/reference/server-api-get-event) response includes the following fields:
| Field | Type | Description |
| ---------- | ------ | ------------------------------------------------ |
| `bot` | string | `good`, `bad`, or `not_detected` |
| `bot_type` | string | Additional classification, e.g. `ai_agent` |
| `bot_info` | object | Extended metadata for recognized agents and bots |
The `bot_info` object contains:
| Field | Description |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `category` | Agent category, e.g. `ai_agent`, `ai_browser`, `browser_automation` ([full list](/docs/bot-detection/bot-directory#bot_info-metadata)) |
| `provider` | The organization that operates the agent, e.g. `OpenAI` or `AWS` |
| `name` | Human-readable agent name, e.g. `ChatGPT Agent` |
| `identity` | Verification status: `signed`, `verified`, `unknown` or `spoofed` |
`bot_info` is available in Server API v4 and only when Fingerprint recognizes the agent or bot. See the [Server API reference](/reference/server-api-get-event#response-bot-info) for the full schema.
## Supported AI agents
The following agents are currently detected and return `bot_info` metadata:
| Name | Provider | Category | Identity |
| ----------------- | ----------- | ------------ | -------- |
| ChatGPT Agent | OpenAI | `ai_agent` | `signed` |
| Bedrock AgentCore | AWS | `ai_agent` | `signed` |
| Browserbase Agent | Browserbase | `ai_agent` | `signed` |
| Manus Agent | Manus | `ai_agent` | `signed` |
| AGI Agent | AGI Inc | `ai_agent` | `signed` |
| Anchor Browser | Anchor | `ai_browser` | `signed` |
For the full list of all detectable bots and agents, see the [Bot Directory](/docs/bot-detection/bot-directory).
## Using detection results
The `bot` field (`good`, `bad`, `not_detected`) is a legacy signal. For AI agents and other recognized bots, use `bot_info.identity` instead, as it provides more precise, actionable information.
Note that `identity` is only available for well-known bots and agents in the [Bot Directory](/docs/bot-detection/bot-directory). Some generic browser automation tools (Selenium, Playwright, headless Chrome, etc.) will not have a `bot_info` entry and should be handled using the `bot: bad` signal.
| Scenario | Suggested action |
| -------------------------------------- | ------------------------------------------------------------------------------------------ |
| `bot_info.identity: verified` | **Allow**: agent identity is confirmed and safety verified |
| `bot_info.identity: signed` | **Allow**: agent identity is cryptographically verified |
| `bot_info.identity: unknown` | **Apply custom logic**: agent is recognized but unverified |
| `bot_info.identity: spoofed` | **Review**: verification failed, which may indicate misconfiguration or malicious spoofing |
| `bot` is `bad`, no `bot_info` (legacy) | **Review or Block**: unrecognized and potentially malicious automation |
## Next steps
Full list of detectable bots and agents
Cryptographically sign your agent to verify identity
Submit your agent or bot to the Fingerprint Bot Directory
# AI assistant detection
Source: https://docs.fingerprint.com/docs/ai-assistants
Detect and verify AI assistants visiting your web properties or API endpoints and distinguish them from malicious bots.
AI assistant detection requires a [Flow worker](/docs/flow-deployments#using-a-flow-worker-to-detect-bots-at-the-edge) deployment.
AI assistants are LLM chat applications that can answer questions, summarize information, and make requests to websites to gather information. When an AI assistant is making a request to a website, Fingerprint can detect these requests and identity the assistant category, provider and identity. The most common AI assistants are ChatGPT, Gemini, and Claude.ai.
Fingerprint's AI assistant detection lets you detect and verify AI assistants visiting your website or using your APIs and distinguish them from malicious bots.
It uses [Web Bot Auth](/docs/bot-detection/web-bot-auth-implementation), IP, and DNS checks to verify AI assistant identity. When Fingerprint detects an assistant, it returns structured metadata, including the assistant's provider, name, and the identity verification status in the `bot_info` field of the Server API response.
This lets you make precise access decisions: allow authorized assistants, block unverified automation, or apply different logic based on assistant category and provider.
## How identity verification works
Fingerprint will use all available methods to verify the assistant identity and will also detect spoofing / impersonation attempts.
The `identity` value reflects the outcome of the verification:
| Value | Meaning |
| ---------- | ----------------------------------------------------------- |
| `verified` | Identity verified |
| `unknown` | Assistant recognized, but the identity couldn't be verified |
| `spoofed` | Assistant presented an identity that failed verification |
## API response
When Fingerprint detects a bot, the [Server API](/reference/server-api-get-event) response includes the following fields:
| Field | Type | Description |
| ---------- | ------ | ------------------------------------------------------------- |
| `bot` | string | `good`, `bad`, or `not_detected` |
| `bot_type` | string | Additional classification, e.g. `ai_assistant` |
| `bot_info` | object | Extended metadata for recognized assistants, agents, and bots |
The `bot_info` object contains:
| Field | Description |
| ---------- | -------------------------------------------------------------------------- |
| `category` | `ai_assistant` |
| `provider` | The organization that operates the assistant, e.g. `OpenAI` or `Anthropic` |
| `name` | Human-readable assistant name, e.g. `ChatGPT-User` |
| `identity` | Verification status: `verified`, `unknown` or `spoofed` |
`bot_info` is available in Server API v4 and only when Fingerprint recognizes the AI tool or a bot. See the [Server API reference](/reference/server-api-get-event#response-bot-info) for the full schema. The full list of bots is available here: [full list](/docs/bot-detection/bot-directory#bot_info-metadata).
## Supported AI assistants
For the full list of all detectable AI tools and bots, see the [Bot Directory](/docs/bot-detection/bot-directory).
## Using detection results
The `bot` field (`good`, `bad`, `not_detected`) is a legacy signal. For AI assistants and other recognized bots, use `bot_info.identity` instead, as it provides more precise, actionable information.
Note that `identity` is only available for well-known bots and assistants in the [Bot Directory](/docs/bot-detection/bot-directory). Some generic browser automation tools (Selenium, Playwright, headless Chrome, etc.) will not have a `bot_info` entry and should be handled using the `bot: bad` signal.
| Scenario | Suggested action |
| -------------------------------------- | ------------------------------------------------------------------------------------------ |
| `bot_info.identity: verified` | **Allow**: assistant identity is confirmed and safety verified |
| `bot_info.identity: unknown` | **Apply custom logic**: assistant is recognized but unverified |
| `bot_info.identity: spoofed` | **Review**: verification failed, which may indicate misconfiguration or malicious spoofing |
| `bot` is `bad`, no `bot_info` (legacy) | **Review or Block**: unrecognized and potentially malicious automation |
## Next steps
Full list of detectable bots and agents
Detect and verify AI agents
Submit your assistant or bot to the Fingerprint Bot Directory
# Overview
Source: https://docs.fingerprint.com/docs/akamai-proxy-integration
Fingerprint JavaScript agent v4.0.0 or later is required.
*Fingerprint Akamai Proxy Integration* is responsible for proxying identification and agent-download requests between your website and Fingerprint through your Akamai infrastructure. The integration consists of a set of property rules you need to add to your Akamai property configuration. The property rules template is available on [GitHub][proxy-github-repo].
**Limitations and expectations**
### Limited to Enterprise plan
Support for the Akamai Proxy Integration is provided only to customers on the **Enterprise** Plan. Other customers are encouraged to use [Custom subdomain setup][custom-domain] or [Cloudflare Proxy Integration][cloudflare-proxy].
### Manual updates occasionally required
The underlying data contract in the identification logic can change to keep up with browser and device releases. Using the Akamai Proxy Integration might require occasional manual updates on your side. Ignoring these updates will lead to lower accuracy or service disruption.
### Tested property types
This integration has been tested with the **Dynamic Site Accelerator (DSA)** Akamai property type only. **Ion** (Standard / Premier) and **API Acceleration (AA)** have not been formally tested. These property types might work because Akamai's Property Manager exposes the same rule tree across them, but compatibility is not guaranteed. Validate the integration in a staging property before activating it in production. Contact [support][support] if you encounter issues.
## The benefits of using the Akamai Proxy Integration
* Significant increase in accuracy in browsers with strict privacy features such as Safari or Firefox.
* Cookies are now recognized as "first-party". This means they can live longer in the browser and extend the lifetime of visitor IDs.
* Ad blockers will not block our Fingerprint JavaScript agent from loading. Attempts to connect to an external URL will be stopped by most ad blockers while attempts to connect to the same site URL will be allowed.
* Ad blockers will not block our identification requests since they are sent to the specific path or subdomain that belongs to the same site.
* Insight and control over the identification requests that can be combined with other Akamai features like property rules and traffic reports.
* With the Akamai Proxy Integration, you can manage unlimited subdomains and paths and provide Fingerprint services to all your customers at any scale while benefiting from all the 1st-party integration improvements.
* Cookie security: Akamai Proxy Integration drops all the cookies sent from the origin website. The source is open-source so this behavior can be transparently verified and audited.
* Easy to meet compliance and auditing requirements.
## Integration setup overview
1. Issue a proxy secret in the Fingerprint Dashboard.
2. Create a path variable used by the Akamai property rules and client agent configuration on your website or mobile app.
3. Install and configure the integration in your Akamai account. You have 2 options:
1. [Using Terraform][akamai-terraform] (recommended).
2. [Using Akamai Property Manager API][akamai-papi].
Finally, configure the Fingerprint JavaScript agent on your website or client application.
## Step 1: Issue a Fingerprint proxy secret
Issue a Fingerprint proxy secret to authenticate requests from your Akamai infrastructure.
1. Go to the [Fingerprint dashboard][fingerprint-dashboard] and select your workspace.
2. In the left menu, navigate to [**API keys**][fingerprint-api-keys].
3. Click **Create Proxy Key**.
4. Give it a name, for example, `Akamai proxy integration`.
5. Optionally, you can [choose an environment][fingerprint-environments] for the proxy secret.
1. By default, the proxy secret works for all environments in your workspace.
2. A proxy secret scoped to a specific environment can only authenticate identification requests made with a public API key from the same environment.
6. Click **Create API Key**.
You will use the proxy secret value in the following step, so store it somewhere safe. You cannot retrieve the secret later.
## Step 2: Create a path variable
Set the path variable you will use throughout your property configuration and the JavaScript agent configuration on your website. This value is arbitrary. Just decide what your value is and write it down somewhere.
In this guide, we will use a readable value corresponding to the variable name to make it easier to follow:
```text theme={"theme":"github-dark-dimmed"}
FPJS_INTEGRATION_PATH="FPJS_INTEGRATION_PATH"
```
However, your value used in production should look more like a random string:
```text theme={"theme":"github-dark-dimmed"}
FPJS_INTEGRATION_PATH="ore54guier"
```
That is because ad blockers might automatically block requests from any URL containing fingerprint-related terms like "fingerprint", "fpjs", "track", etc. Random strings are the safest. So whenever you see a value like `FPJS_INTEGRATION_PATH` in this guide, you should use your random value instead.
## Step 3: Install and configure the integration in your Akamai property
You can choose from two available installation methods.
### Using Terraform (recommended)
If you manage your infrastructure as code using Terraform, you can use the Fingerprint Akamai proxy integration Terraform module. This installation method does not include any mechanism for automatic updates. The proxy function code is updated every time you run `terraform apply`.
If you prefer the Terraform installation method, continue with [**Install Akamai proxy integration using Terraform**][akamai-terraform], then come back to this page to configure the Fingerprint JavaScript agent on your website.
### Using Akamai Property Manager API
If you don't use Terraform to manage your infrastructure, you can deploy the integration using Akamai Property Manager API. This installation method does not include any mechanism for automatic updates. You will need to manually repeat the deployment process every time you update the integration.
If you prefer the Akamai Property Manager API installation method, continue with [**Install Akamai proxy integration using Akamai Property Manager API**][akamai-papi], then come back to this page to configure the Fingerprint JavaScript agent on your website.
**Note: Usage of `Origin IP Access Control List` rule**
* To support IPv6 origins, the integration uses the `Origin IP Access Control List` rule.
* Without additional configuration, this results in a warning in Akamai Control Center: "You have Origin IP Access Control List enabled in a rule in your property. You should also enable Tiered Distribution in either the same rule or a parent rule. This will better support caching on edge servers."
* This warning is expected and means the integration is working. We recommend following the recommendation in the warning and enabling Tiered Distribution in your property rules. See the [Akamai documentation for Origin IP ACL][akamai-ip-acl] for more details.
## Configure Fingerprint Agent
1. Use the random path variable created in the deployment steps according to your deployment method to construct the `endpoints` URL.
2. Configure the Fingerprint JavaScript agent on your website accordingly:
```javascript NPM package theme={"theme":"github-dark-dimmed"}
import * as Fingerprint from '@fingerprint/agent';
// Initialize the agent at application startup.
const fp = Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
endpoints: ['https://yourwebsite.com/FPJS_INTEGRATION_PATH?region=us'],
region: 'us',
});
```
```javascript CDN installation theme={"theme":"github-dark-dimmed"}
const url = 'https://yourwebsite.com/FPJS_INTEGRATION_PATH/web/v4/PUBLIC_API_KEY?region=us';
const fpPromise = import(url)
.then(Fingerprint => Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
endpoints: ['https://yourwebsite.com/FPJS_INTEGRATION_PATH?region=us'],
region: 'us',
}));
```
**Parameter URL nuances**
* The `region` query parameter value needs to reflect the [region](/docs/regions) of your application.
* Note that the import URL for the CDN installation method and the `endpoints` URL used by NPM packages are different and **cannot be used interchangeably**.
If everything is configured correctly, you should receive the latest Fingerprint client-side script and the identification result through your Akamai integration. If you have any questions, reach out to our [support team][support].
## Monitoring the integration
Go to **Dashboard** > [**SDKs & integrations**][fingerprint-integrations] > **Akamai** to see the status of your integration. Here you can monitor:
* If the integration is up to date.
* How many identification requests are coming through the integration (and how many are not).
* The error rate of proxied identification requests (caused by missing or incorrect proxy secret).
The information on the status page is cached so allow a few minutes for the latest data points to be reflected.
[akamai-ip-acl]: https://techdocs.akamai.com/origin-ip-acl/docs/welcome
[akamai-papi]: /docs/deploy-akamai-proxy-integration-via-papi
[akamai-terraform]: /docs/deploy-akamai-proxy-integration-via-terraform
[cloudflare-proxy]: /docs/cloudflare-integration
[custom-domain]: /docs/custom-subdomain-setup
[fingerprint-api-keys]: https://dashboard.fingerprint.com/api-keys
[fingerprint-dashboard]: https://dashboard.fingerprint.com
[fingerprint-environments]: /docs/multiple-environments#proxy-integrations-and-proxy-secrets
[fingerprint-integrations]: https://dashboard.fingerprint.com/integrations
[proxy-github-repo]: https://github.com/fingerprintjs/akamai-proxy
[support]: https://fingerprint.com/support/
# Analytics
Source: https://docs.fingerprint.com/docs/analytics
## Overview Page
The Overview page serves as the homepage for the Dashboard, displaying key insights and metrics at a glance. The page features a row of selected key metrics, a timeline chart, and distribution lists. The page also features a date range filter, and an environment filter.
You can access the Overview page by visiting **Dashboard** > [**Overview**](https://dashboard.fingerprint.com/overview).
The **key metrics** (1) provide you with information and insights about your integration at a glance.
| Metric | Description |
| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **API usage** | Number of identification requests made in the selected period |
| **Number of unique visitors** | Number of distinct visitor IDs seen in the selected period |
| **Events per visitor ratio** | Average number of identification events for every uniquely identified visitor |
| **Number of restricted API calls** | Number of API calls that were restricted due to request filtering rules, exceeding limits on your environments, or exceeding limits on your workspace plan |
| **Number of throttled API calls** | Number of API calls that went over the RPS limit and were throttled |
| **Average requests per second (RPS)** | Rolling average number of requests per second in the selected period |
| **Peak requests per second (RPS)** | Peak number of requests per second in 5-minute window |
The **timeline chart** (2) provides a view into key metrics over time (hourly, daily, monthly), and shows comparison to the previous period.
| Timeline view | Description |
| ----------------------------- | -------------------------------------------------------------------------------------------------- |
| **API usage** | Displays a timeline chart of API usage, comparing currently selected period to the previous period |
| **Events per visitor** | Shows the timeline chart of the ratio of identification requests per unique visitor ID |
| **Failed API calls** | Shows the number of throttled and restricted API calls for the selected period |
| **Requests per second (RPS)** | Shows API usage bandwidth as average and peak requests per second, for the selected period |
The **distribution lists** (3) display top lists of observed entities sorted by number of events associated with each entity.
| Distribution list | Description |
| ------------------------- | ------------------------------------------------ |
| **Top browsers** | List of web browsers |
| **Top client types** | List of client SDK platforms |
| **Top client versions** | List of specific client SDK versions |
| **Top countries** | List of originating countries |
| **Top operating systems** | List of originating operating systems |
| **Top origins** | List of origin domains or mobile app identifiers |
| **Top URLs** | List of specific URLs where events were recorded |
You can view statistics for up to the past 12 months.
## Identification Events
The Identification page lists all recent identification events, allows you to find specific events, and dive deep into their details. The page also features a date range filter, and a wide array of other filtering options.
You can access the Identification page by visiting **Dashboard** > [**Identification**](https://dashboard.fingerprint.com/events).
The page can be filtered by a very large number of **available filters**.
| Filter | Description |
| --------------- | ---------------------------------------------------------------------------------------- |
| **Event ID** | Navigate to an event with specific ID |
| **Visitor ID** | Find events reported with a specific visitor ID |
| **Linked ID** | Find events reported with a specific linked ID |
| **Environment** | Find events associated with a specific environment |
| **Client SDK** | Find events reported from a specific SDK platform or version |
| **IP address** | Find events coming from a specific IP address, or an IP address matching a CIDR notation |
| **URL** | Find events reported from the specific URL or mobile app package identifier |
| **0rigin** | Find events reported from a specific domain or mobile app package identifier |
| Smart Signal filter | Description |
| ---------------------------- | ------------------------------------------------------------------------------ |
| **Suspect score** | Find events with a suspect score greater than provided value |
| **IP blocklist** | Find events coming from IP addresses that were found in blocklists |
| **Residential proxy** | Find events coming from IP addresses that were detected as residential proxy |
| **Man-in-the-middle attack** | Find events where a request to Fingerprint was intercepted and modified |
| **Bot detection** | Find events that were detected as bots - good bots or bad bots |
| **Incognito** | Find events where incognito (or private mode) was detected |
| **VPN** | Find events that were reported behind a VPN |
| **Browser tampering** | Find events where browser tampering was detected |
| **Virtual machine** | Find events where the browser was detected as running inside a virtual machine |
| **Privacy settings** | Find events where the browser has privacy settings turned on |
| **Developer tools** | Find events where developer tools were open in the browser |
| **Jailbroken device** | Find events coming from a jailbroken iOS device |
| **Emulator** | Find events coming from an Android emulator |
| **Rooted device** | Find events coming from a rooted Android device |
| **Cloned app** | Find events coming from a cloned app |
| **Frida** | Find events coming from an app modified by a Frida toolkit |
| **Location spoofing** | Find events coming from a mobile device where location was spoofed |
You can view identification events for up to the past 3 months.
## Event details page
Clicking on an event on the Identification page opens the **Event Details** view, which provides thorough information about that specific request.
On the page you can find:
1. **Device and visitor information**: basic details about the device, client, browser, operating system, and Visitor ID
2. **Location**: includes the IP address and a visual map of the approximate location
3. **Suspect Score**: displays the score and indicates if it is considered low, medium, or high compared to recent traffic
4. **Smart Signal details**: details for all detected Smart Signals, including velocity signals
5. **JSON view**: explore the entire event by looking through the JSON payload
6. **Visitor history**: allows you to see all events detected from that single Visitor ID, which is a convenient way to find recurring patterns or similar events
## Smart Signals Page
The Smart Signals page focuses on providing statistics for [**Smart Signals**](), which are identifiers that give you actionable device intelligence for browsers and devices interacting with your application.
You can access the Smart Signals page by visiting **Dashboard** > [**Smart Signals**](https://dashboard.fingerprint.com/smart-signals).
The page features a **list of Smart Signals** (1) and the number of events where each signal was detected for the selected time period.
On the **timeline chart** (2), you can plot the selected signal over time, allowing you to compare the trend with previous period.
This page also includes a date range filter, and an environment filter (3).
**Bot Detection**
By visiting the Bot Detection tab, you can gather more detailed information about detected bots - see the numbers of detected bad or good bots, their trends on the timeline chart, and inspect event details.
Learn more about [bot detection](/docs/bot-detection/overview).
## CSV export
On the Identification page, you can **export** identification events into a **CSV file** for use in your own analysis, or integration with external fraud prevention systems.
To get CSV export of your data, visit **Dashboard** > [**Identification**](https://dashboard.fingerprint.com/events), and click on **Export** (1).
You can filter up to 3 months of data, and any applied filters will also apply on the CSV file. The data provided in the CSV matches the data from the [Server API payload](/reference/server-api-get-event), and available columns are determined by your billing plan.
* event\_id
* timestamp
* replayed
* identification.visitor\_id
* identification.visitor\_found
* identification.confidence.score
* identification.confidence.version
* identification.first\_seen\_at
* identification.last\_seen\_at
* environment\_id
* sdk.platform
* sdk.version
* linked\_id
* tags
* url
* ip\_address
* user\_agent
* client\_referrer
* browser\_details.browser\_name
* browser\_details.browser\_major\_version
* browser\_details.browser\_full\_version
* browser\_details.os
* browser\_details.os\_version
* browser\_details.device
* bot
* developer\_tools
* ip\_blocklist.email\_spam
* ip\_blocklist.attack\_source
* ip\_blocklist.tor\_node
* ip\_info.address
* ip\_info.geolocation.accuracy\_radius
* ip\_info.geolocation.latitude
* ip\_info.geolocation.longitude
* ip\_info.geolocation.postal\_code
* ip\_info.geolocation.timezone
* ip\_info.geolocation.city\_name
* ip\_info.geolocation.country\_code
* ip\_info.geolocation.country\_name
* ip\_info.geolocation.continent\_code
* ip\_info.geolocation.continent\_name
* ip\_info.geolocation.subdivisions.0.iso\_code
* ip\_info.geolocation.subdivisions.0.name
* ip\_info.asn
* ip\_info.asn\_name
* ip\_info.asn\_network
* ip\_info.asn\_type
* ip\_info.datacenter\_result
* ip\_info.datacenter\_name
* proximity\_id
* proximity\_confidence
* proximity\_precision\_radius
* proxy
* proxy\_confidence
* proxy\_details.proxy\_type
* incognito
* privacy\_settings
* suspect\_score
* tampering
* tampering\_details.anomaly\_score
* tampering\_details.anti\_detect\_browser
* velocity.distinct\_ip.5\_minutes
* velocity.distinct\_ip.1\_hour
* velocity.distinct\_ip.24\_hours
* velocity.distinct\_country.5\_minutes
* velocity.distinct\_country.1\_hour
* velocity.distinct\_country.24\_hours
* velocity.events.5\_minutes
* velocity.events.1\_hour
* velocity.events.24\_hours
* velocity.ip\_events.5\_minutes
* velocity.ip\_events.1\_hour
* velocity.ip\_events.24\_hours
* velocity.distinct\_linked\_id.5\_minutes
* velocity.distinct\_linked\_id.1\_hour
* velocity.distinct\_linked\_id.24\_hours
* velocity.distinct\_ip\_by\_linked\_id.5\_minutes
* velocity.distinct\_ip\_by\_linked\_id.1\_hour
* velocity.distinct\_ip\_by\_linked\_id.24\_hours
* velocity.distinct\_visitor\_id\_by\_linked\_id.5\_minutes
* velocity.distinct\_visitor\_id\_by\_linked\_id.1\_hour
* velocity.distinct\_visitor\_id\_by\_linked\_id.24\_hours
* virtual\_machine
* vpn
* vpn\_confidence
* vpn\_origin\_timezone
* vpn\_origin\_country
* vpn\_methods.timezone\_mismatch
* vpn\_methods.public\_vpn
* vpn\_methods.auxiliary\_mobile
* vpn\_methods.os\_mismatch
* vpn\_methods.relay
* high\_activity\_device
* root\_apps
* emulator
* cloned\_app
* factory\_reset.time
* factory\_reset.timestamp
* jailbroken
* frida
* location\_spoofing
* mitm\_attack
* remote\_control
* simulator
* raw\_device\_attributes
***
[Using the Dashboard](/docs/using-the-dashboard)
# Android API call allowance
Source: https://docs.fingerprint.com/docs/android-api-call-allowance
Prevent fraud in your Android apps for free with Fingerprint device intelligence.
Free and Pro Plus plans receive 500,000 complimentary Android API calls per month. These API calls apply when using our [Android SDK](/docs/android-sdk), [React Native](/docs/fingerprintjs-pro-react-native), or [Flutter](/docs/flutter) SDKs within an Android app.
## How can I get access to the Android API call allowance?
New customers can start with our [unlimited 14-day free trial](https://dashboard.fingerprint.com/signup).\
When the trial ends, your workspace will default to the Free plan unless you upgrade to Pro Plus. Either way, the 500,000 Android allowance is automatically added to the workspace.
Existing customers with a Pro Plus workspace will automatically have the allowance added at the **start of their next billing cycle**. It will automatically be added to the first or oldest Pro Plus workspace on the account.
## What are the limitations?
* The Android API call allowance is limited to 500,000 Android API calls per billing cycle.
* **Pro Plus plan** – continue usage past 500,000 at a rate of \$4 per additional 1,000 API calls.
* **Free plan** – Android calls are restricted after 500,000 until the next billing cycle.
* The allowance is limited to 1 workspace per account.
## Frequently Asked Questions
### Is the Android API call allowance a permanent or limited-time offer?
Accounts created during the offer will retain this free usage cap permanently.
### How will it work if I have API calls on both Android and iOS?
Billing will be separated by platform and billed accordingly, API calls from other platforms will count towards the original plan’s usage.
For more details on how identification API requests are billed, please refer to our [documentation](/docs/billing#identification-api-request-as-a-billing-unit).
### What happens if I exceed 500K Android API calls?
As your workspace nears the 500,000 Android API call limit, you will be alerted and prompted to upgrade to our [Enterprise plan](https://fingerprint.com/pricing/).
Upon reaching the 500,000 limit within the billing cycle, Pro Plus customers can continue usage at a rate of \$4 per additional 1,000 API calls.
For Free subscriptions that reach the limit during the billing cycle, we will restrict Android API calls for the remainder of the billing cycle.
### Is support included with the Android API call allowance?
Accounts receive access to our best-in-class developer documentation anytime, along with dedicated support available 8 hours a day, 5 days a week.
### Is 500K free Android API calls a limit per app, email, domain, or company?
The API call limit applies per company. Only one workspace from each account is eligible for the offer.
### Where can I check the free API usage?
API call usage will be available on the [Plan](https://dashboard.fingerprint.com/plan/) page, with a breakdown by platform.
### I’m on the legacy Pro plan. Do I get the 500K Android API calls?
Yes. Legacy Pro plan customers will also receive 500,000 complimentary Android API calls per month. This allowance will be automatically added at the start of your next billing cycle and applies to one workspace per account. After the 500,000 API call limit is exceeded within a billing cycle, additional Android usage will be billed at a rate of \$2 per additional 1,000 API calls.
# Android Quickstart
Source: https://docs.fingerprint.com/docs/android-quickstart
Get started using the Android SDK
## Overview
In this quickstart, you'll add Fingerprint to a new Android Studio project and identify the user's device.
The example use case in this quickstart is stopping new account fraud, where attackers create multiple fake accounts to abuse promotions, exploit systems, or evade bans. However, the steps you'll follow apply to most use cases. By identifying the device behind each sign-up attempt, login, or transaction, you can flag and block suspicious users early.
This guide focuses on the frontend mobile integration. You'll install the [Fingerprint Android SDK](/docs/android-sdk) and initialize the client agent to generate a request ID to send to your backend for analysis. [**To see how to implement fraud prevention with this ID, continue to one of the backend quickstarts after completing this quickstart.**](/docs/server-quickstarts-overview)
> Estimated time: \< 10 minutes
## Prerequisites
Before you begin, make sure you have the following:
* [Android Studio](https://developer.android.com/studio) installed (latest stable version recommended)
* Basic knowledge of [Kotlin](https://kotlinlang.org/docs/kotlin-tour-welcome.html) and [Android development](https://developer.android.com/courses/android-basics-compose/course)
This quickstart only covers the **frontend setup**. You'll need a [backend
server](/reference/server-sdks) to receive and process the device identification event to enable
fraud detection. Check out one of the [backend quickstarts](/docs/server-quickstarts-overview)
after completing this quickstart.
## 1. Create a Fingerprint account and get your API key
1. [Sign up](https://dashboard.fingerprint.com/signup) for a free Fingerprint trial if you don't already have an account.
2. After signing in, go to the [**API keys**](https://dashboard.fingerprint.com/api-keys) page in the dashboard.
3. Copy your **public API key**; you'll need it to initialize the client agent.
## 2. Set up your project
To get started, create a new Android Studio project. If you already have a project you want to use, you can skip to the next section.
1. Open **Android Studio** and choose **New Project**.
2. Select **Empty Activity**, then click **Next**.
3. Name your project (for example, "Fingerprint Quickstart").
4. Keep the default settings (as of this writing, that's **API 24: Android 7.0 (Nougat)** as the minimum SDK and **Kotlin** as the language).
5. Click **Finish**.
Android Studio will generate a basic project for you. Once the project is ready, you'll add the client agent.
## 3. Install the client agent
To integrate Fingerprint into your Android app, first add the Fingerprint Android SDK to your project.
### Add the Fingerprint Maven repository
1. In Android Studio, make sure you're in **Android view** (top-left dropdown in the Project pane).
2. Expand the **Gradle Scripts** section and open `settings.gradle.kts`.
3. Find the `dependencyResolutionManagement` block and update the repositories section to include the Fingerprint Maven repository:
```kotlin settings.gradle.kts theme={"theme":"github-dark-dimmed"}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://maven.fpregistry.io/releases") } // [!code ++]
}
}
```
### Add the SDK dependency
1. In the same **Gradle Scripts** section, open `build.gradle.kts (Module: app)`.
2. Inside the `dependencies` block, add:
```kotlin build.gradle.kts theme={"theme":"github-dark-dimmed"}
implementation("com.fingerprint.android:pro:2.17.0")
```
### Sync your project
After editing the files, sync your project by either:
* Clicking **Sync Now** in the banner that appears.
* Or going to **File > Sync Project with Gradle Files**.
Once syncing is complete, you're ready to initialize the SDK.
## 4. Initialize the SDK
Now that the SDK is installed, you can import and initialize it in your app.
1. In **Android** view, open `MainActivity.kt` (in app > kotlin+java > \[your.package.name]) and add the following imports:
```kotlin MainActivity.kt theme={"theme":"github-dark-dimmed"}
import com.fingerprintjs.android.fpjs_pro.Configuration
import com.fingerprintjs.android.fpjs_pro.FingerprintException
import com.fingerprintjs.android.fpjs_pro.FingerprintJSFactory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
```
2. Additionally, add these imports you'll need for some basic UI:
```kotlin MainActivity.kt theme={"theme":"github-dark-dimmed"}
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.compose.ui.graphics.Color
import android.util.Log
```
3. Next, initialize the client agent before `setContent` in your `onCreate` function:
```kotlin MainActivity.kt theme={"theme":"github-dark-dimmed"}
// ...
enableEdgeToEdge()
val configuration = Configuration( // [!code ++:7]
apiKey = "PUBLIC_API_KEY",
region = Configuration.Region.US // Ensure this matches your workspace region
// For more information, see /docs/regions
)
val fpjsClient = FingerprintJSFactory(applicationContext).createInstance(configuration)
setContent {...}
// ...
```
4. Replace `PUBLIC_API_KEY` with your actual public API key from the Fingerprint [dashboard](https://dashboard.fingerprint.com/api-keys).
## 5. Trigger visitor identification
Now that the client agent is initialized, you can identify the visitor only when needed. In this case, that's when the user taps a **Create Account** button.
When making the visitor identification request, you will receive the `visitorId` as well as a `requestId`. Instead of using the `visitorId` returned directly on the frontend (which could be tampered with), you’ll send the `requestId` to your backend. This ID is unique to each identification event. Your server can then use the [Fingerprint Events API](/reference/server-api-get-event) and retrieve the complete identification data, including the trusted visitor ID and other actionable insights like whether they are using a VPN or are a bot.
### Add a basic sign-up UI with Compose
1. Create a new composable function for the account creation screen. It will display a simple form with username and password fields, along with a **Create Account** button:
```kotlin MainActivity.kt theme={"theme":"github-dark-dimmed"}
@Composable
fun CreateAccountScreen(
onSubmit: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
val accentColor = Color(0xFFF35B22)
Text(
text = "Create account",
style = MaterialTheme.typography.headlineSmall,
color = accentColor
)
Spacer(modifier = Modifier.height(24.dp))
var username by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
OutlinedTextField(
value = username,
onValueChange = { username = it },
label = { Text("Username") },
modifier = Modifier.fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = accentColor,
unfocusedBorderColor = accentColor,
cursorColor = accentColor
)
)
Spacer(modifier = Modifier.height(12.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = accentColor,
unfocusedBorderColor = accentColor,
cursorColor = accentColor
)
)
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = onSubmit,
colors = ButtonDefaults.buttonColors(containerColor = accentColor)
) {
Text("Create Account")
}
}
}
```
2. Replace the existing call to `Greeting` in your `onCreate` function with the `CreateAccountScreen` composable and pass in a placeholder for the submit function. Be sure to apply the provided `innerPadding` so the content respects system UI insets:
```kotlin MainActivity.kt theme={"theme":"github-dark-dimmed"}
setContent {
FingerprintQuickstartTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
CreateAccountScreen( // [!code ++:4]
onSubmit = { /* TODO: Handle account creation */ },
modifier = Modifier.padding(innerPadding)
)
}
}
}
```
3. Now replace the placeholder `onSubmit` function with a call to the client agent's `getVisitorId` method. This will capture the device's details and return a `requestId`, which you can then send to your backend for your fraud detection logic:
```kotlin MainActivity.kt theme={"theme":"github-dark-dimmed"}
setContent {
val scope = rememberCoroutineScope() // [!code ++]
FingerprintQuickstartTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
CreateAccountScreen(
onSubmit = { // [!code ++:10]
scope.launch(Dispatchers.IO) {
try {
val response = fpjsClient.getVisitorId()
Log.d("Fingerprint", "Request ID: ${response.requestId}")
// Send this request ID to your backend
} catch (e: FingerprintException) {
Log.e("Fingerprint", "Error: ${e.error.description}")
}
}
},
modifier = Modifier.padding(innerPadding)
)
}
}
}
```
This setup ensures you're triggering device identification only when needed.
## 6. Run the app
1. Run the app on an emulator or physical device.
> To learn how to set up Android Studio to [run an emulator](https://developer.android.com/studio/run/emulator) or [connect a physical Android device](https://developer.android.com/studio/run/device#connect), see the official [Android Studio setup guide](https://developer.android.com/studio/run).
2. Type in a username and password and click the **Create Account** button.
3. Open **Logcat** (View > Tool Windows > Logcat) and filter by the Fingerprint tag (`tag:Fingerprint`) to find your log message:
```text Output theme={"theme":"github-dark-dimmed"}
Request ID: 1234566477745.abc1GS
```
You're now successfully identifying the device and capturing the request ID and are ready to send it to your backend for further fraud prevention logic!
## Next steps
To use the identification data for fraud detection (like blocking repeat fake account creation attempts), you'll need to send the `requestId` to your [backend](/docs/server-quickstarts-overview). From there, your server can call the [Fingerprint Events API](/reference/server-api-get-event) to retrieve the full visitor information and make decisions.
***
Check out these related resources:
* [Android SDK reference](/docs/android-sdk)
* [Node.js backend quickstart](/docs/node-server-quickstart)
* [API reference for the Events endpoint](/reference/server-api-get-event)
* [Use case tutorial: Detecting new account fraud](/docs/new-account-fraud-use-case-tutorial)
* [Protecting from client-side tampering and replay attacks](/docs/protecting-from-client-side-tampering)
# Android SDK
Source: https://docs.fingerprint.com/docs/android-sdk
A simple guide to integrate Fingerprint's device intelligence platform in your native Android apps.
In this guide, you will learn to
* Include Android SDK in your mobile apps.
* Get a `visitorId`.
* Read the SDK response.
* Enable location data collection (optional; to start getting additional location-based proximity data).
* Configure the SDK as per your needs.
* Specify additional metadata in your identification request.
* Handle errors.
For a complete example of how to use this SDK in your app, please visit the [GitHub repo of our demo app](https://github.com/fingerprintjs/fingerprint-device-intelligence-android-demo). View our [Android quickstart](/docs/android-quickstart) for a step-by-step guide to get started.
## Prerequisites
[Sign up](https://dashboard.fingerprintjs.com/signup) for an account with Fingerprint to get your API key.
## Including the SDK in your app
1. Add the repositories to your **Project Settings** file. Depending on your build configuration language, this file can either be `settings.gradle.kts` or `settings.gradle`.
```kotlin settings.gradle.kts (Kotlin DSL) theme={"theme":"github-dark-dimmed"}
...
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://maven.fpregistry.io/releases") }
}
}
```
```groovy settings.gradle (Groovy DSL) theme={"theme":"github-dark-dimmed"}
...
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url 'https://maven.fpregistry.io/releases' }
}
}
```
Alternatively, if your project is configured to [ignore the repositories declared in the Project Settings](https://docs.gradle.org/current/javadoc/org/gradle/api/initialization/resolve/RepositoriesMode.html) file, you can add the repositories to the **project-level** build file. Depending on your build configuration language, this file can either be `build.gradle.kts` or `build.gradle`.
```kotlin build.gradle.kts (Kotlin DSL) theme={"theme":"github-dark-dimmed"}
allprojects {
repositories {
google()
mavenCentral()
maven { url = uri("https://maven.fpregistry.io/releases") }
}
}
```
```groovy build.gradle (Groovy DSL) theme={"theme":"github-dark-dimmed"}
...
allprojects {
repositories {
google()
mavenCentral()
maven { url 'https://maven.fpregistry.io/releases' }
}
}
```
2. Add the dependencies to your **module-level** build file. Depending on your build configuration language, this file can either be `build.gradle.kts` or `build.gradle`.
```kotlin build.gradle.kts (Kotlin DSL) theme={"theme":"github-dark-dimmed"}
dependencies {
implementation("com.fingerprint.android:pro:2.17.0")
}
```
```groovy build.gradle (Groovy DSL) theme={"theme":"github-dark-dimmed"}
dependencies {
implementation "com.fingerprint.android:pro:2.17.0"
}
```
3. Perform a Gradle sync to update all the dependencies.
## Getting a `visitorId`
To get a `visitorId`, you need the public API key that you obtained when signing up for an account with Fingerprint. You can find this API key in the **Dashboard** > [**API Keys**](https://dashboard.fingerprint.com/api-keys).
Here is an example that shows you how to get a `visitorId`:
```kotlin Kotlin theme={"theme":"github-dark-dimmed"}
val factory = FingerprintJSFactory(applicationContext)
val configuration = Configuration(apiKey = "PUBLIC_API_KEY")
val fpjsClient = factory.createInstance(configuration)
scope.launch(Dispatchers.IO) {
try {
val response = fpjsClient.getVisitorId()
val visitorId = response.visitorId
} catch (e: FingerprintException) {
// Handle error
}
}
```
```java Java theme={"theme":"github-dark-dimmed"}
FingerprintJSFactory factory = new FingerprintJSFactory(getApplicationContext());
Configuration configuration = new Configuration("PUBLIC_API_KEY");
FingerprintJS fpjsClient = factory.createInstance(configuration);
fpjsClient.getVisitorId(
response -> {
String visitorId = response.getVisitorId();
return null;
},
error -> {
// Handle error
return null;
}
);
```
The `getVisitorId()` suspend functions perform blocking I/O and should be called from a background
dispatcher such as `Dispatchers.IO`. Cancelling the coroutine does not cancel the underlying
signal collection or network request. The coroutine API is Kotlin-only; Java users should use the
callback-based API.
### Specifying a custom timeout
**Default timeout value:** `null`
The identification requests made from an Android SDK do not have a default timeout.
You can use `timeoutMillis` to provide a custom timeout value of your choice. If the `getVisitorId()` call does not complete within the specified timeout, you will receive a `ClientTimeout` error.
Here is an example that shows you how to specify a custom timeout:
```kotlin Kotlin theme={"theme":"github-dark-dimmed"}
scope.launch(Dispatchers.IO) {
try {
// Get a visitor ID, will timeout after 10s
val response = fpjsClient.getVisitorId(
timeoutMillis = 10_000
)
val visitorId = response.visitorId
} catch (e: FingerprintException) {
if (e.error is ClientTimeout) {
// Handle timeout
}
}
}
```
```java Java theme={"theme":"github-dark-dimmed"}
fpjsClient.getVisitorId(
// Pass a timeout value of 10 seconds
10000,
visitorIdResponse -> {
// Get a 'visitorId', will timeout after 10s
visitorId = visitorIdResponse.getVisitorId();
return null;
}
);
```
## Using Location Data for Proximity Detection
This is optional. Enable this only if you want to include the additional location-based [proximity detection](/docs/smart-signals-reference#proximity-detection) signal in your response.
Starting with [Android SDK 2.10.0](/docs/changelog-android-sdk#v2-10-0), you can receive additional location-based signals (proximity ID, confidence, precision radius) by enabling [`allowUseOfLocationData`](#allowuseoflocationdata) and declaring the corresponding [location permissions](/docs/android-sdk#location-permissions).
### Location permissions
#### Declaring permissions
To calculate Proximity ID and related data (confidence score, precision radius), Fingerprint needs the following location permissions in the manifest:
```xml XML theme={"theme":"github-dark-dimmed"}
```
At least coarse location permission is required. Please note that accuracy will be lower without fine location permission.
#### Asking for permissions
Your app is responsible for asking for permissions. Refer to [Android documentation](https://developer.android.com/training/permissions/requesting) or refer to the [Fingerprint demo app](https://github.com/fingerprintjs/fingerprint-device-intelligence-android-demo) if you need to implement this flow. If you already have this process set up, skip to the next section.
### Enabling location collection
Fingerprint can only collect location data if [`allowUseOfLocationData`](#allowuseoflocationdata) is set to `true`. Optionally, you can control the timeout by setting [`locationTimeoutMillis`](#locationtimeoutmillis) to the desired value. Note that the value is 5 seconds by default.
**Timeout Recommendation:**
Because location accuracy and performance can vary based on user settings, device models, and use cases, **we do not enforce a fixed default timeout.** We recommend that you explicitly configure a timeout value that aligns with the app's user experience and performance expectations.
## Reading the response
The function `FingerprintJS.getVisitorId()` returns the response in a `FingerprintJSProResponse` object. This object has the following fields, some of which can be empty/invalid when [Sealed Client Results](/docs/sealed-client-results) is enabled for your account.
| Field | Sealed Client Results is disabled | Sealed Client Results is enabled |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **`requestId`** | String. An identifier that uniquely identifies this particular request to `FingerprintJS.getVisitorID()`. | String. An identifier that uniquely identifies this particular request to `FingerprintJS.getVisitorID()`. |
| **`visitorId`** | String. An identifier that uniquely identifies the device. | N/A |
| **`confidenceScore`** | [ConfidenceScore](/docs/android-sdk#confidencescore). A score that indicates the probability of this device being accurately identified. The value ranges from 0 to 1. | N/A |
| **`sealedResult`** | `null` | String?. An encrypted, binary, Base64-encoded String that contains the same response as you would receive with an [/events API](/reference/server-api-get-event) request |
## Configuring the SDK
Using the [Configuration](/docs/android-sdk#configuration) object, it is possible to configure the SDK as per your requirements. As of now, the following options are supported:
### `region`
**Type:** [Region](/docs/android-sdk#region).
**Default value:** `Region.US`
This option allows you to specify a region where you want your data to be stored and processed. This region must be the same as the one you specified when registering your app with Fingerprint. See [region](/reference/js-agent-start-function#region) for more information.
### `endpointUrl`
**Type:** String
**Default value:** `Region.US.getEndpointUrl()`
This option allows you to specify a custom endpoint, particularly when you have set up either a custom sub-domain or a proxy integration.
### `fallbackEndpointUrls`
**Type:** List\
**Default value:** emptyList()
This option allows you to specify alternate, fallback endpoints to redirect failed requests.
### `extendedResponseFormat`
**Type:** Boolean
**Default value:** `false`
When set to true, in addition to those [fields returned by default](/docs/android-sdk#reading-the-response), the `FingerprintJSProResponse` object will also contain the following fields:
* **`visitorFound`** - Boolean. Indicates if this device has already been encountered by your app.
* **`ipAddress`** - String. The IPv4 address of the device.
* **`ipLocation`** - [IPLocation](/docs/android-sdk#iplocation). \[This field is **deprecated** and will not return a result for **applications created after January 23rd, 2024**. See [IP Geolocation](/docs/smart-signals-reference#ip-geolocation) for a replacement available in our Smart Signals product.] Indicates the location as deduced from the IP address.
* **`osName`** - String. Indicates the name of the underlying operating system.
* **`osVersion`** - String. Indicates the version of the underlying operating system of the device.
* **`firstSeenAt`**, **`lastSeenAt`** - [Timestamp](/docs/android-sdk#timestamp). See [Visitor Footprint Timestamps](/docs/useful-timestamps).
When Sealed Client Results is enabled for your account, several of these fields may not have valid values.
### `allowUseOfLocationData`
**Type:** Boolean
**Default value:** `false`
This option allows you to enable the location data collection needed to calculate the [location-based proximity detection signal](/docs/smart-signals-reference#proximity-detection).
### `locationTimeoutMillis`
**Type:** Long
**Default value:** `5_000L` (5 seconds)
This option allows you to configure the maximum timeout for location collection (controlled by [`allowUseOfLocationData`](#allowuseoflocationdata)).
The SDK will delay identification up to the specified timeout to collect the device location. If it cannot collect the location information within the specified time, identification continues without location information.
## Specifying `linkedID` and `tag`
Like the [JavaScript agent](/reference/js-agent-get-function#linkedid), the Android SDK also supports providing custom metadata with your identification request. To learn more about this capability, please visit [Linking and tagging information](/docs/tagging-information).
Here is an example that shows you how to associate your identification request with an account ID and additional metadata:
```kotlin Kotlin theme={"theme":"github-dark-dimmed"}
scope.launch(Dispatchers.IO) {
try {
val response = fpjsClient.getVisitorId(
// Associate additional metadata to this request
tags = mapOf("orderID" to orderId, "zip" to zipCode),
// Associate an account number to this request
linkedId = accountID
)
val visitorId = response.visitorId
} catch (e: FingerprintException) {
// Handle error
}
}
```
```java Java theme={"theme":"github-dark-dimmed"}
Map tags = new HashMap<>();
tags.put("orderID", orderId);
tags.put("zip", zipCode);
fpjsClient.getVisitorId(
// Associate additional metadata to this request
tags,
// Associate an account number to this request
accountId,
// Get a 'visitorId'
visitorIdResponse -> {
// handle visitorId and other data
},
);
```
The metadata you want to include may not be available when making the identification request. For such scenarios, you can [update that event](/reference/server-api-update-event/) later when the required metadata becomes available. A typical workflow is explained in [`Update linked_id and tags on the server`](/docs/tagging-information#updating-linked_id-and-tags-on-the-server).
## Handling errors
The SDK provides an [Error](/docs/android-sdk#error) class that helps you identify the reasons behind an unsuccessful identification request. Here is an example that shows you how to handle errors in your app:
```kotlin Kotlin theme={"theme":"github-dark-dimmed"}
scope.launch(Dispatchers.IO) {
try {
val response = fpjsClient.getVisitorId()
val visitorId = response.visitorId
} catch (e: FingerprintException) {
when (e.error) {
is ApiKeyRequired -> {
val requestId = e.requestId
// Handle error
}
is NetworkError -> {
// Handle network error
}
is ClientTimeout -> {
// Handle timeout
}
else -> {
Log.e("Fingerprint", "${e.requestId}: ${e.error.description}")
}
}
}
}
```
```java Java theme={"theme":"github-dark-dimmed"}
Map tags = new HashMap<>();
tags.put("orderID", orderId);
tags.put("zip", zipCode);
fpjsClient.getVisitorId(
// Associate additional metadata to this request
tags,
// Associate an account number to this request
accountID,
// Get a 'visitorId'
visitorIdResponse -> {
// Handle visitorId and other data
String visitorId = visitorIdResponse.getVisitorId();
return null;
},
// Handle errors
error -> {
// Handle errors as per their type
if (error.getClass() == ApiKeyRequired.class) {
Log.e(TAG, error.getRequestId() + error.getDescription());
}
return null;
}
);
```
## Data Classes
### ConfidenceScore
```kotlin Kotlin theme={"theme":"github-dark-dimmed"}
data class ConfidenceScore(
val score: Double
)
```
### Configuration
```kotlin Kotlin theme={"theme":"github-dark-dimmed"}
public class Configuration @JvmOverloads constructor(
public val apiKey: String,
public val region: Region = Region.US,
public val endpointUrl: String = region.endpointUrl,
public val extendedResponseFormat: Boolean = false,
public val fallbackEndpointUrls: List = emptyList(),
public val integrationInfo: List> = emptyList(),
public val allowUseOfLocationData: Boolean = false,
public val locationTimeoutMillis: Long = DEFAULT_LOCATION_TIMEOUT_MILLIS,
)
```
### Error
It's a sealed class, which can be one of:
* ApiKeyRequired
* ApiKeyNotFound
* ApiKeyExpired
* RequestCannotBeParsed
* Failed
* RequestTimeout
* TooManyRequest
* OriginNotAvailable
* PackageNotAuthorized
* HeaderRestricted
* NotAvailableForCrawlBots
* NotAvailableWithoutUA
* WrongRegion
* SubscriptionNotActive
* UnsupportedVersion
* InstallationMethodRestricted
* ResponseCannotBeParsed
* InvalidProxyIntegrationHeaders
* InvalidProxyIntegrationSecret
* NetworkError
* UnknownError
* ClientTimeout
* ProxyIntegrationSecretEnvironmentMismatch
```kotlin Kotlin theme={"theme":"github-dark-dimmed"}
sealed class Error(
// The request ID of the identification request
val requestId: String = UNKNOWN,
// A self-explanatory description of the error
val description: String? = UNKNOWN
)
```
### FingerprintException
Used with the coroutine-based `getVisitorId()` suspend functions. Wraps the [`Error`](/docs/android-sdk#error) sealed class and is thrown as an exception for use with `try/catch`.
```kotlin Kotlin theme={"theme":"github-dark-dimmed"}
class FingerprintException(
// The error that caused the exception
val error: Error
) : Exception(error.description) {
// The request ID associated with this error
val requestId: String get() = error.requestId
}
```
### IPLocation
```kotlin Kotlin theme={"theme":"github-dark-dimmed"}
data class IpLocation(
val accuracyRadius: Int,
val latitude: Double,
val longitude: Double,
val postalCode: String,
val timezone: String,
val city: City,
val country: Country,
val continent: Continent,
val subdivisions: List
) {
data class City(
val name: String
)
data class Country(
val code: String,
val name: String
)
data class Continent(
val code: String,
val name: String
)
data class Subdivisions(
val isoCode: String,
val name: String
)
}
```
### Region
```kotlin Kotlin theme={"theme":"github-dark-dimmed"}
public enum class Region(public val endpointUrl: String) {
US("https://api.fpjs.io"),
EU("https://eu.api.fpjs.io"),
AP("https://ap.api.fpjs.io")
}
```
### Timestamp
```kotlin Kotlin theme={"theme":"github-dark-dimmed"}
data class Timestamp(
val global: String,
val subscription: String
}
```
***
[iOS SDK](/docs/ios-sdk)
# Angular SDK
Source: https://docs.fingerprint.com/docs/angular
The [Fingerprint Angular SDK](https://github.com/fingerprintjs/angular) integrates Fingerprint into Angular applications. It supports all capabilities of the JavaScript agent including a built-in [caching mechanism](/reference/js-agent-start-function#cache). See the [Angular quickstart](/docs/angular-quickstart) for a step-by-step guide to get started.
### How to install
Add `@fingerprint/angular` as a dependency to your application via npm, yarn or pnpm.
```shell NPM theme={"theme":"github-dark-dimmed"}
npm install @fingerprint/angular
```
```shell Yarn theme={"theme":"github-dark-dimmed"}
yarn add @fingerprint/angular
```
```shell PNPM theme={"theme":"github-dark-dimmed"}
pnpm add @fingerprint/angular
```
Add `provideFingerprint` to your application's providers. You need to specify your public API key and other configuration options based on your chosen region and active integration.
```typescript TypeScript theme={"theme":"github-dark-dimmed"}
// src/app/app.config.ts
// ...other imports
import { provideFingerprint } from '@fingerprint/angular'
export const appConfig: ApplicationConfig = {
providers: [
// ...other config options
provideFingerprint({
startOptions: {
apiKey: 'PUBLIC_API_KEY',
endpoints: 'https://metrics.yourwebsite.com',
// region: "eu"
},
}),
],
}
```
Inject the `FingerprintService` in your component's constructor. Now you can identify visitors using the `getVisitorData()` method from the service.
```typescript TypeScript theme={"theme":"github-dark-dimmed"}
// src/app/home/home.component.ts
import { Component } from '@angular/core'
import { FingerprintService, Fingerprint } from '@fingerprint/angular'
@Component({
selector: 'app-home',
template: `
VisitorId: {{ visitorId }}
`,
})
export class HomeComponent {
constructor(
private fingerprintService: FingerprintService
) {}
visitorId?: string = 'Press "Identify" button to get visitorId'
result: null | Fingerprint.GetResult = null
async onIdentifyButtonClick(): Promise {
const data = await this.fingerprintService.getVisitorData()
this.visitorId = data.visitor_id
this.result = data
}
}
```
### Documentation
You can find the full documentation in the official [GitHub repository](https://github.com/fingerprintjs/angular). The repository also contains [an example app](https://github.com/fingerprintjs/angular/tree/main/src) demonstrating the usage of the library.
### Migration guide for Angular SDK v3.0.0
Version 3.0.0 of the Angular SDK switches from JavaScript agent v3 to [JavaScript agent v4](/reference/migrating-from-v3-to-v4).
1. Install a new version of the package:
```bash NPM theme={"theme":"github-dark-dimmed"}
npm install @fingerprint/angular
```
```bash Yarn theme={"theme":"github-dark-dimmed"}
yarn add @fingerprint/angular
```
```bash PNPM theme={"theme":"github-dark-dimmed"}
pnpm add @fingerprint/angular
```
2. Update your module import and configuration:
The default caching strategy has changed from `sessionStorage` caching to **no caching by default**, aligned with the underlying [JavaScript agent v4 default](/reference/js-agent-start-function#cache). To preserve the previous behavior, explicitly configure caching in the module options (see example below).
```typescript Change module imports and configuration theme={"theme":"github-dark-dimmed"}
// [!code --:4]
import {
FingerprintjsProAngularModule,
FingerprintJSPro
} from '@fingerprintjs/fingerprintjs-pro-angular'
import { provideFingerprint } from '@fingerprint/angular' // [!code ++]
// [!code --:13]
FingerprintjsProAngularModule.forRoot({
loadOptions: {
apiKey: 'PUBLIC_API_KEY',
endpoint: [
'https://metrics.yourwebsite.com',
FingerprintJSPro.defaultEndpoint
],
scriptUrlPattern: [
'https://metrics.yourwebsite.com/web/v//loader_v.js',
FingerprintJSPro.defaultScriptUrlPattern
],
},
})
// [!code ++:10]
provideFingerprint({
startOptions: {
apiKey: 'PUBLIC_API_KEY',
endpoints: 'https://metrics.yourwebsite.com',
cache: {
storage: 'sessionStorage',
duration: 3600,
},
},
})
```
3. Update service usage and result field names:
```typescript Update service usage theme={"theme":"github-dark-dimmed"}
// [!code --:5]
import {
FingerprintjsProAngularService,
ExtendedGetResult,
GetResult,
} from '@fingerprintjs/fingerprintjs-pro-angular'
import { FingerprintService } from '@fingerprint/angular' // [!code ++]
// Update constructor injection
constructor(private fingerprintService: FingerprintjsProAngularService) {} // [!code --]
constructor(private fingerprintService: FingerprintService) {} // [!code ++]
// [!code --:2]
const { visitorId, requestId } = await this.fingerprintService.getVisitorData()
console.log(visitorId, requestId)
// [!code ++:2]
const { visitor_id, event_id } = await this.fingerprintService.getVisitorData()
console.log(visitor_id, event_id)
```
The v4 agent uses snake\_case field names. `requestId` is now `event_id`, and `visitorId` is now `visitor_id`. The `clearCache()` method has been removed from the service - if you need to clear the cache, interact directly with the storage you configured.
# Angular Quickstart
Source: https://docs.fingerprint.com/docs/angular-quickstart
Get started using the Angular SDK
## Overview
In this quickstart, you'll add Fingerprint to a new [Angular](https://angular.dev/) v21 project and identify the user's device.
The example use case in this quickstart is stopping new account fraud, where attackers create multiple fake accounts to abuse promotions, exploit systems, or evade bans. However, the steps you'll follow apply to most use cases. By identifying the device behind each sign-up attempt, login, or transaction, you can flag and block suspicious users early.
This guide focuses on the frontend integration. You'll install the [Fingerprint Angular SDK](/docs/angular) and initialize the JavaScript agent to generate an event ID to send to your backend for analysis. **To see how to implement fraud prevention with this ID, continue to one of the [backend quickstarts](/docs/server-quickstarts-overview) after completing this quickstart.**
> Estimated time: \< 10 minutes
## Prerequisites
Before you begin, make sure you have the following:
* [**Node.js**](https://nodejs.org/) (v20 or later) and npm installed
* Your favorite code editor
* Basic knowledge of [**Angular**](https://angular.dev/tutorials/learn-angular) and JavaScript/TypeScript
This quickstart only covers the **frontend setup**. You'll need a [backend
server](/reference/server-sdks) to receive and process the device identification event to enable
fraud detection. Check out one of the [backend quickstarts](/docs/server-quickstarts-overview)
after completing this quickstart.
## 1. Create a Fingerprint account and get your API key
1. [Sign up](https://dashboard.fingerprint.com/signup) for a free Fingerprint trial if you don't already have an account.
2. After signing in, go to the [**API keys**](https://dashboard.fingerprint.com/api-keys) page in the dashboard.
3. Copy your **public API key**; you'll need it to initialize the JavaScript agent.
## 2. Set up your project
To get started, create a new Angular project. If you already have a project you want to use, you can skip to the next section.
1. Install the Angular CLI:
```bash Terminal theme={"theme":"github-dark-dimmed"}
npm install -g @angular/cli
```
2. Create a new project:
```bash Terminal theme={"theme":"github-dark-dimmed"}
ng new fingerprint-angular-quickstart
```
You will be presented with some configuration options for your project. Use the arrow and enter keys to navigate and select which options you desire. If you don't have any preferences, just hit the enter key to take the default options and continue with the setup.
After you select the configuration options and the CLI runs through the setup, you should see the following message:
```bash Terminal theme={"theme":"github-dark-dimmed"}
✔ Packages installed successfully.
Successfully initialized git.
```
3. Change into the project folder:
```bash Terminal theme={"theme":"github-dark-dimmed"}
cd fingerprint-angular-quickstart
```
4. Open the `fingerprint-angular-quickstart` folder in your code editor and you're ready to go! To run your project, run:
```bash Terminal theme={"theme":"github-dark-dimmed"}
npm start
```
5. In your browser, go to [http://localhost:4200](http://localhost:4200), and you should see the Angular welcome page. The application will automatically reload whenever you modify any of the source files.
## 3. Set up your account creation form
1. Before hooking up Fingerprint, create a new component at `src/app/create-account-form/create-account-form.component.ts` with the following:
```angular-ts src/app/create-account-form/create-account-form.component.ts theme={"theme":"github-dark-dimmed"}
import { Component } from "@angular/core";
import { FormsModule } from "@angular/forms";
@Component({
selector: "app-create-account-form",
imports: [FormsModule],
template: `
Create an account
`,
styles: `
.wrapper {
display: flex;
align-items: center;
flex-direction: column;
margin-top: 200px;
}
.input-group {
margin-bottom: 1.5rem;
display: flex;
flex-direction: column;
}
label {
font-weight: 600;
margin-bottom: 0.5rem;
color: #333;
}
input[type="text"],
input[type="password"] {
padding: 0.75rem 1rem;
border: 1px solid #ccc;
border-radius: 6px;
font-size: 1rem;
transition: border-color 0.2s ease;
}
input:focus {
border-color: #007bff;
outline: none;
}
button[type="submit"] {
width: 200px;
padding: 0.75rem;
font-size: 1rem;
font-weight: 600;
background-color: #e36132;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s ease;
}
button[type="submit"]:hover:not(:disabled) {
background-color: #e3531f;
}
button[type="submit"]:disabled {
opacity: 0.4;
cursor: not-allowed;
}
`,
})
export class CreateAccountFormComponent {
isLoading = signal(false);
username = "";
password = "";
// We will populate this in a later step.
async handleSubmit() {}
}
```
2. Import and add the component to your `App` in `src/app/app.ts`:
```angular-ts src/app/app.ts theme={"theme":"github-dark-dimmed"}
// ... other imports here
import { CreateAccountFormComponent } from "./create-account-form/create-account-form.component";
@Component({
selector: "app-root",
imports: [CreateAccountFormComponent],
template: ``,
})
export class App {}
```
## 4. Install and initialize the JavaScript agent
1. To integrate Fingerprint into your Angular app, first add the Fingerprint Angular SDK via npm:
```bash Terminal theme={"theme":"github-dark-dimmed"}
npm install @fingerprint/angular
```
2. Open `src/app/app.config.ts` and add `provideFingerprint` to the `providers` array:
```angular-ts src/app/app.config.ts theme={"theme":"github-dark-dimmed"}
// ... other imports here
import { provideFingerprint } from "@fingerprint/angular";
export const appConfig: ApplicationConfig = {
providers: [
// ...other config options
provideFingerprint({
startOptions: {
apiKey: "PUBLIC_API_KEY",
region: "us",
// Ensure this matches your workspace region
// For more information, see https://docs.fingerprint.com/docs/regions
},
}),
],
};
```
3. Replace `PUBLIC_API_KEY` with your actual public API key from the [Fingerprint dashboard](https://dashboard.fingerprint.com/api-keys).
*Note: For production, consider using environment variables to configure the API key.*
## 5. Trigger visitor identification
Now that the JavaScript agent is initialized, you can identify the visitor only when needed. In this case, that's when the user taps/clicks the **Create Account** button.
When making the visitor identification request, you will receive the `visitor_id` as well as an `event_id`. Instead of using the `visitor_id` returned directly on the frontend (which could be tampered with), you'll send the `event_id` to your backend. This ID is unique to each identification event. Your server can then use the [Fingerprint Events API](/reference/server-api-get-event) to retrieve complete identification data, including the trusted visitor ID and other actionable insights like whether they are using a VPN or are a bot.
1. Import `FingerprintService` in `create-account-form.component.ts` and configure it by passing it into the `CreateAccountFormComponent` class `constructor`. This automatically initializes it via `Dependency Injection`:
```angular-ts src/app/create-account-form/create-account-form.component.ts theme={"theme":"github-dark-dimmed"}
import { FingerprintService } from "@fingerprint/angular";
// ... rest of component here
export class CreateAccountFormComponent {
// ... other properties here
constructor(private fingerprintService: FingerprintService) {}
}
```
2. Now populate the `handleSubmit` method to trigger identification when the user clicks **Create Account**:
```angular-ts src/app/create-account-form/create-account-form.component.ts theme={"theme":"github-dark-dimmed"}
async handleSubmit() {
this.isLoading.set(true);
try {
const data = await this.fingerprintService.getVisitorData();
console.log(`
Visitor ID: ${data.visitor_id}
Event ID: ${data.event_id}`);
// Send the event_id to your server
// await fetch("/api/create-account", {
// method: "POST",
// headers: { "Content-Type": "application/json" },
// body: JSON.stringify({
// username: this.username,
// password: this.password,
// eventId: data.event_id,
// }),
// });
} catch (err) {
console.error('Registration failed', err);
} finally {
this.isLoading.set(false);
}
}
```
In this function:
* `this.isLoading.set(true)` is used to monitor the process stage for the function call.
* `const data = await this.fingerprintService.getVisitorData();` - This triggers Fingerprint's device identification and returns a `data` object containing the visitor's `visitor_id` and `event_id`, which you can then send to your backend along with the username and password.
## 6. Test the app
1. If your dev server isn't already running, start it with:
```bash Terminal theme={"theme":"github-dark-dimmed"}
npm start
```
2. In your browser, go to [http://localhost:4200](http://localhost:4200/) (Angular's default).
3. If you have any ad blockers, turn them off for localhost. View the [documentation](/docs/protecting-the-javascript-agent-from-adblockers) to learn how to protect your Fingerprint implementation from ad blockers in production.
4. Enter a username and password, then click **Create Account**.
5. Open the developer console in your browser, and you should see the visitor ID and event ID in the output:
```text Output theme={"theme":"github-dark-dimmed"}
Visitor ID: kLmvO1y70BHKyTgoNoPq
Event ID: 9171022083823.zox1GS
```
## Next steps
To use the identification data for fraud detection (like blocking repeat fake account creation attempts), you'll need to send the `event_id` to your [backend](/docs/server-quickstarts-overview). From there, your server can call the [Fingerprint Events API](/reference/server-api-get-event) to retrieve the full visitor information data and use it to make decisions and prevent fraud.
Check out these related resources:
* [Angular SDK reference](https://github.com/fingerprintjs/angular)
* [Node.js backend quickstart](/docs/node-server-quickstart)
* [API reference for the Events endpoint](/reference/server-api-get-event)
* [Use case tutorial: Detecting new account fraud](/docs/new-account-fraud-use-case-tutorial)
* [Protecting from client-side tampering and replay attacks](/docs/protecting-from-client-side-tampering)
# Audit logs
Source: https://docs.fingerprint.com/docs/audit-logs
The Dashboard provides an audit log of changes made to workspace resources: what was changed, by whom, and when. This helps you trace configuration changes, troubleshoot issues, and support compliance reporting.
### Activity overview
The audit log lives on the Activity page in Settings.
To check it out, visit **Dashboard** > **Settings** > [**Activity**](https://dashboard.fingerprint.com/audit-log).
The Activity page is only accessible to team members with the **admin** role.
Activity is listed in reverse chronological order, grouped by month. Each entry shows:
* **Who** performed the action - a team member or an automated system action.
* **What** was done - the action taken and the resource affected.
* **When** it happened - relative to now.
### Inspecting an event
Click an event to expand it and see the full details:
* **At** - the exact timestamp.
* **Action** - the type of change (Create, Update, Delete, etc.).
* **Resource** - the resource type that was affected (API key, Ruleset, Webhook, Environment, etc.).
* **Performed by** - the user (with email) or system that initiated the change.
### Filtering
To find specific events, use the filters at the top of the Activity page:
* **Search** - full-text search across event descriptions, resource names, and performer names.
* **Resource** - filter by resource type.
* **Performed by** - filter by a specific team member or system source.
* **Date range** - narrow the time window down to a specific window of dates and times.
# Install CloudFront Integration using Terraform
Source: https://docs.fingerprint.com/docs/aws-cloudfront-integration-via-terraform
**Before you start: Read the general CloudFront guide**
This document only covers deploying the Fingerprint CloudFront proxy integration to your AWS account using Terraform. It assumes you use you have already read the [general AWS CloudFront Proxy Integration v2 guide](/docs/cloudfront-proxy-integration-v2) and completed the following steps:
* [Step 1](/docs/cloudfront-proxy-integration-v2#step-1-issue-a-proxy-secret): You have issued a proxy secret in the Fingerprint Dashboard (`FPJS_PRE_SHARED_SECRET`).
* [Step 2](/docs/cloudfront-proxy-integration-v2#step-2-create-path-variable): You have defined a path variable for the integration (`FPJS_BEHAVIOR_PATH`)
If want to use CloudFormation instead of Terraform to install the integration, see [Install CloudFront integration using CloudFormation](/docs/install-cloudfront-integration-using-cloudformation).
**Limitations**
This integration is exclusively supported for customers on the **Enterprise** Plan. Other customers are encouraged to use [Custom subdomain setup](/docs/custom-subdomain-setup) or [Cloudflare Proxy Integration](/docs/cloudflare-integration).
## Prerequisites
* AWS Account.
* Access to an IAM role in AWS with privileges to manage IAM roles, CloudFront distributions, Secrets Manager, Lambda Functions, and S3 Read Only access.
* Terraform project using the [AWS provider](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) with the IAM role described above.
* [Terraform CLI](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli).
## Step 3: Add the Fingerprint Terraform module
Add the Fingerprint CloudFront integration [Terraform module](https://registry.terraform.io/modules/fingerprintjs/fingerprint-cloudfront-proxy-integration/aws/latest) into your Terraform project.
* Use the proxy secret created in Step 1 as `fpjs_shared_secret`.
* Make sure to deploy your integration in the `us-east-1` AWS region.
* Due to [AWS limitations](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-edge-function-restrictions.html#lambda-at-edge-restrictions-region), Lambda functions must be deployed in the `us-east-1` region.
* The Terraform module pulls the Lambda function source code from an S3 bucket in the `us-east-1` region.
* Make sure your AWS CLI has a policy allowing it to read from S3 buckets, for example [`AmazonS3ReadOnlyAccess`](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonS3ReadOnlyAccess.html).
```terraform fingerprint.tf theme={"theme":"github-dark-dimmed"}
module "fingerprint_cloudfront_integration" {
source = "fingerprintjs/fingerprint-cloudfront-proxy-integration/aws"
fpjs_shared_secret = "FPJS_PRE_SHARED_SECRET"
}
provider "aws" {
// You must deploy the integration in the us-east-1 region
region = "us-east-1"
}
```
Run `terraform init` to install the module. The Terraform module source code is [available on GitHub](https://github.com/fingerprintjs/terraform-aws-fingerprint-cloudfront-proxy-integration).
You can update the module by running `terraform init -upgrade`. Specify the [version constraint](https://developer.hashicorp.com/terraform/language/modules/syntax#version) according to your needs.
**Note: Proxy secret required**
Proxied identification requests without a valid proxy secret will result in an authentication error and not receive identification results.
## Step 4: Use Terraform module outputs in your CloudFront distribution
In this step, choose between using an existing CloudFront distribution or creating a new one. The module's GitHub repository contains [example projects](https://github.com/fingerprintjs/terraform-aws-fingerprint-cloudfront-proxy-integration?tab=readme-ov-file#examples) for both approaches.
### A) Use an existing CloudFront distribution (recommended)
If your website is already running on CloudFront, you can use the same distribution and domain for the proxy integration. The proxy endpoints will be available on your chosen path such as`yourwebsite.com/random-path/...`
This is the recommended setup. Your website and the proxy function will be same-site, served from the same IP address or IP address range. Having the same or similar IP improves cookie lifetimes in Safari — they will be stored in the browser for up to one year instead of 7 days.
1. Add the following code to the definition of your CloudFront distribution.
2. Replace `FPJS_BEHAVIOR_PATH` with the value you defined in Step 2.
```terraform main.tf theme={"theme":"github-dark-dimmed"}
resource "aws_cloudfront_distribution" "cloudfront_dist" {
// Your existing CloudFront configuration
# region Fingerprint CloudFront integration start
origin {
domain_name = module.fingerprint_cloudfront_integration.fpjs_origin_name
origin_id = module.fingerprint_cloudfront_integration.fpjs_origin_id
custom_origin_config {
origin_protocol_policy = "https-only"
http_port = 80
https_port = 443
origin_ssl_protocols = ["TLSv1.2"]
}
custom_header {
name = "FPJS_SECRET_NAME"
value = module.fingerprint_cloudfront_integration.fpjs_secret_manager_arn
}
}
ordered_cache_behavior {
path_pattern = "FPJS_BEHAVIOR_PATH*"
allowed_methods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
cached_methods = ["GET", "HEAD"]
cache_policy_id = module.fingerprint_cloudfront_integration.fpjs_cache_policy_id
origin_request_policy_id = module.fingerprint_cloudfront_integration.fpjs_origin_request_policy_id
target_origin_id = module.fingerprint_cloudfront_integration.fpjs_origin_id
viewer_protocol_policy = "https-only"
compress = true
lambda_function_association {
event_type = "origin-request"
lambda_arn = module.fingerprint_cloudfront_integration.fpjs_proxy_lambda_arn
include_body = true
}
}
#endregion
}
```
**Multi-segment behavior paths**
We recommend using **single** behavior path segments for simplicity, but it's possible to use more complex ones if needed.
For example:
```terraform theme={"theme":"github-dark-dimmed"}
path_pattern = "ore54guier/vbcnkxb654*"
```
However, this requires adjustment of an additional parameter `integration_path_depth` which describes the number of path segments in the behavior path:
```terraform main.tf theme={"theme":"github-dark-dimmed"}
module "fingerprint_cloudfront_integration" {
source = "fingerprintjs/fingerprint-cloudfront-proxy-integration/aws"
fpjs_shared_secret = "FPJS_PRE_SHARED_SECRET"
// The number of path segments in the behavior path, in this case 2: ore54guier/vbcnkxb654 // [!code ++]
integration_path_depth = 2 // [!code ++]
}
resource "aws_cloudfront_distribution" "cloudfront_dist" {
// Your existing CloudFront configuration
ordered_cache_behavior {
path_pattern = "ore54guier/vbcnkxb654*"
// The rest of the configuration is the same as above
}
}
```
How to count path segments:
```
Segment Segment
↓ ↓
ore54guier / vbcnkxb654 → 2 path segments
```
### B) Create a new CloudFront distribution
If your website is not running on CloudFront, you can create a new CloudFront distribution just for the proxy integration and serve it from a subdomain of your website like `metrics.yourwebsite.com`.
This setup limits Safari cookie lifetime to 7 days. Because your website and the Lambda proxy function will likely have [different IP ranges](https://github.com/WebKit/WebKit/pull/5347), Safari will apply the same cookie lifetime cap as for third-party CNAME cloaking. This is still an improvement over third-party cookies getting blocked entirely by Safari. But we recommend serving your website and the proxy integration using the same CloudFront distribution if possible (option A above).
* Add the following code to create a CloudFront distribution with the Fingerprint Terraform module as the default cache behavior.
* Configure the CloudFront distribution (for example `price_class`, `viewer_certificate`, `restrictions`, etc.) according to your needs.
```terraform main.tf theme={"theme":"github-dark-dimmed"}
resource "aws_cloudfront_distribution" "fpjs_cloudfront_distribution" {
comment = "Fingerprint distribution (created via Terraform)"
origin {
domain_name = module.fingerprint_cloudfront_integration.fpjs_origin_name
origin_id = module.fingerprint_cloudfront_integration.fpjs_origin_id
custom_origin_config {
origin_protocol_policy = "https-only"
http_port = 80
https_port = 443
origin_ssl_protocols = ["TLSv1.2"]
}
custom_header {
name = "FPJS_SECRET_NAME"
value = module.fingerprint_cloudfront_integration.fpjs_secret_manager_arn
}
# This header is required, since it informs the integration that it's served under the root path
custom_header {
name = "FPJS_INTEGRATION_PATH_DEPTH"
value = "0"
}
}
enabled = true
http_version = "http1.1"
price_class = "PriceClass_100"
default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
cached_methods = ["GET", "HEAD"]
cache_policy_id = module.fingerprint_cloudfront_integration.fpjs_cache_policy_id
origin_request_policy_id = module.fingerprint_cloudfront_integration.fpjs_origin_request_policy_id
target_origin_id = module.fingerprint_cloudfront_integration.fpjs_origin_id
viewer_protocol_policy = "https-only"
compress = true
lambda_function_association {
event_type = "origin-request"
lambda_arn = module.fingerprint_cloudfront_integration.fpjs_proxy_lambda_arn
include_body = true
}
}
restrictions {
geo_restriction {
restriction_type = "none"
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
```
#### Creating a subdomain for the new CloudFront distribution
If you use Terraform to manage your DNS records, you can also configure a subdomain for your proxy CloudFront distribution.
```terraform theme={"theme":"github-dark-dimmed"}
resource "aws_cloudfront_distribution" "fpjs_cloudfront_distribution" {
# Same configuration as above
viewer_certificate {
cloudfront_default_certificate = true # [!code --]
acm_certificate_arn = "" # [!code ++]
ssl_support_method = "sni-only" # [!code ++]
}
aliases = ["metrics.yourwebsite.com"] # [!code ++:10]
}
resource "aws_route53_record" "cloudfront_terraform_new_distribution_record" {
zone_id = ""
name = "metrics.yourwebsite.com"
type = "CNAME"
ttl = 300
records = [aws_cloudfront_distribution.fpjs_cloudfront_distribution.domain_name]
}
```
## Step 5: Apply the Terraform changes
1. Run `terraform plan` to verify the planned configuration changes.
2. Run `terraform apply` to apply the changes.
## Step 6: Configure the client agent
Please see the main CloudFront proxy integration guide to [Configure the client agent on your website or mobile app](/docs/cloudfront-proxy-integration-v2#configure-the-fingerprint-javascript-agent-on-your-client).
## Updating the integration
Unlike the [CloudFormation](/docs/install-cloudfront-integration-using-cloudformation) installation method, the Terraform installation of the CloudFront proxy integration does not include any mechanism for automatic updates.
* The module does not include a Management lambda function or any related resources.
* No need to configure automatic updates in the Fingerprint Dashboard.
To keep your integration up to date, please run `terraform apply` regularly.
**Update expectations**
The underlying data contract in the identification logic can change to keep up with browser and device releases. Using the AWS CloudFront Proxy Integration might require occasional manual updates on your side. Ignoring these updates will lead to lower accuracy or service disruption.
## Defining a permission boundary for the proxy function
If you need to, you can define the proxy function's [permission boundary](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html) as an input of the Terraform module.
```terraform theme={"theme":"github-dark-dimmed"}
module "fingerprint_cloudfront_integration" {
source = "fingerprintjs/fingerprint-cloudfront-proxy-integration/aws"
fpjs_shared_secret = "FPJS_PRE_SHARED_SECRET"
fpjs_proxy_lambda_role_permissions_boundary_arn = "arn:aws:iam:::policy/YOUR_POLICY_NAME" # [!code ++]
}
```
Make sure your permissions boundary allows the function to:
* Access the Secret manager secret created for the integration (`secretsmanager:GetSecretValue`)
* Create logs (`logs:CreateLogStream`, `logs:CreateLogGroup`, `logs:PutLogEvents`).
Example permission boundary definition:
```json JSON theme={"theme":"github-dark-dimmed"}
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"logs:CreateLogStream",
"logs:CreateLogGroup",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:secretsmanager:us-east-1::secret:fingerprint-pro-cloudfront-integration-settings-secret-",
"arn:aws:logs:*::log-group:*"
]
}
]
}
```
## What's next
To learn more about calculating AWS costs and monitoring your CloudFront integration, go back to the [general CloudFront Proxy Integration guide](/docs/cloudfront-proxy-integration-v2#monitoring-and-managing-the-integration).
# Ban Enforcement
Source: https://docs.fingerprint.com/docs/ban-enforcement-use-case-tutorial
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
## 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:
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 `visitor_id` and an `event_id`. Instead of relying on the `visitor_id` from the browser, you'll send the `event_id` to your server along with the listing data. The server will then call the [Fingerprint Events API](/reference/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/v4/${window.FP_PUBLIC_API_KEY}`).then((Fingerprint) =>
Fingerprint.start({ 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 `event_id` when sending the listing to the server:
```javascript public/index.js theme={"theme":"github-dark-dimmed"}
submitBtn.addEventListener("click", async () => {
// ...
const fp = await fpPromise;
const { event_id: eventId } = 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,
eventId,
}),
});
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 `event_id` 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/sealed-client-results) to return full identification details as an encrypted payload from the `get()` method.
## 4. Receive and use the event ID to get visitor insights
Next, pass the `eventId` 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 visitor ID 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. The `POST /api/listings` route passes the request body to the `postListing` function defined in `server/listings.js`. Because the frontend now sends `eventId` in the payload, that value will be available in the body when `postListing` runs:
```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 { FingerprintServerApiClient, Region } from "@fingerprint/node-sdk";
config();
const fpServerApiClient = new FingerprintServerApiClient({
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 `eventId` and use it to fetch the full identification event details from Fingerprint so you can log the visitor ID alongside each listing:
```javascript server/listings.js theme={"theme":"github-dark-dimmed"}
export async function postListing(body) {
const { sellerEmail, eventId } = body;
const event = await fpServerApiClient.getEvent(eventId);
const visitorId = event.identification.visitor_id;
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 `eventId`, the getEvent method will retrieve the full data for the visitor identification event. 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/protecting-from-client-side-tampering).
## 5. Block suspicious devices
This optional step uses Smart Signals, which are only available on paid plans.
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, eventId } = body;
const event = await fpServerApiClient.getEvent(eventId);
const visitorId = event.identification.visitor_id;
const suspectScore = event.suspect_score || 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 visitor ID from the `event` object to enforce your ban rules. If a visitor (identified by visitor ID) 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 { eventId } = body;
const event = await fpServerApiClient.getEvent(eventId);
const visitorId = event.identification.visitor_id;
const suspectScore = event.suspect_score || 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 `visitor_id` 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.
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.
## 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/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](/reference/node-server-sdk)
* [Vue frontend quickstart](/docs/vue-quickstart)
* [React frontend quickstart](/docs/react-quickstart)
* [API reference for the Events endpoint](/reference/server-api-get-event)
* [Use case tutorial: Detecting new account fraud](/docs/new-account-fraud-use-case-tutorial)
* [Low-latency identification with Sealed Client Results](/docs/sealed-client-results)
# Overview
Source: https://docs.fingerprint.com/docs/billing
A guide to understanding our API request-based billing and related concepts
## Fingerprint Platform Billing
Customers are billed monthly based on the number of identification API requests made over the billing period. The Fingerprint Platform has three plans: [Free, Pro Plus and Enterprise](https://fingerprint.com/pricing/?utm_source=/docs/quick-start-guide).
> Check out our [Pricing](https://fingerprint.com/pricing/) to learn which plan is best for your needs.
Custom pricing options are available for businesses that require 9,000,000 annual API requests or more. Please reach out to our [Sales team](https://fingerprint.com/contact-sales/).
### Identification API request as a billing unit
A billable API request happens when you *successfully* use the Fingerprint client agent to identify a browser or mobile device. Identification requests successfully processed by Fingerprint generate an `event_id` that is returned in the response.
* All identification requests with an assigned event ID are billable.
* Requests that do not generate an event ID are not billed.
The following requests are **billable**:
* Calling the [`get()`](/reference/js-agent-get-function) function of the Fingerprint JavaScript agent.
* Calling the [`getVisitorId()`](/docs/ios-sdk#getting-a-visitorid) function of the iOS SDK.
* Calling the [`getVisitorId()`](/docs/android-sdk#getting-a-visitorid) function of the Android SDK.
* Calling equivalent functions in our [Flutter](/docs/flutter), [React Native](/docs/fingerprintjs-pro-react-native), or [frontend libraries](/docs/frontend-libraries).
The following requests are **not billable**:
* Downloading the client agent (calling the [`start()`](/reference/js-agent-start-function) function) is not billable.
* Requests to [Server API](/reference/server-api) are not billable.
* Requests blocked by ad blockers or application firewalls are generally not billed.
* Failed requests that result in an error without generating an event ID are not billed.
* Throttled identification requests are not billed.
* Requests filtered by [Request Filtering](/docs/request-filtering) are not billed.
If you encounter any billing issues or believe the billing is not working correctly, please reach out to [our support team](https://fingerprint.com/support/) to resolve your issue.
#### How many API calls will I need?
If you're unsure how many API calls you'll need, try these two options:
1. Start with an [unlimited 14-day free trial](https://dashboard.fingerprint.com/signup) to test all Fingerprint products. By the end, you'll know how many API calls you need.
2. If a trial isn’t possible, estimate the number of important actions where you'll use Fingerprint, like logins, account creation, payments, refunds, or return requests.
### Android API Call Allowance
Pro Plus customers receive 500,000 free Android API calls per month. These API calls apply when using our [Android SDK](/docs/android-sdk), [React Native](/docs/fingerprintjs-pro-react-native), or [Flutter](/docs/flutter) SDKs within an Android app. See [more details](/docs/android-api-call-allowance) on the allowance.
### Caching
When your application code retrieves visitor data from the application cache, it is not billed. Only requests that hit the Fingerprint backend are billed. Our [frontend libraries](https://fingerprint.com/sdk-libraries/) for specific web frameworks provide various caching strategies.
The number of billed requests will depend on your selected caching strategy. See [Caching visitor information](/docs/identify-visitors#caching-the-visitor-id) for more details.
### Smart Signals
[Smart Signals](/docs/smart-signals-reference) (for example, Bot Detection, VPN Detection, and more) are available to users on the Pro Plus or Enterprise plans.
### Billing changes
We reserve the right to change the billing model for our current or future products. In the event of an upcoming billing change, we'll send a notification to all our customers with a notification at least 30 days before the billing change event.
### Other Fingerprint platform features
Other Fingerprint platform features, such as [Server API requests](/reference/server-api), [Webhooks](/docs/webhooks), [Custom subdomains](/docs/custom-subdomain-setup), [Request filtering](/docs/request-filtering), [Proxy integrations](/docs/protecting-the-javascript-agent-from-adblockers#cloud-proxy-integrations), [libraries, and SDKs](https://fingerprint.com/sdk-libraries/) are free of charge with certain limitations per account. See below for more details.
## Account limits
All Fingerprint **accounts** are limited to **one unlimited 14-day free trial**. After you sign up, you are automatically enrolled in the free trial and can try the Fingerprint API without limitations. When the trial ends, your workspace will default to the Free plan unless you upgrade to a paid plan. On the Free plan, you can continue using Fingerprint with some limitations.
The following limits apply to all Fingerprint **accounts**:
| Resource | Default limit | Notes |
| ------------ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Team members | 10 | Includes users that signed up and pending invitations. Reach out to [Customer Support](https://fingerprint.com/support/) if you need more user slots. |
All **workspaces** under your account have the following limits:
| Resource | Default limit | Notes |
| --------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Monthly API requests | **unlimited** (paid plans) | On the Free plan, monthly API requests are limited to 1,000 for Web and iOS, and 500,000 for Android. Only Identification API requests count towards your monthly plan. Server API requests are free. |
| API rate limit | 5 requests per second ([\*bursts](/docs/billing#rate-limiting-burst-protection)) | Applies to requests made using public keys (Identification API). It is possible to increase the limit on the [Enterprise plan](https://fingerprint.com/pricing/?utm_source=/docs/billing#account-limits) by emailing support. |
| Server API rate limit | 5 requests per second ([\*bursts](/docs/billing#rate-limiting-burst-protection)) | Applies to requests made using secret keys ([Server API](/reference/server-api)). A separate rate limit applies to the [DELETE API](/reference/server-api-delete-visitor-id). |
| Visitor history | 30 days | For plans, visit history is available through the Dashboard or the [Server API](/reference/server-api) for the past 30 days. For Enterprise plans, the data is kept for 90 days or longer depending on the enterprise contract. |
| SSL certificates | 50 | Limited to 5 certificates on Free and trialing plans. |
| Environments | 2 | Number of multiple environments you can setup. |
| Management API keys | 5 | Number of API keys used to interact with the [Management API](). |
With support for [multiple environments](/docs/multiple-environments), some limits are applied **per environment**. For resources that are **workspace-scoped**, they take up a slot in every environment.
| Resource | Default limit | Notes |
| ----------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Public API keys | 2 | API keys used to make billable [identification requests](/docs/quick-start-guide). |
| Secret API keys | 5 | API keys used to make requests to the [Server API](/reference/server-api). Optionally they can be workspace-scoped. |
| Proxy keys | 5 | Keys used to authenticate with [proxy integrations](/docs/protecting-the-javascript-agent-from-adblockers). Optionally they can be workspace-scoped. |
| Encryption keys | 5 | Keys used to encrypt client payloads to enable [Sealed Client Results](/docs/sealed-client-results). |
| Request filtering rules | 10 | [Filtering rules](/docs/request-filtering) that are used to filter out unwanted requests. Includes allowed/forbidden origins and forbidden HTTP headers. |
| Webhooks | 5 | Number of configured [webhooks](/docs/webhooks). Optionally they can be workspace-scoped. |
### Increasing Limits
If you are a customer and want to increase a limit, please reach out to [our support team](https://fingerprint.com/support/).
### Rate Limiting - Burst Protection
Short unexpected traffic spikes could normally cause request throttling if the API key exceeded the API Rate Limit. **Burst protection** prevents that by allowing peak traffic to be higher than usual for a short period of time. The allowed burst size (bucket) is always `API Rate Limit * 3` (15 RPS in the default configuration). The burst bucket automatically regenerates every second by `API Rate Limit - RPS` when the current RPS is lower than the `API Rate Limit`.
#### Examples
Real world examples of burst bucket depletion with different RPS values (considering the default `API Rate Limit` value above):
* Burst bucket **never depletes** when RPS is 5
* Burst bucket depletes **in 15 seconds** on 6 RPS
* Burst bucket depletes **in 3 seconds** on 10 RPS
* Burst bucket depletes **in 1 second** on 20 RPS
Whenever the bucket depletes, the service starts dropping requests above `API Rate Limit`.
### How am I protected from overpaying?
Fingerprint offers two types of **surge protection** for all customer accounts. First is DDoS protection, so you won't be charged for API calls during an attack. Second is a web application firewall (WAF), which inspects all incoming requests and blocks harmful or abusive traffic (including certain classes of bot activity).
Each request also goes through a customizable [request filtering](/docs/request-filtering) layer. This lets you block requests from unknown sources, limit API calls to certain web apps, or ignore requests based on HTTP headers.
In rare cases where a malicious surge or implementation issue still causes unexpected usage, you may be eligible for **partial** **bill forgiveness**.
* See our [Spike Troubleshooting guide](/docs/troubleshooting-unexpected-usage-spikes) to diagnose the root cause.
* If you confirm the spike was unintentional or malicious, you can [submit a forgiveness request](https://dashboard.fingerprint.com/settings/billing) through the Billing page on the Fingerprint dashboard.
**Important:** Forgiveness can only be applied to your **most recent completed invoice** (i.e. the last completed billing cycle). Older invoices are not eligible, even if they experienced a spike.
***
[Browser and device support](/docs/browser-and-device-support)
# Browser and device support
Source: https://docs.fingerprint.com/docs/browser-and-device-support
An overview of Fingerprint compatibility across various browsers and devices.
## Browsers
Fingerprint supports all popular browsers. We aim to cover at least 99% of all users according to our Fingerprint Identification statistics. The overall identification accuracy of traffic coming from supported browsers is industry-leading based on all Fingerprint identification statistics. Our research and development team continuously monitors releases of supported browsers and adjusts our identification algorithm if necessary.
At the moment, the supported browsers are:
| Browser | Version | Notes |
| ----------------------- | --------- | ---------------------------------------------------------------------- |
| Edge | **105+** | |
| Chrome | **100+** | |
| Firefox | **115+** | |
| Desktop Safari | **15.0+** | See [below](#safari-intelligent-tracking-prevention) for more details. |
| Mobile Safari | **15.0+** | |
| Mobile Samsung Internet | **18.0+** | See [below](#samsung-internet-browser-support) for more information |
| Brave | **1.37+** | See [below](#brave-browser-support) for more details. |
Browsers not listed here may work, but we don't make guarantees about identification functionality or accuracy.
The identification accuracy of individual browsers may vary, and their impact on your overall identification accuracy depends on their prevalence in your traffic. See [Understanding our accuracy](/docs/identification-accuracy-and-confidence) for an explanation of how we measure our accuracy.
### Safari Intelligent Tracking Prevention
Apple Safari has an on-by-default privacy protection feature called [Intelligent Tracking Protection](https://webkit.org/blog/9521/intelligent-tracking-prevention-2-3/), or ITP. ITP has been very effective in preventing cross-site tracking by completely disabling 3rd-party cookies and capping the lifetime of all script-writable website data.
By default, the Fingerprint API uses 3rd party cookies (both client-side set with `document.cookie` and server-side set with the `Set-Cookie` HTTP header) and script-writable website data (localStorage) to more reliably [identify returning visitors](https://fingerprint.com/blog/browser-fingerprinting-techniques/). All of these methods are affected by ITP, lowering identification accuracy. View the methods for [keeping identification accuracy high](#keeping-identification-accuracy-high) to maximize identification accuracy.
### Brave browser support
[Brave](https://brave.com/) is a privacy-focused browser based on Chromium. For our purposes, it behaves similarly to Chrome with a strict ad-blocking extension installed. That means it blocks the agent download and identification requests to Fingerprint CDN and API. View the methods for [keeping identification accuracy high](#keeping-identification-accuracy-high) to ensure your requests are not blocked.
### Samsung Internet browser support
[Samsung Internet](https://play.google.com/store/apps/details?id=com.sec.android.app.sbrowser\&hl=en_US\$0) is a browser optimized for Samsung Galaxy devices, but available for all Androids. Fingerprint only supports the Android version of this browser. iOS, desktop, and TV versions of Samsung browser are not supported.
### Keeping identification accuracy high
Ad-blocking extensions and browser features can disrupt visitor identification. To maintain high identification accuracy even with Safari and Brave, you need to use a [custom subdomain setup](/docs/custom-subdomain-setup) or a cloud proxy integration like [Cloudflare](/docs/cloudflare-integration) or [Cloudfront](/docs/cloudfront-proxy-integration-v2). These methods allow you to route Fingerprint HTTP requests through your domain, preventing browsers from blocking them and making cookies set by Fingerprint considered "first-party." See [Protecting the JavaScript agent from ad-blockers](/docs/protecting-the-javascript-agent-from-adblockers) for more details.
## Mobile devices
Fingerprint supports most iOS and Android devices in circulation today. The overall identification accuracy of traffic coming from mobile devices is even higher than that of browsers, thanks to long-lasting system-level attributes available on mobile platforms.
At the moment, the supported OS versions are:
| OS | Version | Notes |
| ------- | ------------------------------------- | ------------------------------------------------------------------------------------ |
| Android | 5.0 Lollipop (API Level 21) or higher | See [Android](/docs/native-android-integration#supported-versions) for more details. |
| iOS | 13+ | See [iOS](/docs/ios#supported-versions) for more details. |
***
[Regions and data retention](/docs/regions)
# Card Testing
Source: https://docs.fingerprint.com/docs/card-testing-use-case-tutorial
Learn how to detect and prevent credit card testing fraud
## 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 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 backend. For language- or framework-specific setups, see the 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](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 `card-testing` folder. The project is organized as follows:
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 checkout page from the starter app. You can test the basic payment form by entering some fake card details and clicking **Place order**.
7. 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.
```bash Terminal theme={"theme":"github-dark-dimmed"}
node test-bot.js
```
## 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 **Place order**. The JavaScript agent returns both a `visitor_id` and an `event_id`. Instead of relying on the `visitor_id` from the browser, you'll send the `event_id` to your server along with the checkout payload. The server will then call the [Fingerprint Events API](/reference/server-api-get-event) to securely retrieve the full identification details, including bot detection and other 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/v4/${window.FP_PUBLIC_API_KEY}`).then((Fingerprint) =>
Fingerprint.start({ 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 **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 `event_id` when sending the checkout to the server:
```javascript public/index.js theme={"theme":"github-dark-dimmed"}
placeOrderBtn.addEventListener("click", async () => {
// ...
const fp = await fpPromise;
const { event_id: eventId } = 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,
eventId,
}),
});
// ...
});
```
The `get()` method sends signals collected from the browser to Fingerprint servers, where they are analyzed to identify the visitor. The returned `event_id` 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/sealed-client-results) to return full identification details as an encrypted payload from the `get()` method.
## 4. Receive and use the event ID to get visitor insights
Next, pass the `eventId` through to your order processing logic, initialize the [Fingerprint Node Server SDK](/reference/node-server-sdk), and fetch the full visitor identification event so you can access the trusted visitor ID and Bot Detection [Smart Signal](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/place-order` route there to also extract `eventId` from the request body and pass it into the `placeOrder` function:
```javascript server/server.js theme={"theme":"github-dark-dimmed"}
app.post("/api/place-order", async (req, reply) => {
const result = await placeOrder(req.body);
return reply.send(result);
});
```
2. The `server/orders.js` file contains the logic for handling orders. Start by importing and initializing the Fingerprint Node Server SDK there, and load your environment variables with `dotenv`.
```javascript server/orders.js theme={"theme":"github-dark-dimmed"}
import { db } from "./db.js";
import { config } from "dotenv";
import { FingerprintServerApiClient, Region } from "@fingerprint/node-sdk";
config();
const fpServerApiClient = new FingerprintServerApiClient({
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 `placeOrder` function to also extract `eventId` and use it to fetch the full identification event details from Fingerprint:
```javascript server/orders.js theme={"theme":"github-dark-dimmed"}
export async function placeOrder(body) {
const { recipientEmail, amount, cardNumber, cardExp, cardCvv, eventId } = body;
const event = await fpServerApiClient.getEvent(eventId);
// ...
}
```
Using the `eventId`, the getEvent method will retrieve the full data for the visitor identification event. 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/protecting-from-client-side-tampering).
## 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 `not_detected` 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 `not_detected` 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:
```javascript server/orders.js theme={"theme":"github-dark-dimmed"}
export async function placeOrder(body) {
const { recipientEmail, amount, cardNumber, cardExp, cardCvv, eventId } = body;
const event = await fpServerApiClient.getEvent(eventId);
const botDetected = event.bot !== "not_detected";
if (botDetected) {
console.error("Bot detected.");
return { success: false, message: "Order failed." };
}
// ...
}
```
You can also add [Suspect Score](/docs/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.
2. 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):
```javascript server/orders.js theme={"theme":"github-dark-dimmed"}
export async function placeOrder(body) {
// ...
const botDetected = event.bot !== "not_detected";
if (botDetected) {
console.error("Bot detected.");
return { success: false, message: "Order failed." };
}
const suspectScore = event.suspect_score || 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:
```text SQLite database tables theme={"theme":"github-dark-dimmed"}
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`:
```javascript server/orders.js theme={"theme":"github-dark-dimmed"}
// 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;
}
```
2. Also update the existing `saveOrder` helper function to accept and use the `visitorId`:
```javascript server/orders.js theme={"theme":"github-dark-dimmed"}
// 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());
}
```
3. 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:
```javascript server/orders.js theme={"theme":"github-dark-dimmed"}
export async function placeOrder(body) {
// ...
const suspectScore = event.suspect_score || 0;
if (suspectScore > 20) {
console.error(`High Suspect Score detected: ${suspectScore}`);
return { success: false, message: "Order failed." };
}
const visitorId = event.identification.visitor_id;
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](http://localhost:3000):
```bash Terminal theme={"theme":"github-dark-dimmed"}
npm run dev
```
2. Try placing an order by entering some fake payment details and clicking **Place order**. You should see a success response.
3. 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.
4. 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:
```bash Terminal theme={"theme":"github-dark-dimmed"}
node test-bot.js
```
*Note: If you encounter errors launching the automated browser, make sure you have the testing browser installed:*
```bash Terminal theme={"theme":"github-dark-dimmed"}
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](/docs/smart-signals-reference), fine-tune rules based on your business policies, or layer in additional defenses or step-up verification.
To dive deeper, explore the other use case tutorials for more step-by-step examples.
Check out these related resources:
* [Node SDK Reference](/reference/node-server-sdk)
* [Vue frontend quickstart](/docs/vue-quickstart)
* [React frontend quickstart](/docs/react-quickstart)
* [API reference for the Events endpoint](/reference/server-api-get-event)
* [Use case tutorial: Detecting new account fraud](/docs/new-account-fraud-use-case-tutorial)
* [Low-latency identification with Sealed Client Results](/docs/sealed-client-results)
# Chargeback Dispute
Source: https://docs.fingerprint.com/docs/chargeback-dispute-use-case-tutorial
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
## 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:
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**. The JavaScript agent returns both a `visitor_id` and an `event_id`. Instead of relying on the `visitor_id` from the browser, you'll send the `event_id` to your server along with the purchase data. The server will then call the [Fingerprint Events API](/reference/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/v4/${window.FP_PUBLIC_API_KEY}`).then((Fingerprint) =>
Fingerprint.start({ 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 `event_id` when sending the purchase to the server:
```javascript public/index.js theme={"theme":"github-dark-dimmed"}
purchaseButton.addEventListener("click", async () => {
// ...
const fp = await fpPromise;
const { event_id: eventId } = 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,
eventId,
}),
});
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 `event_id` 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/sealed-client-results) to return full identification details as an encrypted payload from the `get()` method.
## 4. Receive and use the event ID to get visitor insights
Next, pass the `eventId` 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 visitor ID 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. The `POST /api/purchases` route passes the request body to the `postPurchase` function defined in `server/purchases.js`. Because the frontend now sends `eventId` in the payload, that value will be available in the body when `postPurchase` runs:
```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 { FingerprintServerApiClient, Region } from "@fingerprint/node-sdk";
config();
const fpServerApiClient = new FingerprintServerApiClient({
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 `eventId` and use it to fetch the full identification event details from Fingerprint so you can log the visitor ID alongside each purchase:
```javascript server/purchases.js theme={"theme":"github-dark-dimmed"}
export async function postPurchase(body) {
const createdAt = Date.now();
const { eventId } = body;
const event = await fpServerApiClient.getEvent(eventId);
const visitorId = event.identification.visitor_id;
// ...
}
```
Using the `eventId`, the getEvent method will retrieve the full data for the visitor identification event. 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/protecting-from-client-side-tampering).
## 5. Link purchase history with visitor IDs
Next, use the trusted visitor ID 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 visitor ID, 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 { eventId } = body;
const event = await fpServerApiClient.getEvent(eventId);
const visitorId = event.identification.visitor_id;
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"}
Visitor ID
Visitor ID
```
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.
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.
## 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/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](/reference/node-server-sdk)
* [Vue frontend quickstart](/docs/vue-quickstart)
* [React frontend quickstart](/docs/react-quickstart)
* [API reference for the Events endpoint](/reference/server-api-get-event)
* [Use case tutorial: Detecting new account fraud](/docs/new-account-fraud-use-case-tutorial)
* [Low-latency identification with Sealed Client Results](/docs/sealed-client-results)
# Overview
Source: https://docs.fingerprint.com/docs/cloudflare-integration
Fingerprint JavaScript agent v4.0.0 or later is required.
Fingerprint Cloudflare Proxy Integration proxies JavaScript agent download and identification requests between your website and Fingerprint through Cloudflare. Your website does not strictly need to be behind Cloudflare to use this proxy integration, although that is optimal for ease of setup and maximum accuracy benefits.
This guide assumes you are using [JavaScript agent v4](/reference/js-agent). If you are still
using JavaScript agent v3, see [Cloudflare Proxy Integration
(v3)](/docs/v3/cloudflare-integration) or migrate to [JavaScript agent
v4](/reference/migrating-from-v3-to-v4).
The integration consists of three components:
* Fingerprint infrastructure
* Cloudflare worker, created and managed by Fingerprint but running in your Cloudflare account
* Fingerprint JavaScript agent installed on your website
Fingerprint creates a [Cloudflare Worker](https://workers.cloudflare.com/) on a specific path on your site. The rest of your site is not affected.
Cloudflare worker code is open-source and available [on GitHub](https://github.com/fingerprintjs/cloudflare-worker-proxy). Once the Fingerprint JavaScript agent is configured correctly, the worker delivers the latest client-side logic and proxies identification requests and responses between your site and Fingerprint APIs.
## The benefits of using Cloudflare Proxy Integration
* Significant increase in accuracy in browsers with strict privacy features such as Safari or Firefox
* Cookies are recognized as first-party, so they can live longer in the browser and extend the lifetime of visitor IDs
* Ad blockers do not block the Fingerprint JavaScript agent from loading
* Ad blockers do not block identification requests because they are sent to a path or subdomain that belongs to your site
* Insight and control over identification requests that you can combine with other Cloudflare features such as WAF or Analytics
* The ability to manage unlimited subdomains and paths and provide Fingerprint services to all your customers at any scale while benefiting from first-party integration improvements
* Cookie security: the Cloudflare integration drops all cookies sent from the origin website. The worker code is open-source, so you can verify and audit this behavior
* Easier compliance and auditing
## Setup
The Cloudflare configuration guide in the Fingerprint [dashboard](https://dashboard.fingerprint.com/) will help you set everything up step by step. Start the guide from **SDKs & integrations**.
**Prerequisites**
* Only users with the **Admin** or **Owner** roles can launch the Cloudflare configuration guide in the dashboard.
* Cloudflare Proxy Integration uses the Cloudflare Workers [Custom Routes](https://developers.cloudflare.com/workers/platform/triggers/routes/) feature. Your site must be [added to Cloudflare](https://developers.cloudflare.com/fundamentals/get-started/setup/add-site/) and [proxied](https://developers.cloudflare.com/dns/manage-dns-records/reference/proxied-dns-records/) through Cloudflare, not DNS-only. If you can only add your domain to Cloudflare without proxying, see [Alternative worker subdomain setup (for DNS-only domains)](#alternative-worker-subdomain-setup-for-dns-only-domains).
### Step 1: Follow the Cloudflare configuration wizard
1. Go to **SDKs & integrations**, then select **Cloudflare**.
2. In the Cloudflare configuration guide, click **Connect**.
3. Optionally, [scope the integration to a specific environment](/docs/multiple-environments#proxy-integrations-and-proxy-secrets).
* By default, the integration works for all environments in your workspace.
* If you scope the integration to a specific environment, it will only proxy identification requests made with public API keys from that environment.
4. Add information about your Cloudflare account.
| Name | Example | Short description |
| :-------------------- | :--------------------------------------- | :--------------------------------------------------------------------------------------------------------- |
| Cloudflare Account ID | 88e2a7348d589a61edd0918e57fb136f | The Account ID from the [Cloudflare Dashboard](https://dash.cloudflare.com/). |
| Cloudflare API Token | YQSnnxWAQiiEh9qM58wZNnyQS7FUdoqGIUAbrh7T | API token generated on Cloudflare's [**API Tokens** page](https://dash.cloudflare.com/profile/api-tokens). |
#### Cloudflare Account ID
The account ID is required to deploy workers. Go to [Cloudflare Workers](https://dash.cloudflare.com/?to=/:account/workers) and copy the account ID.
#### Cloudflare API token
The API token is required to deploy workers. Go to the [**API Tokens** page](https://dash.cloudflare.com/profile/api-tokens), select [**Create Custom Token**](https://dash.cloudflare.com/profile/api-tokens?permissionGroupKeys=%5B%7B"key"%3A"workers_routes"%2C"type"%3A"edit"%7D%2C%7B"key"%3A"workers_scripts"%2C"type"%3A"edit"%7D%5D\&name=fingerprint.com), and follow these steps:
* Type `fingerprint.com` in the name field
* Add the **Workers Scripts** > **Edit** account permission
* Add the **Workers Routes** > **Edit** zone permission
* Select the account in **Account Resources**
* Select the website in **Zone Resources**
* Add IP filtering for `3.23.16.20`
* Do not set a TTL
In the next step, review the summary and click **Create Token**.
**API token safety**
When creating an API token, grant as few privileges as possible. In the example above, the token can only manage workers in your account. Fingerprint uses your API token only for managing the Fingerprint Cloudflare worker.
Fingerprint encrypts customer data, including API tokens and configuration items. Client IP address filtering adds another layer of security. Allowing the Fingerprint Cloudflare service IP ensures the API token can only be used by Fingerprint services.
5. After entering the account ID and API token, continue to the next step.
6. Select the same domain you used when creating the API token. If you do not see the domain you want to use, contact [support](https://fingerprint.com/support).
7. Confirm the deployment. This process can take several minutes.
Once deployment completes, Fingerprint creates a worker named like `fingerprint-pro-cloudflare-worker-random-id`. You can see it in the [Cloudflare Workers Dashboard](https://dash.cloudflare.com/?to=/:account/workers/overview).
**Do not disrupt worker authentication or updates**
The integration wizard creates a proxy secret in your Fingerprint dashboard and passes it to your Cloudflare worker through environment variables.
Do not delete the proxy secret in either place. Proxied identification requests without a valid proxy secret fail with an authentication error.
Fingerprint updates your Cloudflare worker to keep visitor identification working correctly as browsers change. Outdated worker configuration can lower accuracy or break visitor identification completely. Do not make changes that would prevent Fingerprint from updating your worker:
* Do not reduce the API token permissions or revoke it
* Do not change the worker name
* Do not change the original worker route or configuration
### Step 2: Configure your JavaScript agent
Once the worker is deployed, configure your client-side application to use it. You can always return to **SDKs & integrations** to get code snippets for different frameworks that match your Cloudflare setup.
#### JavaScript agent configuration example
```javascript NPM package theme={"theme":"github-dark-dimmed"}
// The same pattern applies to React SDK, Vue SDK, and other frontend SDKs.
import * as Fingerprint from '@fingerprint/agent';
const fp = Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
region: 'us',
endpoints: 'https://yourwebsite.com/WORKER_PATH/?region=us',
});
```
```javascript CDN installation theme={"theme":"github-dark-dimmed"}
const fpPromise = import('https://yourwebsite.com/WORKER_PATH/web/v4/PUBLIC_API_KEY').then(
(Fingerprint) =>
Fingerprint.start({
region: 'us',
endpoints: 'https://yourwebsite.com/WORKER_PATH/?region=us',
}),
);
```
* Include the region in the `endpoints` URL query string using the format `?region=REGION`. Replace `REGION` with the [region](/docs/regions) of your application.
**Migrating from JavaScript agent v3**
Cloudflare Proxy Integration remains compatible with JavaScript agent v3, so you can upgrade when it works for your rollout plan.
When you migrate to JavaScript agent v4:
* Remove `scriptUrlPattern` and `endpoint`
* Replace them with a single `endpoints` option that points to the worker route, for example `https://yourwebsite.com/WORKER_PATH/?region=us`
* See [Migrating JavaScript agent from v3 to v4](/reference/migrating-from-v3-to-v4#use-endpoints-instead-of-scripturlpattern) for more details
### Using multiple worker routes
Cloudflare Proxy Integration uses Cloudflare Workers, which supports [multiple routes](https://developers.cloudflare.com/workers/platform/triggers/routes/). You can add more routes if you need to, but do not change the original worker route or configuration created by Fingerprint.
## Monitoring and troubleshooting the integration
Go to **Dashboard** > [**SDKs & integrations**](https://dashboard.fingerprint.com/integrations) > **Cloudflare** to see the integration status, usage statistics, and configuration. You can monitor:
* Whether the integration is up to date
* How many identification requests go through the integration, and how many do not
* The error rate of proxied identification requests, usually caused by a missing or incorrect proxy secret
The information on the status page is cached, so allow a few minutes for the latest data points to appear.
The Cloudflare integration has limited visibility into your Cloudflare environment. If you run into issues, verify that firewall rules, rate limiting rules, or other Cloudflare restrictions are not disrupting the Fingerprint worker and its path. Contact [support](https://fingerprint.com/support/) if needed.
### Updating the Cloudflare token
If you accidentally delete the Cloudflare API token you provided to Fingerprint, you can update it in the Fingerprint dashboard.
* Go to [**SDKs & integrations**](https://dashboard.fingerprint.com/integrations) > **Cloudflare** and click **Edit API token**
### Deleting the integration
If something goes irreversibly wrong with your integration or Cloudflare worker configuration, you can delete everything and create a new integration from scratch.
1. Make sure your Fingerprint JavaScript agent is not using the integration URLs you are about to delete.
2. Go to [**SDKs & integrations**](https://dashboard.fingerprint.com/integrations) > **Cloudflare** and click **Delete integration** at the bottom of the page.
This deletes the Cloudflare worker and all associated records.
## Alternative worker subdomain setup (for DNS-only domains)
The Cloudflare configuration guide above assumes your website is added to Cloudflare and [proxied through Cloudflare](https://developers.cloudflare.com/dns/manage-dns-records/reference/proxied-dns-records/) instead of DNS-only. If your website uses DNS-only mode, the worker is not accessible on the generated path and the provided code snippets do not work.
If you cannot proxy your primary domain through Cloudflare, you can host the Fingerprint worker on a subdomain instead. This still requires that your website is [added to Cloudflare](https://developers.cloudflare.com/fundamentals/setup/manage-domains/add-site/) without proxying.
1. Follow the installation steps above to deploy the proxy integration worker in your Cloudflare account.
2. Go to your [Cloudflare dashboard](https://dash.cloudflare.com/).
3. In the left-hand navigation, click **Workers & Pages**.
4. Click your Fingerprint Cloudflare worker. It will be named like **fingerprint-pro-cloudflare-worker-yourwebsite-com**.
5. At the top, click **Settings**, then find **Domains & Routes**.
6. Click **+ Add** and select **Custom domain**.
7. Enter a subdomain like `metrics.yourwebsite.com` and click **Add domain**. Avoid terms commonly blocked by ad blockers like `fingerprint`, `fpjs`, or `track`.
Your new subdomain will be proxied through Cloudflare, even though your primary domain is not:
Your Fingerprint worker is now accessible on the chosen subdomain. Update the code snippets shown on [**SDKs & integrations**](https://dashboard.fingerprint.com/integrations) accordingly. For example, `endpoints: https://yourwebsite.com/VWmFUKL1dfIjc8gg/?region=us` becomes `endpoints: https://metrics.yourwebsite.com/VWmFUKL1dfIjc8gg/?region=us`.
# Blocking Origins and IPs
Source: https://docs.fingerprint.com/docs/cloudflare-integration-blocking-ips-and-origins
Fingerprint allows you to [filter requests](/docs/request-filtering) to prevent bad actors from making identification requests using your account. Filtered-out requests do not count toward your Fingerprint billing. However, if you use Cloudflare Proxy Integration, those requests can still be proxied through your Cloudflare worker, which can increase your Cloudflare costs.
To prevent this, you can block specific IPs or origins at the Cloudflare worker level by using Cloudflare's Web Application Firewall.
**Cloudflare Web Application Firewall**
* You are limited to 5 WAF custom rules on the Cloudflare [free plan](https://www.cloudflare.com/plans/).
* This guide uses the Cloudflare dashboard to create WAF custom rules manually. You can also create and modify custom rules programmatically using the [Cloudflare API](https://developers.cloudflare.com/waf/custom-rules/create-api/).
## 1. Find the worker route
The JavaScript agent v4 uses a single worker route for all requests to Fingerprint servers.
The easiest way to find your route is from the JavaScript agent configuration snippet on **SDKs & integrations** > **Cloudflare**:
1. Go to **SDKs & integrations** > **Cloudflare** in the Fingerprint dashboard.
2. Find the JavaScript agent configuration snippet.
3. Copy the `WORKER_PATH` part from the `endpoints` value.
For example, if your snippet uses `https://yourwebsite.com/icXhT6JSJ2MhAdk6/?region=us`, then your worker route to protect is `/icXhT6JSJ2MhAdk6*`.
## 2. Create a firewall rule
Create a [custom rule](https://developers.cloudflare.com/waf/custom-rules/) in your Cloudflare WAF.
1. In the Cloudflare dashboard, go to **Websites** > **Your website** > **Security** > **WAF** > **Custom rules**.
2. Click **Create rule**.
3. Enter a name for the rule.
4. Use the visual editor or click **Edit expression** to define the rule using the [Rules language](https://developers.cloudflare.com/ruleset-engine/rules-language/).
5. Choose `Block` as the rule action.
6. Click **Deploy**.
### Block requests from specific IP addresses
For example, to block IPs `111.111.112.113`, `114.112.222.33`, and the entire `211.12.82.0/24` subnet for worker route `/icXhT6JSJ2MhAdk6*`, use this expression:
```txt theme={"theme":"github-dark-dimmed"}
(
ip.src in {111.111.112.113, 114.112.222.33, 211.12.82.0/24}
and starts_with(http.request.uri.path, "/icXhT6JSJ2MhAdk6")
)
```
### Block requests from specific `origin` and `referer`
For example, to block requests from `example1.com` for the same worker route, use this expression:
```txt theme={"theme":"github-dark-dimmed"}
(
starts_with(http.request.uri.path, "/icXhT6JSJ2MhAdk6")
and
(
any(http.request.headers["origin"][*] == "https://example1.com")
or
any(http.request.headers["referer"][*] contains "https://example1.com/")
)
)
```
Block requests by `referer` in addition to `origin` because some request types [do not include the `origin` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin#description).
See the [Cloudflare rules language documentation](https://developers.cloudflare.com/ruleset-engine/rules-language/) for more details.
### Cloudflare limitations
* Each rule expression can contain up to [4096 symbols](https://developers.cloudflare.com/ruleset-engine/rules-language/expressions/#maximum-rule-expression-length)
* The number of custom rules depends on [your Cloudflare plan](https://www.cloudflare.com/plans/)
# Migrating CloudFront proxy integration to JavaScript Agent V4
Source: https://docs.fingerprint.com/docs/cloudfront-integration-migration-to-js-agent-v4
This migration guide describes how to switch to JavaScript Agent V4 in your Fingerprint CloudFront integration with zero downtime and without the need to update the proxy path used by your [JavaScript Agent](/reference/js-agent).
## Migration overview
**Upgrade to v2 first**
If your CloudFront integration is still on v1, follow the [migrating CloudFront proxy integration from v1 to v2 guide](/docs/v3/cloudfront-integration-migration-from-v1-to-v2) to upgrade to **2.2.0** (or later) first. Then, return to this guide to finish migrating to JavaScript Agent V4.
1. Update your CloudFront Integration to the latest version that supports JavaScript Agent V4 (**2.2.0** or later) using your preferred deployment method.
2. Update your CloudFront distribution cache behavior path.
3. Verify that the updated integration works.
4. Switch from JavaScript Agent V3 to V4 in your application.
## Step 1: Update your CloudFront Integration's Lambda function
Follow the update guide for your deployment method:
* **CloudFormation**:
* If you've enabled the [automatic updates](/docs/install-cloudfront-integration-using-cloudformation#step-6-enable-automatic-updates-in-the-fingerprint-dashboard) you should already be using the latest version of the Lambda function.
* **Important**: Due to recent changes on AWS, you might need to update your Management Lambda function permissions. To do so, follow the [CloudFront Management Lambda permissions update](/docs/cloudfront-cloudformation-mgmt-lambda-permissions-update) guide.
* If you are not using automatic updates, you need to [update the Lambda function manually](/docs/manually-updating-your-aws-cloudfront-integration-lambda-function).
* **Terraform**:
* [Update the Lambda function in your Terraform configuration](/docs/aws-cloudfront-integration-via-terraform#updating-the-integration).
To verify the integration version, check your integration status page available at `https://yourwebsite.com/FPJS_BEHAVIOR_PATH/status`.
## Step 2: Update your CloudFront distribution cache behavior path
Depending on your deployment method, you need to update the cache behavior path in your CloudFront distribution in the following ways:
The latest version of the Lambda function maintains compatibility with your existing CloudFront
configuration and JavaScript agent v3, but the following CloudFront configuration change is
recommended before upgrading to JavaScript agent v4.
### CloudFormation
1. Head to your CloudFront distribution in the AWS console.
2. Select the **Behaviors** tab and edit the **fpcdn.io** behavior.
3. Update the **Path Pattern** from `FPJS_BEHAVIOR_PATH/*` to `FPJS_BEHAVIOR_PATH*` (without the trailing slash):
```diff theme={"theme":"github-dark-dimmed"}
- FPJS_BEHAVIOR_PATH/*
+ FPJS_BEHAVIOR_PATH*
```
4. Save the changes.
5. If your behavior path contains more than one segment (e.g. `ore54guier/vbcnkxb654`):
1. Head to the **Origins** tab and edit the **fpcdn.io** origin.
2. Add a custom header:
* Set **Header name** to `FPJS_INTEGRATION_PATH_DEPTH`.
* Set **Value** to the number of path segments in the behavior path, in our example it's `2`.
```
Segment Segment
↓ ↓
ore54guier / vbcnkxb654 → 2 path segments
```
3. Save the changes.
### Terraform
Ensure that you're using our Terraform Module version [v2.0.0](https://github.com/fingerprintjs/terraform-aws-fingerprint-cloudfront-proxy-integration/releases/tag/v2.0.0) or later.
Update your CloudFront distribution cache behavior `path_pattern` from `FPJS_BEHAVIOR_PATH/*` to `FPJS_BEHAVIOR_PATH*` (without the trailing slash)
```terraform main.tf theme={"theme":"github-dark-dimmed"}
resource "aws_cloudfront_distribution" "cloudfront_dist" {
ordered_cache_behavior {
path_pattern = "FPJS_BEHAVIOR_PATH/*" // [!code --]
path_pattern = "FPJS_BEHAVIOR_PATH*" // [!code ++]
}
}
```
If your behavior path contains more than one segment (e.g. `ore54guier/vbcnkxb654`), you need to count the number of path segments in the behavior path and add the `integration_path_depth` parameter with value equal to the number of path segments:
```
Segment Segment
↓ ↓
ore54guier / vbcnkxb654 → 2 path segments
```
```terraform main.tf theme={"theme":"github-dark-dimmed"}
module "fingerprint_cloudfront_integration" {
source = "fingerprintjs/fingerprint-cloudfront-proxy-integration/aws"
fpjs_shared_secret = "FPJS_PRE_SHARED_SECRET"
// The number of path segments in the behavior path, in this case 2: ore54guier/vbcnkxb654 // [!code ++]
integration_path_depth = 2 // [!code ++]
}
resource "aws_cloudfront_distribution" "cloudfront_dist" {
// Your existing CloudFront configuration
ordered_cache_behavior {
path_pattern = "ore54guier/vbcnkxb654/*" // [!code --]
path_pattern = "ore54guier/vbcnkxb654*" // [!code ++]
}
}
```
Finally, apply the changes:
1. Run `terraform plan` to verify the planned configuration changes.
2. Run `terraform apply` to apply the changes.
## Step 3: Update your application to use the JavaScript Agent V4
At this point, you should have CloudFront integration running that will work with both v3 and v4 JavaScript agents.
Try using the integration with the V4 agent. For example:
```javascript JavaScript theme={"theme":"github-dark-dimmed"}
import * as Fingerprint from "@fingerprint/agent";
const fp = Fingerprint.start({
apiKey: "PUBLIC_API_KEY",
region: "us",
endpoints: "https://yourwebsite.com/FPJS_BEHAVIOR_PATH/?region=us",
});
fp.get().then((result) => console.log(result));
```
All requests from the agent should go through your integration and resolve successfully. Confirm by using the Network tab in your browser Developer tools, as the JavaScript agent can fall back to default endpoints in case of an integration error.
## Step 4: Switch from JavaScript Agent V3 to V4
At this point, you can start migrating your application to use the new JavaScript Agent V4.
```javascript JavaScript theme={"theme":"github-dark-dimmed"}
import * as FingerprintJS from "@fingerprintjs/fingerprintjs-pro"; // [!code --]
import * as Fingerprint from "@fingerprint/agent"; // [!code ++]
// [!code --:10]
const fpPromise = FingerprintJS.load({
apiKey: "PUBLIC_API_KEY",
region: "us",
scriptUrlPattern: [
"https://yourwebsite.com/FPJS_BEHAVIOR_PATH/FPJS_AGENT_DOWNLOAD_PATH?apiKey=&version=&loaderVersion=",
],
endpoint: ["https://yourwebsite.com/FPJS_BEHAVIOR_PATH/FPJS_GET_RESULT_PATH?region=us"],
});
// [!code ++:10]
const fp = Fingerprint.start({
apiKey: "PUBLIC_API_KEY",
region: "us",
endpoints: "https://yourwebsite.com/FPJS_BEHAVIOR_PATH/?region=us",
});
```
## What's next
To learn more, see the [JavaScript Agent V4 migration guide](/reference/migrating-from-v3-to-v4).
# Overview
Source: https://docs.fingerprint.com/docs/cloudfront-proxy-integration-v2
*Fingerprint CloudFront Proxy Integration* is responsible for proxying identification and agent-download requests between your website and Fingerprint through your AWS infrastructure. Your website does not strictly need to be behind CloudFront or run on AWS to use this proxy integration, although that is optimal for ease of setup and maximum accuracy benefits.
**JavaScript Agent V4 Support**
Support for JavaScript Agent V4 was introduced in version **2.2.0**.
**Limitations**
This integration is exclusively supported for customers on the **Enterprise** Plan. Other customers are encouraged to use [Custom subdomain setup](/docs/custom-subdomain-setup) or [Cloudflare Proxy Integration](/docs/cloudflare-integration).
**Update expectations**
The underlying data contract in the identification logic can change to keep up with browser and device releases. Using the AWS CloudFront Proxy Integration might require occasional manual updates on your side. Ignoring these updates will lead to lower accuracy or service disruption.
The integration consists of several components:
* Your website, which may be running on AWS CloudFront but doesn't have to.
* An [AWS CloudFront](https://aws.amazon.com/cloudfront/) distribution
* If your website is already running on CloudFront, you can use the same distribution for the proxy integration. This is the recommended setup to maximize the accuracy benefits of the integration.
* If your website is not running on CloudFront, you can create a new CloudFront distribution just for the proxy integration.
* [AWS Lambda@Edge Function](https://aws.amazon.com/lambda/edge/) that handles proxying requests to Fingerprint resources. We will refer to it as *"Lambda function*" going forward. It is available on a specific path on your site. The rest of your site is not affected.
* Additional cache behavior that enables using a Lambda function for proxying requests.
* AWS Secrets that store settings for Lambda and Management functions.
* If you choose the [CloudFormation installation method](/docs/install-cloudfront-integration-using-cloudformation), the integration also includes:
* Management Lambda function that is responsible for updating the integration. We will refer to it as *"Management function"* going forward.
* A [CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/Welcome.html) template and stack responsible for deploying the AWS resources listed above.
The Lambda proxy function code and the entire CloudFormation stack are 100% open-source and [available on GitHub](https://github.com/fingerprintjs/aws-cloudfront-proxy). Once the Fingerprint JavaScript agent is configured on your site correctly, the Lambda function is responsible for delivering the latest fingerprinting client-side logic as well as proxying identification requests and responses between your site and Fingerprint APIs.
## The benefits of using the CloudFront Integration
* Ad blockers will not block the Fingerprint JavaScript agent from loading. Connecting to an external URL is stopped by most ad blockers but connecting to the same site URL is allowed.
* Significant increase in accuracy in browsers with strict privacy features such as Safari or Firefox.
* Cookies are now recognized as “first-party.” This means they can live in the browser even when third-party cookies are blocked and extend the lifetime of visitor information.
* Insight and control over the identification requests that can be combined with other AWS features like [CloudWatch](https://aws.amazon.com/cloudwatch/).
* With the CloudFront Integration, you can manage an unlimited number of subdomains and paths. You can package and provide Fingerprint services to all your customers at any scale while benefiting from all the 1st-party integration improvements.
* Cookie security: CloudFront integration drops all cookies sent from the origin website. The Lambda function code is open-source so this behavior can be transparently verified and audited.
* Easy to meet compliance and auditing requirements.
## Prerequisites
* An AWS account.
* Optionally: An existing CloudFront distribution that serves your web application.
* If your application does not run on CloudFront, the installation guide will help you create a new CloudFront distribution deployed on the same [eTLD + 1](https://developer.mozilla.org/en-US/docs/Glossary/eTLD) domain as your website, or any of its subdomains.
## Integration setup overview
The integration setup consists of several manual and automatic steps. Each step is discussed in detail below.
1. Issue a proxy secret in the Fingerprint Dashboard.
2. Create a path variable that is used by the AWS configuration and the client agent configuration on your website or mobile app.
3. Install and configure the integration in your AWS account. You have 2 options:
1. [Using a CloudFormation template](/docs/install-cloudfront-integration-using-cloudformation) and the AWS user interface.
2. [Using Terraform](/docs/aws-cloudfront-integration-via-terraform).
Finally, configure the Fingerprint JavaScript agent on your website or client application.
## Step 1: Issue a proxy secret
Your integration needs a proxy secret to authenticate requests from your Lambda function to Fingerprint servers.
1. Go to the Fingerprint [Dashboard](https://dashboard.fingerprint.com) and select your workspace in the top left.
2. Navigate to [**API Keys**](https://dashboard.fingerprint.com/api-keys).
3. Click **Create proxy key**.
4. Name it something like `CloudFront integration`.
5. Optionally, you can [choose an environment](/docs/multiple-environments#proxy-integrations-and-proxy-secrets) for the proxy secret.
1. By default, the proxy secret works for all environments in your workspace.
2. A proxy secret scoped to a specific environment can only authenticate identification requests made with a public API key from the same environment.
6. Click **Create API key**.
7. Save the secret value somewhere. You will use it later as the `FPJS_PRE_SHARED_SECRET` variable.
## Step 2: Create path variable
Set the path variable you will use throughout your AWS configuration and the JavaScript agent configuration on your website. This value is arbitrary. Just decide what your value is and write it down somewhere.
In this guide, we will use readable values corresponding to the variable names to make it easier to follow:
```dotenv theme={"theme":"github-dark-dimmed"}
FPJS_BEHAVIOR_PATH="FPJS_BEHAVIOR_PATH"
```
However, your values used in production should look more like random strings:
```dotenv theme={"theme":"github-dark-dimmed"}
FPJS_BEHAVIOR_PATH="ore54guier"
```
That is because some adblockers might automatically block requests from any URL containing fingerprint-related terms like "fingerprint", "fpjs", "track", etc. Random strings are the safest. So whenever you see a value like `FPJS_BEHAVIOR_PATH` in this guide, you should use your random value instead.
## Step 3: Install and configure the integration in your AWS account
You can choose from two available installation methods.
### Using Terraform
If you manage your infrastructure as code using Terraform, you can use the Fingerprint CloudFront proxy integration Terraform module. This installation method does not include any mechanism for automatic updates. The proxy function code is updated every time you run `terraform apply`.
If you prefer the Terraform installation method, continue with [**Install CloudFront integration using Terraform**](/docs/aws-cloudfront-integration-via-terraform).
### Using a CloudFormation template
If you don't use Terraform to manage your infrastructure, you can deploy the integration inside the AWS Console using a CloudFormation template. This installation method includes a Management Lambda function responsible for keeping your integration up-to-date. You will need to provide credentials for the Management function to the Fingerprint Dashboard to enable automatic updates.
If you prefer the CloudFormation installation method, continue with [**Install CloudFront integration using CloudFormation**](/docs/install-cloudfront-integration-using-cloudformation).
## Configure the Fingerprint JavaScript agent on your client
Use the path variable created in [Create path variable](#step-2-create-path-variable) to construct the endpoint URL.
If your website and the proxy integration are behind the same CloudFront distribution, the JavaScript Agent configuration will use randomized paths inside your domain, for example:
```javascript NPM package theme={"theme":"github-dark-dimmed"}
import * as Fingerprint from '@fingerprint/agent'
// Initialize the agent at application startup.
const fp = Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
region: 'us',
endpoints: 'https://yourwebsite.com/FPJS_BEHAVIOR_PATH/?region=us'
});
```
```javascript CDN theme={"theme":"github-dark-dimmed"}
const url = 'https://yourwebsite.com/FPJS_BEHAVIOR_PATH/web/v4/PUBLIC_API_KEY';
const fpPromise = import(url)
.then(Fingerprint => Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
region: 'us',
endpoints: 'https://yourwebsite.com/FPJS_BEHAVIOR_PATH/?region=us',
}));
```
If you set up a separate CloudFront distribution on your subdomain, the JavaScript Agent configuration will use that subdomain to interact with Fingerprint, for example:
```javascript NPM package theme={"theme":"github-dark-dimmed"}
import * as Fingerprint from '@fingerprint/agent'
// Initialize the agent at application startup.
const fp = Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
region: 'us',
endpoints: 'https://metrics.yourwebsite.com/FPJS_BEHAVIOR_PATH/?region=us'
});
```
```javascript CDN theme={"theme":"github-dark-dimmed"}
const url = 'https://metrics.yourwebsite.com/FPJS_BEHAVIOR_PATH/web/v4/PUBLIC_API_KEY';
const fpPromise = import(url)
.then(Fingerprint => Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
region: 'us',
endpoints: 'https://metrics.yourwebsite.com/FPJS_BEHAVIOR_PATH/?region=us',
}));
```
If everything is configured correctly, you should receive data through your CloudFront distribution successfully.
**Parameter URL nuances**
* Pass a `region` query parameter to the `endpoints` URL in the following format: `?region=eu`. The value needs to reflect the [region](/docs/regions) of your workspace.
## Monitoring and managing the integration
Go to **Dashboard** > [**SDKs & integrations**](https://dashboard.fingerprint.com/integrations) > **AWS CloudFront** to see the status of your integration. Here you can monitor:
* If automatic updates are enabled (only relevant for the [CloudFormation installation method](/docs/install-cloudfront-integration-using-cloudformation)).
* If the integration is up to date.
* How many identification requests are coming through the integration (and how many are not).
* The error rate of proxied identification requests (caused by a missing, incorrect, or environment-mismatched proxy secret).
The information on the status page is cached, so allow a few minutes for the latest data points to be reflected.
If you installed the integration [using CloudFormation](/docs/install-cloudfront-integration-using-cloudformation), you can also disable automatic updates (not recommended) or update the provided Management function details.
## Cost calculation for AWS
You can use [AWS Calculator](https://calculator.aws/) to estimate your expenses.
**Lambda\@Edge service**
* The **number of requests** is roughly equal to visitor identification events multiplied by three.
There are up to 3 requests for each visitor identification.
* The first request downloads the agent script. This request is typically cached by the browser.
* The second request calls the identification endpoint. Caching the identification request is up to you, but most of our [frontend libraries](/docs/frontend-libraries) have a caching mechanism built in.
* Potentially, a third helper request may be used to improve accuracy. This request is typically cached by the browser.
* The **duration of each request** depends on your CloudFront availability settings. The typical duration of the agent download request is 200ms, for identification requests it is 500ms.
* The **amount of memory allocated** is 128MB.
* The Lambda function has [CloudWatch logs](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/edge-functions-logs.html) enabled.
**Secrets Manager**
* The **number of secrets** is 2.
* The **number of API calls** is approximately 12 calls per hour for each used AWS region.
**Additional costs for the CloudFront distribution**
* Data transfer **from** CloudFront distribution to clients:
* The agent’s size is approximately 50-60 KB, depending on the exact version and network compression algorithm used (some browsers may not support `br` encoding).
* The identification request result is up to 1 KB per request.
* Data transfer from clients **to** the CloudFront distribution:
* The payload size for the identification requests is approximately 5-10kB.
# Configuring resources
Source: https://docs.fingerprint.com/docs/configuration
The Dashboard allows you to manage all the resources and settings that govern how your Fingerprint implementation operates.
## API keys
API keys are necessary for your Fingerprint setup. Primarily to be able to identify visitors and generate visitor IDs, but also interact with other Fingerprint APIs and features.
To manage API keys, go to **Dashboard** > [**API keys**](https://dashboard.fingerprint.com/api-keys).
| Keys | Description |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Public** | Used to make **client-side requests** to identify visitors and generate the visitor ID. Used in the [browser](/docs/identify-visitors) or in [mobile apps](/docs/mobile-identification). |
| **Server API** | Used to make **server-side requests** to the Fingerprint [Server API](/reference/server-api) to securely obtain visitor and request information. |
| **Management API** | Used to access the [**Management API**](/reference/management-api), allowing management of dashboard resources. |
| **Sealed Client Results** | Used to enable **Sealed Client Results** to [encrypt client-side payloads](/docs/sealed-client-results). |
| **Proxy** | Used to authenticate with third-party **proxy integrations** which allow you to [evade ad blockers](/docs/protecting-the-javascript-agent-from-adblockers). |
To monitor API key usage, throttling, and latency, see [API key monitoring](/docs/health#api-key-monitoring).
## Request filtering
Request filtering is a feature that enables you to filter out unwanted identification requests using specific rules. On the Dashboard, you can **manage filtering rules** by adjusting which events should be allowed or denied.
To learn more information, see [request filtering](/docs/request-filtering).
To manage webhooks, go to **Dashboard** > [**Security**](https://dashboard.fingerprint.com/traffic-rules).
## Webhooks
Webhooks allow you to receive an HTTP request to your server with Fingerprint results for every identification event the moment it happens. On the Dashboard, you can **manage webhooks** and monitor their activity.
To learn more information, see [webhooks](/docs/webhooks).
To manage webhooks, go to **Dashboard** > [**Webhooks**](https://dashboard.fingerprint.com/webhooks).
To inspect webhook delivery attempts and diagnose failures, see [Webhook monitoring](/docs/health#webhook-monitoring).
## Custom subdomains
By using a custom subdomain, Fingerprint cookies are treated as "first-party", which increases identification accuracy while also allows you to avoid ad blockers. On the Dashboard, you can **register your subdomains** and manage their certificates.
To learn more information, see [Custom subdomain setup](/docs/custom-subdomain-setup).
To manage subdomains, go to **Settings** > [**Subdomains**](https://dashboard.fingerprint.com/subdomains).
## Proxy integrations
Proxy integrations are an alternative to custom subdomains, they allow you to proxy requests to Fingerprint through your own domain. Our cookies are treated as "first-party", which increased identification accuracy while allowing you to avoid ad blockers. On the Dashboard, you can **setup proxy integrations** and monitor their status and activity.
To learn more information, see [Evading ad blockers with proxy integrations](/docs/protecting-the-javascript-agent-from-adblockers).
To manage proxy integrations, go to **Dashboard** > [**Libraries & integrations**](https://dashboard.fpjs.sh/integrations/active)
## Suspect Score
Suspect Score is an easy way to integrate Smart Signals into your fraud protection workflow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. On the Dashboard, you can configure the **weights for suspect scoring** by adjusting for each detected Smart Signal.
To learn more information, see [Suspect Score](/docs/suspect-score).
To manage suspect score weights, go to **Dashboard** > **Smart Signals** > [**Suspect Score**](https://dashboard.fpjs.sh/suspect-score)
***
[Viewing analytics](/docs/analytics)
# Coupon Abuse
Source: https://docs.fingerprint.com/docs/coupon-abuse-use-case-tutorial
Learn how to prevent coupon abuse and protect your promotions from fraud
## Overview
This tutorial walks through implementing Fingerprint to prevent coupon abuse, where customers or fraudsters repeatedly redeem a discount code meant for one-per-user use.
You'll begin with a starter app that includes a mock checkout page and a basic coupon flow. From there, you'll add the JavaScript agent to identify each visitor and use server-side logic with Fingerprint data to check if they've already redeemed the coupon. This way, you can block repeat coupon redemptions from the same visitor.
By the end, you'll have a sample app that enforces one-time coupon usage per visitor 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 backend. For language- or framework-specific setups, see the quickstarts.
> Estimated time: \< 15 minutes
## 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 `coupon-abuse` folder. The project is organized as follows:
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 checkout page from the starter app. You can test out the basic coupon flow by entering a valid coupon code (e.g., `WELCOME20`, `SAVE10`) and clicking **Apply**. You'll notice the same coupon can currently be redeemed multiple times.
## 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 **Apply**. The JavaScript agent returns both a `visitor_id` and an `event_id`. Instead of relying on the `visitor_id` from the browser, you'll send the `event_id` to your server along with the coupon code. The server will then call the [Fingerprint Events API](/reference/server-api-get-event) to securely retrieve the full identification details, including the verified visitor ID 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/v4/${window.FP_PUBLIC_API_KEY}`).then((Fingerprint) =>
Fingerprint.start({ 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 **Apply** button already has an event handler set up for submitting a coupon. Inside this handler, request visitor identification from Fingerprint using the `get()` method and include the returned `event_id` when sending the coupon to the server:
```javascript public/index.js theme={"theme":"github-dark-dimmed"}
applyBtn.addEventListener("click", async () => {
// ...
const fp = await fpPromise;
const { event_id: eventId } = await fp.get();
try {
const res = await fetch("/api/validate-coupon", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ coupon: code, eventId }),
});
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 `event_id` 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/sealed-client-results) to return full identification details as an encrypted payload from the `get()` method.
## 4. Receive and use the event ID to get visitor insights
Next, pass the `eventId` through to your coupon validation logic, initialize the [Fingerprint Node Server SDK](/reference/node-server-sdk), and fetch the full visitor identification event so you can access the trusted visitor ID 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/validate-coupon` route there to also extract `eventId` from the request body and pass it into the `validateCoupon` function:
```javascript server/server.js theme={"theme":"github-dark-dimmed"}
app.post("/api/validate-coupon", async (req, reply) => {
const { coupon, eventId } = req.body || {};
const code = (coupon || "").toUpperCase().trim();
const result = await validateCoupon(code, eventId);
return reply.send(result);
});
```
2. The `server/coupons.js` file contains the logic for validating coupons. Start by importing and initializing the Fingerprint Node Server SDK there, and load your environment variables with `dotenv`.
```javascript server/coupons.js theme={"theme":"github-dark-dimmed"}
import { db } from "./db.js";
import { config } from "dotenv";
import { FingerprintServerApiClient, Region } from "@fingerprint/node-sdk";
config();
const fpServerApiClient = new FingerprintServerApiClient({
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 `validateCoupon` function to accept `eventId` and use it to fetch the full identification event details from Fingerprint:
```javascript server/coupons.js theme={"theme":"github-dark-dimmed"}
export async function validateCoupon(code, eventId) {
if (!code) {
console.error("Missing coupon code.");
return { success: false, error: "Coupon validation failed." };
}
if (!eventId) {
console.error("Missing eventId.");
return { success: false, error: "Coupon validation failed." };
}
const coupon = getValidCoupon(code);
if (!coupon) {
console.error("Invalid coupon code.");
return { success: false, error: "Coupon validation failed." };
}
const event = await fpServerApiClient.getEvent(eventId);
// ...
}
```
Using the `eventId`, the getEvent method will retrieve the full data for the visitor identification event. 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/protecting-from-client-side-tampering).
## 5. Block bots and suspicious devices
This optional step uses the Bot Detection and Suspect Score Smart Signals, which are only
available on paid plans.
A simple but powerful way to prevent fraudulent coupon abuse is to block automated applications that come from bots. 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 `not_detected` when no bot activity is found.
1. Continuing in the `validateCoupon` function in `server/coupons.js`, check the bot signal returned in the `event` object:
```javascript server/coupons.js theme={"theme":"github-dark-dimmed"}
export async function validateCoupon(code, eventId) {
// ...
const event = await fpServerApiClient.getEvent(eventId);
const botDetected = event.bot !== "not_detected";
if (botDetected) {
console.error("Bot detected.");
return { success: false, error: "Coupon validation failed." };
}
// ...
}
```
You can also use Fingerprint's [Suspect Score](/docs/suspect-score) to flag suspicious coupon redemption attempts. 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 actions 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 some additional friction.
2. Below the bot detection check, add a condition that reads the Suspect Score from the `event` object and blocks the coupon if it exceeds a chosen threshold (for example, 20):
```javascript server/coupons.js theme={"theme":"github-dark-dimmed"}
export async function validateCoupon(code, eventId) {
// ...
const suspectScore = event.suspect_score || 0;
if (suspectScore > 20) {
console.error(`High Suspect Score detected: ${suspectScore}`);
return { success: false, error: "Coupon validation failed." };
}
// ...
}
```
## 6. Prevent multiple uses of coupons per visitor
Next, use the trusted visitor ID from the `event` object to enforce one-time coupon usage per visitor. If the same visitor ID tries to reuse the same coupon code, return a failure; otherwise, record the redemption and allow it. *(This example simplifies coupon redemption logic for demonstration purposes.)*
This makes coupon rules enforceable at the device level, not just the account level. Even if someone creates new accounts or checks out as a guest, the redemption is still linked to their device, keeping one-time coupons truly one-time.
Note: The starter app includes a SQLite database with these tables already created for you:
```text SQLite database tables theme={"theme":"github-dark-dimmed"}
coupons - Stores available coupon codes and discounts
code TEXT PRIMARY KEY
discountPct INTEGER NOT NULL
redemptions - Records which devices (visitorId) have redeemed a coupon
id INTEGER PRIMARY KEY AUTOINCREMENT
code TEXT NOT NULL
visitorId TEXT
createdAt INTEGER NOT NULL
```
1. Add some helper functions to the bottom of the `server/coupons.js` file to check and record coupon redemptions:
```javascript server/coupons.js theme={"theme":"github-dark-dimmed"}
// Check if the visitor has already redeemed the coupon code
function hasRedeemed(code, visitorId) {
return !!db
.prepare(`SELECT 1 FROM redemptions WHERE code = ? AND visitorId = ? LIMIT 1`)
.get(code, visitorId);
}
// Record the visitor redeeming the coupon
function recordRedemption(code, visitorId) {
db.prepare(`INSERT INTO redemptions (code, visitorId, createdAt) VALUES (?, ?, ?)`).run(
code,
visitorId,
Date.now(),
);
}
```
2. Update `validateCoupon` to retrieve the `visitorId` and use it to enforce the one-per-visitor coupon redemption rule and record successful redemptions:
```javascript server/coupons.js theme={"theme":"github-dark-dimmed"}
export async function validateCoupon(code, eventId) {
// ...
if (botDetected) {
console.error("Bot detected.");
return { success: false, error: "Coupon validation failed." };
}
const visitorId = event.identification.visitor_id;
if (hasRedeemed(coupon.code, visitorId)) {
console.error("Coupon has already been redeemed.");
return { success: false, error: "Coupon has already been redeemed." };
}
recordRedemption(coupon.code, visitorId);
return { success: true, ...coupon };
}
```
This gives you a system to detect and block coupon abuse. You can extend it by allowing a limited number of redemptions per device, tailoring responses to your business rules, checking only recent redemption activity, etc.
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 data handling that align with your
production standards.
## 7. Test your implementation
Now that everything is wired up, you can test the full protected coupon flow using the checkout page.
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. In the checkout form, enter a valid coupon (e.g., `WELCOME20`, `SAVE10`) and click **Apply**. You will see the discount applied.
3. Try applying the same coupon again. The second attempt will be rejected since the visitor has already redeemed that coupon.
4. Open the page in incognito and try to use the coupon again, your visitor ID will remain the same and the redemption is still blocked.
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 coupon redemptions are blocked.
## Next steps
You now have a working coupon redemption flow secured with Fingerprint. From here, you can expand the logic with more [Smart Signals](/docs/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](/reference/node-server-sdk)
* [Vue frontend quickstart](/docs/vue-quickstart)
* [React frontend quickstart](/docs/react-quickstart)
* [API reference for the Events endpoint](/reference/server-api-get-event)
* [Use case tutorial: Detecting new account fraud](/docs/new-account-fraud-use-case-tutorial)
* [Low-latency identification with Sealed Client Results](/docs/sealed-client-results)
# Credential Stuffing
Source: https://docs.fingerprint.com/docs/credential-stuffing-use-case-tutorial
Learn how to protect your application from credential stuffing attacks
## Overview
This tutorial walks through implementing Fingerprint to prevent credential stuffing, where attackers use bots to rapidly test stolen or guessed usernames and passwords across many accounts.
You'll begin with a starter app that includes a mock login page and a basic login 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 automated login attempts.
By the end, you'll have a sample app that rejects credential stuffing 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 backend. For language- or framework-specific setups, see the 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](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 `credential-stuffing` folder. The project is organized as follows:
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 login page from the starter app. You can test the basic login form using the included test account (`demo@example.com` / `password123`) and clicking **Log in**.
7. Then try to log in using the included headless bot test script `test-bot.js`. While the app is running, execute `node test-bot.js` and observe that the automated script logs in successfully. By default, the server does not distinguish between bots and real users.
```bash Terminal theme={"theme":"github-dark-dimmed"}
node test-bot.js
```
## 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 **Log in**. The JavaScript agent returns both a `visitor_id` and an `event_id`. Instead of relying on the `visitor_id` from the browser, you'll send the `event_id` to your server along with the login payload. The server will then call the [Fingerprint Events API](/reference/server-api-get-event) to securely retrieve the full identification details, including bot detection and other 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/v4/${window.FP_PUBLIC_API_KEY}`).then((Fingerprint) =>
Fingerprint.start({ 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 **Log in** 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 `event_id` when sending the login to the server:
```javascript public/index.js theme={"theme":"github-dark-dimmed"}
loginBtn.addEventListener("click", async () => {
// ...
const fp = await fpPromise;
const { event_id: eventId } = await fp.get();
try {
const res = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password, eventId }),
});
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 `event_id` 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/sealed-client-results) to return full identification details as an encrypted payload from the `get()` method.
## 4. Receive and use the event ID to get visitor insights
Next, pass the `eventId` through to your login logic, initialize the [Fingerprint Node Server SDK](/reference/node-server-sdk), and fetch the full visitor identification event so you can access the trusted visitor ID and Bot Detection [Smart Signal](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/login` route there to also extract `eventId` from the request body and pass it into the `attemptLogin` function:
```javascript server/server.js theme={"theme":"github-dark-dimmed"}
app.post("/api/login", async (req, reply) => {
const { email, password, eventId } = req.body || {};
const result = await attemptLogin({ email, password, eventId });
return reply.send(result);
});
```
2. The `server/accounts.js` file contains the logic for handling logins. 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 { FingerprintServerApiClient, Region } from "@fingerprint/node-sdk";
config();
const fpServerApiClient = new FingerprintServerApiClient({
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 `attemptLogin` function to accept `eventId` and use it to fetch the full identification event details from Fingerprint:
```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
export async function attemptLogin({ email, password, eventId }) {
if (!email || !password) {
console.error("Missing credentials.");
return { success: false, error: "Login failed." };
}
if (!eventId) {
console.error("Missing eventId.");
return { success: false, error: "Login failed." };
}
const event = await fpServerApiClient.getEvent(eventId);
const user = findAccountByEmail(email);
if (!user || user.password !== password) {
console.error("Invalid credentials");
return { success: false, error: "Login failed." };
}
return { success: true };
}
```
Using the `eventId`, the getEvent method will retrieve the full data for the visitor identification event. 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/protecting-from-client-side-tampering).
## 5. Block credential stuffing bots
Credential stuffing attacks rely heavily on automated login attempts, so rejecting bots outright can stop the abuse. Fingerprint returns `not_detected` 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 `not_detected` can be blocked from logging in.
1. Continuing in the `attemptLogin` function in `server/accounts.js`, check the bot signal returned in the `event` object and block bots:
```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
export async function attemptLogin({ email, password, eventId }) {
// ...
const event = await fpServerApiClient.getEvent(eventId);
const botDetected = event.bot !== "not_detected";
if (botDetected) {
console.error("Bot detected.");
return { success: false, error: "Bot detected. Login failed." };
}
// ...
}
```
You can also add [Suspect Score](/docs/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 wouldn't normally block logins based only on a high risk score, you could flag them for review, modify rate-limits, or add step-up authentication.
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 attemptLogin({ email, password, eventId }) {
// ...
const botDetected = event.bot !== "not_detected";
if (botDetected) {
console.error("Bot detected.");
return { success: false, error: "Bot detected. Login failed." };
}
const suspectScore = event.suspect_score || 0;
if (suspectScore > 20) {
console.error(`High Suspect Score detected: ${suspectScore}`);
return { success: false, error: "Login failed." };
}
// ...
}
```
## 6. Recognize repeat offenders by visitor ID
As a secondary measure, you can log the visitor ID from failed login attempts to spot repeat attackers. This way, even if they change accounts or rotate IPs, you can recognize and block the same device when it comes back or tries other actions on your site.
Note: The starter app includes a SQLite database with these tables already created for you:
```text SQLite database tables theme={"theme":"github-dark-dimmed"}
accounts - Stores login credentials for test accounts
email TEXT PRIMARY KEY
password TEXT NOT NULL
failed_logins - Logs visitorIds tied to repeated failed attempts
id INTEGER PRIMARY KEY AUTOINCREMENT
visitorId TEXT NOT NULL
createdAt INTEGER NOT NULL
```
1. Add some helper functions to the bottom of the `server/accounts.js` file to record and check failed attempts:
```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
// Log a failed login attempt
function logFailedAttempt(visitorId) {
db.prepare(`INSERT INTO failed_logins (visitorId, createdAt) VALUES (?, ?)`).run(
visitorId,
Date.now(),
);
}
// Get the number of recent failed login attempts
function getRecentFailedAttempts(visitorId) {
const since = Date.now() - 24 * 60 * 60 * 1000; // 24 hours ago
const row = db
.prepare(
`SELECT COUNT(*) as count
FROM failed_logins
WHERE visitorId = ? AND createdAt >= ?`,
)
.get(visitorId, since);
return row.count;
}
```
2. Update `attemptLogin` to retrieve the `visitorId`, log failed attempts, and block repeat offenders:
```javascript server/accounts.js theme={"theme":"github-dark-dimmed"}
export async function attemptLogin({ email, password, eventId }) {
if (!email || !password) {
console.error("Missing email or password.");
return { success: false, error: "Login failed." };
}
if (!eventId) {
console.error("Missing eventId.");
return { success: false, error: "Login failed." };
}
const event = await fpServerApiClient.getEvent(eventId);
const visitorId = event.identification.visitor_id;
const botDetected = event.bot !== "not_detected";
if (botDetected) {
logFailedAttempt(visitorId);
console.error("Bot detected.");
return { success: false, error: "Bot detected. Login failed." };
}
const suspectScore = event.suspect_score || 0;
if (suspectScore > 20) {
logFailedAttempt(visitorId);
console.error(`High Suspect Score detected: ${suspectScore}`);
return { success: false, error: "Login failed." };
}
if (getRecentFailedAttempts(visitorId) >= 3) {
logFailedAttempt(visitorId);
console.error("Too many failed login attempts.");
return { success: false, error: "Too many failed login attempts." };
}
const user = findAccountByEmail(email);
if (!user || user.password !== password) {
logFailedAttempt(visitorId);
console.error("Invalid credentials");
return { success: false, error: "Login failed." };
}
return { success: true };
}
```
Together with the bot detection Smart Signal, this allows you to protect your logins and prevent credential stuffing. You can extend it by analyzing additional signals, changing rate limit thresholds, varying your response, etc.
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 password handling that align with your
production standards.
## 7. Test your implementation
Now that everything is wired up, you can test the full protected login 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 logging in with the valid test account (`demo@example.com` / `password123`). You should see a success response.
3. Now try a few failed login attempts using the wrong password. After 3 repeated failures, additional attempts from the same device will be blocked based on the failed login check.
4. Next, run the included headless bot test script from the `credential-stuffing` folder. This will attempt to log in using a headless browser, which will be flagged by the Bot Detection signal and rejected:
```bash Terminal theme={"theme":"github-dark-dimmed"}
node test-bot.js
```
*Note: If you encounter errors launching the automated browser, make sure you have the testing browser installed:*
```bash Terminal theme={"theme":"github-dark-dimmed"}
npx puppeteer browsers install chrome
```
## Next steps
You now have a working login flow that blocks credential stuffing bots with Fingerprint. From here, you can expand the logic with more [Smart Signals](/docs/smart-signals-reference), fine-tune rules based on your business policies, or layer in additional defenses, such as rate limiting with multi-factor authentication.
To dive deeper, explore the other use case tutorials for more step-by-step examples.
Check out these related resources:
* [Node SDK Reference](/reference/node-server-sdk)
* [Vue frontend quickstart](/docs/vue-quickstart)
* [React frontend quickstart](/docs/react-quickstart)
* [API reference for the Events endpoint](/reference/server-api-get-event)
* [Use case tutorial: Detecting new account fraud](/docs/new-account-fraud-use-case-tutorial)
* [Low-latency identification with Sealed Client Results](/docs/sealed-client-results)
# Custom subdomain setup
Source: https://docs.fingerprint.com/docs/custom-subdomain-setup
Using a custom subdomain is required for correct identification while using Fingerprint.
## The benefits of using a custom subdomain
* Significant increase in accuracy in browsers with strict privacy features such as Safari or Firefox.
* Ad blockers can't block the JavaScript agent. Most ad blockers stop requests sent to an external URL, but allow requests sent to an internal URL like a subdomain.
* Cookies are recognized as "first-party." This means they can be saved in the visitor's browser and used to reliably identify visitors when third-party cookies are blocked completely (for example, Safari blocks all third-party cookies).
* Fingerprint becomes harder to detect. Requests made directly to the Fingerprint domain are easy to detect. Routing through a subdomain on your own domain makes Fingerprint harder for automated blockers and fraudsters to detect.
**Limitations of the subdomain integration**
* Since Safari 16.4, cookie lifetime in Safari is reduced to 7 days when using the custom subdomain setup. To keep cookies lasting up to one year in Safari, use a cloud proxy integration such as the [Cloudflare proxy integration](/docs/cloudflare-integration).
**A note on DNS setup**
This process requires adding DNS records to your domain, so make sure you have access to your DNS through your DNS provider. Here are guides for accessing DNS with some popular providers:
* [Cloudflare](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/)
* [Route53](https://repost.aws/knowledge-center/route-53-create-alias-records)
* [GoDaddy](https://www.godaddy.com/help/add-a-cname-record-19236)
* [Netlify](https://docs.netlify.com/domains-https/custom-domains/configure-external-dns)
* [Dreamhost](https://help.dreamhost.com/hc/en-us/articles/360035516812-Adding-custom-DNS-records)
* [Bluehost](https://my.bluehost.com/hosting/help/resource/714)
## 1. Register your custom subdomain
To use a custom subdomain, the connection between your site and Fingerprint must be secure, so Fingerprint registers every subdomain with an SSL certificate. Each workspace can have up to 50 unique subdomains. During your free trial, you can use up to 5. If you need more than 50, contact the [support team](https://fingerprint.com/support/).
To start, go to **Settings** > [**Subdomains**](https://dashboard.fingerprint.com/subdomains) and select **New subdomain**.
Enter your domain, then select **Add domain**. Add each subdomain individually. After you add a subdomain, you can't edit it. You can view its DNS details at any time, but to change a subdomain, delete it and add it again.
Follow these guidelines when choosing a subdomain:
* Use the subdomain only for sending requests to Fingerprint.
* Match the subdomain's primary domain to the request origin. For example, use `metrics.yourwebsite.com` when you run Fingerprint on `yourwebsite.com`.
* Avoid the words `fingerprint` and `fp` in the subdomain name. Names like `fingerprint.yourwebsite.com` or `fp.yourwebsite.com` are easier for ad blockers to detect and block. Use a neutral name like `metrics.yourwebsite.com` instead.
## 2. Add DNS records
After you add your domain, the dashboard shows every DNS record you need in one place:
* **One CNAME record** verifies that you own the domain.
* **Two A records** route identification traffic to Fingerprint.
To add every record at once, select **Copy all records** and paste them into your DNS provider. This uses the standard zone file format, which providers like Cloudflare and Route 53 accept for bulk import. You can also copy each record individually.
You can add the two A records right away. They start routing traffic once your certificate is issued.
Fingerprint also checks your domain for a conflicting CAA record. A CAA record controls which certificate authorities can issue certificates for your domain, and a conflicting one prevents Fingerprint's SSL provider (Cloudflare) from issuing yours. If Fingerprint finds a conflict, the dashboard shows an extra CAA record to add alongside the others, for example:
```
metrics.yourwebsite.com. CAA 0 issue "pki.goog"
```
After you add the records, Fingerprint checks for them automatically and updates the status on the Subdomains page. To run a check right away, click **Check DNS records**. Fingerprint also emails you once your certificate is issued.
DNS changes can take up to 24 hours to propagate. If Fingerprint doesn't detect your records yet, wait a while and click **Check DNS records** again. If your records aren't validated within 14 days, the subdomain becomes invalid and you'll need to start over.
### Subdomain statuses
Each subdomain shows one of these statuses:
* **Pending**: Fingerprint is validating your DNS records and issuing the SSL certificate. This can take up to 24 hours. To confirm a record was added correctly, run `dig HOST +short` in your terminal, using the host value of the record. If the record is set up correctly, its value is returned.
* **Active**: Your records are validated and the certificate is issued. Your subdomain is ready to use.
* **Failed**: The records were found, but the certificate can't be issued. This usually happens when a conflicting CAA record blocks Fingerprint's SSL provider (Cloudflare) from issuing certificates. Add the CAA record shown in the dashboard, wait for it to propagate, then delete the failed subdomain and add it again.
**Using Cloudflare as your DNS provider?**
* Disable DNS proxying for all records associated with your subdomain.
* Cloudflare sometimes adds CAA records that aren't visible in the DNS panel. To see them, run `dig caa yourwebsite.com`, replacing `yourwebsite.com` with your domain.
* Cloudflare's [CNAME flattening](https://developers.cloudflare.com/dns/cname-flattening/) can prevent CNAME validation from working. Disable it if you have validation issues with Cloudflare DNS.
## 3. Configure the JavaScript agent
Once your subdomain is active, update the JavaScript agent [`endpoints`](/reference/js-agent-start-function#endpoints) property to point to your subdomain.
```ts CDN theme={"theme":"github-dark-dimmed"}
```
```javascript NPM theme={"theme":"github-dark-dimmed"}
import Fingerprint from '@fingerprint/agent';
// Initialize the agent at application startup:
const fp = Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
endpoints: 'https://metrics.yourwebsite.com',
});
// When you need the visitor identifier:
fp.get().then((result) => console.log(result.visitor_id));
```
This snippet is also available in the dashboard on the subdomain setup page, prefilled with your custom subdomain.
**Notes:**
* The endpoint subdomain should match the domain of your website.
* If you use a [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) on your website, add the custom subdomain to the `connect-src` directive of your policy. See [the CSP guide](/docs/js-agent-csp) for more details.
* If you issued a wildcard SSL certificate, the URL shows an asterisk, for example `*.yourwebsite.com`. Replace the asterisk with the subdomain you plan to use, for example `metrics.yourwebsite.com`.
## Frequently asked questions
### I completed the setup. How can I add more subdomains?
To add more subdomains, add them individually at any time:
1. Go to **Settings** > [**Subdomains**](https://dashboard.fingerprint.com/subdomains) and select **New subdomain**.
2. Repeat the [setup steps](/docs/custom-subdomain-setup#1-register-your-custom-subdomain) for each new subdomain.
Each workspace can have up to 50 unique subdomains. During your free trial, you can use up to 5. If you need to manage more subdomains easily, check out the [Cloudflare proxy integration](/docs/cloudflare-integration). To request additional subdomains, contact the [support team](https://fingerprint.com/support/).
## What's next
* [Start your free workspace now](https://dashboard.fingerprintjs.com/signup/)
# Deploy Akamai Integration using Terraform
Source: https://docs.fingerprint.com/docs/deploy-akamai-proxy-integration-via-terraform
**Before you start: Read the general Akamai guide**
This document only covers deploying the Fingerprint Akamai proxy integration using Terraform. It assumes you have already read the [general Akamai guide][akamai-guide] and completed the following steps:
* [Step 1][akamai-step-1]: You have issued a proxy secret in the Fingerprint Dashboard (`FPJS_PROXY_SECRET`).
* [Step 2][akamai-step-2]: You have defined a path variable for the integration (`FPJS_INTEGRATION_PATH`)
If you want to use Akamai Property Manager API instead of Terraform to install the integration, see [Install Akamai proxy integration using Akamai Property Manager API][akamai-papi].
**Limitations**
This integration is exclusively supported for customers on the **Enterprise** Plan. Other customers are encouraged to use [Custom subdomain setup][custom-subdomain] or [Cloudflare Proxy Integration][cloudflare-proxy].
**Requirements: Terraform setup and `latest` rule format expected**
This guide assumes you use Terraform to manage your site infrastructure on Akamai and that your site uses the `latest` Akamai rule format. If your Akamai property uses a different rule format or a different deployment method, please contact our [support team][support].
To install the integration, adjust your Terraform configuration to incorporate the variable blocks, property rules, and property variables required by the integration.
## Step 3: Add `variable` blocks to your rules template
The following section assumes you are using the [Rules template][akamai-rules-template] to manage your Akamai property rules. If you are using a plain JSON file instead, reach out to our [support team][support].
Specify a randomized path value and the Fingerprint proxy secret you have created in [Step 1][akamai-step-1]:
* `fpjs_integration_path`
* `fpjs_proxy_secret`
For the path value, pick any random string that is a valid URL path. Be careful not to include words often blocked by ad blockers, such as *fingerprint* or *fpjs*. You can use [any random value generator][random-value-generator] or Terraform's [Random Provider][terraform-random-provider] if you prefer.
Find the `akamai_property_rules_template` data block in your Terraform configuration file and add the three variables:
```terraform main.tf theme={"theme":"github-dark-dimmed"}
data "akamai_property_rules_template" "rules" {
# Assuming this is property's rules file
template_file = "/rules/main.json"
variables {
name = "fpjs_integration_path"
value = "FPJS_INTEGRATION_PATH"
type = "string"
}
variables {
name = "fpjs_integration_path_escaped"
value = "YOUR_INTEGRATION_PATH_ESCAPED" # same as FPJS_INTEGRATION_PATH but with / escaped as \\/ and . escaped as \.
type = "string"
}
variables {
name = "fpjs_proxy_secret"
value = "FPJS_PROXY_SECRET"
type = "string"
}
}
```
**Note: Proxy secret required**
Proxied identification requests without a valid proxy secret will result in an authentication error and not receive identification results.
## Step 4: Add Fingerprint property rules
1. Go to the integration's [latest releases on GitHub][proxy-latest-release].
2. Download the `terraform-fingerprint-property-rules.json` file.
3. Add it to your Terraform project's `rules` directory as `fingerprint-property-rules.json`.
4. Reference the file in `rules/main.json` like below:
```javascript rules/main.json theme={"theme":"github-dark-dimmed"}
// rules/main.json
{
"rules": {
"name": "default",
"behaviors": [
// ...
],
"children": [
// ...other rule files
// Add the downloaded rules file
"#include:fingerprint-property-rules.json"
],
// ...
}
}
```
## Step 5: Add Fingerprint property variables
1. Go to the integration's [latest releases on GitHub][proxy-latest-release].
2. Download the `terraform-fingerprint-property-variables.json` file.
3. Add it to your Terraform project's `rules` directory as `fingerprint-property-variables.json`.
4. Reference the file in `rules/main.json`:
* If you don't have a `variables` field, add the `variables: "#include:fingerprint-property-variables.json"` line.
* If you already have a `variables` field, merge `fingerprint-property-variables.json` with your existing variables file.
```javascript rules/main.json theme={"theme":"github-dark-dimmed"}
// rules/main.json
{
"rules": {
"name": "default",
"behaviors": [
// ...
],
"children": [
//...
"#include:fingerprint-property-rules.json"
],
// Add the downloaded variables file (or merge it with existing variables file)
"variables": "#include:fingerprint-property-variables.json"
// ...
}
}
```
**Property variables vs Terraform variable blocks**
Note that the Akamai property variables added in Step 5 are different from the Terraform variable blocks added in Step 3.
* Property variables are used by property rules and you cannot change them.
* You can change the variable block values (randomized path and the proxy secret) according to your needs.
## Step 6: Apply Terraform changes
1. Run `terraform plan` to review your changes.
2. Run `terraform apply`.
After your property deploys, you can access Fingerprint CDN and API through the chosen path on your website.
## Step 7: Configure the client agent
Please see the main Akamai proxy integration guide to [Configure the client agent on your website or mobile app][akamai-configure-agent].
[akamai-configure-agent]: /docs/akamai-proxy-integration#configure-fingerprint-agent
[akamai-guide]: /docs/akamai-proxy-integration
[akamai-papi]: /docs/deploy-akamai-proxy-integration-via-papi
[akamai-rules-template]: https://techdocs.akamai.com/terraform/docs/pm-ds-rules-template
[akamai-step-1]: /docs/akamai-proxy-integration#step-1-issue-a-fingerprint-proxy-secret
[akamai-step-2]: /docs/akamai-proxy-integration#step-2-create-a-path-variable
[cloudflare-proxy]: /docs/cloudflare-integration
[custom-subdomain]: /docs/custom-subdomain-setup
[proxy-latest-release]: https://github.com/fingerprintjs/akamai-proxy/releases/latest
[random-value-generator]: https://www.random.org/strings/?num=3&len=12&digits=on&loweralpha=on&unique=on&format=html&rnd=new
[support]: https://fingerprint.com/support/
[terraform-random-provider]: https://registry.terraform.io/providers/hashicorp/random/latest/docs
# Deploy Fastly Compute Proxy Integration Manually
Source: https://docs.fingerprint.com/docs/deploy-fastly-compute-manually
**Before you start: Read the general Fastly Compute guide**
This document only covers deploying the Fingerprint Fastly Compute proxy integration using the Fastly web interface. It assumes you have already read the general [Fastly Compute guide](/docs/fastly-compute-proxy-integration) and completed the [step 1](/docs/fastly-compute-proxy-integration#step-1-create-a-fingerprint-proxy-secret).
If you want to use Terraform instead of the Fastly web interface to install the integration, see [Deploy Fastly Compute using Terraform](/docs/deploy-fastly-compute-using-terraform).
**Limitations**
This integration is exclusively supported for customers on the **Enterprise** Plan. Other customers are encouraged to use [Custom subdomain setup](/docs/custom-subdomain-setup) or [Cloudflare Proxy Integration](/docs/cloudflare-integration).
## Step 2.1: Create a Compute Service
Create a Compute service in your Fastly account.
1. Go to [Fastly Compute services](https://manage.fastly.com/compute).
2. Click **Create service** > **Create an empty service**.
3. On the top of the page, click **Options** > **Edit service name**.
4. Give it a readable name, for example, `fingerprint-fastly-compute-proxy-integration`, and click **Apply**.
## Step 2.2: Add a domain to the service
To get the proxy integration's accuracy benefits, we recommend using a subdomain of the website you want to use Fingerprint on, for example, `metrics.yourwebsite.com`.
To avoid ad blockers, do not use subdomains like `fingerprint`, `fpjs`, and other fingerprint-related terms.
1. Inside the service configuration left-hand menu, click **Domains** > **Add domain**.
2. Input your domain for the proxy integration, for example, `metrics.yourwebsite.com`.
3. Click **Create** to save changes.
**Safari accuracy on non-Fastly websites**
For maximum accuracy, both your website and your proxy integration should be served by Fastly.
You can use the Fastly Compute proxy integration even if your website is not served by Fastly, but this setup will likely limit Safari cookie lifetime to 7 days, resulting in lower accuracy. Because your website and the proxy integration will likely have different IP ranges, Safari will apply the same cookie lifetime cap as for third-party CNAME cloaking. This is still an improvement over third-party cookies getting blocked entirely by Safari.
## Step 2.3: Configure the service backend in Fastly
Fingerprint API is available in three different [regions](/docs/regions): `api.fpjs.io` (Global), `eu.api.fpjs.io` (EU), and `ap.api.fpjs.io` (Asia). Use the right regional hostname depending on your Fingerprint workspace data region.
1. Go back to [Compute services](https://manage.fastly.com/compute) and open the configuration of your service.
2. Inside the service configuration left-hand menu, click **Origins**.
3. Under **Hosts**, type `fingerprint` and click **Add**.
4. Click **Edit** on the created host.
* Keep the name as `fingerprint`. The proxy integration expects this exact name.
* Set the **Address** and **Override host** to the regional Fingerprint API host for your workspace:
* `api.fpjs.io` - Global (US)
* `eu.api.fpjs.io` - EU
* `ap.api.fpjs.io` - Asia
* Click **Update** to save changes.
## Step 2.4: Add a secret store to the service
1. In Fastly, navigate to **Resources** > [**Secret stores**](https://manage.fastly.com/resources/secret-stores).
2. Click **Create store**.
3. Name the config store `Fingerprint_Compute_Secret_Store_`, where the suffix is your proxy integration's [Compute Service ID](https://docs.fastly.com/en/guides/about-services). Make sure you don't have any spaces in the Secret store name after copy-pasting your service ID.
4. Click **Create**.
5. In the **Linked Services** tab, click on **Link service** and select `fingerprint-fastly-compute-proxy-integration`.
6. Click **Next**, select the current (draft) version of your service, and click **Link only**.
7. Click **Finish**.
8. Click **Add item** and set `PROXY_SECRET` to the proxy secret value you generated in [Step 1](/docs/fastly-compute-proxy-integration#step-1-create-a-fingerprint-proxy-secret).
**Note: Proxy secret required**
Proxied identification requests without a valid proxy secret will result in an authentication error and not receive identification results.
## Step 2.5: Deploy the proxy package and activate your service
Deploy the proxy service code to your Fastly Compute Service.
1. Go to the [latest release](https://github.com/fingerprintjs/fastly-compute-proxy/releases/latest) in the Fastly Compute Proxy Integration GitHub Repository.
2. Download `fingerprint-fastly-compute-proxy-integration.tar.gz` to your computer.
3. Inside Fastly, navigate to **Compute** > [**Compute services**](https://manage.fastly.com/compute), open your proxy integration service, and switch to the **Service configuration** tab.
4. In the left-hand menu, click **Package**.
5. Click to **Browse For Package** and upload the `fingerprint-fastly-compute-proxy-integration.tar.gz` package.
6. Click **Activate** in the top right corner of the page (if you see **Validating** instead, wait for it to complete).
Wait a couple of minutes for the activation. You can go to `metrics.yourwebsite.com/status` to verify that your integration is running.
## Step 2.6: Issue a TLS certificate for your subdomain
1. Inside the Fastly Dashboard, navigate to **Domains** > **TLS Management** > [**Domains**](https://manage.fastly.com/network/domains).
2. Click **Secure domain** or **Secure another domain**.
3. Select **Use certificates Fastly obtains for you**.
4. Enter your domain (for example `metrics.yourwebsite.com`) and click **Add**.
5. Configure *Certification Authority* and *TLS configuration* according to your needs and click **Submit**.
6. Follow the instructions on the screen to [verify ownership of your domain](https://docs.fastly.com/en/guides/setting-up-tls-with-certificates-fastly-manages#verifying-domain-ownership). This usually involves creating a CNAME DNS record on your domain like `_acme-challenge.metrics.yourwebsite.com`.
Give Fastly some time to verify the DNS record. Once verified, Fastly will issue a certificate for your domain and display a green **Issued** status.
## Step 2.7: Point your subdomain to Fastly
Once your domain is verified and active, you need to [choose the right hostname for your DNS record](https://docs.fastly.com/en/guides/working-with-cname-records-and-your-dns-provider#choosing-the-right-fastly-hostname-value-for-your-cname-record).
1. Go to **Security** > **TLS Management** > [**Domains**](https://manage.fastly.com/network/domains).
2. Find your domain and under *TLS configuration and DNS details,* click **View/Edit**.
3. The **CNAME records** section contains the value for your CNAME record, for example, `t.sni.global.fastly.net`.
4. [Add a subdomain CNAME record](https://docs.fastly.com/en/guides/working-with-cname-records-and-your-dns-provider#setting-up-the-cname-record-with-your-dns-provider) to your domain's DNS and point it to the Fastly hostname from the previous step. For example:
```txt theme={"theme":"github-dark-dimmed"}
metrics.yourwebsite.com. 3600 CNAME t.sni.global.fastly.net.
```
## Step 3: Configure the client agent
Go back to the general Fastly Compute guide to [Configure the Fingerprint client agent to use your service](/docs/fastly-compute-proxy-integration#step-3-configure-the-fingerprint-client-agent-to-use-your-service).
# Deploy Fastly Compute Proxy Integration with Terraform
Source: https://docs.fingerprint.com/docs/deploy-fastly-compute-using-terraform
**Before you start: Read the general Fastly Compute guide**
This document only covers deploying the Fingerprint Fastly Compute proxy integration using the [Terraform module](https://github.com/fingerprintjs/terraform-fastly-compute-fingerprint-proxy-integration). It assumes you have already read the general [Fastly Compute guide](/docs/fastly-compute-proxy-integration) and completed the [step 1](/docs/fastly-compute-proxy-integration#step-1-create-a-fingerprint-proxy-secret).
If you want to deploy manually using the Fastly web interface instead of the Terraform module to install the integration, see [Deploy Fastly Compute manually](/docs/deploy-fastly-compute-manually).
**Limitations**
This integration is exclusively supported for customers on the **Enterprise** Plan. Other customers are encouraged to use [Custom subdomain setup](/docs/custom-subdomain-setup) or [Cloudflare Proxy Integration](/docs/cloudflare-integration).
## Step 2.1: Create an empty Fastly Compute service
Due to Terraform's resource dependency limitations, you must create an empty Fastly Compute service using the Fastly web interface before using the Terraform module:
1. Log in to your [Fastly web interface](https://manage.fastly.com/).
2. Navigate to **Compute** > **Create service** and click **Create an Empty Service**.
3. Set a name that suits you, for example, `fingerprint-fastly-compute-proxy-integration`.
4. Copy the Compute Service ID displayed below the service name at the top of the page. You'll need it in the next steps to import the service into the Terraform state.
## Step 2.2: Get a Fastly API token
Grab your existing Fastly API token or [create a new one](https://manage.fastly.com/account/personal/tokens/new).
* Your token needs to have a `global` scope.
* See the [Fastly documentation](https://www.fastly.com/documentation/guides/account-info/account-management/using-api-tokens/#creating-api-tokens) for more details.
## Step 2.3: Add and configure the Terraform module
Create the `main.tf` file in your Terraform project directory. Populate the properties with the values generated in the previous steps.
```terraform theme={"theme":"github-dark-dimmed"}
terraform {
required_version = ">=1.5"
}
module "fingerprint_fastly_compute_integration" {
source = "fingerprintjs/compute-fingerprint-proxy-integration/fastly"
fastly_api_token = "FASTLY_API_TOKEN"
integration_domain = "metrics.yourwebsite.com"
service_id = "FASTLY_COMPUTE_SERVICE_ID"
region = "us" # or "eu" or "ap"
}
```
You can see the full list of the Terraform module's variables below:
| Variable | Description | Required | Example |
| ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------- |
| `fastly_api_token` | Your Fastly API token | Required | `"ABC123...xyz"` |
| `integration_domain` | Domain used for your proxy integration | Required | `"metrics.yourwebsite.com"` |
| `service_id` | ID of your empty Fastly Compute service | Required | `"SU1Z0isxPaozGVKXdv0eY"` |
| `region` | Region for the Fingerprint API. One of: `us`, `eu`, `ap`. | Optional | `"us"` |
| `agent_script_download_path` | Path to download the JavaScript agent (only required for JavaScript agent v3) | Optional | `"4fs80xgx"` |
| `get_result_path` | Path for identification requests (only required for JavaScript agent v3) | Optional | `"vpyr9bev"` |
| `integration_name` | Name of the Fastly service | Optional | `"fingerprint-fastly-compute-proxy-integration"` |
| `download_asset` | Whether to auto-download the latest release | Optional | `true` |
| `compute_asset_name` | Custom filename if not downloading the official artifact | Optional | `"fingerprint-fastly-compute-proxy-integration.tar.gz"` |
| `asset_version` | GitHub release version of the proxy integration (See [GitHub releases](https://github.com/fingerprintjs/terraform-fastly-compute-fingerprint-proxy-integration/releases)) | Optional | `"latest"` |
| `manage_fastly_config_store_entries` | Manage Fastly Config Store entries via terraform, see [Fastly documentation](https://registry.terraform.io/providers/fastly/fastly/latest/docs/resources/configstore_entries#manage_entries-1) | Optional | `false` |
> Updating Config Store Entries
>
> To have Terraform manage your Fastly Config Store entries, set the `manage_fastly_config_store_entries` variable to `true`.
> By default, these entries must be managed outside of Terraform.
> You can learn more about the reason behind this default behavior in the [official Fastly documentation](https://registry.terraform.io/providers/fastly/fastly/latest/docs/resources/configstore_entries#manage_entries-1).
> After updating Config Store Entries, whether through Terraform or manually, it may take a few minutes for the changes to take effect.
## Step 2.4: Deploy your changes
1. Initialize Terraform:
```shell theme={"theme":"github-dark-dimmed"}
terraform init
```
2. Download the latest available proxy integration release artifact:
```shell theme={"theme":"github-dark-dimmed"}
terraform apply -target=module.fingerprint_fastly_compute_integration.module.compute_asset
```
You will get this warning: `Warning: Resource targeting is in effect...`. You can safely ignore it. The module is downloading the latest release artifact so that it can upload it as a Compute package in the next `apply` command.
3. Import your empty Compute service into Terraform state:
```shell theme={"theme":"github-dark-dimmed"}
terraform import module.fingerprint_fastly_compute_integration.fastly_service_compute.fingerprint_integration ""
```
> Note: The module appends the service ID to all resource names to support multiple integrations in the same Fastly account.
4. Deploy the integration:
```shell theme={"theme":"github-dark-dimmed"}
terraform apply
```
**Safari accuracy on non-Fastly websites**
For maximum accuracy, both your website and your proxy integration should be served by Fastly.
You can use the Fastly Compute proxy integration even if your website is not served by Fastly, but this setup will likely limit Safari cookie lifetime to 7 days, resulting in lower accuracy. Because your website and the proxy integration will likely have different IP ranges, Safari will apply the same cookie lifetime cap as for third-party CNAME cloaking. This is still an improvement over third-party cookies getting blocked entirely by Safari.
## Step 2.5: Add the proxy secret
Fastly [does not allow](https://registry.terraform.io/providers/fastly/fastly/latest/docs/resources/secretstore) storing or setting secrets using Terraform. Instead, you need to add the proxy secret manually:
1. Navigate to the Fastly web interface > **Resources** > **Secret Stores**.
2. Find the secret store created by Terraform: `Fingerprint_Compute_Secret_Store_`.
3. Open it and click **Add item**.
4. Set **Key** to `PROXY_SECRET`.
5. Set **Value** to your Fingerprint proxy secret created in [Step 1](/docs/fastly-compute-proxy-integration#step-1-create-a-fingerprint-proxy-secret).
Alternatively, you could use the Fastly API or CLI to set the secret.
**Note: Proxy secret required**
Proxied identification requests without a valid proxy secret will result in an authentication error and not receive identification results.
## Step 2.6: Configure your domain
To complete your domain setup, see [Issue a TLS certificate for your subdomain](/docs/deploy-fastly-compute-manually#step-2-6-issue-a-tls-certificate-for-your-subdomain).
## Step 3: Configure the client agent
Go back to the general Fastly Compute guide to [Configure the Fingerprint client agent to use your service](/docs/fastly-compute-proxy-integration#step-3-configure-the-fingerprint-client-agent-to-use-your-service).
## Updating the integration
The Terraform installation of the Fastly Compute proxy integration does not include any mechanism for automatic updates. To keep your integration up to date, please run `terraform apply` regularly.
## Removing the integration
To remove the integration, run:
```shell theme={"theme":"github-dark-dimmed"}
terraform destroy
```
# Deploy Fastly VCL Manually
Source: https://docs.fingerprint.com/docs/deploy-fastly-vcl-manually
**Before you start: Read the general Fastly VCL guide**
This document only covers deploying the Fingerprint Fastly VCL proxy integration using the Fastly web interface. It assumes you have already read the general [Fastly VCL guide](/docs/fastly-vcl-proxy-integration) and completed the following steps:
* [Step 1](/docs/fastly-vcl-proxy-integration#step-1-create-a-fingerprint-proxy-secret): You have issued a proxy secret in the Fingerprint Dashboard (`PROXY_SECRET`).
* [Step 2](/docs/fastly-vcl-proxy-integration#step-2-create-integration-path-variable): You have defined the path variable for the integration (`INTEGRATION_PATH`)
If you want to use Terraform instead of the Fastly web interface to install the integration, see [Deploy Fastly VCL using Terraform](/docs/deploy-fastly-vcl-using-terraform).
**Limitations**
This integration is exclusively supported for customers on the **Enterprise** Plan. Other customers are encouraged to use [Custom subdomain setup](/docs/custom-subdomain-setup) or [Cloudflare Proxy Integration](/docs/cloudflare-integration).
## Step 3: Deploy Fastly VCL manually
The recommended setup is to add the VCL template to your existing Fastly CDN Service serving your website.
If your website is running on Fastly CDN, skip to [Step 3.1](#step-3-1-create-a-dictionary-for-your-cdn-service).
If your website is not running on Fastly, you can still use the Fastly proxy integration by creating a dedicated Fastly CDN Service for it and hosting it on the subdomain of your website.
If so, please follow [Step 3.0](#step-3-0-create-a-new-fastly-cdn-service-if-required) to create a new Fastly CDN Service.
**Safari accuracy on non-Fastly websites**
For maximum accuracy, both your website and your proxy integration should be served by Fastly.
You can use the Fastly VCL proxy integration even if your website is not served by Fastly, but this setup will likely limit Safari cookie lifetime to 7 days, resulting in lower accuracy. Because your website and the proxy integration will likely have different IP ranges, Safari will apply the same cookie lifetime cap as for third-party CNAME cloaking. This is still an improvement over third-party cookies getting blocked entirely by Safari.
### Step 3.0: Create a new Fastly CDN service (if required)
1. Go to **Fastly** > **CDN Services** and click **Create Service** in the top right.
2. Fill in the form:
1. Set **Name** to something descriptive, for example: `Fingerprint Proxy Integration`.
2. Set **Domain** to the subdomain you have chosen for the integration, for example, `metrics.yourwebsite.com`. Avoid terms commonly blocked by ad blockers like `fingerprint`, `fpjs`, etc.
3. Set **Origin** to something like `metrics-origin.yourwebsite.com`. This origin does not need to exist because the VCL template will forward all valid requests to the appropriate Fingerprint servers.
4. If your website needs to be accessible on HTTP (not just HTTPS), disable **Force TLS & HSTS**.
3. Click **Activate**.
4. Using the left-hand menu, go to **Domains** > **TLS Management** > **Domains**.
5. Click **Secure my domain** and pick **Use certificates Fastly obtains for you**.
6. Enter your domain (for example, `metrics.yourwebsite.com`) and click **Add**.
7. Keep the default settings and click **Submit**.
8. Follow the instructions on the screen to [verify ownership of your domain](https://docs.fastly.com/en/guides/setting-up-tls-with-certificates-fastly-manages#verifying-domain-ownership).
This usually involves creating a CNAME DNS record on your domain like `_acme-challenge.metrics.yourwebsite.com`.
Give Fastly some time to verify the DNS record. After verification is completed, Fastly will issue a certificate for your domain and display a green **Active** status.
9. Once your domain is verified and active, you need to [choose the right hostname for your DNS record](https://docs.fastly.com/en/guides/working-with-cname-records-and-your-dns-provider#choosing-the-right-fastly-hostname-value-for-your-cname-record).
1. Your hostname is likely [TLS-enabled](https://docs.fastly.com/en/guides/working-with-cname-records-and-your-dns-provider#tls-enabled-hostnames) so you can go to **Domains** > **TLS Management** > **Domains**.
2. Find your domain and click **View details**.
3. The **CNAME records** section contains the value for your CNAME record, for example: `t.sni.global.fastly.net`.
10. [Add a subdomain CNAME record](https://docs.fastly.com/en/guides/working-with-cname-records-and-your-dns-provider#setting-up-the-cname-record-with-your-dns-provider) to your domain's DNS and point it to the Fastly hostname from the previous step. For example:
```txt theme={"theme":"github-dark-dimmed"}
metrics.yourwebsite.com. 3600 CNAME t.sni.global.fastly.net.
```
### Step 3.1: Create a dictionary for your CDN Service
In this step, you are going to create a dictionary for your CDN Service and use it to store the configuration of your proxy integration.
1. Navigate to **CDN** > **CDN Services** and find your CDN service.
2. If you don't have an editable version of your service, navigate to the **Service configuration** tab, then click **Clone to edit**.
3. Using the left-hand Service menu, navigate to **Data** > **Dictionaries**.
4. Click **Create Dictionary**.
5. Set the name to `fingerprint_config` and click **Add**. The default VCL template relies on this exact name.
6. Click **Add Item** to add the following key-value pairs:
1. Set `PROXY_SECRET` to the proxy secret you created in the Fingerprint Dashboard in [Step 1](/docs/fastly-vcl-proxy-integration#step-1-create-a-fingerprint-proxy-secret).
2. Set `INTEGRATION_PATH` to the random string you defined in [Step 2](/docs/fastly-vcl-proxy-integration#step-2-create-integration-path-variable).
**Note: Proxy secret required**
Proxied identification requests without a valid proxy secret will result in an authentication error and not receive identification results.
### Step 3.2: Download the Fastly VCL template
Go to [the latest integration release on GitHub](https://github.com/fingerprintjs/fastly-vcl-proxy/releases/latest), and download the artifact named `fingerprint-pro-fastly-vcl-integration.vcl`.
### Step 3.3: Upload VCL template
1. Using the left-hand Service menu, navigate to **VCL** > **Custom VCL** and click **Upload custom VCL**.
2. Set a name that suits you, for example, `fingerprint_proxy_integration`.
3. Upload the `fingerprint-pro-fastly-vcl-integration.vcl` file that you've downloaded in the previous step.
4. Click **Add**.
### Step 3.4: Activate your version and verify your setup
1. Scroll up to the Service version toolbar and click **Activate** to deploy your changes.
2. Go to `https://metrics.yourwebsite.com/INTEGRATION_PATH/status`. You should see an integration status page showing the template version and a checklist of the set dictionary variables.
## Step 4: Configure the client agent
Go back to the general Fastly VCL guide to [Configure the Fingerprint client agent to use your service](/docs/fastly-vcl-proxy-integration#step-4-configure-the-fingerprint-javascript-agent-on-your-client).
# Deploy Fastly VCL using Terraform Module
Source: https://docs.fingerprint.com/docs/deploy-fastly-vcl-using-terraform
**Before you start: Read the general Fastly VCL guide**
This document only covers deploying the Fingerprint Fastly VCL proxy integration using the official [Terraform module](https://github.com/fingerprintjs/terraform-fastly-vcl-fingerprint-proxy-integration). It assumes you have already read the general [Fastly VCL guide](/docs/fastly-vcl-proxy-integration) and completed the following steps:
* [Step 1](/docs/fastly-vcl-proxy-integration#step-1-create-a-fingerprint-proxy-secret): You have issued a proxy secret in the Fingerprint Dashboard (`PROXY_SECRET`).
* [Step 2](/docs/fastly-vcl-proxy-integration#step-2-create-integration-path-variable): You have defined the path variable for the integration (`INTEGRATION_PATH`)
If you want to deploy manually using the Fastly web interface instead of the Terraform module to install the integration, see [Deploy Fastly VCL manually](/docs/deploy-fastly-vcl-manually).
**Limitations**
This integration is exclusively supported for customers on the **Enterprise** Plan. Other customers are encouraged to use [Custom subdomain setup](/docs/custom-subdomain-setup) or [Cloudflare Proxy Integration](/docs/cloudflare-integration).
## Step 3.1: Get a Fastly API token
Grab your existing Fastly API token or [create a new one](https://manage.fastly.com/account/personal/tokens/new).
* Your token needs to have a `global` scope.
* See the [Fastly documentation](https://www.fastly.com/documentation/guides/account-info/account-management/using-api-tokens/#creating-api-tokens) for more details.
## Step 3.2: Add and configure the Terraform module
Create the `main.tf` file in your Terraform project directory. Populate the properties with the values generated in the previous steps.
```terraform theme={"theme":"github-dark-dimmed"}
terraform {
required_version = ">=1.5"
}
module "fingerprint_fastly_vcl_integration" {
source = "fingerprintjs/vcl-fingerprint-proxy-integration/fastly"
fastly_api_token = "FASTLY_API_TOKEN"
integration_domain = "metrics.yourwebsite.com"
integration_path = "INTEGRATION_PATH"
main_host = "origin.mydomain.com" # Your website's origin server domain
proxy_secret = "abcdef01234"
}
```
* Proxied identification requests without a valid proxy secret will result in an authentication error and not receive identification results.
* This module configures a Fastly dictionary named `fingerprint_config` by default. You can change it using the `dictionary_name` variable, for example to avoid conflicts with your existing Fastly dictionaries.
If you want to change the `dictionary_name`, you must also use a [custom VCL asset](https://github.com/fingerprintjs/terraform-fastly-vcl-fingerprint-proxy-integration?tab=readme-ov-file#using-a-custom-vcl-asset-optional).
In that case, skip the download step, place your file at `/assets/my_vcl_file.vcl`, and set `vcl_asset_name` to `my_vcl_file.vcl`.
You can see the full list of the Terraform module's variables below:
| Variable | Description | Required | Example |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------- |
| `fastly_api_token` | Your Fastly API token | Required | `"ABC123...xyz"` |
| `integration_domain` | Domain used for the proxy integration | Required | `"metrics.yourwebsite.com"` |
| `main_host` | Your origin server domain | Required | `"yourwebsite.com"` |
| `proxy_secret` | Your Fingerprint proxy secret | Required | `"9h7jk2s1"` |
| `integration_path` | Path prefix for proxy requests | Required | `"kyfy7t0a"` |
| `dictionary_name` | Name of the Fastly Dictionary for config values | Optional | `"fingerprint_config"` |
| `integration_name` | Name of the Fastly CDN service | Optional | `"fingerprint-fastly-vcl-proxy-integration"` |
| `download_asset` | Whether to auto-download the latest VCL release | Optional | `true` |
| `vcl_asset_name` | Custom VCL asset file if not downloading the official one | Optional | `"fingerprint-pro-fastly-vcl-integration.vcl"` |
| `asset_version` | GitHub release version used for the VCL asset (See [GitHub releases](https://github.com/fingerprintjs/terraform-fastly-vcl-fingerprint-proxy-integration/releases)) | Optional | `"latest"` |
| `manage_fastly_dictionary_items` | Manage Fastly Dictionary items via terraform, see [Fastly documentation](https://registry.terraform.io/providers/fastly/fastly/latest/docs/resources/service_dictionary_items#manage_items-1) | Optional | `false` |
> Updating Dictionary Items
>
> To have Terraform manage your Fastly Dictionary items, set the `manage_fastly_dictionary_items` variable to `true`.
> By default, these items — `integration_path`, and `proxy_secret` — must be managed outside of Terraform.
> You can learn more about the reason behind this default behavior in the [official Fastly documentation](https://registry.terraform.io/providers/fastly/fastly/latest/docs/resources/service_dictionary_items#manage_items-1).
> After updating Dictionary Items, whether through Terraform or manually, it may take a few minutes for the changes to take effect.
## Step 3.3: Deploy your changes
1. Initialize Terraform:
```shell theme={"theme":"github-dark-dimmed"}
terraform init
```
2. Import the VCL asset into the Terraform state:
```shell theme={"theme":"github-dark-dimmed"}
terraform apply -target=module.fingerprint_fastly_vcl_integration.module.vcl_asset
```
You will get this warning: `Warning: Resource targeting is in effect...`. You can safely ignore it. The module is downloading the latest release artifact so that it can upload it as a VCL file in the next `apply` command.
3. Deploy the integration:
```shell theme={"theme":"github-dark-dimmed"}
terraform apply
```
**Safari accuracy on non-Fastly websites**
For maximum accuracy, both your website and your proxy integration should be served by Fastly.
You can use the Fastly VCL proxy integration even if your website is not served by Fastly, but this setup will likely limit Safari cookie lifetime to 7 days, resulting in lower accuracy. Because your website and the proxy integration will likely have different IP ranges, Safari will apply the same cookie lifetime cap as for third-party CNAME cloaking. This is still an improvement over third-party cookies getting blocked entirely by Safari.
## Step 4: Configure your domain
If you are creating a dedicated Fastly service for this integration, go to [Step 3.0: Create a new Fastly CDN Service](/docs/fastly-vcl-proxy-integration#step-3-add-fingerprint-vcl-to-your-fastly-cdn-service) and follow sub-steps 4 to 10 to complete your domain setup.
## Step 5: Configure the client agent
Go back to the general Fastly VCL guide to [Configure the Fingerprint client agent to use your service](/docs/fastly-vcl-proxy-integration#step-4-configure-the-fingerprint-javascript-agent-on-your-client).
## Updating the integration
The Terraform installation of the Fastly VCL proxy integration does not include any mechanism for automatic updates.
To keep your integration up to date, run `terraform apply` regularly.
## Removing the integration
To remove the integration, run:
```shell theme={"theme":"github-dark-dimmed"}
terraform destroy
```
# C#/.NET Server Quickstart
Source: https://docs.fingerprint.com/docs/dotnet-server-quickstart
Get started using the C#/.NET Server SDK
## Overview
In this quickstart, you'll add Fingerprint to a [C# ASP.NET Core](https://dotnet.microsoft.com/en-us/apps/aspnet) server using [ASP.NET APIs](https://dotnet.microsoft.com/en-us/apps/aspnet/apis) to prevent fraudulent account creation.
The example use case in this quickstart is stopping new account fraud, where attackers create multiple fake accounts to abuse promotions, exploit systems, or evade bans. However, the steps you'll follow apply to most use cases. You can flag and block suspicious users by identifying the device behind each sign-up attempt, login, or transaction.
In this quickstart, you'll learn how to:
* Set up a ASP.NET Core API server with a Fingerprint integration
* Retrieve visitor identification data using the Server API
* Block bots and suspicious devices
* Prevent multiple signups from the same device
This guide focuses on the backend integration and must be completed after identifying a visitor and generating a request ID. **Before starting this quickstart, start with one of the [frontend](/docs/web-quickstarts-overview) or [mobile](/docs/mobile-quickstarts-overview) quickstarts to see how to identify a visitor in your frontend.**
> Estimated time: \< 10 minutes
## Prerequisites
Before you begin, make sure you have the following:
* **A completed frontend or mobile Fingerprint implementation (See the quickstarts)**
* [.NET SDK](https://learn.microsoft.com/en-us/dotnet/core/install/) (.NET 8.0 or later)
* Your favorite code editor
* Basic knowledge of C# and .NET
## 1. Get your secret API key
Before starting this quickstart, you should already have a frontend Fingerprint implementation
that sends the `event_id` to your server. If not, pause here and check out one of our
[frontend](/docs/web-quickstarts-overview) or [mobile](/docs/mobile-quickstarts-overview)
quickstarts first.
If you're ready:
1. Sign in and go to the [**API keys**](https://dashboard.fingerprint.com/api-keys) page in the Fingerprint dashboard.
2. Create a new **secret API key**.
3. Copy it somewhere safe so you can use it to retrieve full visitor identification data from the Server API.
## 2. Set up your project
To get started, set up a basic server. If you already have a project you want to use, you can skip to the next section.
1. Create a new [ASP.NET](http://asp.net/) project and add the Fingerprint .NET Server SDK:
```bash Terminal theme={"theme":"github-dark-dimmed"}
dotnet new web -o fingerprint-dotnet-quickstart -f net9.0
cd fingerprint-dotnet-quickstart
dotnet add package Fingerprint.ServerSdk
```
*Note: This quickstart is written for version 8.x of the Fingerprint .NET Server SDK*
2. The `dotnet new web` command creates a starter `Program.cs` file. Replace the entire contents of `Program.cs` with the following code:
```csharp Program.cs theme={"theme":"github-dark-dimmed"}
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseUrls("http://localhost:3000");
var app = builder.Build();
app.MapPost("/api/create-account", () => Results.Ok("{ status: \"Account created!\" }"));
app.Run();
```
Here we are running the server at port 3000 and have created a new POST route for account creation. Note that the `/api/create-account` route should match what you have set up in your frontend implementation where you are sending the Fingerprint `event_id` to your server. Your server will receive the initial identification information from identifying a visitor on the frontend and use it to get the full visitor data on the backend.
## 3. Initialize Fingerprint and retrieve visitor data
Now you'll configure the Fingerprint .NET Server SDK using your secret API key and use it to fetch detailed visitor data for each signup attempt.
When making the initial visitor identification request in the frontend, you received an `event_id`. This ID is unique to each identification event. Your server can then use the [Fingerprint Events API](/reference/server-api-get-event) to retrieve complete identification data, including the trusted visitor ID and other actionable insights like whether they are using a VPN or are a bot.
1. At the top of your `Program.cs` file, import and initialize the SDK with your secret API key just after the builder object is constructed:
```csharp Program.cs theme={"theme":"github-dark-dimmed"}
using Fingerprint.ServerSdk.Api;
using Fingerprint.ServerSdk.Extensions;
using Fingerprint.ServerSdk.Client;
using Fingerprint.ServerSdk.Model;
...
builder.Services.AddFingerprint(options =>
{
options.AddTokens(new BearerToken("SECRET_API_KEY")); // Replace with your actual secret key
// Uncomment and change if necessary:
// options.Region = Region.Eu;
});
```
*For a production implementation make sure to store and reference your secret key securely.*
2. In your `/api/create-account` route, retrieve the `event_id` you are sending from the frontend and fetch the full visitor identification details with `GetEventAsync()`:
**requestId vs event\_id:** Depending on which quickstart you completed, your frontend may send
either `requestId` (older SDKs) or `event_id` (v4 and newer). Both refer to the same
identification event and work with the Server API in the same way. Use whichever value your
frontend sends (`requestId` or `event_id`) as the `eventId` you pass to GetEventAsync() below.
```csharp Program.cs theme={"theme":"github-dark-dimmed"}
app.MapPost("/api/create-account", async (CreateAccountRequest request, IFingerprintApi api) =>
{
var getEvent = await api.GetEventAsync(request.EventId);
return Results.Ok("{ status: \"Account created!\" }");
});
```
3. Add the declaration of `CreateAccountRequest` to the bottom of `Program.cs`:
```csharp Program.cs theme={"theme":"github-dark-dimmed"}
public record CreateAccountRequest(string EventId, string Username, string Password);
```
Using the `eventId` 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, incognito mode detection, and detections for VPN or virtual machine use.
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/protecting-from-client-side-tampering).
## 4. Block bots and suspicious devices
This optional step uses the Bot Detection Smart Signal which is available only on paid plans.
A simple but powerful way to prevent fraudulent account creation is to block automated signups that come from bots. 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.
1. Continuing in your `/api/create-account` route, after getting `getEvent`, check the operation status:
```csharp Program.cs theme={"theme":"github-dark-dimmed"}
if (!getEvent.TryOk(out var result))
{
return Results.InternalServerError("{ status: \"Internal server error!\" }");
}
```
Now we can check the bot signal returned in the object:
```csharp Program.cs theme={"theme":"github-dark-dimmed"}
if (result.Bot != BotResult.NotDetected)
{
return Results.Problem("{ error: \"Failed to create account.\" }", statusCode: 403);
}
```
This signal returns `BotResult.Good` for known bots like search engine crawlers, `BotResult.Bad` for automation tools, headless browsers, or other signs of automation, and `BotResult.NotDetected` when no bot activity is found. You can also layer in other Smart Signals to catch more suspicious devices. For example, you can use Fingerprint's [Suspect Score](/docs/suspect-score) to determine when to add additional friction to create an account.
## 5. Prevent multiple signups from the same device
To catch repeated signups from the same device, you can use the `visitorId` from the Fingerprint identification event. By saving this ID alongside each created account, you can detect and block duplicate signups from the same device. We'll be using a simple database to demonstrate how this works with SQLite.
1. Install the SQLite package:
```bash Terminal theme={"theme":"github-dark-dimmed"}
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
```
2. At the top of your `Program.cs` file, import and initialize the database:
```csharp Program.cs theme={"theme":"github-dark-dimmed"}
using Microsoft.Data.Sqlite;
var connectionString = "Data Source=database.db";
using var connection = new SqliteConnection(connectionString);
connection.Open();
var command = connection.CreateCommand();
command.CommandText = @"
CREATE TABLE IF NOT EXISTS accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT,
password TEXT,
visitorId TEXT
)";
command.ExecuteNonQuery();
```
3. In your `/api/create-account` route handler, after getting the event and the bot detection code, extract the `visitorId`:
```csharp Program.cs theme={"theme":"github-dark-dimmed"}
var visitorId = result.Identification.VisitorId;
```
4. Then check if this device (visitor ID) has already created an account; if yes, block the account creation:
```csharp Program.cs theme={"theme":"github-dark-dimmed"}
using var checkConnection = new SqliteConnection(connectionString);
checkConnection.Open();
var checkCommand = checkConnection.CreateCommand();
checkCommand.CommandText = "SELECT COUNT(*) FROM accounts WHERE visitorId = @visitorId";
checkCommand.Parameters.AddWithValue("@visitorId", visitorId);
var count = (long?)checkCommand.ExecuteScalar();
if (count.HasValue && count > 0)
{
return Results.Problem("{ error: \"Failed to create account.\" }", statusCode: 429);
}
// Otherwise, insert the new account
var insertCommand = checkConnection.CreateCommand();
insertCommand.CommandText = "INSERT INTO accounts (username, password, visitorId) VALUES (@username, @password, @visitorId)";
insertCommand.Parameters.AddWithValue("@username", request.Username);
insertCommand.Parameters.AddWithValue("@password", request.Password);
insertCommand.Parameters.AddWithValue("@visitorId", visitorId);
insertCommand.ExecuteNonQuery();
```
This gives you a basic system to detect and block repeat signups. You can expand on this by allowing a limited number of accounts per device, adjusting your response based on business rules, only evaluating recent signups, etc.
This is a minimal example to show how to use the Fingerprint .NET Server SDK. In a real
application, make sure to implement proper security practices, especially around password handling
and storage.
## 6. Test your implementation
Now that everything is set up, you can test the full flow using your existing frontend.
### Before you test
If your frontend is running on a different port (like localhost:5173 or localhost:3001), you may run into CORS issues for testing. To quickly fix this for local development & testing:
1. Add the following lines in `Program.cs`, just after the builder object is constructed:
```csharp Program.cs theme={"theme":"github-dark-dimmed"}
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
var app = builder.Build(); // <- Existing code!
app.UseCors();
```
### Test the implementation
1. Start your [ASP.NET](http://asp.net/) server:
```bash Terminal theme={"theme":"github-dark-dimmed"}
dotnet run
```
2. In your frontend, trigger a sign-up request that sends the `event_id`, `username`, and `password` to your `/api/create-account` endpoint. To see the reply messages make sure to parse and display or console log the response from your server.
3. Within your frontend, input a username and password to create a user. Then try to create another user and see that the second attempt will be rejected.
4. Bonus: Try creating an account using a headless browser.
## Next steps
You now have a working backend fraud check using Fingerprint. From here, you can expand your logic with more Smart Signals, adjust thresholds based on your risk tolerance, or introduce additional checks for suspicious users.
These same techniques apply to a wide range of fraud prevention use cases, from detecting fake reviews to blocking payment abuse or preventing account takeovers.
To go further, check out the use case tutorials for step-by-step guides tailored to specific problems you can solve with Fingerprint.
Check out these related resources:
* [.NET SDK Reference](/reference/net-server-sdk)
* [Vue frontend quickstart](/docs/vue-quickstart)
* [API reference for the Events endpoint](/reference/server-api-get-event)
* [Use case tutorial: Detecting new account fraud](/docs/new-account-fraud-use-case-tutorial)
* [Low-latency identification with Sealed Client Results](/docs/sealed-client-results)
# Overview
Source: https://docs.fingerprint.com/docs/fastly-compute-proxy-integration
Fingerprint Fastly Compute Proxy Integration is responsible for proxying identification and agent-download requests between your website and Fingerprint through your Fastly infrastructure. Your website does not strictly need to be behind Fastly to use this proxy integration, although that is optimal for maximum accuracy benefits.
The integration is a JavaScript WebAssembly Compute Service you can deploy to your Fastly account. The source code is [available on GitHub](https://github.com/fingerprintjs/fastly-compute-proxy).
**Limitations and expectations**
**Integration in Beta**
This integration is currently in Beta. If you find any issues, please contact our [support team](https://fingerprint.com/support).
**Limited to Enterprise plan**
Support for the Fastly Compute Proxy Integration is provided only for customers on the **Enterprise** Plan. Other customers are encouraged to use [Custom subdomain setup](/docs/custom-subdomain-setup) or [Cloudflare Proxy Integration](/docs/cloudflare-integration).
**Manual updates occasionally required**
The underlying data contract in the identification logic can change to keep up with browser and device releases. Using the Fastly Compute Proxy Integration might require occasional manual updates on your side. Ignoring these updates will lead to lower accuracy or service disruption.
## The benefits of using the Fastly Compute Proxy Integration
* Significant increase in accuracy in browsers with strict privacy features, such as Safari or Firefox.
* Cookies are now recognized as "first-party". This means they can live longer in the browser and extend the lifetime of visitor IDs.
* Ad blockers will not block the Fingerprint JavaScript agent from loading. Requests to Fingerprint domains are stopped by most ad blockers, but requests to same-site URLs are always allowed.
* Ad blockers will not block Fingerprint identification requests since they are sent to a subdomain that belongs to the same site.
* Insight and control over the identification requests that can be combined with other Fastly features like Compute and traffic reports.
* Cookie security: Fastly Compute Proxy Integration drops all the cookies sent from the origin website. The code is open-source, so you can transparently verify this behavior.
* Easy to meet compliance and auditing requirements.
## Integration setup overview
The integration setup consists of the following three steps. Each step is described in detail below.
1. Issue a proxy secret in the Fingerprint dashboard.
2. Deploy Fingerprint proxy integration Compute service in your Fastly account.
3. Configure the Fingerprint client agent on your website or mobile app.
## Step 1: Create a Fingerprint proxy secret
Issue a Fingerprint proxy secret to authenticate requests from your Fastly infrastructure.
1. Go to the [Fingerprint dashboard](https://dashboard.fingerprint.com/) and select your workspace.
2. In the left menu, click [**API keys**](https://dashboard.fingerprint.com/api-keys).
3. Click **Create Proxy Key**.
4. Give it a name, for example, `Fastly Compute proxy integration`.
5. Optionally, you can [choose an environment](/docs/multiple-environments#proxy-integrations-and-proxy-secrets) for the proxy secret.
1. By default, the proxy secret works for all environments in your workspace.
2. A proxy secret scoped to a specific environment can only authenticate identification requests made with a public API key from the same environment.
6. Click **Create API Key**.
You will use the proxy secret value in the following steps, so store it somewhere safe.
## Step 2: Deploy the proxy integration Compute service
You can choose from two available installation methods.
### Using Terraform
If you manage your infrastructure using Terraform, you can use the official Fastly Compute Proxy Integration Terraform module. This is the recommended method for deploying the integration. It provides a streamlined, versioned setup with fewer manual steps.
Continue to [Deploy Fastly Compute Proxy integration using Terraform](/docs/deploy-fastly-compute-using-terraform).
### Deploying Manually
If you don't use Terraform, you can manually deploy the integration using the Fastly web interface. This method requires more manual steps.
If you prefer the manual installation method, continue to [Deploy Fastly Compute Proxy integration manually](/docs/deploy-fastly-compute-manually).
## Step 3: Configure the Fingerprint client agent to use your service
Configure the Fingerprint client agent on your website or mobile app accordingly:
```javascript NPM package theme={"theme":"github-dark-dimmed"}
// The same pattern applies to React SDK, Vue SDK, etc.
import * as Fingerprint from '@fingerprint/agent';
// Initialize the agent at application startup.
const fp = Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
endpoints: 'https://metrics.yourwebsite.com/',
region: 'us',
});
```
```javascript CDN installation theme={"theme":"github-dark-dimmed"}
const url = 'https://metrics.yourwebsite.com/web/v4/PUBLIC_API_KEY';
const fpPromise = import(url)
.then(Fingerprint => Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
endpoints: 'https://metrics.yourwebsite.com/',
region: 'us',
}));
```
If everything is configured correctly, you should receive the latest Fingerprint client-side script and the identification result through your Fastly Compute proxy integration.
## Monitoring and troubleshooting the integration
You can go to the integration's status page at `/status` (for example `https://metrics.yourwebsite.com/status`) to check that the integration is running and all required configuration variables have been set correctly.
Inside your [Fingerprint Dashboard](https://dashboard.fingerprint.com/integrations), go to **SDKs & integrations** > **Fastly Compute** to see the usage metrics of your integration. Here you can monitor:
* If the integration is up to date.
* How many identification requests are coming through the integration (and how many are not).
* The error rate of proxied identification requests (caused by missing or incorrect proxy secret).
The information on the status page is cached so allow a few minutes for the latest data points to be reflected.
If you have any questions, reach out to our [support team](https://fingerprint.com/support).
### Fastly logging
If your integration isn't working as expected, it can be useful to look at the Fastly Compute service logs. Fastly offers a [variety of logging options and integrations](https://www.fastly.com/documentation/guides/integrations/logging).
For simple debugging purposes, we recommend using [Fastly log-tail](https://www.fastly.com/documentation/guides/compute/testing/):
1. [Install and configure the Fastly CLI](https://www.fastly.com/documentation/reference/tools/cli/#installing) on your machine.
2. Run `fastly log-tail --service-id ` to see a stream of log messages from the integration Compute service.
## Keeping your integration up to date
The Fastly Compute proxy integration does not update automatically. To stay current:
* **If you're managing your integration manually**, regularly check the [GitHub Releases](https://github.com/fingerprintjs/fastly-compute-proxy/releases/) page and follow the [manual deployment steps](/docs/deploy-fastly-compute-manually) to update your service package when needed.
* **If you're using Terraform**, refer to the [Terraform deployment guide](/docs/deploy-fastly-compute-using-terraform#updating-the-integration) to redeploy your package after reviewing the latest release.
If there is a new major version or another reason you need to update your integration, our support team will get in touch with you.
## Cost calculation
The resources required by the proxy integration fit within the Fastly Free Tier:
* The integration uses 1 out of the 5 available Origins per Compute Service.
* The integration uses 0-2 out of the 100 available Config Store Items (only if supporting JavaScript agent v3).
* By default, the integration uses 200 out of the 1000 [theoretically](https://docs.fastly.com/products/network-services-resource-limits#service-domain-and-origin-limits) available connections per Compute service. Please contact our support team if you need to configure this value.
For more details on limitations affecting your setup see [Compute resource limits](https://docs.fastly.com/products/network-services-resource-limits).
# Migrate Fastly Compute proxy integration to JavaScript agent v4
Source: https://docs.fingerprint.com/docs/fastly-compute-v3-to-v4-migration-guide
This guide covers upgrading the Fastly Compute proxy integration from `v0.3` to `v0.4`. The new version adds support for [JavaScript agent v4](/reference/migrating-from-v3-to-v4) while maintaining compatibility with v3, and also includes the following breaking changes:
* **Single backend**: The integration now uses one backend named `fingerprint` instead of region-specific backend names (`api.fpjs.io`, `eu.api.fpjs.io`, `ap.api.fpjs.io`). The `fpcdn.io` (CDN) backend has also been removed. Agent script downloads now go through the same `fingerprint` backend.
* **Plugin system removed**: Open Client Response plugins, sealed result decryption, and KV store saving are no longer supported.
* **Path variables now optional**: `AGENT_SCRIPT_DOWNLOAD_PATH` and `GET_RESULT_PATH` are only needed for backward compatibility with JavaScript agent v3.
## Migration overview
1. Upgrade the proxy integration to v0.4. Choose the migration path that matches how you deployed your integration: [using Terraform](#migrate-using-terraform) or [manually](#migrate-manually).
2. [Switch to JavaScript agent v4](#switch-to-javascript-agent-v4).
3. [Remove the plugin configuration resources](#removing-plugin-configuration-resources-optional) (if you were using them).
## Migrate using Terraform
### Step 1: Add the `region` variable
Add the `region` variable to your module configuration. Set it to the [region](/docs/regions) of your Fingerprint workspace:
```terraform theme={"theme":"github-dark-dimmed"}
module "fingerprint_fastly_compute_integration" {
source = "fingerprintjs/compute-fingerprint-proxy-integration/fastly"
fastly_api_token = "FASTLY_API_TOKEN"
integration_domain = "metrics.yourwebsite.com"
service_id = "FASTLY_COMPUTE_SERVICE_ID"
region = "us" # or "eu" or "ap" // [!code ++]
# agent_script_download_path and get_result_path are only needed for JS agent v3
}
```
You can also remove `kv_store_enabled = true` if you had it set, see [Removing plugin configuration resources](#removing-plugin-configuration-resources-optional) for more details.
### Step 2: Apply the Terraform changes
```shell theme={"theme":"github-dark-dimmed"}
terraform apply
```
Terraform automatically replaces the old region-specific backends and the `fpcdn.io` backend with a single `fingerprint` backend. If you had `kv_store_enabled = true`, the KV Store is also destroyed.
## Migrate manually
### Step 1: Update the proxy package
1. Go to the [latest release](https://github.com/fingerprintjs/fastly-compute-proxy/releases/latest) in the Fastly Compute Proxy Integration GitHub repository and download `fingerprint-fastly-compute-proxy-integration.tar.gz`.
2. In Fastly, open your proxy integration Compute service and go to **Package**.
3. Upload the new package and click **Activate**.
### Step 2: Replace the service backends
Replace all existing backends with a single backend named `fingerprint`:
1. In Fastly, open your proxy integration Compute service and go to **Origins**.
2. Add a new backend:
* Set the **Name** to `fingerprint`. The proxy integration expects this exact name.
* Set the **Address** and **Override host** to the Fingerprint API host for your workspace region:
* `api.fpjs.io` — Global (US)
* `eu.api.fpjs.io` — EU
* `ap.api.fpjs.io` — Asia
3. Delete the old region-specific backends (`api.fpjs.io`, `eu.api.fpjs.io`, `ap.api.fpjs.io`) and the CDN backend (`fpcdn.io`).
4. Click **Activate** to deploy the changes.
## Switch to JavaScript agent v4
With the proxy upgraded, update your JavaScript agent to v4. See [Migrating from JavaScript agent v3 to v4](/reference/migrating-from-v3-to-v4) for more details.
```javascript NPM package theme={"theme":"github-dark-dimmed"}
import * as FingerprintJS from '@fingerprintjs/fingerprintjs-pro' // [!code --]
import * as Fingerprint from '@fingerprint/agent' // [!code ++]
const fpPromise = FingerprintJS.load({ // [!code --]
const fp = Fingerprint.start({ // [!code ++]
apiKey: 'PUBLIC_API_KEY',
scriptUrlPattern: [ // [!code --:4]
'https://metrics.yourwebsite.com/AGENT_SCRIPT_DOWNLOAD_PATH?apiKey=&version=&loaderVersion=',
FingerprintJS.defaultScriptUrlPattern,
],
endpoint: [ // [!code --:4]
'https://metrics.yourwebsite.com/GET_RESULT_PATH?region=us',
FingerprintJS.defaultEndpoint,
],
endpoints: 'https://metrics.yourwebsite.com/', // [!code ++]
region: 'us', // [!code ++]
})
```
```javascript CDN theme={"theme":"github-dark-dimmed"}
const fpPromise = import( // [!code --]
const fp = import( // [!code ++]
'https://metrics.yourwebsite.com/AGENT_SCRIPT_DOWNLOAD_PATH?apiKey=PUBLIC_API_KEY' // [!code --]
'https://metrics.yourwebsite.com/web/v4/PUBLIC_API_KEY' // [!code ++]
).then((FingerprintJS) => FingerprintJS.load({ // [!code --]
).then((Fingerprint) => Fingerprint.start({ // [!code ++]
endpoint: [ // [!code --:4]
'https://metrics.yourwebsite.com/GET_RESULT_PATH?region=us',
FingerprintJS.defaultEndpoint,
],
apiKey: 'PUBLIC_API_KEY',
endpoints: 'https://metrics.yourwebsite.com/', // [!code ++]
region: 'us', // [!code ++]
}))
```
> Note: The `?region=us` query parameter inside the `endpoints` URL is no longer needed.
Once all your clients are on v4, you can also remove `AGENT_SCRIPT_DOWNLOAD_PATH` and `GET_RESULT_PATH` from your Config Store and Terraform configuration. They are only needed for JavaScript agent v3 support.
## Removing plugin configuration resources (optional)
If you were using the [Open Client Response plugins](/docs/v3/using-open-client-response-with-fastly-compute-proxy-integration-plugins), remove the following resources from your Fastly service.
### Remove plugin configuration from your Secret Store
1. In Fastly, go to **Resources** > [**Secret stores**](https://manage.fastly.com/resources/secret-stores).
2. Open `Fingerprint_Compute_Secret_Store_`.
3. Delete the `DECRYPTION_KEY` item.
> Note: Terraform does not manage Secret Store items. Remove `DECRYPTION_KEY` manually if you had it set.
### Remove plugin configuration from your Config Store
1. In Fastly, go to **Resources** > [**Config stores**](https://manage.fastly.com/resources/config-stores).
2. Open `Fingerprint_Compute_Config_Store_`.
3. Delete `OPEN_CLIENT_RESPONSE_PLUGINS_ENABLED` and `SAVE_TO_KV_STORE_PLUGIN_ENABLED` if they exist.
### Remove the KV Store
If you had the KV Store plugin enabled, remove the associated KV Store resource.
#### Manually
1. In Fastly, go to **Resources** > [**KV Stores**](https://manage.fastly.com/resources/kv-stores).
2. Find `Fingerprint_Results_`, unlink it from your service, and delete it.
#### Using Terraform
1. In Terraform, remove the `kv_store_enabled` variable from your module configuration.
2. Run `terraform apply` to remove the KV Store resource.
# Overview
Source: https://docs.fingerprint.com/docs/fastly-vcl-proxy-integration
Fingerprint Fastly VCL (Varnish Configuration Language) Proxy Integration is responsible for proxying identification and agent-download requests between your website and Fingerprint through your Fastly infrastructure. The integration consists of a set of VCL rules you can add to your Fastly CDN Service. The VCL template is available on [GitHub](https://github.com/fingerprintjs/fastly-vcl-proxy/releases/latest).
**Limitations and expectations**
**Limited to Enterprise plan**
The Fastly VCL Proxy Integration is supported only for customers on the **Enterprise** Plan. Other customers are encouraged to use [Custom subdomain setup](/docs/custom-subdomain-setup) or [Cloudflare Proxy Integration](/docs/cloudflare-integration).
**Manual updates occasionally required**
The underlying data contract in the identification logic can change to keep up with browser and device releases. Using the Fastly VCL Proxy Integration might require occasional manual updates on your side. Ignoring these updates will lead to lower accuracy or service disruption.
**We recommend using a subdomain instead of using the apex domain**
Fastly recommends configuring this integration on a subdomain (e.g. `www.yourwebsite.com` or `metrics.yourwebsite.com`) rather than an apex domain (e.g. `example.com`). Apex domains cannot use CNAME records per DNS specification, and while Fastly does offer anycast IP addresses (A/AAAA records) as a workaround, these may be less performant. See [Fastly's documentation on using Fastly with apex domains](https://www.fastly.com/documentation/guides/full-site-delivery/domains-and-origins/using-fastly-with-apex-domains) for more details.
## The benefits of using the Fastly VCL Proxy Integration
* Significant increase in accuracy in browsers with strict privacy features, such as Safari or Firefox.
* Cookies are now recognized as “first-party.” This means they can live longer in the browser and extend the lifetime of visitor IDs.
* Ad blockers will not block our Fingerprint JavaScript agent from loading. Attempts to connect to an external URL will be stopped by most ad blockers, but attempts to connect to the same site URL will be allowed.
* Ad blockers will not block our identification requests since they are sent to the specific path or subdomain that belongs to the same site.
* Insight and control over the identification requests that can be combined with other Fastly features like VCL and traffic reports.
* With the Fastly VCL Proxy Integration, you can manage unlimited subdomains and paths and provide Fingerprint services to all your customers at any scale while benefiting from all the 1st-party integration improvements.
* Cookie security: Fastly VCL Proxy Integration drops all the cookies sent from the origin website. The code is open-source, so this behavior can be transparently verified and audited.
* Easy to meet compliance and auditing requirements.
## Integration setup overview
The integration setup consists of the following four steps. Each step is described in detail below.
1. Issue a proxy secret in the Fingerprint dashboard.
2. Create integration path variable.
3. Apply Fingerprint VCL and configuration values to your Fastly CDN Service.
4. Configure the Fingerprint JavaScript agent on your website.
## Step 1: Create a Fingerprint proxy secret
Issue a Fingerprint proxy secret to authenticate requests from your Fastly infrastructure.
1. Go to the [Fingerprint dashboard](https://dashboard.fingerprint.com) and select your workspace.
2. In the left menu, navigate to [**API keys**](https://dashboard.fingerprint.com/api-keys).
3. Click **Create Proxy Key**.
4. Give it a name, for example, `Fastly VCL proxy integration`.
5. Optionally, you can [choose an environment](/docs/multiple-environments#proxy-integrations-and-proxy-secrets) for the proxy secret.
1. By default, the proxy secret works for all environments in your workspace.
2. A proxy secret scoped to a specific environment can only authenticate identification requests made with a public API key from the same environment.
6. Click **Create API Key**.
You will use the proxy secret value during deployment, so store it somewhere safe.
## Step 2: Create integration path variable
You need to set the path variable for your integration you will use throughout your Fastly configuration ([Step 3.1](/docs/deploy-fastly-vcl-manually)) and the JavaScript agent configuration on your website ([Step 4](#step-4-configure-the-fingerprint-javascript-agent-on-your-client)). This value is arbitrary. Just decide what your value is and write them down somewhere.
In this guide, we will use a readable value corresponding to the variable name to make it easier to follow:
```text theme={"theme":"github-dark-dimmed"}
INTEGRATION_PATH="INTEGRATION_PATH"
```
However, your value used in production should look more like random string:
```text theme={"theme":"github-dark-dimmed"}
INTEGRATION_PATH="ore54guier"
```
That is because some ad blockers might automatically block requests from any URL containing fingerprint-related terms like "fingerprint", "fpjs", "track", etc. Random strings are the safest. So whenever you see a value like `INTEGRATION_PATH` in this guide, you should use your random value instead.
## Step 3: Add Fingerprint VCL to your Fastly CDN Service
You can choose from two available installation methods.
### Using Terraform
If you manage your infrastructure using Terraform, you can use the official Fastly VCL Proxy Integration Terraform module. This is the recommended method for deploying the integration. It provides a streamlined, versioned setup with fewer manual steps.
Continue to [Deploy Fastly VCL Proxy integration using Terraform](/docs/deploy-fastly-vcl-using-terraform).
### Deploying Manually
If you don’t use Terraform, you can manually deploy the integration using the Fastly web interface. This method requires more manual steps.
If you prefer the manual installation method, continue with [Deploy Fastly VCL Proxy integration manually](/docs/deploy-fastly-vcl-manually).
## Step 4: Configure the Fingerprint JavaScript agent on your client
Use the integration path variable created in [Step 2](#step-2-create-integration-path-variable) to construct the integration endpoint URLs.
Configure the Fingerprint JavaScript agent on your website accordingly:
```javascript NPM Package theme={"theme":"github-dark-dimmed"}
import * as Fingerprint from '@fingerprint/agent'
// Initialize the agent at application startup.
const fp = Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
endpoints: ['https://metrics.yourwebsite.com/INTEGRATION_PATH?region=us'],
region: 'us',
});
```
```javascript CDN theme={"theme":"github-dark-dimmed"}
const url = 'https://metrics.yourwebsite.com/INTEGRATION_PATH/web/v4/PUBLIC_API_KEY?region=us';
const fpPromise = import(url)
.then(Fingerprint => Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
endpoints: ['https://metrics.yourwebsite.com/INTEGRATION_PATH?region=us'],
region: 'us',
}));
```
**Pay attention to differences in query parameters**
**Pass region** to the `endpoints` URL in the following format: `?region=eu`. The value needs to reflect the [region](/docs/regions) of your application.
If everything is configured correctly, you should receive the latest Fingerprint client-side script and the identification result through your Fastly VCL integration. If you have any questions, reach out to our [support team](https://fingerprint.com/support/?_gl=1*1f8iyeh*_ga*MTM2ODkyMjcyNS4xNzA1MDk5MjQ0*_ga_XVKDT0L5WV*MTcxNjQ1NTczOS40OC4xLjE3MTY0NTg4ODIuMTIuMC41MTAwNTMzMTA.).
## Monitoring and troubleshooting the integration
Go to **Dashboard** > [**SDKs & integrations**](https://dashboard.fingerprint.com/integrations) > **Fastly VCL** to see the status of your integration. Here you can monitor:
* If the integration is up to date.
* How many identification requests are coming through the integration (and how many are not).
* The error rate of proxied identification requests (caused by a missing, incorrect, or environment-mismatched proxy secret).
The information on the status page is cached so allow a few minutes for the latest data points to be reflected.
If you run into problems:
1. Go to the integration's status page at `https://metrics.yourwebsite.com/INTEGRATION_PATH/status` to check if the integration is running and all required configuration variables have been set correctly.
2. When contacting our [support team](https://fingerprint.com/support/?_gl=1*1f8iyeh*_ga*MTM2ODkyMjcyNS4xNzA1MDk5MjQ0*_ga_XVKDT0L5WV*MTcxNjQ1NTczOS40OC4xLjE3MTY0NTg4ODIuMTIuMC41MTAwNTMzMTA.), please provide them with the VCL file of your CDN service.
1. Navigate to **CDN** > **CDN Services**, open your CDN service, and pick the **Active** version.
2. Click **Show VCL** and then **Download**.
## Cost calculation
The resources required by the proxy integration fit within the Fastly Free Tier:
* The integration uses 3 out of the 5 available Origins per CDN Service.
* The integration uses 2 out of the 1000 available Dictionary Items.
* By default, the integration uses 200 out of the 1000 [theoretically](https://docs.fastly.com/products/network-services-resource-limits#service-domain-and-origin-limits) available connections per CDN service. Please contact our support team if you need to configure this value.
For more details and other limitations affecting your setup, please see [Fastly Network services resource limits](https://docs.fastly.com/products/network-services-resource-limits).
# Migrate from API v3 to API v4
Source: https://docs.fingerprint.com/docs/fastly-vcl-v3-to-v4-migration-guide
**Welcome!** This guide helps you migrate your Fastly VCL integration from API v3 to API v4.
You will first gather your existing integration details, then upgrade your Fastly VCL config so it can handle both v3 and v4 routes, validate v4 traffic through the proxy before changing production, update your app to JavaScript Agent v4, and finally monitor the rollout and clean up any temporary migration paths once v3 traffic has fully stopped.
Follow the steps below in order. If you keep v3 working while you validate v4, you can roll
forward safely and avoid user impact.
## Step 0: Collect Inputs (no changes yet)
You should have:
* Your current integration domain (example: `https://metrics.yourwebsite.com`)
* Your current proxy secret variable (the one you already use with the v3 guide) `PROXY_SECRET`
* Your current integration path variables (the ones you already use with the v3 guide)
* `INTEGRATION_PATH`
* `AGENT_SCRIPT_DOWNLOAD_PATH`
* `GET_RESULT_PATH`
* Note: once migration is complete and your website is fully on agent v4, `AGENT_SCRIPT_DOWNLOAD_PATH` and `GET_RESULT_PATH` are no longer required
* Your public API key
* Your region (example: `us`, `eu`, `ap`)
## Step 1: Upgrade your Fastly VCL integration first
Update the VCL asset / configuration to a version that supports agent v4.
The steps below are for the manual deployment method. For Terraform, run `terraform apply` to update to the latest version.
### 1.1 Get the latest VCL file from GitHub
* Go to our [GitHub repository](https://github.com/fingerprintjs/fastly-vcl-proxy) for the Fastly VCL proxy integration.
* Open [Releases](https://github.com/fingerprintjs/fastly-vcl-proxy/releases).
* Download the [latest release](https://github.com/fingerprintjs/fastly-vcl-proxy/releases/latest) artifact for the VCL file.
### 1.2 Update your Fastly service
* In Fastly, open your CDN service and **clone** the active version.
* Replace the **corresponding VCL file** with the contents of the downloaded VCL file.
* If you see a separate fingerprint related file you should update that.
* If you only have main.vcl file you can just replace it.
* Publish (activate) the new version.
### 1.3 Verify
Acceptance criteria:
* The integration can still serve the v3 agent download path and v3 identification path.
* The integration can also route v4 requests, for example requests under `/web/v4/...`.
Verification ideas:
* Open `https://INTEGRATION_DOMAIN/INTEGRATION_PATH/status` and confirm the integration is healthy.
* Confirm your existing (v3) production client still works and can identify.
* Validate your v4 traffic through the proxy. Try the code snippet below.
```javascript NPM theme={"theme":"github-dark-dimmed"}
import * as Fingerprint from '@fingerprint/agent';
const fp = Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
region: 'us',
endpoints: ['https://metrics.yourwebsite.com/INTEGRATION_PATH?region=us'], // Replace "?region" query param with your region as well
});
fp.get().then((result) => console.log(result));
```
## Step 2: Validate v4 routing before changing your production client
Before flipping your app, test v4 through the proxy.
Options:
* Use a staging site / feature flag to load agent v4.
* Temporarily run agent v4 in a dev environment.
What to check:
* The agent script loads through the proxy (no ad blocker or CDN errors)
* You may use browser dev tools' network tab to confirm the script is loaded from the proxy.
* Identification requests go through the proxy (not directly to Fingerprint default endpoints)
* Results are returned successfully
## Step 3: Update your application to JavaScript Agent v4
Update your website to load and initialize agent v4 using your proxy base URL.
Replace the placeholders with your values:
```javascript NPM theme={"theme":"github-dark-dimmed"}
// Replace `region` query param with your region
// Replace `PUBLIC_API_KEY` with your public API key
// Replace `INTEGRATION_PATH` with your integration path
import * as Fingerprint from '@fingerprint/agent';
const fp = Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
region: 'us',
endpoints: ['https://metrics.yourwebsite.com/INTEGRATION_PATH?region=us'], // Replace "?region" query param with your region as well
})
fp.get()
.then((result) => {
console.log(result);
})
.catch((err) => {
// Optional: log errors; endpoint fallback is controlled via the `endpoints` option
console.error(err);
});
```
```javascript CDN theme={"theme":"github-dark-dimmed"}
// Replace `region` query param with your region
// Replace `PUBLIC_API_KEY` with your public API key
// Replace `INTEGRATION_PATH` with your integration path
const fp =
import('https://metrics.yourwebsite.com/INTEGRATION_PATH/web/v4/PUBLIC_API_KEY?region=us').then(
(Fingerprint) =>
Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
region: 'us', // Replace with your region
endpoints: ['https://metrics.yourwebsite.com/INTEGRATION_PATH?region=us'], // Replace "?region" query param with your region as well
}),
);
fp
.then((fp) => fp.get())
.then((result) => {
console.log(result);
})
.catch((err) => {
// Optional: log errors; endpoint fallback is controlled via the `endpoints` option
console.error(err);
});
```
## Step 4: Monitor and finalize
After rollout:
* Monitor proxied traffic and error rate from the integration status UI
* Confirm the majority of traffic is using v4 paths
Optional cleanup (later):
* If you introduced any temporary parallel paths for testing, remove them
* When you are confident no clients use v3, you can simplify any v3-specific configuration
* You may remove the `AGENT_SCRIPT_DOWNLOAD_PATH` and `GET_RESULT_PATH` variables from your dictionary.
# Google Chrome Extension
Source: https://docs.fingerprint.com/docs/fingerprintjs-pro-and-chrome-extension
### Using Fingerprint in Google Chrome Extensions
A typical use-case represents getting a valid [`visitor_id`](/reference/js-agent-get-function#visitor_id) via your extension. After validating it with the [Server API](/reference/server-api) or [webhooks](/docs/webhooks), data provided by Fingerprint might be crucial for your internal decision making, user scoring, and protecting sensitive actions for your business.
There are two general strategies to integrate Fingerprint into an extension. You can get Fingerprint result by opening your website temporarily in a new window. Another way demonstrates injecting the [Fingerprint JavaScript agent](/reference/js-agent) into the website's iframe and performing fingerprinting there.
Both solutions are showcased in the [Chrome extension repository](https://github.com/fingerprintjs/fingerprintjs-pro-chrome-extension-example). The example extension is also available on the [Chrome Web Store](https://chrome.google.com/webstore/detail/fingerprintjs-example-bro/knppbjgkegnlbhddedbilnfmnkdocekn).
The example extension is currently still implemented using JavaScript agent v3, see the [migration
guide](/reference/migrating-from-v3-to-v4) for more information.
### New window strategy
With this approach, the Chrome extension creates a new window that points to an external website hosted by you. This website uses our Fingerprint agent to obtain the `result`. This data is then passed back to the extension using a [native communication channel](https://developer.chrome.com/docs/extensions/mv3/messaging/#external-webpage).
#### Sample use
1. Configure and serve a publicly available web page containing the Fingerprint JavaScript agent. It's recommended to use the [Custom subdomain setup](/docs/custom-subdomain-setup) or one of our cloud proxy integration to [protect the JavaScript agent from ad blockers](/docs/protecting-the-javascript-agent-from-adblockers) and increase accuracy. This website must be served over HTTPS.
```javascript JavaScript theme={"theme":"github-dark-dimmed"}
// Script on your website
// Your chrome extension id
const extensionId = 'CHROME_EXTENSION_ID';
// Initialize the agent
const fpPromise = import('https://fpjscdn.net/v4/PUBLIC_API_KEY').then((Fingerprint) =>
Fingerprint.start({
endpoints: 'https://metrics.yourwebsite.com/',
}),
);
fpPromise
.then((fp) => fp.get())
.then((result) => {
// Pass the result back to the chrome extension
// Note: this API is only available in chromium based browsers and only on pages served via HTTPS
chrome.runtime.sendMessage(extensionId, {
type: 'fpjs-result',
data: result,
});
});
```
2. In the extension's `manifest.json`, add the `externally_connectable` manifest property. Make sure you've specified the `service_worker` property in the `background` section as well.
```javascript JavaScript theme={"theme":"github-dark-dimmed"}
// manifest.json of your chrome extension
{
...
"externally_connectable": {
// URL to the external site that uses our Agent
"matches": ["https://your-website.com/*"]
},
...
"background": {
// Name of your background script file
"service_worker": "background.js"
},
...
}
```
3. Add the following code snippet into your background script. It will allow you to obtain results from Fingerprint inside your extension's codebase.
```javascript JavaScript theme={"theme":"github-dark-dimmed"}
let currentWindow;
async function closeCurrentWindow() {
if (currentWindow?.id) {
try {
await chrome.windows.remove(currentWindow.id);
currentWindow = undefined;
} catch (error) {
// Handle error
}
}
}
async function getFingerprint() {
await closeCurrentWindow();
currentWindow = await chrome.windows.create({
url: 'WEBSITE_URL',
type: 'popup',
focused: false,
});
return new Promise((resolve) => {
const handleExternalMsg = async (externalMessage) => {
if (externalMessage?.type === 'fpjs-result' && externalMessage?.data?.visitor_id) {
resolve(externalMessage.data);
chrome.runtime.onMessageExternal.removeListener(handleExternalMsg);
// Close created window after receving result
await closeCurrentWindow();
}
};
// Register listener for messages from our website
chrome.runtime.onMessageExternal.addListener(handleExternalMsg);
});
}
// Add a listener for messages from your extension requesting data from the JavaScript agent
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'get-visitor-id') {
getFingerprint().then(sendResponse);
// Required for async operations, otherwise, chrome won't pass the result back to the sender
return true;
}
});
```
4. Receive and use the `result` data in your extension's logic.
```javascript JavaScript theme={"theme":"github-dark-dimmed"}
export function getFingerprintResult() {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage(
{
type: 'get-visitor-id',
},
(message) => {
if (message?.visitor_id) {
resolve(message);
} else {
reject(new Error('Failed to get visitor data'));
}
},
);
});
}
getFingerprintResult().then((result) => {
// Use result
});
```
### Iframe strategy
With this strategy, the extension appends an iframe into the DOM with the URL of the external website and communicates with it using [Window.postMessage() API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage).
Alternatively, you can append the iframe into the DOM served on the extension page (e.g. in the popup). There are several benefits of not using a content script:
* Other extensions don't have access to the iframe content (e.g. adblockers).
* There are no required special permissions in the `manifest.json`.
* Several anti-fingerprinting techniques and tracking protections can't identify and block this approach.
#### Sample use
1. Configure and serve a publicly available web page containing the Fingerprint JavaScript agent. It's recommended to use the [Custom subdomain](/docs/custom-subdomain-setup). The website must be served over HTTPS.
```javascript theme={"theme":"github-dark-dimmed"}
// Script on your website
const parentOrigin = 'chrome-extension://CHROME_EXTENSION_ID';
// Check if we are in iframe
if (window.parent !== window) {
// Initialize the agent
const fpPromise = import('https://fpjscdn.net/v4/PUBLIC_API_KEY').then((Fingerprint) =>
Fingerprint.start({
endpoints: 'https://fp.yourdomain.com', // Subdomain setup URL
}),
);
fpPromise
.then((fp) => fp.get())
.then((result) => {
// Send the result to parent window
window.parent.postMessage(
{
type: 'fpjs-result',
data: result,
},
parentOrigin,
);
});
}
```
2. Add the following code to your extension where you need to get `result` data.
**DOM API and background scripts**
With this approach, your extension needs access to the DOM API, therefore, according to the [Manifest v3 limitations](https://developer.chrome.com/docs/extensions/mv3/intro/), it's not possible to use the following snippet in the background script.
```javascript JavaScript theme={"theme":"github-dark-dimmed"}
export function getFingerprintResult(container) {
const iframeUrl = new URL('https://yourwebsite.com');
const iframe = document.createElement('iframe');
// Apply styles to the iframe
iframe.style.width = '100%';
iframe.style.height = '200px';
iframe.style.border = 'none';
iframe.src = iframeUrl.href;
return new Promise((resolve) => {
const handler = (event) => {
if (event.origin !== iframeUrl.origin || event.source !== iframe.contentWindow) {
return;
}
const eventData = event.data;
if (eventData?.type === 'fpjs-result' && eventData?.data?.visitor_id) {
window.removeEventListener('message', handler);
iframe.remove();
resolve(eventData.data.visitor_id);
}
};
// Listen for messages from iframe
window.addEventListener('message', handler);
container.appendChild(iframe);
});
}
const container = document.querySelector('.main');
getFingerprintResult(container).then((result) => {
// Use result
});
```
### Documentation
You can find the full documentation in the official [GitHub repository](https://github.com/fingerprintjs/fingerprintjs-pro-chrome-extension-example).
# Next
Source: https://docs.fingerprint.com/docs/fingerprintjs-pro-nextjs
The [Fingerprint React SDK](https://github.com/fingerprintjs/react) is an easy way to integrate Fingerprint into your Next.js application. It supports all capabilities of the JavaScript agent and provides a built-in caching mechanism.
### How to install
Add `@fingerprint/react` as a dependency to your application via npm or yarn.
```shell NPM theme={"theme":"github-dark-dimmed"}
npm install @fingerprint/react
```
```shell Yarn theme={"theme":"github-dark-dimmed"}
yarn add @fingerprint/react
```
```shell PNPM theme={"theme":"github-dark-dimmed"}
pnpm add @fingerprint/react
```
Wrap your application (or component) in `FingerprintProvider`. You need to specify your public API key and other configuration options based on your chosen region and active integration.
```javascript JavaScript theme={"theme":"github-dark-dimmed"}
// pages/_app.tsx
import {FingerprintProvider} from '@fingerprint/react'
import {AppProps} from 'next/app'
export default function MyApp({Component, pageProps}: AppProps) {
return (
)
}
```
Use the `useVisitorData` hook in your components to identify visitors.
```javascript JavaScript theme={"theme":"github-dark-dimmed"}
// pages/index.tsx
import {useVisitorData} from '@fingerprint/react'
export default function Home() {
const { isLoading, error, data, getData } = useVisitorData(
{ immediate: true },
)
return (
)
}
```
### Documentation
You can find the full documentation in the official [GitHub repository](https://github.com/fingerprintjs/react). The repository also contains [an example app](https://github.com/fingerprintjs/react/tree/main/examples/next) demonstrating the usage of the library.
### Migration guide for React SDK v3.0.0
You can find the migration guide on the [React SDK page](/docs/react).
# React Native
Source: https://docs.fingerprint.com/docs/fingerprintjs-pro-react-native
Fingerprint React Native is an official [open-source](https://github.com/fingerprintjs/fingerprintjs-pro-react-native) library for projects written in React Native for iOS and Android platforms. This library allows developers to use Fingerprint capabilities in the React Native context. All Fingerprint agent capabilities are fully supported. View our [React Native quickstart](/docs/react-native-quickstart) for a step-by-step guide to get started.
## Requirements
The React Native SDK uses the [Android](/docs/native-android-integration) and [iOS](/docs/ios) SDKs under the hood, so it has the same minimum OS version requirements:
* Android 5.0 (API level 21+) or higher
* iOS 12 or higher (or tvOS 12 or higher), Swift 5.7 or higher
## How to install
### 1. Install the package using your favorite package manager:
```shell NPM theme={"theme":"github-dark-dimmed"}
npm install @fingerprintjs/fingerprintjs-pro-react-native --save
```
```shell Yarn theme={"theme":"github-dark-dimmed"}
yarn add @fingerprintjs/fingerprintjs-pro-react-native
```
```shell PNPM theme={"theme":"github-dark-dimmed"}
pnpm add @fingerprintjs/fingerprintjs-pro-react-native
```
### 2. Configure iOS dependencies (if developing on iOS)
```shell theme={"theme":"github-dark-dimmed"}
cd ios && pod install
```
### 3. Configure Android dependencies (if developing on Android)
Add the repositories to your Gradle configuration file. The location for these additions depends on your project's structure and the Gradle version you're using:
#### Gradle 7 or newer
For Gradle 7.0 and higher (if you've adopted [the new Gradle settings file approach](https://developer.android.com/build#settings-file)), you likely manage repositories in the `dependencyResolutionManagement` block in `{rootDir}/android/settings.gradle`. Add the Maven repositories in this block:
```groovy Groovy theme={"theme":"github-dark-dimmed"}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
google()
mavenCentral()
maven {
url("https://maven.fpregistry.io/releases") // Add this
}
}
}
```
#### Gradle 6.0 or older
For Gradle versions before 7.0, you likely have an `allprojects` block in `{rootDir}/android/build.gradle`. Add the Maven repositories within this block:
```groovy Groovy theme={"theme":"github-dark-dimmed"}
allprojects {
repositories {
mavenCentral()
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url("$rootDir/../node_modules/react-native/android")
}
maven {
// Android JSC is installed from npm
url("$rootDir/../node_modules/jsc-android/dist")
}
maven {
url("https://maven.fpregistry.io/releases") // Add this
}
google()
}
}
```
## Usage
Configure the SDK by wrapping your application in `FingerprintJsProProvider`.
```javascript JavaScript theme={"theme":"github-dark-dimmed"}
// src/index.js
import React from 'react';
import { AppRegistry } from 'react-native';
import { FingerprintJsProProvider } from '@fingerprintjs/fingerprintjs-pro-react-native';
import App from './App';
import { name as appName } from './app.json';
const WrappedApp = () => (
)
AppRegistry.registerComponent(appName, () => WrappedApp);
```
Use the `useVisitorData` hook in your components to perform visitor identification and get the data.
```javascript JavaScript theme={"theme":"github-dark-dimmed"}
// src/App.js
import React from 'react'
import {Button, SafeAreaView, Text, View} from 'react-native'
import {useVisitorData} from '@fingerprintjs/fingerprintjs-pro-react-native'
export default function App() {
const {isLoading, error, data, getData} = useVisitorData()
return (
)
}
```
## Documentation
You can find the full documentation in the official [GitHub repository](https://github.com/fingerprintjs/fingerprintjs-pro-react-native). The repository also contains [an example app](https://github.com/fingerprintjs/fingerprintjs-pro-react-native/tree/main/TestProject) demonstrating usage of the library.
### Limitations
* Fingerprint [request filtering](https://dev.fingerprintjs.com/docs/request-filtering) is not supported right now. Allowed and forbidden origins cannot be used.
* Using inside [Expo environment](https://docs.expo.dev) is not supported right now.
# Flow deployments on Cloudflare (Beta)
Source: https://docs.fingerprint.com/docs/flow-deployments
Deploy Fingerprint identification and protection to the edge with little to no changes to your application code.
**Feature in Beta**
This feature is currently in an open Beta testing phase. We recommend testing it inside an isolated staging environment. All feedback is welcome! If you encounter any issues, please [contact](https://fingerprint.com/support/) our support team.
**Pre-requisites**
* A Fingerprint account with an **Admin** role.
* A website [proxied](https://developers.cloudflare.com/dns/proxy-status/) (not DNS-only) through Cloudflare.
**Flow** is a Fingerprint deployment method that requires little to no application code changes, currently supported for websites hosted on **Cloudflare**.
The Flow worker sits between the browser accessing your website and the origin server. It automatically handles all the steps required to identify visitors on your website and protect sensitive actions:
* It injects the JavaScript agent into the pages you define.
* For the API endpoints you define, it triggers visitor identification. Optionally, it can protect those endpoints using the Rules engine.
* Requests are naturally same-site, no need for a separate proxy integration to deal with ad blockers.
For a more detailed conceptual overview, see [How Cloudflare Flow deployments work](#how-cloudflare-flow-deployments-work).
There are two ways to use Flow workers:
* [**In combination with the Rules engine (Protection mode)**](#using-a-flow-worker-with-the-rules-engine)
* [**On its own (Monitoring mode)**](#using-a-flow-worker-on-its-own-monitoring-mode)
## Using a Flow worker with the Rules Engine
The Rules Engine allows you to define rules evaluated against the Fingerprint identification events. For example, you can define a ruleset that prevents browsers in incognito mode from creating accounts in your application. See [Rules engine](/docs/rules-engine) for more details.
One way of using the ruleset is by deploying it using a Flow worker. For example, you can protect the `POST api.yourwebsite.com/create-account` API, called from the `yourwebsite.com/signup` page.
You cannot protect the signup page itself, only the API endpoint it calls. Calling the protected API endpoint from the identification page is necessary to trigger visitor identification.
1. Open your ruleset and switch to the **Settings** tab.
2. Under **Deployment**, click **Deploy with Cloudflare**.
3. Enter your **Cloudflare Account ID**, your Cloudflare **Token**, and click **Validate**.
1. You can get your Cloudflare Account ID from the [Cloudflare Dashboard -> Workers and Pages](https://dash.cloudflare.com/?to=/:account/workers-and-pages), on the bottom right corner of the page.
2. You need to get or create a Cloudflare token with the following permissions. [Click here to create a Cloudflare token from a pre-filled template](https://dash.cloudflare.com/profile/api-tokens?permissionGroupKeys=%5B%7B%22key%22%3A%22dns%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22workers_routes%22%2C%22type%22%3A%22edit%22%7D%2C%7B%22key%22%3A%22workers_scripts%22%2C%22type%22%3A%22edit%22%7D%5D\&name=fingerprint.com+flow+worker+deployment\&accountId=*\&zoneId=all):
1. `Account:Workers Scripts:Edit`
2. `Zone:Workers Routes:Edit` for the zone the worker will use
3. `Zone:DNS:Read` for the zone you want to deploy to
4. Pick the [environment](/docs/multiple-environments) of the Public and Secret API keys generated for the deployment.
5. Define at least one page for identifying visitors. For example, to identify visitors on your sign-up page, add:
1. `[no subdomain]`
2. `yourwebsite.com`
3. `/signup`
6. Define at least one API endpoint to protect with your ruleset. These protected APIs need to be called from the pages in the last step. They can be on a different subdomain, but must be inside the same zone. See [How Cloudflare Flow deployments work](#how-cloudflare-flow-deployments-work) for more details. For example, to protect the sign-up endpoint, add:
1. `api.`
2. `yourwebsite.com`
3. `POST`
4. `/create-account`.
7. Define the Failure mode. By default, the worker will *Fail open*, meaning it will let requests through if an unexpected identification error occurs. We recommend starting with this configuration for the initial testing, then switching to *Fail closed* for security use cases.
8. Click **Create Cloudflare worker**.
Within a few seconds, the Flow worker is deployed into your Cloudflare account.
* You can open your Cloudflare dashboard to confirm.
* You can navigate to the identification page in Incognito mode and try triggering the protected API. You will be blocked by the ruleset and receive the response you defined in your [rule action](/docs/rules-engine#rule-action).
Note: The Flow worker will start intercepting protected requests and applying rules immediately after deployment. If the failure mode is set to *Fail closed*, any errors in the [request instrumentation](#how-cloudflare-flow-deployments-work) (e.g., missing or corrupted browser data) will be treated as tampering attempts and blocked with a `403` Error response from the Flow worker.
* We recommend testing your ruleset and Flow deployment in a separate staging environment before applying it to production.
* You can deploy the Flow worker set to *Fail open* for the initial test of your configuration.
* You can deploy an empty or [disabled](/docs/rules-engine#ruleset-settings) ruleset first to test the Flow worker without applying any rules.
* You can delete the Flow deployment at any time using the Fingerprint Dashboard, or even manually remove the worker from your Cloudflare dashboard if needed.
### Editing Flow deployments
You can update your Flow deployment any time:
1. Navigate to **\** > **Settings** tab.
2. Under **Deployment**, click **⋯ (More actions)**, then **Edit worker**.
3. Add, remove, or update the identification pages and protected APIs.
4. Click **Save and redeploy** to save your changes.
This redeploys the Flow worker with the updated configuration. Do not make manual changes to the worker code or environment variables directly through Cloudflare as they will be overwritten by Fingerprint on the next redeployment.
**Rules are part of the Ruleset, not the Flow deployment**
* The Flow worker only knows its Ruleset ID. Rulesets are stored and evaluated inside the Fingerprint platform, not on Cloudflare.
* To update rules, make changes to the Ruleset and save it. It might take up to 60 seconds for the changes to take effect on your website.
* We recommend testing your rule changes in a separate staging ruleset and staging web environment before applying them to production.
### Deleting Flow deployment
To delete the Flow deployment:
1. Navigate to **\** > **Settings** tab.
2. Under **Deployment**, click **⋯ (More actions)**, then **Delete worker**.
3. Confirm by typing your domain name and clicking **Delete deployment**.
This completely removes the Flow worker and its associated routes from your Cloudflare account. The ruleset itself remains stored in your Fingerprint workspace. Note that you cannot delete a deployed ruleset without removing the Flow deployment first.
## Using a Flow worker on its own (Monitoring mode)
You can deploy a Flow worker without a ruleset. This allows you to identify visitors on your website and collect data about your traffic without applying any protective actions.
To deploy a Flow worker in monitoring mode:
1. In the Fingerprint Dashboard, navigate to **Libraries & integrations** -> **Directory** -> [**Cloudflare No-Code Worker**](https://dashboard.fingerprint.com/integrations/cloudflare-flow).
2. Click **New worker**.
3. Provide your **Cloudflare credentials**, define the **Identification pages** and **API endpoints**.
1. It works the same as when [using Flow with the Rules engine](#using-a-flow-worker-with-the-rules-engine); see that section above for more details. The only difference is that there is no ruleset associated with the Flow worker.
2. Note that you still need to define the API endpoints even if you are not using any ruleset. The identification page calling the API endpoint is what triggers visitor identification and sends the data to the worker, even if no protection is applied.
3. In Monitoring mode, we recommend keeping the **Failure mode** set to *Fail open* to let requests through if an unexpected identification error occurs.
4. Click **Create Cloudflare worker** to deploy.
You can switch to the **Workers** tab to see all your workers, whether they are using rulesets or not.
Clicking on a worker opens the worker detail, where you can view, manage, or delete the worker.
## Using a Flow worker to detect bots at the edge
Flow workers can detect AI agents, AI assistants, and bots that access your pages and API endpoints.
To enable this capability, when deploying or editing a Flow worker, expand the **Advanced** section, below the **Cloudflare credentials** section, and select the toggle labeled **Edge Bot Detection**.
After enabling **Edge Bot Detection**, the Flow worker provides bot detection data to your pages and APIs by setting HTTP request headers in the requests it forwards.
This data falls into two categories: [IP intelligence](#ip-intelligence-request-headers) and [bot intelligence](#bot-intelligence-request-headers). The following tables list the request headers and a description of their values for each category. For the mapping from each header to its corresponding [Server API event](/reference/server-api-get-event) property, see [Flow worker request headers](/reference/flow-worker-headers).
### IP intelligence request headers
IP intelligence headers are provided for all requests, including those that are made by AI automation, bots, or humans.
A single request is either IPv4 or IPv6, so only the matching set of `fp-ip-info-v4-*` or `fp-ip-info-v6-*` headers is set. Headers with no detected value are omitted (for example, `fp-ip-info-v4-datacenter-name` is only set when a datacenter is identified).
Header values are encoded as [RFC 9651 structured fields](https://www.rfc-editor.org/info/rfc9651/): strings are quoted (`"AWS"`), booleans are sent as `?1` only for true values (false values are omitted entirely), and Unix timestamps use the `@` prefix (`@1733943233`).
#### Address
| Request Header | Description |
| ----------------------- | -------------------------------------------------- |
| `fp-ip-info-v4-address` | The IPv4 address of the requesting browser or bot. |
| `fp-ip-info-v6-address` | The IPv6 address of the requesting browser or bot. |
#### Geolocation, ASN, and datacenter
The following headers are provided for both IPv4 and IPv6 (replace `v4` with `v6` for IPv6 requests).
| Request Header | Description |
| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `fp-ip-info-v4-geolocation-accuracy-radius` | Radius in kilometers around the estimated location. |
| `fp-ip-info-v4-geolocation-latitude` | Estimated latitude of the IP address. |
| `fp-ip-info-v4-geolocation-longitude` | Estimated longitude of the IP address. |
| `fp-ip-info-v4-geolocation-postal-code` | Postal code associated with the IP address. |
| `fp-ip-info-v4-geolocation-timezone` | IANA timezone, for example, `"America/Los_Angeles"`. |
| `fp-ip-info-v4-geolocation-city-name` | City associated with the IP address. |
| `fp-ip-info-v4-geolocation-country-code` | ISO 3166-1 alpha-2 country code, for example, `"US"`. |
| `fp-ip-info-v4-geolocation-continent-code` | Two-letter continent code, for example, `"NA"`. |
| `fp-ip-info-v4-asn-name` | Name of the organization that owns the ASN. |
| `fp-ip-info-v4-asn-network` | ASN network range in CIDR notation. |
| `fp-ip-info-v4-asn-type` | ASN type, for example, `"hosting"`, `"isp"`, `"business"`. |
| `fp-ip-info-v4-datacenter-name` | Datacenter operator name when the IP belongs to a known datacenter, for example, `"AWS"`. Omitted when not detected. |
#### Proxy
Proxy headers are only set when a proxy is detected. When no proxy is detected, none of the `fp-proxy-*` headers are sent.
| Request Header | Description |
| ------------------------------- | ------------------------------------------------------------------------------------ |
| `fp-proxy` | `?1` when the request is identified as coming through a proxy. |
| `fp-proxy-confidence` | Confidence level of the proxy detection, for example, `"low"`, `"medium"`, `"high"`. |
| `fp-proxy-details-proxy-type` | Type of proxy, for example, `"residential"`, `"datacenter"`. |
| `fp-proxy-details-last-seen-at` | Unix timestamp of when the proxy was last seen, for example, `@1733943233`. |
| `fp-proxy-details-provider` | Provider operating the proxy, when known. |
#### VPN
VPN headers are only set when a VPN is detected. Individual `fp-vpn-methods-*` headers are only set for detection methods that triggered; methods that did not trigger are omitted.
| Request Header | Description |
| ---------------------------------- | ---------------------------------------------------------------------------------- |
| `fp-vpn` | `?1` when the request is identified as coming through a VPN. |
| `fp-vpn-confidence` | Confidence level of the VPN detection, for example, `"low"`, `"medium"`, `"high"`. |
| `fp-vpn-methods-timezone-mismatch` | `?1` when the browser timezone does not match the IP location. |
| `fp-vpn-methods-public-vpn` | `?1` when the IP belongs to a known public VPN service. |
| `fp-vpn-methods-auxiliary-mobile` | `?1` when an auxiliary mobile-network signal indicates VPN use. |
| `fp-vpn-methods-os-mismatch` | `?1` when OS-level signals indicate VPN use. |
| `fp-vpn-methods-relay` | `?1` when the IP belongs to a known relay service (e.g. iCloud Private Relay). |
#### IP blocklist
| Request Header | Description |
| -------------------------- | ----------------------------------------------------------------------------- |
| `fp-ip-blocklist-tor-node` | `?1` when the IP address is a known Tor exit node. Omitted when not detected. |
### Bot intelligence request headers
Bot intelligence headers are provided for bots and AI automation. If the request cannot be attributed to a bot or AI automation, the fp-bot-info-\* request headers will not be set.
| Request Header | Description |
| ---------------------- | -------------------------------------------------------------------------------------------- |
| `fp-bot-info-category` | The `bot_info.category` for the bot e.g., `ai_agent`, `ai_browser`, `search_engine_crawler`. |
| `fp-bot-info-provider` | The organization that operates the bot, e.g., `OpenAI` or `AWS`. |
| `fp-bot-info-name` | Human-readable bot name, e.g., `ChatGPT Agent`. |
| `fp-bot-info-identity` | Verification status: `signed`, `verified`, `unknown`, or `spoofed`. |
### Learn more
* Refer to the [Bot directory](/docs/bot-detection/bot-directory) for a list of the bots and AI agents identified by edge bot detection, and the request header field values you can expect for each agent.
* Read the [AI agent detection](/docs/ai-agents) guide to learn how Fingerprint detects AI agents.
* Read the [AI assistant detection](/docs/ai-assistants) guide to learn how Fingerprint detects AI assistants.
## How Cloudflare Flow deployments work
Cloudflare Flow allows you to integrate Fingerprint protection into your website with little to no changes to your application code. It replaces the need to [install the JavaScript agent on your website](/docs/install-the-javascript-agent), [set up a proxy to avoid ad-blockers](/docs/protecting-the-javascript-agent-from-adblockers), [integrate Fingerprint Server API into your backend](/docs/get-server-side-intelligence), and write custom protection logic based on Fingerprint results.
Importantly, Cloudflare Flow deployment is different from the [Cloudflare Proxy Integration](/docs/cloudflare-integration), which only proxies requests between your website and Fingerprint when integrating Fingerprint manually.
Here is how it works in detail:
* Using your Cloudflare API token and credentials, Fingerprint deploys a Cloudflare worker on your domain, which intercepts HTTP requests between browsers accessing your website and the origin server.
* When serving the pages that identify visitors (for example, `yourwebsite.com/signup` page), the worker injects the Fingerprint [JavaScript agent](/reference/js-agent) and an instrumentation script into the page markup.
* Fingerprint creates a Public [API key](/docs/configuration#api-keys) and a Secret API key used only by the Flow worker and the injected JavaScript agent.
* For HTTP requests to APIs you want to protect (for example, the `POST api.yourwebsite.com/create-account` request), the instrumentation script collects information about the browser configuration and attaches it to the request as a custom header, `fp-data`.
* The `fp-data` header takes up approximately 20 KB, around 16% of the Cloudflare's [total request header size limit](https://developers.cloudflare.com/workers/platform/limits/#request-limits) of 128 KB.
* The Flow worker intercepts the protected request and sends the browser information to the Fingerprint Identification API. If the worker is using a ruleset, the request also includes the ruleset ID.
* In return, the worker receives the full [identification event](/reference/server-api-get-event) and (if applicable) the [rule action](/docs/rules-engine#rule-action) produced by evaluating your ruleset.
* If using a ruleset, the Flow worker processes the rule action by blocking the protected request or allowing it to reach the origin server.
* The Flow worker code is [open-sourced on GitHub](https://github.com/fingerprintjs/flow-cloudflare-worker).
### Route pattern matching
The identification pages and protected APIs are converted to Cloudflare worker [route patterns](https://developers.cloudflare.com/workers/configuration/routing/routes/#matching-behavior) and follow the same format and [matching behavior](https://developers.cloudflare.com/workers/configuration/routing/routes/#matching-behavior).
* Each Cloudflare route can only be served by one Cloudflare worker. If you try to use a route pattern already served by another worker, your deployment will fail.
* You can use a wildcard (`*`) at the end of the path but not in the middle of it.
* Using a wildcard suffix (`/path*`) is [required to match requests with query parameters](https://developers.cloudflare.com/workers/configuration/routing/routes/#:~:text=Route%20pattern%20matching%20considers%20the%20entire%20request%20URL%2C%20including%20the%20query%20parameter%20string.%20Since%20route%20patterns%20may%20not%20contain%20query%20parameters%2C%20the%20only%20way%20to%20have%20a%20route%20pattern%20match%20URLs%20with%20query%20parameters%20is%20to%20terminate%20it%20with%20a%20wildcard%2C%20*.) like `path?query=value`.
You might encounter edge cases where Cloudflare caching interacts with wildcard route matching in a way that produces [unexpected behavior](https://community.cloudflare.com/t/presence-of-query-string-make-route-match-fail/63400). In that case, do not hesitate to contact our [support team](https://fingerprint.com/support/). Defensively using both the base and wildcard variants of the path (`path` and `path*`) can help.
### Current limitations
* You can only protect API calls (actions, transactions) made by the visitor on the identification page. You cannot protect the page itself.
* For example, you cannot block bots from visiting the sign-up page, but you can block the `create-account` API call made by the page if the bot clicks "Create account".
* You can define a maximum of 10 identification pages and 10 protected APIs per Flow deployment.
* You must define at least one identification page and one API endpoint per Flow deployment, even if you are using the worker in Monitoring mode. The identification page calling the API endpoint is what triggers visitor identification.
* Customizations of the JavaScript agent behavior, such as [tags](/reference/js-agent-get-function#tag), [linked IDs](/reference/js-agent-get-function#linkedid), [custom timeout](/reference/js-agent-get-function#timeout), etc., are not supported at this time.
* At the moment, you cannot rotate the Public and Secret [API keys](/docs/configuration#api-keys) used by the worker. The secret API key is encrypted in both the Fingerprint and Cloudflare dashboards, so exposing it is unlikely. If you need to replace either key anyway, you need to delete your Flow deployment and recreate it. Alternatively, you can contact our [support team](https://fingerprint.com/support/) for assistance.
* Protected API calls made with `fetch` must NOT use [`mode: 'no-cors'`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit#mode). In `no-cors` mode, the browser strips the `fp-data` header, so identification fails and the request is either blocked or forwarded to the origin server (based on your chosen failure mode).
### Troubleshooting
If you encounter any issues with your Flow deployment, you can:
* Use the [Fingerprint Dashboard](https://dashboard.fingerprint.com/) > **Identification** page to confirm that Flow deployment is generating identification events as expected. Note that any Smart Signals not enabled for your workspace will always evaluate to false.
* Hard reload the identification page of your website to ensure you are getting the latest version from the Flow worker. Look for `instrumentor.iife.js` in the Network tab of your browser's developer tools to confirm.
* Make sure the protected API is actually called from the identification page you defined. Use the browser's developer tools to confirm that the `fp-data` header is being attached to the request.
* Navigate to your Cloudflare dashboard to confirm the worker configuration matches your Flow deployment (worker code, environment variables, routes, etc.).
* Enable [Worker logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs) and examine them for errors. Any error returned from the Fingerprint Identification API will only be visible here, not propagated to your application.
* Most common errors include [Request filtering](/docs/request-filtering-for-websites) blocking identification requests by origin.
* Contact our [support team](https://fingerprint.com/support/). Include relevant logs or screenshots of the above to aid troubleshooting.
# Flutter
Source: https://docs.fingerprint.com/docs/flutter
### JavaScript agent v4 support coming soon
This client SDK currently supports JavaScript agent v3. For the current v3 documentation, see [Flutter SDK (v3)](/docs/v3/flutter). You can also check the current [integration compatibility table](/reference/migrating-from-v3-to-v4#client-sdk-compatibility-for-javascript-agent-v4).
# Flutter Quickstart
Source: https://docs.fingerprint.com/docs/flutter-quickstart
### JavaScript agent v4 support coming soon
This client SDK currently supports JavaScript agent v3. For the current v3 quickstart, see [Flutter Quickstart (v3)](/docs/v3/flutter-quickstart). You can also check the current [integration compatibility table](/reference/migrating-from-v3-to-v4#client-sdk-compatibility-for-javascript-agent-v4).
# Overview
Source: https://docs.fingerprint.com/docs/frontend-libraries
Fingerprint offers SDKs for the most popular client-side frameworks and libraries.
If your application does not use a framework, or Fingerprint does not provide a dedicated SDK for your framework, use the [JavaScript agent](/reference/js-agent) directly. JavaScript agent v4 includes a built-in [cache option](/reference/js-agent-start-function#cache), so the deprecated SPA wrapper is no longer necessary.
# Generic JavaScript agent wrapper for SPAs
Source: https://docs.fingerprint.com/docs/generic-js-agent-wrapper-for-spas
**Deprecated in v4**
In Fingerprint v4, the caching functionality from `@fingerprintjs/fingerprintjs-pro-spa` has been incorporated directly into the [JavaScript agent](/reference/js-agent). You no longer need this wrapper library.
If you are using Fingerprint v3, see the [v3 documentation](/docs/v3/generic-js-agent-wrapper-for-spas).
## Migration guide
Follow these steps to migrate from `@fingerprintjs/fingerprintjs-pro-spa` to the v4 JavaScript agent.
### 1. Update dependencies
Remove the SPA library and install the v4 agent:
```shell theme={"theme":"github-dark-dimmed"}
npm uninstall @fingerprintjs/fingerprintjs-pro-spa
npm install @fingerprint/agent
```
### 2. Update imports and initialization
The v4 agent uses `Fingerprint.start()` instead of creating a `FpjsClient` instance and calling `init()`.
```js SPA library to v4 agent theme={"theme":"github-dark-dimmed"}
import { FpjsClient, FingerprintJSPro } from "@fingerprintjs/fingerprintjs-pro-spa"; // [!code --]
import * as Fingerprint from "@fingerprint/agent"; // [!code ++]
const fpjsClient = new FpjsClient({
// [!code --]
loadOptions: {
// [!code --]
apiKey: "PUBLIC_API_KEY", // [!code --]
endpoint: ["https://metrics.yourwebsite.com", FingerprintJSPro.defaultEndpoint], // [!code --]
region: "eu", // [!code --]
}, // [!code --]
cacheLocation: "sessionStorage", // [!code --]
cacheTimeInSeconds: 3600, // [!code --]
}); // [!code --]
await fpjsClient.init(); // [!code --]
const agent = Fingerprint.start({
// [!code ++]
apiKey: "PUBLIC_API_KEY", // [!code ++]
endpoints: "https://metrics.yourwebsite.com", // [!code ++]
region: "eu", // [!code ++]
cache: {
// [!code ++]
storage: "sessionStorage", // [!code ++]
duration: 3600, // [!code ++]
}, // [!code ++]
}); // [!code ++]
```
### 3. Update identification calls
Replace `getVisitorData()` with `get()`.
```js getVisitorData to get theme={"theme":"github-dark-dimmed"}
const visitorData = await fpjsClient.getVisitorData(); // [!code --]
console.log(visitorData.visitorId); // [!code --]
const result = await agent.get(); // [!code ++]
console.log(result.visitor_id); // [!code ++]
```
The v4 agent no longer supports `extendedResult`. Extended data like IP address and geolocation
should be retrieved server-side using the [Server API](/reference/server-api) or [Sealed Client
Results](/docs/sealed-client-results).
### 4. Update cache configuration
The cache options have been renamed:
| SPA library | v4 agent |
| --------------------------------- | ------------------------------------------------------------------------------- |
| `cacheLocation: 'sessionStorage'` | `cache: { storage: 'sessionStorage', ... }` |
| `cacheLocation: 'localStorage'` | `cache: { storage: 'localStorage', ... }` |
| `cacheLocation: 'memory'` | `cache: { storage: 'agent', ... }` |
| `cacheLocation: 'nocache'` | Omit the `cache` option entirely |
| `cacheTimeInSeconds: 3600` | `cache: { duration: 3600, ... }` or `cache: { duration: 'optimize-cost', ... }` |
The v4 agent also provides two convenient duration presets:
* `'optimize-cost'` — caches for 1 hour
* `'aggressive'` — caches for 12 hours (not recommended for fraud prevention)
### 5. Check for cached results
The `cacheHit` property is now `cache_hit` in the response:
```js cacheHit to cache_hit theme={"theme":"github-dark-dimmed"}
const { cacheHit, ...visitorData } = await fpjsClient.getVisitorData(); // [!code --]
if (cacheHit) {
// [!code --]
console.log("Result was from cache"); // [!code --]
} // [!code --]
const result = await agent.get(); // [!code ++]
if (result.cache_hit) {
// [!code ++]
console.log("Result was from cache"); // [!code ++]
} // [!code ++]
```
For more details about the v4 JavaScript agent, see the [JavaScript agent reference](/reference/js-agent).
# Go Server Quickstart
Source: https://docs.fingerprint.com/docs/go-server-quickstart
Get started using the Go Server SDK
## Overview
In this quickstart, you'll add Fingerprint to a [Go](https://go.dev/) server to prevent fraudulent account creation.
The example use case in this quickstart is stopping new account fraud, where attackers create multiple fake accounts to abuse promotions, exploit systems, or evade bans. However, the steps you'll follow apply to most use cases. You can flag and block suspicious users by identifying the device behind each sign-up attempt, login, or transaction.
In this quickstart, you'll learn how to:
* Set up a Go server (using the [gorilla/mux](https://github.com/gorilla/mux) router) with a Fingerprint integration
* Retrieve visitor identification data using the Server API
* Block bots and suspicious devices
* Prevent multiple signups from the same device
This guide focuses on the backend integration and must be completed after identifying a visitor and generating a event ID. **Before starting this quickstart, start with one of the [frontend](/docs/web-quickstarts-overview) or [mobile](/docs/mobile-quickstarts-overview) quickstarts to see how to identify a visitor in your frontend.**
> Estimated time: \< 10 minutes
## Prerequisites
Before you begin, make sure you have the following:
* A completed frontend or mobile Fingerprint implementation **(See the quickstarts)**
* [Go](https://go.dev/) (v1.24 or later)
* Your favorite code editor
* Basic knowledge of Go
## 1. Get your secret API key
Before starting this quickstart, you should already have a frontend Fingerprint implementation
that sends the `event_id` to your server. If not, pause here and check out one of the
[frontend](/docs/web-quickstarts-overview) or [mobile](/docs/mobile-quickstarts-overview)
quickstarts first.
If you're ready:
1. Sign in and go to the [**API keys**](https://dashboard.fingerprint.com/api-keys) page in the Fingerprint dashboard.
2. Create a new **secret API key**.
3. Copy it somewhere safe so you can use it to retrieve full visitor identification data from the Server API.
## 2. Set up your project
To get started, set up a basic server. If you already have a project you want to use, you can skip to the next section.
1. Create a new Go module and install `gorilla/mux` and the Fingerprint Go Server SDK:
```bash Terminal theme={"theme":"github-dark-dimmed"}
mkdir fingerprint-go-quickstart && cd fingerprint-go-quickstart
go mod init fingerprint.com/go-quickstart
go get github.com/gorilla/mux github.com/fingerprintjs/go-sdk/v8
```
*Note: This quickstart is written for version 8.x of the Fingerprint Go Server SDK*
2. Create a new file called `main.go` and add a basic gorilla/mux server setup:
```go main.go theme={"theme":"github-dark-dimmed"}
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/api/create-account", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "Account created!"})
}).Methods("POST")
http.Handle("/", r)
srv := &http.Server{
Handler: r,
Addr: "localhost:3000",
}
log.Printf("Server is running on http://localhost:3000")
log.Fatal(srv.ListenAndServe())
}
```
Here we are running the server at port 3000 and have created a new POST route for account creation. Note that the `/api/create-account` route should match what you have set up in your frontend implementation where you are sending the Fingerprint `event_id` to your server. Your server will receive the initial identification information from identifying a visitor on the frontend and use it to get the full visitor data on the backend.
## 3. Initialize Fingerprint and retrieve visitor data
Now you'll configure the Fingerprint Go Server SDK using your secret API key and use it to fetch detailed visitor data for each signup attempt.
When making the initial visitor identification request in the frontend, you received an `event_id`. This ID is unique to each identification event. Your server can then use the [Fingerprint Events API](/reference/server-api-get-event) to retrieve complete identification data, including the trusted visitor ID and other actionable insights like whether they are using a VPN or are a bot.
1. At the top of your `main.go` file, import the Fingerprint Go Server SDK:
```go main.go theme={"theme":"github-dark-dimmed"}
import (
// ...
// Add this import:
"github.com/fingerprintjs/go-sdk/v8"
)
```
2. At the beginning of the `main()` function, initialize the client agent with your secret API key. Change the [region](/docs/regions), if required:
```go main.go theme={"theme":"github-dark-dimmed"}
func main() {
client := fingerprint.New(
fingerprint.WithAPIKey("SECRET_API_KEY"),
fingerprint.WithRegion(fingerprint.RegionUS),
)
// Rest of function
// ...
}
```
*For a production implementation make sure to store and reference your secret key securely.*
3. In your `/api/create-account` handler, retrieve the `event_id` you are sending from the frontend and fetch the full visitor identification details with `GetEvent()`:
**requestId vs event\_id:** Depending on which quickstart you completed, your frontend may send
either `requestId` (older SDKs) or `event_id` (v4 and newer). Both refer to the same
identification event and work with the Server API in the same way. Use whichever value your
frontend sends (`requestId` or `event_id`) as the `event_id` you pass to GetEvent() below.
```go main.go theme={"theme":"github-dark-dimmed"}
r.HandleFunc("/api/create-account", func(w http.ResponseWriter, r *http.Request) {
var createAccountRequest CreateAccountRequest
err := json.NewDecoder(r.Body).Decode(&createAccountRequest)
if err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
event, _, err := client.GetEvent(r.Context(), createAccountRequest.EventID)
if err != nil {
// An error may occur because the event ID is invalid or missing, or there is an issue with the configuration.
if errResp, ok := fingerprint.AsErrorResponse(err); ok {
switch errResp.Error.Code {
case fingerprint.ErrorCodeEventNotFound:
log.Printf("Event not found")
default:
log.Printf("Error %s: %v", errResp.Error.Code, errResp)
}
}
http.Error(w, "Error processing request", http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "Account created!"})
}).Methods("POST")
```
4. To support the JSON decoding, declare the `CreateAccountRequest` struct just before the `main()` function:
```go main.go theme={"theme":"github-dark-dimmed"}
type CreateAccountRequest struct {
EventID string `json:"event_id"`
Username string `json:"username"`
Password string `json:"password"`
}
```
Using the `event_id` the GetEvent method 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, incognito mode detection, and detections for VPN or virtual machine use.
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/protecting-from-client-side-tampering).
## 4. Block bots and suspicious devices
This optional step uses the Bot Detection Smart Signal which is available only on paid plans.
A simple but powerful way to prevent fraudulent account creation is to block automated signups that come from bots. 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.
1. Continuing in your `/api/create-account` route, check the bot signal returned in the `event` object:
```go main.go theme={"theme":"github-dark-dimmed"}
if *event.Bot != fingerprint.BotResultNotDetected {
log.Printf("Bot detected")
http.Error(w, "Failed to create account.", http.StatusForbidden)
return
}
```
This signal returns `good` for known bots like search engines, `bad` for automation tools, headless browsers, or other signs of automation, and `not_detected` when no bot activity is found. You can also layer in other Smart Signals to catch more suspicious devices. For example, you can use Fingerprint's [Suspect Score](/docs/suspect-score) to determine when to add additional friction to create an account.
## 5. Prevent multiple signups from the same device
To catch repeated signups from the same device, you can use the `visitor_id` from the Fingerprint identification event. By saving this ID alongside each created account, you can detect and block duplicate signups from the same device. We'll be using a simple database to demonstrate how this works with SQLite.
1. Install the `modernc.org/sqlite` SQLite package:
```bash Terminal theme={"theme":"github-dark-dimmed"}
go get modernc.org/sqlite
```
2. At the top of your `main.go` file, add these imports:
```go main.go theme={"theme":"github-dark-dimmed"}
import (
"database/sql"
_ "modernc.org/sqlite"
// ...
)
```
3. Then after the Fingerprint configuration code in `main()`, initialize the database:
```go main.go theme={"theme":"github-dark-dimmed"}
db, err := sql.Open("sqlite", "database.db")
if err != nil {
log.Fatal(err)
os.Exit(1)
}
db.Exec(`CREATE TABLE IF NOT EXISTS accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT,
password TEXT,
visitorId TEXT
)`)
defer db.Close()
```
4. In your `/api/create-account` route handler, after getting the event, extract the `visitor_id`:
```go main.go theme={"theme":"github-dark-dimmed"}
visitorId := event.Identification.VisitorID
```
5. Check if this device has already created an account; if yes, block the account creation:
```go main.go theme={"theme":"github-dark-dimmed"}
// Check if the visitor ID already exists in the database
existingRow := db.QueryRow("SELECT COUNT(*) FROM accounts WHERE visitorId = ?", visitorId)
var count int
existingRow.Scan(&count)
if count > 0 {
log.Printf("Device has already created an account")
http.Error(w, "Failed to create account.", http.StatusTooManyRequests)
return
}
_, err = db.Exec("INSERT INTO accounts (username, password, visitorId) VALUES (?, ?, ?)",
createAccountRequest.Username, createAccountRequest.Password, visitorId)
if err != nil {
log.Printf("Database error")
http.Error(w, "Failed to create account.", http.StatusInternalServerError)
return
}
```
This gives you a basic system to detect and block repeat signups. You can expand on this by allowing a limited number of accounts per device, adjusting your response based on business rules, only evaluating recent signups, etc.
This is a minimal example to show how to use the Fingerprint Go Server SDK. In a real application,
make sure to implement proper security practices, especially around password handling and storage.
## 6. Test your implementation
Now that everything is set up, you can test the full flow using your existing frontend.
### Before you test
If your frontend is running on a different port (like localhost:5173 or localhost:3001), you may run into CORS issues for testing. To quickly fix this for local development & testing:
1. Install the `gorilla/handlers` module:
```bash Terminal theme={"theme":"github-dark-dimmed"}
go get github.com/gorilla/handlers
```
2. Add `"github.com/gorilla/handlers"` to your imports in `main.go`, then set up the CORS configuration and attach it to the handler at the end of your `main()` function:
```go main.go theme={"theme":"github-dark-dimmed"}
originsOk := handlers.AllowedOrigins([]string{"*"})
methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})
headersOk := handlers.AllowedHeaders([]string{"Accept", "Accept-Language", "Content-Type", "Content-Language", "Origin"})
srv := &http.Server{
Handler: handlers.CORS(originsOk, methodsOk, headersOk)(r),
Addr: "localhost:3000",
}
log.Printf("Server is running on http://localhost:3000")
log.Fatal(srv.ListenAndServe())
```
### Test the implementation
1. Start your Go server:
```bash Terminal theme={"theme":"github-dark-dimmed"}
go run main.go
```
2. In your frontend, trigger a sign-up request that sends the `event_id`, `username`, and `password` to your `/api/create-account` endpoint. To see the reply messages make sure to parse and display or console log the response from your server.
3. Within your frontend, input a username and password to create a user. Then try to create another user and see that the second attempt will be rejected.
4. Bonus: Try creating an account using a headless browser.
## Next steps
You now have a working backend fraud check using Fingerprint. From here, you can expand your logic with more Smart Signals, adjust thresholds based on your risk tolerance, or introduce additional checks for suspicious users.
These same techniques apply to a wide range of fraud prevention use cases, from detecting fake reviews to blocking payment abuse or preventing account takeovers.
To go further, check out the use case tutorials for step-by-step guides tailored to specific problems you can solve with Fingerprint.
Check out these related resources:
* [Go SDK Reference](/reference/go-server-sdk)
* [Vue frontend quickstart](/docs/vue-quickstart)
* [API reference for the Events endpoint](/reference/server-api-get-event)
* [Use case tutorial: Detecting new account fraud](/docs/new-account-fraud-use-case-tutorial)
* [Low-latency identification with Sealed Client Results](/docs/sealed-client-results)
# Monitoring health
Source: https://docs.fingerprint.com/docs/health
The Dashboard allows you to stay on top of **integration health**, by informing you of required SDK updates, and monitoring throttling, latency, and error rates.
## Health overview
The Health page gives you a high-level view of your Fingerprint setup, surfacing issues that need attention and pointing you to where to fix them.
To check it out, visit **Dashboard** > [**Health**](https://dashboard.fingerprint.com/health).
Three status widgets at the top of the page highlight anything that needs your attention:
* **Throttled requests** (1) shows the total number of requests throttled in the current period, along with the API keys responsible. Each key links to its API key details, where you can adjust the [RPS limit](/docs/billing#rate-limiting-burst-protection) or investigate the traffic pattern.
* **Webhooks** (2) shows your webhook failure rate and the webhooks with the most recent delivery failures. Each entry links to its delivery logs.
* **SDK versions** (3) shows how many SDK updates are available across your integrations. Each entry shows the current version and the latest available version, and links to corresponding library page for the full breakdown.
When everything is healthy, each widget shows a neutral state - no action needed.
Below the widgets, a single timeline chart (4) lets you switch between three views using the dropdown:
* **API Latency** show the average latency for JavaScript agent requests, in 5-minute buckets. This is the time it takes a Fingerprint identification request to return data from the server. Only requests from client-side JavaScript SDKs are counted.
* **API Errors** shows requests that timed out or returned an error, including both client and server-side errors from the JavaScript agent.
* **Webhook Failed Calls** shows webhook delivery failures over time, broken down by webhook endpoint.
## API key monitoring
For Public and Server API keys, you can view detailed monitoring information on the key details page.
To check it out, visit **Dashboard** > [**API keys**](https://dashboard.fingerprint.com/api-keys) and select a key to view its details.
The key details page shows when the key was last used, by which library version, and on what origin domains or mobile apps.
Two charts give you a closer look at how the key is being used:
* The Request usage chart shows incoming identification requests using this API key, broken down into:
* **Success** - successful requests with response HTTP 200
* **Throttled** - requests over the configured RPS limit, responded with HTTP 429
* **Restricted** - requests filtered out by request filtering rules or that exceeded workspace/environment limits, responded with HTTP 403
* The Server-side latency chart shows percentiles for the average time to process identification requests. Client-side signal collection latency is not included.
## Webhook monitoring
For each registered webhook, you can view a log of every delivery attempt by clicking into the webhook on the Webhooks page.
To check it out, visit **Dashboard** > [**Webhooks**](https://dashboard.fingerprint.com/webhooks) and select a webhook.
The Webhook events page lists each delivery attempt with:
* **Request ID** - the identification request this delivery is associated with
* **Visitor ID** - the visitor ID from the event payload
* **Status** - whether the delivery succeeded or failed
* **Status code** - the HTTP response code returned by your endpoint
* **Date** - when the delivery was attempted
Clicking on a request row will expand it, so you can inspect the full request and response when diagnosing why a delivery failed.
For details on retry behavior and the expected response contract, see [Webhooks > Timeout and errors](https://docs.fingerprint.com/docs/webhooks#timeout-and-errors).
## Outdated libraries
The **Libraries & integrations** page shows you a detailed view of all SDKs in use across your integrations, including both web and mobile SDKs as well as Server API SDKs.
To check it out, visit **Dashboard** > [**Libraries & integrations**](https://dashboard.fingerprint.com/integrations/active).
Each row in the Libraries list shows:
* The library (e.g. JavaScript, Next.js, Node.js, iOS) and where it runs (Website, Server, or App)
* The number of requests in the last 24 hours
* The version in use, and the latest version available
Libraries are refreshed once every 24 hours.
### Per-library health
Clicking an active library opens its **Health** tab, which gives you a detailed view of where that SDK is running and which deployments are out of date.
At the top of the page, four summary cards show:
* The latest version of the library
* The number of outdated origins out of the total observed
* Total requests in the selected window
* How many of those requests came from outdated versions
Below the summary, the origins table lists every domain (for web SDKs) or app (for mobile SDKs) where this library is running:
* **Origin** - the domain or app ID
* **Version** - the SDK version in use, or a "Multiple" label when more than one version is detected
* **Requests** - the number of identification requests from this origin in the selected window
* **Last request** - timestamp of the most recent request
* **Severity** - whether the origin is up to date, or how far behind it is
**Domains**
For web SDKs, clicking a domain row expands it to show the specific URLs observed on that domain and the SDK version detected on each. When more than one version appears on the same URL, each version is listed with its own request count, so you can see whether stale clients are still trickling in or whether a deployment really is out of date.
**Mobile apps**
Mobile apps often have multiple SDK versions in use at once, since users update apps at different rates. Each version is shown with its request count so you can see the adoption of your latest release.
**Activity**
Below the origins table, the Activity chart shows total request volume over time, with errors and timeouts called out separately. This helps you spot whether an outdated origin is also experiencing reliability issues.
# Identification, accuracy, and confidence score
Source: https://docs.fingerprint.com/docs/identification-accuracy-and-confidence
Learn about the different methods Fingerprint uses to identify visitors, how Fingerprint defines and maintains accuracy, and how to use confidence score in your applications.
To identify a browser or a device, Fingerprint collects several properties from them. Our advanced machine-learning models use these properties to create a unique visitor ID.
# Identification methods
Fingerprint uses one of the following approaches to identify browsers and devices:
* **Deterministic Identification**: This method uses browser or device properties that are unique and reliable (*aka* deterministic properties). [HTTP cookie](https://en.wikipedia.org/wiki/HTTP_cookie) is an example of such a property. These properties are accurate, but may not be available in situations like:
* Private or incognito browsing modes.
* Browser storage is cleared.
* Third-party cookies are not available in certain browsers. See [Safari Intelligent Tracking Prevention](/docs/browser-and-device-support#safari-intelligent-tracking-prevention). In such situations, Fingerprint identifies visitors using probabilistic identification.
* **Probabilistic identification:** This method uses non-deterministic properties such as screen resolution, timezone, etc. These properties are combined to create a unique identifier for a browser or device. To learn more, see [What is browser fingerprinting? 6 top techniques to fight fraud](https://fingerprint.com/blog/browser-fingerprinting-techniques/). This method introduces a probability of error because non-deterministic properties change over time and their values could be the same between two identical browsers. These factors can cause Fingerprint to create the same identifier for two different visitors or identify the same visitor differently.
# Identification accuracy
Identification accuracy primarily applies to probabilistic identification. It defines the ability to identify returning browsers and devices precisely.
## How is identification accuracy measured?
We measure accuracy by performing prediction experiments with two sets of data in which:
* The *control* dataset is the set of deterministically identified events.
* The *test* dataset is created by removing the deterministic properties.
* The events in the test dataset are re-identified.
* The visitor IDs in the test dataset are compared with the visitor IDs in the control dataset.
The comparison results can be categorized as one of:
* **True-positive:** The visitor ID remained the same in both control and test datasets.
* **False-positive:** The visitor ID in the test dataset is different from that of the control. This browser has been incorrectly identified as another **existing** browser.
* **False-negative:** The visitor ID does not exist in the control dataset. This browser has been incorrectly identified as a new browser.
**Identification accuracy for mobile devices is higher than that of browsers**
We identify mobile devices using properties that remain stable for longer durations than those used to identify browsers.
At Fingerprint, we maintain industry-leading accuracy by continually striving to minimize the false-positive and false-negative rates.
# Confidence Score
For every identification request, a confidence score is computed internally. You can get the confidence score for a specific request in one of the following ways:
* Via the [Server API](/reference/server-api-get-event)
* Via [webhooks](/reference/posteventwebhook)
```json JSON theme={"theme":"github-dark-dimmed"}
{
"event_id": "1708102555327.NLOjmg",
"identification": {
"visitor_id": "Ibk1527CUFmcnjLwIs4A9",
"confidence": {
"score": 0.97,
"version": "1.1"
},
"visitor_found": false,
"first_seen_at": 1708102555327,
"last_seen_at": 1708102555327
},
}
```
The confidence score indicates the probability of a false-positive identification. It is a floating-point number whose value ranges from 0 to 1. The higher the value, the more confident Fingerprint is about not misidentifying this browser as another.
## Using confidence score in your application
You can use the confidence score to improve security and user experience. A few examples:
* **Prevent account takeover**: You can use the confidence score to request additional verification. For example, request OTP for browsers whose confidence score is below 0.95.
* **Increase conversion:** You can use the confidence score to profile an anonymous user. For example, you can offer tailor-made promotions and enhance the user experience.
The thresholds given here are only examples. Finding the optimal threshold for your use case may require iterative experiments.
## How is the confidence score calculated?
In simple terms, the confidence score is calculated as
```
confidenceScore = 1 - falsePositiveProbability
```
Where `falsePositiveProbability` is deduced from our prediction experiments and represents the probability of a false-positive identification.
## Understanding the confidence score
**First-time browsers or devices**
Fingerprint identifies a browser or a device as new only when it cannot find a match for this browser in your data. In this case, the probability of a false-positive identification becomes 0. Hence, the confidence score for such new browsers or devices is **1.0**
An existing browser could also be incorrectly identified as a first-time browser. The confidence score in this case will still be 1.0. Because it doesn't state the probability of a false-negative identification, only false-positive rather.
**Returning visitors identified deterministically**
The deterministic properties are unique, even among identical browsers. It is not possible to incorrectly identify one browser as another. In other words, the `falsePositiveProbability` is **0**. Hence, the confidence score for these browsers or devices is **1.0.**
**Returning visitors identified probabilistically**
The probabilistic properties are not as unique as the deterministic properties. This increases the chances of incorrectly identifying a browser as another. In other words, the `falsePositiveProbability` is greater than 0. Hence the confidence score for these browsers or devices is always **less than 1.0.** Even when future identifications are deterministic (see Example).
## Example: Confidence score for different scenarios
# How is accuracy different from confidence score?
### Accuracy
* Represents the **overall value** that Fingerprint provides you. For example, a 98% true-positive rate would mean that, in the absence of deterministic properties, only 2% of your visitors may be identified incorrectly.
* It is not associated with an individual identification request.
### Confidence Score
* For an identification request, it represents the probability of **only** the false-positive identification. It should not be used to determine the overall accuracy of the product.
* Unlike accuracy, confidence score enables you to make real-time decisions as described in [using confidence score in your application](/docs/identification-accuracy-and-confidence#using-confidence-score-in-your-application).
***
[Planning your implementation](/docs/planning-implementation)
# Identify visitors
Source: https://docs.fingerprint.com/docs/identify-visitors
Learn best practices for timing identification requests, caching visitor IDs, or using linked IDs to associate your own identifiers with each visitor ID.
Once the [JavaScript agent is installed and initialized](/docs/install-the-javascript-agent), call the [`get()`](/reference/js-agent-get-function) function (or its [SDK equivalent](/docs/frontend-libraries)) to identify the browsers visiting your website. The `get()` function sends the collected browser signals to the Fingerprint Platform and returns a [response](/reference/js-agent-get-function#get-response) containing the visitor ID for the browser and associated metadata.
This guide covers best practices for timing identification requests, caching visitor IDs, and using linked IDs to associate your own identifiers with each visitor ID. To explore further, please refer to the [JavaScript Agent API reference](/reference/js-agent).
If you are looking to identify mobile devices instead, see [Android](/docs/native-android-integration) and [iOS](/docs/ios).
**Note:** The code snippets in this guide assume you have already set up some kind proxy integration for Fingerprint. Proxying requests through your own domain or subdomain like metrics.yourwebsite.com instead of connecting to Fingerprint servers directly helps avoid ad blockers and increases accuracy.
See [Installing the JavaScript agent](/docs/install-the-javascript-agent) and [Evading ad-blockers (proxy integrations)](/docs/protecting-the-javascript-agent-from-adblockers) for more details.
## Timing identification requests
For most use cases, we recommend identifying the visitor on demand, when they perform a specific action:
* Call [`start()`](/reference/js-agent-start-function) as early as possible, ideally, right after the page loads.
* Call [`get()`](/reference/js-agent-get-function) only when needed (e.g., upon login or product selection).
* Wait at least 1 second after calling `start()` before calling `get()`, to give the JavaScript Agent enough time to collect the required signals.
```ts HTML theme={"theme":"github-dark-dimmed"}
```
This just-in-time approach also helps you:
* Ensure browsers are identified only after receiving the required [consent](/docs/privacy-and-compliance#do-i-need-to-have-a-gdpr-consent-management-banner-if-using-fingerprint).
* Avoid unnecessary use of your [billable](/docs/billing#identification-api-request-as-a-billing-unit) API calls.
See [Optimizing the JavaScript agent](/docs/optimize-javascript-agent) to learn more about deploying Fingerprint effectively.
### Response
The `get()` function returns a promise of a [response](/reference/js-agent-get-function#get-response) object with the visitor ID and other metadata.
```javascript get() response wrap theme={"theme":"github-dark-dimmed"}
await fp.get()
// response:
{
"event_id": "8nbmT18x79m54PQ0GvPq",
"visitor_id": "2JGu1Z4d2J4IqiyzO3i4",
"suspect_score": 0,
"sealed_result": null // or BinaryOutput when Sealed Results are enabled
}
```
**JavaScript agent response limitations**
Note that the response received by the JavaScript agent is limited on purpose. Since client-side code can be tampered with, we recommend consuming Fingerprint results securely through one of our server-side integrations. The full identification event including the visitor's [Smart Signals](/docs/smart-signals-reference) is available through the [Server API](/reference/server-api), [Webhooks](/docs/webhooks), or [Sealed results](/docs/sealed-client-results).
See [Protecting from client-side tampering](/docs/protecting-from-client-side-tampering) to learn about getting Fingerprint results securely to your server.
## Linking and tagging information
The `visitor_id` provided by Fingerprint Identification is especially useful when combined with information you already know about your users, for example, account IDs, order IDs, etc. You can follow user actions, track logged-in devices, and recognize malicious behavior.
You can pass a `linkedId` or `tag` parameters to the `get()` function (or its equivalent in your SDK) to associate your own metadata with the visitor ID.
```ts HTML theme={"theme":"github-dark-dimmed"}
```
* [`linkedId`](/reference/js-agent-get-function#linkedid) is a string you can use to filter identification events in the Dashboard or to filter visitor history using the Server API.
* [`tag`](/reference/js-agent-get-function#tag) is a JavaScript object of any shape allowing you to store structured metadata. It is *not indexed*, you cannot use it to search for events.
For more details, see [Linking and tagging information](/docs/tagging-information).
## Caching the visitor ID
If your use case demands highly accurate identification, we recommend identifying the visitor [on demand](#timing-identification-requests) and always using a fresh visitor ID without caching.
Depending on your use case, you can choose to cache (store) the visitor ID in the browser to:
* Improve the responsiveness of your application.
* Stay within the limits of your monthly allowance of API calls.
Caching visitor IDs for extended periods can significantly reduce identification accuracy. Make sure to understand caching best practices and implications before caching visitor IDs.
### Caching using JavaScript agent
JavaScript agent provides caching out of the box but is disabled by default.
To use it, you need to specify the [cache](/reference/js-agent-start-function#cache) option when initializing the agent.
```ts HTML theme={"theme":"github-dark-dimmed"}
```
The `optimize-cost` value caches visitor data for one hour. For all available cache configuration options, see [`CacheConfig`](/reference/js-agent-start-function#cacheconfig) in the `start()` function reference.
### Cache expiration time
We strongly recommend a maximum cache expiration time of **1 hour**.
The properties (e.g. browser version, extensions, screen resolution, IP address, etc) that Fingerprint collects from a browser often change over time. When these incremental changes are captured as soon as they occur, Fingerprint can identify a returning browser with industry-leading accuracy.
Using a longer expiration time leads to:
* Lower identification accuracy. Fingerprint can miss out on the incremental changes thus increasing the risk of incorrectly identifying a returning browser as new.
* Outdated Smart Signals information returned from the [Server API](/reference/server-api-get-event).
* Increased risk of [replay attacks](https://en.wikipedia.org/wiki/Replay_attack).
### Cache location
We strongly recommend using [**sessionStorage**](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) to cache the visitor ID.
Our data indicates sessionStorage is the best option to minimize the impact on identification accuracy compared to both [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) and [cookies](https://developer.mozilla.org/en-US/docs/Web/API/Document/cookie).
> Note: A cached visitor ID stored in sessionStorage, localStorage, and cookies will not be available if the user switches to or from Incognito mode.
## What's next
* To protect the JavaScript agent from ad blockers and get maximum identification accuracy and coverage, see [Proxy integrations](/docs/protecting-the-javascript-agent-from-adblockers).
* To improve your identification latency, see [Optimizing JavaScript agent usage](/docs/optimize-javascript-agent).
* See the full JavaScript agent [API Reference](/reference/js-agent).
* Integrate Fingerprint with your server using the [Server API](/reference/server-api), [Webhooks](/docs/webhooks), or [Sealed results](/docs/sealed-client-results).
* If you are looking for device intelligence about mobile devices instead of browsers, see [Android](/docs/native-android-integration) and [iOS](/docs/ios).
# Install CloudFront Integration using CloudFormation
Source: https://docs.fingerprint.com/docs/install-cloudfront-integration-using-cloudformation
**Before you start: Read the general CloudFront guide**
This document only covers deploying the Fingerprint Cloudfront proxy integration to your AWS account using a CloudFormation template. It assumes you have already read the [general AWS CloudFront Proxy Integration v2 guide](/docs/cloudfront-proxy-integration-v2) and completed the following steps:
* [Step 1](/docs/cloudfront-proxy-integration-v2#step-1-issue-a-proxy-secret): You have issued a proxy secret in the Fingerprint Dashboard (`FPJS_PRE_SHARED_SECRET`).
* [Step 2](/docs/cloudfront-proxy-integration-v2#step-2-create-path-variable): You have defined a path variable for the integration (`FPJS_BEHAVIOR_PATH`)
If want to use Terraform instead of CloudFormation to install the integration, see [Install CloudFront integration using Terraform](/docs/aws-cloudfront-integration-via-terraform).
**Limitations**
This integration is exclusively supported for customers on the **Enterprise** Plan. Other customers are encouraged to use [Custom subdomain setup](/docs/custom-subdomain-setup) or [Cloudflare Proxy Integration](/docs/cloudflare-integration).
## Step 3: Install the CloudFormation application
The Lambda proxy function and associated resources are provided as a CloudFormation template.
### Step 3.1: Create CloudFormation stack
1. Go to the [CloudFormation installation wizard](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=Fingerprint-Pro-CloudFront-Integration-v2\&templateURL=https://fingerprint-pro-cloudfront-integration.s3.amazonaws.com/v2/template.yml) to open the deployment dialog.
2. Select the `us-east-1` region in your AWS console before deploying the application. Due to [AWS limitations](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-at-edge-function-restrictions.html#lambda-at-edge-restrictions-region), the Lambda proxy function, and therefore the entire CloudFormation stack, must be deployed to the `us-east-1` region.
3. Keep predefined settings:
1. **Prepare template** is `Choose an existing template`.
2. **Specify template** is `Amazon S3 URL`.
3. **Amazon S3 URL** is `https://fingerprint-pro-cloudfront-integration.s3.amazonaws.com/v2/template.yml`
4. Click **Next**.
### Step 3.2: Specify stack details
Provide a stack name and fill in the required template parameters.
* Set **FpjsPreSharedSecret** to the `FPJS_PRE_SHARED_SECRET` value you created in Step 1.
The following parameters, **FpjsAgentDownloadPath** and **FpjsGetResultPath**, can be left at
their default values because they are only required for JavaScript Agent v3.
**Note: Proxy secret required**
Proxied identification requests without a valid proxy secret will result in an authentication error and not receive identification results.
In this step, you need to choose between using an existing CloudFront distribution or creating a new one.
#### A) Use an existing CloudFront distribution (recommended)
If your website is already running on CloudFront, you can use the same distribution and domain for the proxy integration.
This is the recommended setup. Your website and the proxy function will be same-site, served from the same IP address or IP address range. Having the same or similar IP improves cookie lifetimes in Safari — they will be stored in the browser for up to one year instead of 7 days.
* Set **DistributionId** to the identifier of your existing CloudFront distribution.
#### B) Create a new CloudFront distribution
If your website is not running on CloudFront, you can create a new CloudFront distribution and website subdomain just for the proxy integration.
This setup limits Safari cookie lifetime to 7 days. Because your website and the Lambda proxy function will likely have [different IP ranges](https://github.com/WebKit/WebKit/pull/5347), Safari will apply the same cookie lifetime cap as for third-party CNAME cloaking. This is still an improvement over third-party cookies getting blocked entirely by Safari. But we recommend serving your website and the proxy integration using the same CloudFront distribution if possible (option A above).
* Leave **DistributionId** *empty*. The template will create and configure a new CloudFront distribution for you.
* If you want to attach an alternate domain name and custom SSL certificate to the new CloudFront distribution, fill in these parameters:
* Set **ACMCertificateARN** to the ARN identifier of your SSL certificate in the AWS Certificate Manager.
* Set **DomainNames** to a list of plus-separated domain names, for example: `domain1.com` or `domain1.com+domain2.com`. These will be attached to the created CloudFront distribution.
The deployment will fail if any of the domain names is not covered by the certificate or if it is already used as an alternate name for another CloudFront distribution.
Once you have made your choice, click **Next**.
> Note: This application creates the custom IAM roles that allow us to modify CloudFront distribution to keep the Lambda\@Edge function up to date. You can review these policies in the [CloudFormation template](https://github.com/fingerprintjs/aws-cloudfront-proxy/blob/main/cloudformation/template.yml).
### Step 3.3: Configure stack options
Fingerprint integration has no specific stack options to configure at this step. You can change them depending on your internal requirements.
In the **Capabilities and transforms** section:
1. Check *I acknowledge that AWS CloudFormation might create IAM resources.*
2. Check *I acknowledge that AWS CloudFormation might create IAM resources with custom names.*
3. Check *I acknowledge that AWS CloudFormation might require the following capability: CAPABILITY\_AUTO\_EXPAND*
Click **Next**.
### Step 3.4: Review and create
Review the **Parameters** section and click **Submit**.
After deployment, you will be redirected to the CloudFormation Stacks page where you can see a list of resources created by the application.
* The **Status** indicates the current state of deployment. It starts from `REVIEW_IN_PROGRESS` to `CREATE_IN_PROGRESS`, and finally `CREATE_COMPLETE`.
* If you see a different status (`CREATE_FAILED`, `ROLLBACK_IN_PROGRESS`, `ROLLBACK_COMPLETE`, etc), please contact our [support team](mailto:support@fingerprint.com).
Once the application is fully deployed (stack has the status `CREATE_COMPLETE`), you can switch to the **Outputs** tab, and find the names of created entities.
* The *Export name* of `LambdaFunctionName` is a unique name of the created Lambda proxy function.
* The *Export name* of `FingerprintProMgmtLambda` is a unique name of the created Management function responsible for delivering updates to the main Lambda proxy function.
* The *Value* of `CloudFrontDistributionId` is an internal ID of the CloudFront distribution created by the CloudFormation template or your existing CloudFront distribution that you specified in [Step 3.2.A](#a-use-an-existing-cloudfront-distribution-recommended).
* The *Export name* of `FingerprintIntegrationSettingsSecret` is the name of the AWS Secret for the Lambda proxy function. You can save this value somewhere. You might need it in [Step 4.1](#step-4-1-create-a-new-origin).
* The *Export name* of `CachePolicyName` is a unique name of the CloudFront cache policy, which will be applied in a cache behavior for Fingerprint requests. You can save this value somewhere. You might need it in [Step 4.2](#step-4-2-create-a-cache-behavior).
* The *Value* of `MgmtLambdaFunctionUrl` is a public URL of the management Lambda function. You can save this value somewhere. You will need it later to enable automatic updates for your integration in [Step 6](#step-6-enable-automatic-updates-in-the-fingerprint-dashboard).
**Do not disrupt the Management function**
The Management Lambda function (`FingerprintProMgmtLambda`) deployed alongside the main proxy function is responsible for updating the integration. It makes sure visitor identification on your website keeps up with new browser releases and fingerprinting evasion techniques. An outdated integration can lead to lower accuracy or break visitor identification completely. Don't disable or delete the management function.
## Step 4: Configure the CloudFront distribution
* If you are using an existing CloudFront distribution already serving your website (you have specified `DistributionId` in [Step 3.2.A](#a-use-an-existing-cloudfront-distribution-recommended)), start from [Step 4.1](#step-4-1-create-a-new-origin) to configure your distribution for the proxy integration.
* If you decided to create a new CloudFront distribution (you have left `DistributionId` empty in [Step 3.2.B](#b-create-a-new-cloudfront-distribution)), the CloudFormation template has deployed a new CloudFront distribution already pre-configured to serve the proxy integration.
1. Attach an alternate domain name and CNAME if you haven't specified it in [Step 3.2.B](#b-create-a-new-cloudfront-distribution).
2. Continue to [Step 5](#step-5-configure-the-fingerprint-javascript-agent-on-your-client).
### Step 4.1: Create a new origin
1. Go to your CloudFront distribution, switch to the **Origins** tab, and click **Create origin**.
2. Fill in the required fields:
1. Set **Origin domain** to `fpcdn.io`.
2. Set **Protocol** to `HTTPS only`.
3. Set **Minimum origin SSL protocol** to `TLSv1.2`.
4. Set **Name** to `fpcdn.io`.
5. Add a custom header:
* Set **Header name** to `FPJS_SECRET_NAME`
* Set **Value** to the *Export name* of `FingerprintIntegrationSettingsSecret` created in [Step 3.4](#step-3-4-review-and-create).
6. Finally, click **Create origin**.
### Step 4.2: Create a cache behavior
In this step, you will create a cache behavior to proxy requests to the Fingerprint API.
1. Go to your CloudFront distribution.
2. Switch to the **Behaviors** tab and click **Create behavior**.
3. Fill in the required fields:
1. Set **Path pattern** to a value that matches all routes under `FPJS_BEHAVIOR_PATH`. For example, if `FPJS_BEHAVIOR_PATH=random-path` fill in `random-path*`.
2. Set **Origin and origin groups** to `fpcdn.io`.
3. Set the **Viewer protocol policy** to `Redirect HTTP to HTTPS`.
4. Set the **Allowed HTTP methods** to `GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE`.
5. Set **Cache key and origin requests** to `Cache policy and origin request policy (recommended)`.
6. Set **Cache policy** to a *Custom* policy named `FingerprintProCDNCachePolicy-{id}`. You can find the name of your cache policy in *CloudFormation* > *Stacks* > \_*>\_Outputs* as the *Export name* of `CachePolicyName`.
7. Set **Origin request policy** to `AllViewer`.
8. Click **Create behavior**.
**Multi-segment behavior paths**
We recommend using **single** behavior path segments for simplicity, but it's possible to use more complex ones if needed.
For example:
```dotenv theme={"theme":"github-dark-dimmed"}
FPJS_BEHAVIOR_PATH="ore54guier/vbcnkxb654"
```
However, this requires adjustment of an additional custom header `FPJS_INTEGRATION_PATH_DEPTH` which describes the number of path segments in the behavior path:
1. Navigate back to your **fpcdn.io** origin.
2. Add an additional custom header:
* Set **Header name** to `FPJS_INTEGRATION_PATH_DEPTH`.
* Set **Value** to the number of path segments in the behavior path, in our example it's `2`.
```
Segment Segment
↓ ↓
ore54guier / vbcnkxb654 → 2 path segments
```
**Do not change the provided Cache Policy**
It is intentionally configured with the minimum TTL of `0`. Misconfiguring the policy could disrupt visitor identification and increase false positives (assigning the same visitor ID to different browsers).
### Step 4.3: Attach the Lambda function to the cache behavior
1. Go to **CloudFormation** > **Stacks** and open your newly created stack (named *Fingerprint-Pro-Cloudfront-Integration-v2* by default).
2. Switch to the **Resources** tab, find the resource with the **Logical ID** of `FingerprintProCloudFrontLambda` and click the link in the corresponding **Physical ID** column.
3. Scroll down and switch to the **Configuration** tab.
4. On the left, click **Triggers**, then click **Add trigger**.
5. Select `CloudFront` as the source and click **Deploy to Lambda\@Edge**.
6. Select **Configure new CloudFront trigger.**
1. Select your distribution.
2. Select the cache behavior you created in the previous step.
3. Set **CloudFront event** to `Origin Request`.
4. Check **Include body**.
5. Check **Confirm deploy to Lambda\@Edge**.
6. Click **Deploy**.
It may take several minutes to add the trigger to the CloudFront distribution. To monitor the progress, go to your CloudFront distribution, switch to the **General** tab, and check the **Last modified** time.
## Step 5: Configure the Fingerprint JavaScript agent on your client
Use the path variables created in [Step 2](/docs/cloudfront-proxy-integration-v2#step-2-create-path-variable) to construct the `endpoints` URL.
If your website and the proxy integration are behind the same CloudFront distribution (you chose [Step 3.2.A](#a-use-an-existing-cloudfront-distribution-recommended)), the JavaScript Agent configuration will use randomized paths inside your domain, for example:
```javascript NPM package theme={"theme":"github-dark-dimmed"}
import * as Fingerprint from '@fingerprint/agent'
// Initialize the agent at application startup.
const fp = Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
endpoints: 'https://yourwebsite.com/FPJS_BEHAVIOR_PATH/?region=us',
region: 'us',
});
```
```javascript CDN theme={"theme":"github-dark-dimmed"}
const url = 'https://yourwebsite.com/FPJS_BEHAVIOR_PATH/web/v4/PUBLIC_API_KEY';
const fpPromise = import(url)
.then(Fingerprint => Fingerprint.start({
endpoints: 'https://yourwebsite.com/FPJS_BEHAVIOR_PATH/?region=us',
region: 'us',
}));
```
If you set up a separate CloudFront distribution on your subdomain according to [Step 3.2.B](#b-create-a-new-cloudfront-distribution), the JavaScript Agent configuration will use that subdomain to interact with Fingerprint, for example:
```javascript NPM package theme={"theme":"github-dark-dimmed"}
import * as Fingerprint from '@fingerprint/agent'
// Initialize the agent at application startup.
const fp = Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
endpoints: 'https://metrics.yourwebsite.com/FPJS_BEHAVIOR_PATH/?region=us',
region: 'us',
});
```
```javascript CDN theme={"theme":"github-dark-dimmed"}
const url = 'https://metrics.yourwebsite.com/FPJS_BEHAVIOR_PATH/web/v4/PUBLIC_API_KEY';
const fpPromise = import(url)
.then(Fingerprint => Fingerprint.start({
endpoints: 'https://metrics.yourwebsite.com/FPJS_BEHAVIOR_PATH/?region=us',
region: 'us',
}));
```
**Parameter URL nuances:** Pass the region to the `endpoints` parameter in the following format:
`?region=eu`. The value needs to reflect the [region](/docs/regions) of your application.
If everything is configured correctly, you should receive data through your CloudFront distribution successfully.
## Step 6: Enable automatic updates in the Fingerprint Dashboard
Keeping the integration up to date is crucial for maintaining maximum identification accuracy. To enable automatic updates, provide the following Management function settings from AWS to the Fingerprint dashboard:
* The public URL of the Management function.
* An authentication token for the Management function.
When a new version of the proxy integration is available, Fingerprint will trigger an update by sending a request to the Management function URL, authenticated with the provided token. The token has only the permissions necessary for this purpose. You can verify the granted permissions in the Management function's Execution role (Stack > Resources > `FpMgmtLambdaFunctionExecutionRole`).
Note: Only users with the **Owner** or **Admin** role assigned can enable automatic updates for
the integration in the Dashboard.
### Step 6.1: Find the Management function details in AWS
1. Go to **CloudFormation** > [**Stacks**](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/) and open your newly created stack (named *Fingerprint-Pro-Cloudfront-Integration-v2* by default).
2. Switch to the **Outputs** tab. The *Value* of `MgmtLambdaFunctionUrl` is a public URL of your Management Lambda function. Save it somewhere.
3. Switch to the **Resources** tab. Click the *Physical ID* of the `MgmtSettingsSecret`.
4. Inside the secret page, click **Retrieve secret value** and copy the token value. This is the authentication token for the Management Lambda function. Save it somewhere.
### Step 6.2: Provide the Management function details to the Fingerprint Dashboard
1. Go to **Dashboard** > [**SDKs & integrations**](https://dashboard.fingerprint.com/integrations) > **AWS CloudFront**.
2. If you have multiple environments in your workspace, select the environment scope you want to enable updates for in the top navigation bar. It should be the same environment scope that you used to [create the integration's proxy secret](/docs/cloudfront-proxy-integration-v2#step-1-issue-a-proxy-secret).
3. Click **Enable updates**.
4. Fill in the values of **CloudFront Management Lambda URL** and **Authentication Token** that you retrieved in Step 6.1.
5. Click **Save Changes**.
The integration page will show **Status: Updates enabled** and Fingerprint will automatically keep your integration up to date.
## Troubleshooting
If you run into problems, please provide our support team with the following information:
1. Open the integration CloudFormation stack.
2. Go to the **Events** tab, and copy the contents of the **Status reason** column for any failed statuses.
## What's next
To learn more about calculating AWS costs and monitoring your CloudFront integration, go back to the [general CloudFront Proxy Integration guide](/docs/cloudfront-proxy-integration-v2#monitoring-and-managing-the-integration).
# Overview
Source: https://docs.fingerprint.com/docs/install-the-javascript-agent
Learn how to install and use the JavaScript agent to identify visitors.
The Fingerprint [JavaScript agent](/reference/js-agent) is a high-performance JavaScript library that collects multiple device and browser signals and sends them to the Fingerprint Platform API for visitor identification and device intelligence analysis. If you are looking for equivalent SDKs for mobile devices, see [Android](/docs/native-android-integration) and [iOS](/docs/ios).
This guide focuses on how to install the JavaScript agent. To explore the JavaScript agent API further, see its [API reference](/reference/js-agent).
You have several options for how to install the JavaScript agent on your website:
* Import the agent directly from a CDN (fastest for a quick test).
* Install the agent loader using NPM (includes TypeScript support).
* Use a frontend SDK for your specific framework like React, Vue, Svelte, or Angular (recommended, includes built-in caching strategies and framework-specific APIs).
**Sandboxed iframe not supported**
Do not run the JavaScript agent inside a [sandboxed](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#sandbox) iframe. This setup is not supported and may prevent the agent from functioning properly.
Each option is described in more detail below. They all require a Fingerprint Public API key, which you can get by navigating to [**Dashboard**](https://dashboard.fingerprint.com/) > **[API keys](https://dashboard.fingerprint.com/api-keys)**.
You can also see **[SDKs & integrations](https://dashboard.fingerprint.com/integrations)** for code snippets of all the options already personalized with your public API key, [region](/docs/regions), and [proxy integration](/docs/protecting-the-javascript-agent-from-adblockers).
## Importing from a CDN
This is the easiest way to start. It's also known as ESM import.
1. Add the HTML code below to your page.
2. Use your public API key at the end of the URL, right after `/v4/`, for example: `https://fpjscdn.net/v4/PUBLIC_API_KEY`.
```ts HTML theme={"theme":"github-dark-dimmed"}
```
**Always use the latest JavaScript agent**
Do not copy the JavaScript agent code from `fpjscdn.net` to your codebase.
We routinely update the JavaScript agent to keep up with the latest browser releases. Always download the latest JavaScript agent, otherwise you may miss critical updates, leading to degraded accuracy or service disruption. If you absolutely must host all JavaScript resources yourself, please reach out to our support at [support@fingerprint.com](mailto:support@fingerprint.com?subject=I%20want%20to%20keep%20JS%20Agent%20code%20on%20my%20server) for a solution.
## Installing the agent loader using NPM
The Fingerprint NPM package works as a **loader** for the JavaScript agent. It does not contain the JavaScript agent code itself; instead, it's responsible for downloading the agent from a CDN at runtime.
We routinely update the JavaScript agent to keep up with the latest browser releases. The NPM package ensures your website always uses the latest, most accurate version of the agent without frequently updating your NPM dependencies and redeploying your application.
* The NPM package includes TypeScript types for a better development experience.
* The package is compatible with various module bundlers such as [Webpack](https://webpack.js.org) and [Rollup](https://rollupjs.org).
To use the NPM package:
1. Install the [@fingerprint/agent](https://www.npmjs.com/package/@fingerprint/agent) NPM package using your favorite package manager.
```shell NPM theme={"theme":"github-dark-dimmed"}
npm install @fingerprint/agent
```
```shell Yarn theme={"theme":"github-dark-dimmed"}
yarn add @fingerprint/agent
```
```shell PNPM theme={"theme":"github-dark-dimmed"}
pnpm add @fingerprint/agent
```
2. Import the package into your code:
```javascript JavaScript theme={"theme":"github-dark-dimmed"}
import * as Fingerprint from '@fingerprint/agent'
// Download the agent at application startup and initialize it
const fp = Fingerprint.start({ apiKey: 'PUBLIC_API_KEY' })
// Analyze the visitor when necessary.
fp.get()
.then(result => console.log(result.event_id, result.visitor_id))
```
3. Pass your public API key as `apiKey` into the `start()` function.
You can use your preferred [import syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#forms_of_import_declarations). However, using a namespace import (`import * as Fingerprint from`) instead of a default import ( `import Fingerprint from`) will result in a slightly smaller bundle size due to improved [tree-shaking](https://en.wikipedia.org/wiki/Tree_shaking).
If you use a server-side rendering framework, make sure the NPM package code runs only in the browser, not on the server. For more information, see [Usage with server-side rendering frameworks](/docs/usage-with-server-side-rendering-frameworks).
## Using a framework-specific SDK
We have SDKs for the following frameworks:
* [React](https://github.com/fingerprintjs/react) (includes support for Preact, Next)
* [Vue](https://github.com/fingerprintjs/vue)
* [Angular](https://github.com/fingerprintjs/angular)
* [Svelte](https://github.com/fingerprintjs/svelte)
The SDKs wrap the agent loader NPM package described above, meaning the latest fingerprinting logic is still downloaded from a CDN at runtime. The SDKs expose the JavaScript agent functionality through framework-specific APIs (for example, a provider component and a hook in React). They also have caching and other best practices built in.
If you can, we recommend using one of the framework SDKs above. You can go to **Dashboard** > **[SDKs & integrations](https://dashboard.fingerprint.com/integrations)** to get started with personalized code snippets for the SDKs.
If your application uses a different frontend framework, use the [NPM agent loader](#installing-the-agent-loader-using-npm) directly - it includes the same built-in caching mechanism and works with any framework. The older [`fingerprintjs-pro-spa`](https://github.com/fingerprintjs/fingerprintjs-pro-spa) wrapper is [deprecated in v4](/docs/generic-js-agent-wrapper-for-spas) now that caching is built into the agent.
## Configuring the agent
No matter which installation methods you pick above, you need to initialize the agent with your API key, region, proxy endpoints, and other load options. See the [JavaScript Agent API Reference](/reference/js-agent-start-function#start-options) for a detailed description of all `start()` options. Most users will need the following three options:
* [`apiKey`](/reference/js-agent-start-function#apikey): Go to **Dashboard** > **[API keys](https://dashboard.fingerprint.com/api-keys)** to find your public API key.
* [`region`](/reference/js-agent-start-function#region): If you choose to store your data in the EU or Asia Pacific [region](/docs/regions), you need to specify it in the region `start()` option.
* [`endpoints`](/reference/js-agent-start-function#endpoints): Requests to Fingerprint CDN and API can be blocked by ad blockers. For maximum coverage, we recommend proxying Fingerprint requests through your own domain or subdomain. See [Proxy integrations](/docs/protecting-the-javascript-agent-from-adblockers) for more information. Once you have a proxy integration in place, you will use the `endpoints` start option to specify the proxy endpoints for agent download and identification requests. For the CDN installation method, use the proxy endpoint for agent download directly in the `import` statement.
```ts HTML & CDN theme={"theme":"github-dark-dimmed"}
```
```javascript NPM theme={"theme":"github-dark-dimmed"}
import Fingerprint from '@fingerprint/agent';
// Start the agent on application start.
const fp = Fingerprint.start({
apiKey: 'PUBLIC_API_KEY',
endpoints: 'https://metrics.yourwebsite.com',
region: 'eu',
});
// Get the visitor_id when you need it.
fp.get().then((result) => console.log(result.visitor_id));
```
If you go to **[SDKs & integrations](https://dashboard.fingerprint.com/integrations)** you can find a code snippet for your chosen installation method that already reflects your public key, chosen region, and active integration:
* If you used the CDN or NPM loader installation methods, you can pass the `start()` options as an object into the `Fingerprint.start()` method. Once you have the promise of the JavaScript agent instance, you can resolve it and call `get()` to send an identification requests to Fingerprint API.
* If you used a [framework-specific client SDK](#using-a-framework-specific-sdk), please refer to its documentation for the SDK-specific way of passing the `start()` options object to the JavaScript agent.
## What's next
* Continue by reading [Identify visitors](/docs/identify-visitors) to learn best practices for timing identification requests, caching visitor information, or using linked IDs to associate your own identifiers with each visitor ID.
* See [Configure Content Security policy](/docs/js-agent-csp) to make sure the JavaScript agent is not blocked by your own CSP.
* Concerned about performance and latency? Read our guide on [Optimizing JavaScript agent loading](/docs/optimize-javascript-agent#only-use-the-javascript-agent-where-you-need-it).
* Ready for a deep dive? Explore the full [JavaScript agent API Reference](/reference/js-agent).
* To learn about equivalent SDKs for mobile devices, see [Android](/docs/native-android-integration) and [iOS](/docs/ios).
# Introduction
Source: https://docs.fingerprint.com/docs/introduction
Learn how you can prevent fraud and improve user experiences with Fingerprint.
## What is Fingerprint?
**Fingerprint is a software-as-a-service that identifies online devices and provides valuable information about them.** Fingerprint doesn't scan human fingerprints like biometric systems do. Instead, it creates a unique identifier for each device, called a "Visitor ID," which stays permanently tied to that device. It works on both web browsers and mobile devices, making sure the devices are accurately identified on any platform.
### How it works
Fingerprint uses browser and device fingerprinting along with advanced server techniques, like statistical ID generation and machine learning, to create a unique visitor ID such as **`xPFysGV25VxZcGQUxVIt`**. This ID stays the same over time, even if the device’s settings or software changes or if cookies are deleted or unavailable.
You can see your own device ID by visiting [our demo](https://demo.fingerprint.com/playground). While there, try revisiting in incognito mode or with your VPN on. Your visitor ID will be the same.
Our stable visitor IDs are helpful when people try to hide their identity online, like in fraud detection or protecting accounts. They also make things easier for trusted users by reducing the need for constant logins or CAPTCHA checks because trusted users can be recognized easily. Fingerprint is designed to not need any permissions, so users won’t see permission prompts in their browser or on their phone.
### Our products
Fingerprint has two main products that work together to help you identify and understand your visitors better.
* [**Identification**](https://fingerprint.com/products/identification/): Our main product creates very accurate and stable visitor IDs. These IDs help stop fraud by looking at how devices are used and can also make the user experience better.
* [**Smart Signals**](https://fingerprint.com/products/smart-signals/): Along with Identification, Smart Signals provide extra insights into visitor behavior, like spotting [bots](/docs/smart-signals-reference#browser-bot-detection), [VPN use](/docs/smart-signals-reference#vpn-detection-for-browsers), or [tampered browsers](/docs/smart-signals-reference#browser-tamper-detection). These insights help you identify and respond to suspicious activity more effectively.
## Fingerprint Identification vs. FingerprintJS
Fingerprint's [Identification](https://fingerprint.com/products/identification/) is significantly better than its open-source counterpart, **FingerprintJS**. While both the open-source and proprietary versions identify devices, the proprietary version, Fingerprint Identification, is more accurate and provides detailed, additional information about the device. Please see the FingerprintJS vs. Fingerprint Identification [comparison table](https://github.com/fingerprintjs/fingerprintjs/blob/master/docs/comparison.md) for a feature-by-feature breakdown of the differences.
### FingerprintJS
FingerprintJS has been a key tool for browser fingerprinting since it was created on [GitHub](https://github.com/fingerprintjs/fingerprintjs) in 2012. It’s still the most accurate open-source fingerprinting library, available to all users at no cost.
FingerprintJS is a JavaScript library that collects browser details to make a unique ID called a "fingerprint." It works quickly and runs on the client side, so no backend is needed. However, it has some limits:
* **Same fingerprints on similar devices**: FingerprintJS can give the same fingerprint for devices with the same setup. For example, two iPhone 15s using Safari will have the same fingerprint, which makes it hard to tell them apart.
* **Changes affect fingerprints**: The fingerprints can change when things like time zone, OS, or browser updates happen. This makes them less reliable for long-term use.
### The advantages of Fingerprint Identification
Fingerprint Identification is our main commercial product, offering big improvements over FingerprintJS. It works as a client-server system, using a client-side SDK to gather data and send it to Fingerprint for processing. This allows Fingerprint Identification to:
* **Give more accurate identification**: Unlike FingerprintJS, it can tell apart devices with the same setup (like two iPhone 15s), making sure each device gets a unique and steady visitor ID.
* **Work on more platforms**: It works not just on web browsers but also on mobile devices and connects easily with APIs, webhooks, and cloud tools.
* **Stay stable over time**: The visitor IDs it creates stay the same for months, even with changes to the device or software, making it more reliable for long-term use.
## Fingerprint design patterns
Fingerprint can be used in various ways to prevent fraud and improve user experience. Here are some common patterns to use its visitor IDs effectively:
* Comparing visitor IDs
* Counting visitor IDs linked to data
* Counting data linked to a visitor ID
### Comparing visitor IDs
This pattern generates a visitor ID before important actions, like a login. It is then compared to the previous ID for the same user. If they match, the user can continue without extra checks. If they don’t match, it could mean a different device or an unauthorized attempt, so extra verification, like a one-time code, is recommended. This helps to make sure only authorized users can perform sensitive actions.
### Counting visitor IDs linked to data
Here, each time a key action happens — like logging in, creating an account, or making a purchase — a visitor ID is added to a list for that user or account. If the list gets too big (e.g., more than five IDs), it might mean several devices are being used by the same user. This could suggest account sharing or unauthorized access. This could trigger alerts or ask for extra verification to protect the account.
### Counting data linked to a visitor ID
This pattern tracks how many internal IDs (like account IDs or transaction IDs) are connected to one visitor ID. For example, if a visitor ID is linked to many new account attempts, it could signal possible fraud, like multiple account creation. By looking at these connections, you can spot and prevent fraud, such as account farming or coupon abuse.
## Use cases
Fingerprint can be used in many ways to prevent fraud and improve user experience based on the design patterns mentioned above. Here are three common examples. You can find more on our [demo hub](https://demo.fingerprint.com/).
### Reducing login friction
The pattern used here is ***comparing visitor IDs***.
Compare the visitor IDs from past and current logins to make logging in easier without compromising security. The first time a user logs in, create and save a visitor ID. For future logins, create a new ID and compare it to the saved one. If they match, the login can go through without extra checks. This reduces friction while keeping the login process secure.
### Account sharing prevention
The pattern used here is ***counting visitor IDs linked to data***.
By counting the number of visitor IDs accessing a single account, you can effectively prevent account sharing. For example, if more than a certain number of devices (e.g., more than 3) are used to log into the same account, you can prompt the user to verify their identity or restrict further logins until they confirm ownership. This approach ensures that the account is used as intended without relying on frequent MFA or other disruptive measures.
### New account fraud
The pattern used here is ***counting data linked to a visitor ID***.
In some use cases, users shouldn’t be able to create multiple accounts. Instead of using methods like Single Sign-On or phone validation every time, which can hurt the user experience, generate a visitor ID each time a new account is created. If the number of accounts linked to that ID is small (e.g., 3 or fewer), let it continue. If the number is high (e.g., more than 3), trigger extra checks, like phone validation, to prevent fraud.
## Future-proof, reliable, and secure
Browsers like Safari, Chrome, and Brave are adding more privacy features, like Safari 17's updates, Google’s Privacy Sandbox, and Brave’s anti-fingerprinting. It’s normal to wonder what this means for the future of fingerprinting. However, Fingerprint is still a strong and reliable solution.
For over 10 years, Fingerprint has provided industry-leading accuracy. Some browsers have added tools to block fingerprinting. These tools haven’t worked well because fingerprinting uses basic browser APIs required for building web applications. Blocking it completely would break parts of the internet.
Even if future updates make fingerprinting less effective, Fingerprint Identification will still be one of the most accurate for two reasons:
1. **Universal Impact**: Any browser changes will affect all identification solutions, but Fingerprint will remain highly accurate in comparison, even if slightly less so.
2. **Hybrid Approach**: Fingerprint Identification combines server data with browser data to create stable visitor IDs. The server-side component keeps accuracy high as browsers change.
Fingerprint is also built to perform well without crashing your app. It has strong error handling and a timeout feature, making integration smooth. If generating a visitor ID takes too long (like on older devices), the process stops safely, avoiding disruptions for users.
On the security side, Fingerprint makes it very hard for attackers to fake a visitor ID. Developers can check for ID mismatches and add extra security checks when needed without annoying users. With these protections, Fingerprint stays a reliable and secure solution that can adapt to browser changes while keeping its accuracy and performance high.
## Want to get help?
If you're a developer, please join our [Discord channel](https://discord.com/invite/39EpE2neBg) for technical discussions.
If you’re a GitHub user, please use [discussions](https://github.com/fingerprintjs/fingerprintjs/discussions) and open [issues](https://github.com/fingerprintjs/fingerprintjs/issues).
If you’re a customer, our [US-based support](https://fingerprint.com/support/) is always ready to help!
***
What’s Next
* [Start your free workspace now](https://dashboard.fingerprintjs.com/signup/)
* [Quick Start Guide](/docs/quick-start-guide)
* [Install the JavaScript agent](/docs/install-the-javascript-agent)
# iOS devices
Source: https://docs.fingerprint.com/docs/ios
Fingerprint's device intelligence platform for iOS devices.
Fingerprint's device intelligence platform is also available for iOS devices. Our device intelligence platform for iOS helps you to accurately identify the devices that interact with your mobile app and provides high-quality Smart Signals that will help you identify risky transactions before they happen.
To make integrations with mobile apps easier, we offer SDKs for apps built natively as well as for apps built using multi-platform frameworks (e.g. React Native).
A Fingerprint account is required to be able to use the Fingerprint Identification SDK in your mobile apps. If you do not have an account yet, please [sign up](https://dashboard.fingerprintjs.com/signup) for one.
**Download our** [**demo app**](https://apps.apple.com/us/app/fingerprint-pro/id1644105278) from App Store to see Fingerprint in action!
## Device Identifier
When Fingerprint Identification SDK is integrated into your mobile app, for every device that interacts with your app, you will be able to receive a unique device identifier (i.e. `visitorId`). The visitor ID is derived solely from Apple's [identifierForVendor](https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor) (IDFV) property. The SDK does not use any fingerprinting techniques to identify a device.
However, despite the visitor ID being derived from IDFV, there are a few important differences between them:
* The visitor ID will remain the same even when the app is removed and re-installed whereas the IDFV will not.
* In addition to being scoped by the vendor, the visitor ID is also scoped by the [Fingerprint Workspace](/docs/glossary#fingerprint-workspace). It means the only time the visitor ID remains the same between two apps is when the apps are from the same vendor **and** when they belong to the same Fingerprint workspace.
**Did you know?**
The device identifier generated by the iOS SDK (embedded within your mobile app) will be **different** from the visitor ID generated by the Javascript agent (embedded within your website).
The identification accuracy, when compared with browsers, is better for iOS devices because the IDFV property has higher longevity than the signals used for identifying browsers.
For your reference, we have listed various scenarios where a visitor ID will remain the same before and after and where a new visitor ID will be created.
| Scenario | Will I receive the same visitor ID before and after? |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| The app or device is restarted | **Yes.** You will receive the same visitor ID even after the app or device is restarted. |
| The app is uninstalled and re-installed again | **Yes.** You will receive the same visitor ID even after the app is removed and installed again |
| The [provisioning profile](https://developer.apple.com/documentation/technotes/tn3125-inside-code-signing-provisioning-profiles) and/or the signing certificate of the app are changed. | **Yes.** You will receive the same visitor ID as long as the [AppID](https://developer.apple.com/library/archive/documentation/General/Conceptual/DevPedia-CocoaCore/AppID.html) remains the same before and after the change. |
| The device is jailbroken | **Yes.** You will receive the same visitor ID as before even after the device is jailbroken. |
| [Lockdown mode](https://support.apple.com/en-us/HT212650) is enabled on the device | **Yes.** You will receive the same visitor ID as before even after lockdown mode is enabled on the device. |
| [Reset device settings to their default values](https://support.apple.com/guide/iphone/return-iphone-settings-to-their-defaults-iphea1c2fe48/ios) | **Yes.** You will receive the same visitor ID as before even after one or more of the device settings are reset to their default. |
| [The device is factory reset](https://support.apple.com/en-us/HT201274)(i.e. all content and settings are erased). | **No**. After the device is factory reset, a new visitor ID will be generated. |
## iOS Smart Signals
Smart Signals are actionable device intelligence signals that help you to make informed decisions about the device and thus prevent fraud. These signals could be used as input to your machine learning models behind your fraud detection or risk mitigation engines. For example, when you know that a request is originating from a *jailbroken* device, you will be able to take additional precautionary security steps (e.g., multi-factor authentication).
When your mobile app requests a visitor ID, the SDK, along with the IDFV, will collect several other attributes from the device. The Smart Signals are deduced by combining these attributes with additional algorithms.
For iOS, the following Smart Signals are available:
* [Factory Reset Detection](/docs/smart-signals-reference#factory-reset-detection)
* [Frida Detection](/docs/smart-signals-reference#frida-detection)
* [Geolocation Spoofing Detection](/docs/smart-signals-reference#geolocation-spoofing-detection)
* [IP Geolocation Detection](/docs/smart-signals-reference#ip-geolocation)
* [IP Blocklist Detection](/docs/smart-signals-reference#ip-blocklist-matching)
* [Jailbroken Device Detection](/docs/smart-signals-reference#jailbroken-device-detection)
* [MitM Attack Detection](/docs/smart-signals-reference#mitm-attack-detection)
* [Tampered Request Detection](/docs/smart-signals-reference#tampered-request-detection-for-mobile-apps)
* [VPN Detection](/docs/smart-signals-reference#vpn-detection-for-mobile-devices)
* [iOS Simulator Detection](/docs/smart-signals-reference#ios-simulator-detection)
* [Developer Tools Detection](/docs/smart-signals-reference#developer-tools-detection-for-mobile-devices)
* [Smart Signals common for browsers and mobile devices](/docs/smart-signals-reference#smart-signals-common-for-browsers-and-mobile-devices)
These Smart Signals are available as part of our [Pro Plus and Enterprise plans](https://fingerprint.com/pricing/). If you are interested in one or more of these signals, please [contact our support team](https://fingerprint.com/support/) to enable them. Signing up for an Enterprise plan does not automatically enable these signals.
## Fingerprint Identification SDK for iOS
### Current Version: [2.17.1](https://docs.fingerprint.com/docs/changelog-ios-sdk#july-2026)
For release notes and older versions, please visit the [Changelog for iOS SDK](/docs/changelog-ios-sdk) page.
### Supported Versions
The minimum version that the iOS SDK for Fingerprint Identification supports is [iOS 13](https://developer.apple.com/documentation/ios-ipados-release-notes/ios-13-release-notes), [tvOS 15](https://developer.apple.com/documentation/tvos-release-notes/tvos-15-release-notes). Per [Apple](https://developer.apple.com/support/app-store/) and [Statista](https://www.statista.com/statistics/565270/apple-devices-ios-version-share-worldwide/), this will allow your app to be compatible with a wide variety of active iOS devices.
As of May 2023, our SDK will allow your app to be compatible with more than 94% of the devices in the world.
### Getting Started Guides
Our Getting Started guides are aimed at helping you to integrate our SDK with your apps easily.
* For native iOS apps, see [Getting Started Guides > iOS](/docs/ios-sdk) and our [iOS quickstart](/docs/ios-quickstart).
* For apps built using Flutter, see our [Flutter SDK on GitHub](https://github.com/fingerprintjs/fingerprintjs-pro-flutter) and our [Flutter quickstart](/docs/flutter-quickstart).
* For apps built using React Native, see our [React Native SDK on GitHub](https://github.com/fingerprintjs/react) and our [React Native quickstart](/docs/react-native-quickstart).
## Privacy Manifest File Requirements for App Store
During WWDC 2023, Apple [introduced a new requirement](https://developer.apple.com/news/?id=av1nevon) where app and SDK developers must create privacy manifest files to declare the data that their app/SDK collects and the reasons for collecting this data. Accordingly, starting from v2.3.2, the Fingerprint Identification SDK for iOS will include a privacy manifest file. To learn more, please see [Understanding privacy manifest files](/docs/mobile-devices-understanding-privacy-manifest-files).
## FingerprintJS-iOS: Open-source identification library for iOS
We also provide a lightweight, open-source library called [fingerprintjs-ios](https://github.com/fingerprintjs/fingerprintjs-ios) which is available to use for free by everyone. Our commercial SDK, Fingerprint Identification for iOS, is based on our well-maintained, free, open-source library.
For your better understanding, here is a complete set of differences between our open-source library (`fingerprintjs-ios`) and our commercial SDK (`fingerprint-ios-pro`).
| | FingerprintJS-iOS(fingerprintjs-ios) | Fingerprint Identification for iOS(fingerprint-ios-pro) |
| ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Core Features | | |
| **Attributes collected from the device** | Basic (*e.g. OS, Model*) | Advanced. Lot more attributes, in addition to those collected by the open-source library. |
| **ID Type** | A visitor ID that is solely derived from Apple's [identifierForVendor](https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor) property. | A visitor ID that is solely derived from Apple's [identifierForVendor](https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor) property. |
| **ID Origin** | The visitor ID is generated only from within the device. External servers are not involved. | The visitor ID is generated from within the device and also processed in our servers. |
| **Is visitor ID the same across multiple apps on a single device?** | **No.** Unless the apps are from the same vendor. | **No.** Unless the apps are from the same vendor and belong to the same Fingerprint workspace. |
| **ID Collisions** | Common | Very rare |
| Support for multi-platform frameworks | | |
| **Flutter** | - | **✓** |
| **React Native** | - | **✓** |
| Advanced Features | | |
| **Server API and Webhooks**\*(Build flexible workflows)\* | - | **✓** |
| **Mobile Smart Signals**\*(VPN Detection, Factory Reset, Geolocation Spoofing, etc)\* | - | **✓** |
| **Geolocation**\*(Based on IP address)\* | - | **✓** |
| Operations | | |
| **Data security** | Depends on your infrastructure | Encrypted at rest |
| **Storage** | Depends on your infrastructure | Unlimited up to 1 year |
| **Regions** | Depends on your infrastructure | Global, EU and Asia data centers |
| **Compliance** | Depends on your infrastructure | Compliant\*\*\* with GDPR, CCPA, SOC 2 Type II, and ISO 27001 |
| **SLA** | SLA is not provided | 99.9% Uptime |
| **General Support** | GitHub Issues/Questions. Response times varies. | Dedicated support team that responds to chat, email, and calls within 1 business day |
| **Support for submitting an app to App Store** | Not available. | Dedicated support will be provided to the extent resulting from using the Fingerprint Identification SDK. |
| How to get started? | [Get it on GitHub](https://github.com/fingerprintjs/fingerprintjs-ios) | [Sign up for a free 14-day trial](https://dashboard.fingerprintjs.com/signup/) |
\*\*In comparison to fingerprints, VisitorIDs are more accurate and stable identifiers because they are deduplicated, are processed further on the server, and utilize fuzzy matching. On the other hand, fingerprint hashes become unstable across time intervals greater than 2 weeks because they rely on an exact match of all browser attributes.
\*\*\*The company, Fingerprint, in its role as data processor is compliant with GDPR, CCPA and SOC 2 Type II. It is also ISO 27001 certified. As a data controller, it is still your responsibility to be compliant with GDPR and CCPA and use the identification result for legitimate purposes
# iOS Quickstart
Source: https://docs.fingerprint.com/docs/ios-quickstart
Get started using the iOS SDK
## Overview
In this quickstart, you'll add Fingerprint to a new Xcode iOS app and identify the user's device.
The example use case in this quickstart is stopping new account fraud, where attackers create multiple fake accounts to abuse promotions, exploit systems, or evade bans. However, the steps you'll follow apply to most use cases. By identifying the device behind each sign-up attempt, login, or transaction, you can flag and block suspicious users early.
This guide focuses on the frontend mobile integration. You'll install the [Fingerprint iOS SDK](/docs/ios-sdk) and initialize the client agent to generate a request ID to send to your backend for analysis. **To see how to implement fraud prevention with this ID, continue to one of the [backend quickstarts](/docs/server-quickstarts-overview) after completing this quickstart.**
> Estimated time: \< 10 minutes
## Prerequisites
Before you begin, make sure you have the following:
* [Xcode](https://developer.apple.com/xcode/) installed (latest version)
* Basic knowledge of [Swift](https://www.swift.org/) and iOS development
* A Fingerprint account and public API key (see below)
This quickstart only covers the **frontend setup**. You'll need a [backend
server](/reference/server-sdks) to receive and process the device identification event to enable
fraud detection. Check out one of the [backend quickstarts](/docs/server-quickstarts-overview)
after completing this quickstart.
## 1. Create a Fingerprint account and get your API key
1. [Sign up](https://dashboard.fingerprint.com/signup) for a free Fingerprint trial if you don't already have an account.
2. After signing in, go to the [**API keys**](https://dashboard.fingerprint.com/api-keys) page in the dashboard.
3. Copy your **public API key**; you'll need it to initialize the client agent.
## 2. Set up your project
To get started, create a new Xcode project. If you already have a project you want to use, you can skip to the next section.
1. Open Xcode and create a new project.
2. Click on iOS and select App, then click Next.
3. Enter a name (e.g., `FingerprintQuickstart`), and an organization identifier (e.g., `com.example`), and make sure to use Swift and SwiftUI.
4. Click Next, choose a save location, and create the project.
## 3. Install the client agent
To integrate Fingerprint into your iOS app, add the package to your project.
1. In Xcode, go to File > Add Package Dependencies…
2. Enter the package URL: `https://github.com/fingerprintjs/fingerprintjs-pro-ios`
3. Choose the latest version and click Add Package.
4. In the Choose Package Products pop-up, make sure to check your app target (e.g., `FingerprintQuickstart`) under "Add to Target" so the SDK is properly linked.
## 4. Initialize the SDK
Open your `ContentView.swift` or app entry point and:
1. Import the SDK:
```swift ContentView.swift theme={"theme":"github-dark-dimmed"}
import FingerprintPro
```
2. Initialize the SDK with your public API key and region:
```swift ContentView.swift theme={"theme":"github-dark-dimmed"}
static let region: Region = .eu
static let configuration = Configuration(
apiKey: "PUBLIC_API_KEY",
region: region,
extendedResponseFormat: true
)
```
3. Replace `PUBLIC_API_KEY` with your actual public API key from the Fingerprint [dashboard](https://dashboard.fingerprint.com/api-keys). Use .global, .eu, or .ap based on your workspace region. See region [**guide**](/docs/regions).
## 5. Trigger visitor identification
Now that the client agent is initialized, you can identify the visitor when needed. In this case, that's when the user taps a **Create Account** button.
When making the visitor identification request, you will receive the `visitorId` as well as a `requestId`. Instead of using the `visitorId` returned directly on the frontend (which could be tampered with), you'll send the `requestId` to your backend. This ID is unique to each identification event. Your server can then use the [Fingerprint Events API](/reference/server-api-get-event) to retrieve complete identification data, including the trusted visitor ID and other actionable insights like whether they are using a VPN or are a bot.
## Add a basic sign-up UI
1. Create a new SwiftUI function for the account creation screen. It will display a simple form with username and password fields, along with a **Create Account** button:
```swift ContentView.swift theme={"theme":"github-dark-dimmed"}
func AccountCreationForm(
username: Binding,
password: Binding,
logOutput: Binding,
onSubmit: @escaping () -> Void
) -> some View {
VStack(spacing: 20) {
Text("Create Account")
.font(.title)
.foregroundColor(.orange)
TextField("Username", text: username)
.textFieldStyle(.roundedBorder)
.autocapitalization(.none)
SecureField("Password", text: password)
.textFieldStyle(.roundedBorder)
Button("Create Account") {
onSubmit()
}
.buttonStyle(.borderedProminent)
.tint(.orange)
if !logOutput.wrappedValue.isEmpty {
Text("Log: \(logOutput.wrappedValue)")
.font(.caption)
.foregroundColor(.gray)
.multilineTextAlignment(.center)
.padding(.top, 10)
}
}
}
```
2. In the ContentView struct; just before the `body` variable; add the `createAccount()` function that would be triggered when the **Create Account** button is clicked:
```swift ContentView.swift theme={"theme":"github-dark-dimmed"}
struct ContentView: View {
@State private var username = ""
@State private var password = ""
@State private var logOutput = ""
let fingerprintClient = FingerprintProFactory.getInstance(configuration)
func createAccout() async {
do {
let response = try await fingerprintClient.getVisitorIdResponse()
let requestId = response.requestId
print("Request ID: \(requestId)")
// Send username, password, and requestId to your backend server for processing
} catch {
print("Fingerprint error: \(error)")
logOutput = "Error: \(error.localizedDescription)"
}
}
//...
}
```
3. Update the body variable of the ContentView struct with the AccountCreationForm function:
```swift ContentView.swift theme={"theme":"github-dark-dimmed"}
struct ContentView: View {
@State private var username = ""
@State private var password = ""
@State private var logOutput = ""
let fingerprintClient = FingerprintProFactory.getInstance(configuration)
func createAccount() async {
do {
let response = try await fingerprintClient.getVisitorIdResponse()
let requestId = response.requestId
print("Request ID: \(requestId)")
// Send username, password, and requestId to your backend server for processing
} catch {
print("Fingerprint error: \(error)")
logOutput = "Error: \(error.localizedDescription)"
}
}
var body: some View {
VStack(spacing: 20) {
AccountCreationForm(
username: $username,
password: $password,
logOutput: $logOutput,
onSubmit:{
Task {
await createAccount()
}
}
)
}
.padding()
}
}
```
This interface:
* Displays a basic form for account creation.
* Calls `getVisitorIdResponse()` to identify the visitor.
* Captures the `requestId`, which you'll forward to your server for analysis.
## 6. Run the app
1. Select an iOS simulator or real device in Xcode.
2. Build and run the app (⌘R).
3. Enter a username and password then click **Create Account**.
4. Check your Xcode console for something like:
```text Output theme={"theme":"github-dark-dimmed"}
Request ID: 1753730090897.s4bWk4
```
You're now successfully identifying the device and capturing the request ID and are ready to send it to your backend for further fraud prevention logic!
## Next steps
To use the identification data for fraud detection (like blocking repeat fake account creation attempts), you'll need to send the `requestId` to your [backend](/docs/server-quickstarts-overview). From there, your server can call the [Fingerprint Events API](/reference/server-api-get-event) to retrieve the full visitor information and make decisions.
Check out these related resources:
* [iOS SDK reference](/docs/ios-sdk)
* [Node.js backend quickstart](/docs/node-server-quickstart)
* [API reference for the Events endpoint](/reference/server-api-get-event)
* [Use case tutorial: Detecting new account fraud](/docs/new-account-fraud-use-case-tutorial)
* [Protecting from client-side tampering and replay attacks](/docs/protecting-from-client-side-tampering)
# iOS SDK
Source: https://docs.fingerprint.com/docs/ios-sdk
A simple guide to integrate Fingerprint's device intelligence platform in your native iOS apps.
In this guide, you will learn how to:
* Include the SDK in your iOS apps.
* Get a `visitorId`.
* Read the SDK response.
* Enable location data collection (optional; to start getting additional location-based proximity data).
* Configure the SDK as per your needs.
* Specify additional metadata in your identification request.
* Handle errors.
For a complete example of how to use this SDK in your app, please visit the [GitHub repo of our demo app](https://github.com/fingerprintjs/fingerprint-device-intelligence-ios-demo/). View our [iOS quickstart](/docs/ios-quickstart) for a step-by-step guide to get started.
## Prerequisites
1. [Sign up](https://dashboard.fingerprintjs.com/signup) for an account with Fingerprint to get your API key.
2. [Xcode 15.0](https://developer.apple.com/documentation/xcode-release-notes/xcode-15-release-notes) or higher. See [App Store submission requirements](https://developer.apple.com/news/?id=fxu2qp7b) for more information.
## Including the SDK in your app
Add the SDK as a dependency:
```Text SPM theme={"theme":"github-dark-dimmed"}
// Add the below lines to Package.swift
let package = Package(
// ...
dependencies: [
.package(url: "https://github.com/fingerprintjs/fingerprintjs-pro-ios", from: "2.0.0")
],
// ...
)
```
```Text CocoaPods theme={"theme":"github-dark-dimmed"}
# Add the below line to the Podfile
pod 'FingerprintPro', '~> 2.0'
```
## Getting a `visitorId`
To get a `visitorId`, you need the public API key that you obtained when signing up for an account with Fingerprint. You can find this API key in the **Dashboard** > [**API Keys**](https://dashboard.fingerprint.com/api-keys).
Here is an example that shows you how to get a `visitorId`:
```swift theme={"theme":"github-dark-dimmed"}
import FingerprintPro
// Configure the SDK.
//
// The "region" must match the region associated with your API key.
let region: Region = .ap
//
// For custom subdomain or proxy integration, use ".custom(domain:fallback:)" region that lets
// you specify a custom endpoint.
// let region: Region = .custom(domain: "https://example.com")
//
// Additionally, you may also specify alternate, fallback endpoints to redirect failed requests
// let region: Region = .custom(
// domain: "https://example.com",
// fallback: [
// "https://one.fallback.com",
// "https://two.fallback.com",
// ]
// )
//
let configuration = Configuration(
apiKey: "PUBLIC_API_KEY",
region: region,
extendedResponseFormat: true
)
// Initialize the SDK with the configuration created above.
let client = FingerprintProFactory.getInstance(configuration)
Task {
do {
// Get a "visitorId".
let visitorId = try await client.getVisitorId()
print(visitorId)
} catch {
print(error.localizedDescription)
}
}
```
### Specifying a custom timeout
**Default timeout value:** 60 seconds
You can override the default timeout with a custom value of your choice. If the `getVisitorId()` call does not complete within the specified (or default) timeout, you will receive a `FPJSError.clientTimeout` error.
Here is an example that shows you how to specify a custom `timeout`:
```swift Swift theme={"theme":"github-dark-dimmed"}
Task {
do {
// Get a `visitorId` or, otherwise, time-out after 5 seconds.
let visitorId = try await client.getVisitorId(timeout: 5.0)
print(visitorId)
} catch {
print(error.localizedDescription)
}
}
```
## Reading the response
The function `FingerprintClientProviding.getVisitorIdResponse()` returns the response in a `FingerprintResponse` object.
```swift Swift theme={"theme":"github-dark-dimmed"}
Task {
do {
// Get a `sealedResult`.
let response = try await client.getVisitorIdResponse()
if let sealedResult = response.sealedResult {
print(sealedResult)
}
} catch {
print(error.localizedDescription)
}
}
```
The FingerprintResponse object has the following fields, some of which can be empty/invalid when [Sealed Client Results](/docs/sealed-client-results) is enabled for your account.
| Field | Sealed Client Results is disabled | Sealed Client Results is enabled |
| :----------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`version`** | [String](https://developer.apple.com/documentation/swift/string). The API version. | [String](https://developer.apple.com/documentation/swift/string). The API version. |
| **`requestId`** | [String](https://developer.apple.com/documentation/swift/string). An identifier that uniquely identifies the request associated with the API call. | [String](https://developer.apple.com/documentation/swift/string). An identifier that uniquely identifies the request associated with the API call. |
| **`visitorId`** | [String](https://developer.apple.com/documentation/swift/string). An identifier that uniquely identifies the device. | N/A |
| **`confidence`** | [Float](https://developer.apple.com/documentation/swift/float). A score that indicates the probability of this device being accurately identified. The value ranges from 0.0 to 1.0. | N/A |
| **`visitorFound`** | [Bool](https://developer.apple.com/documentation/swift/bool). Indicates if a device has already been encountered by your app. | N/A |
| **`sealedResult`** | `nil` | [String](https://developer.apple.com/documentation/swift/string)?. An encrypted, binary, Base64-encoded String that contains the same response as you would receive with an [`/events API`](/reference/server-api-get-event) request. |
## Using Location Data for Proximity Detection
This is optional. Enable this only if you want to include the additional location-based [proximity detection](/docs/smart-signals-reference#proximity-detection) signal in your response.
Starting the [iOS SDK v2.10.0+](/docs/changelog-ios-sdk#v2-10-0), new property [allowUseOfLocationData](/docs/ios-sdk#allowuseoflocationdata) has been added to the [Configuration](/docs/ios-sdk#configuring-the-sdk) structure. This allows the Fingerprint client to access device location data when available. By default, it is set to `false`. To start getting Proximity ID and related data, set this property to `true` in your SDK configuration.
**Important note:** For the best location precision, we recommend initializing the Fingerprint client **as early as possible - ideally during your app startup** - and maintaining the same instance throughout the app’s lifecycle.
```swift theme={"theme":"github-dark-dimmed"}
let configuration = Configuration(
apiKey: "PUBLIC_API_KEY",
region: region,
extendedResponseFormat: true,
allowUseOfLocationData: true
)
```
### Handling Location Permissions
Your app is responsible for asking for location permissions. Refer to [Apple documentation](https://developer.apple.com/documentation/bundleresources/choosing-the-location-services-authorization-to-request) or check the [Fingerprint demo app](https://apps.apple.com/us/app/fingerprint-pro/id1644105278) if you need to implement this flow. If you already have this process set up, skip to the next section. If your app doesn't currently implement a flow for requesting and managing location permissions, we've included a utility to help simplify this process.
The `LocationPermissionHelper` is designed to assist host apps with requesting, observing, and responding to user location permission changes. It leverages a `LocationPermissionDelegate` to notify the host app whenever the authorization status is updated. This simplifies the integration process without needing to build a full permission handling flow from scratch.
Here is an example that shows how to create a location permission helper:
```swift Swift theme={"theme":"github-dark-dimmed"}
// Create location permission helper.
let helper = FingerprintProFactory.getLocationPermissionHelperInstance()
helper.delegate = LocationPermissionDelegateImpl()
class LocationPermissionDelegateImpl: NSObject, LocationPermissionDelegate {
var authorizationStatus: CLAuthorizationStatus = .notDetermined
// Request permission or prompt to change settings.
func locationPermissionHelper(
_ helper: LocationPermissionHelperProviding,
shouldShowLocationRationale rationale: LocationPermissionRationale
) {
switch rationale {
case .needInitialPermission:
helper.requestPermission()
case .upgradeToPrecise:
helper.requestPermanentPrecisePermission()
case .needSettingsChange:
helper.requestPermanentPrecisePermission()
@unknown default:
os_log("Received unknown rationale")
}
}
// React to permission state changes.
func locationPermissionHelper(
_ helper: LocationPermissionHelperProviding,
didUpdateLocationPermission status: CLAuthorizationStatus
) {
authorizationStatus = status
}
}
/// Represents reasons for showing a location permission rationale to the user.
public enum LocationPermissionRationale {
/// The app has not yet requested any location permissions.
/// A rationale should be shown before requesting for the first time.
case needInitialPermission
/// The user has denied or restricted location access.
/// A rationale should be shown with instructions to change settings manually.
case needSettingsChange
/// The app has "When In Use" permission, but only reduced (approximate) accuracy is granted.
/// A rationale should be shown encouraging the user to enable full (precise) accuracy via settings
/// or give it temporarily.
case upgradeToPrecise
}
```
Make sure to declare location permissions in `Info.plist` as it is required for App Store submission:
* `NSLocationWhenInUseUsageDescription` - required; for general system request when asking for location access.
* `NSLocationTemporaryUsageDescriptionDictionary`with `ProximityID` purpose key - optional; for general system request when asking for temporary precise location in case the user has a setting that hides accurate location.
* `NSLocationAlwaysAndWhenInUseUsageDescription` - optional, only if your app requires checking location in background mode; for general system request when asking to always have access to the user's location (including background). It will also require `UIBackgroundModes` to have `location` in `Info.plist` to access the user's location in the background.
* Request permission in your app's code
* Call `requestPermission()` or `requestPrecisePermission()` from the SDK, depending on the level of access needed.
* Or use your own location permission flow.
* Show rationale UI (optional)
* You may present your own UI to explain why location is needed.
## Configuring the SDK
Using the [Configuration](/docs/ios-sdk#configuration) object, it is possible to configure the SDK as per your requirements. As of now, the following options are supported:
### `region`
**Type:** [Region](/docs/ios-sdk#region).
**Default value:** `Region.global`
Specifies the region where your data will be stored and processed. The selected region must match the one configured when registering your app with Fingerprint.\
For custom subdomain or proxy integrations, use the `Region.custom(domain:fallback:)` enumeration case. This allows you to define:
* `domain` - Your primary custom endpoint (e.g., custom subdomain or proxy URL) where requests will be sent.
* `fallback` *(optional)* - A list of alternate endpoints that will be used automatically if requests to the primary domain fail. This helps improve reliability and availability in case of connectivity or routing issues.
Use this option when you have configured a custom subdomain or implemented a proxy integration. See [region](/reference/js-agent-start-function#region) for more information.
### `extendedResponseFormat`
**Type:** [Bool](https://developer.apple.com/documentation/swift/bool)
**Default value:** `false`
When set to true, in addition to those [fields returned by default](/docs/ios-sdk#reading-the-response), the `FingerprintResponse` object will also contain the following fields:
* **`ipAddress`** - [String](https://developer.apple.com/documentation/swift/string). The IPv4 address of the device.
* **`ipLocation`** - [IPLocation](/docs/ios-sdk#iplocation). \[This field is **deprecated** and will not return a result for **applications created after January 23rd, 2024**. See [IP Geolocation](/docs/smart-signals-reference#ip-geolocation) for a replacement available in our Smart Signals product.] Indicates the location as deduced from the IP address.
* **`firstSeenAt`**, **`lastSeenAt`** - [Timestamp](/docs/ios-sdk#timestamp). See [Visitor Footprint Timestamps](/docs/useful-timestamps).
### `allowUseOfLocationData`
**Type:** [Bool](https://developer.apple.com/documentation/swift/bool)
**Default value:** `false`
This option allows you to enable the location data collection needed to calculate the [location-based proximity detection signal](/docs/smart-signals-reference#proximity-detection).
## Specifying `linkedId` and `tag`
Like the [JavaScript agent](/reference/js-agent-get-function#linkedid), the iOS SDK also supports providing custom metadata with your identification request. To learn more about this capability, please visit [Linking and tagging information](/docs/tagging-information).
Here is an example that shows you how to associate your identification request with an account ID and additional metadata:
```swift Swift theme={"theme":"github-dark-dimmed"}
let client = FingerprintProFactory.getInstance("PUBLIC_API_KEY")
// Associate an account number to this request.
var metadata = Metadata(linkedId: "accountID")
// Associate additional metadata to this request.
metadata.setTag("purchase", forKey: "actionType")
metadata.setTag(10, forKey: "purchaseCount")
Task {
// Get a `visitorId`.
if let visitorId = try? await client.getVisitorId(metadata) {
print(visitorId)
}
}
```
The metadata you want to include may not be available when making the identification request. For such scenarios, you can [update that event](/reference/server-api-update-event) later when the required metadata becomes available. A typical workflow is explained in [`Update linked_id and tags on the server`](/docs/tagging-information#updating-linked_id-and-tags-on-the-server).
## Handling errors
The SDK provides a [FPJSError](/docs/ios-sdk#error) enumeration that helps you identify the reasons behind an unsuccessful identification request. Here is an example that shows you how to handle errors in your app:
```swift Swift theme={"theme":"github-dark-dimmed"}
do {
let visitorId = try await client.getVisitorId()
print(visitorId)
} catch FPJSError.networkError(let error) {
print("Network error: \(error.localizedDescription)")
// Handle network error (e.g. check Internet connection and try again)
} catch {
print("Could not obtain visitor ID due to an error: \(error.localizedDescription)")
}
```
## Data Classes
### Configuration
```swift Swift theme={"theme":"github-dark-dimmed"}
/// A type that represents a single key/value parameter with the library integration
/// information.
public typealias IntegrationInfo = (String, String)
/// A library configuration specifying various options for configuring the Fingerprint Client
/// instance.
public struct Configuration: Sendable {
/// A public API key obtained from [Fingerprint Dashboard](https://dashboard.fingerprint.com).
public var apiKey: String
/// The Fingerprint Server API region.
public var region: Region
/// An array of key/value parameters identifying a particular library integration.
///
/// This property should only be used when creating an integration wrapper around the library
/// (e.g. Flutter or React Native wrapper).
public var integrationInfo: [IntegrationInfo]
/// A Boolean value that determines whether the backend should respond with an extended
/// result. See
/// [developer documentation](/docs/ios-sdk#extendedresponseformat
public var extendedResponseFormat: Bool
/// Creates a library configuration object with the specified parameters.
///
/// - Note: API keys are region-specific, so make sure you have selected the correct region
/// when calling this initializer.
///
/// - Parameters:
/// - apiKey: A public API key obtained from
/// [Fingerprint Dashboard](https://dashboard.fingerprint.com).
/// - region: The Fingerprint Server API region. Defaults to ``Region/global``.
/// - integrationInfo: An array of key/value parameters identifying a particular library
/// integration. Defaults to an empty array.
/// - extendedResponseFormat: A Boolean value that determines whether the backend should
/// respond with an extended result. Defaults to `false`.
public init(
apiKey: String,
region: Region = .global,
integrationInfo: [IntegrationInfo] = [],
extendedResponseFormat: Bool = false
) {
self.apiKey = apiKey
self.region = region
self.integrationInfo = integrationInfo
self.extendedResponseFormat = extendedResponseFormat
}
}
```
### Error
```swift Swift theme={"theme":"github-dark-dimmed"}
/// An error type that indicates problems with the device fingerprinting.
public enum FPJSError: Error {
/// An error that indicates the server URL is invalid.
case invalidURL
/// An error that indicates the integration info URL parameters are invalid.
case invalidURLParams
/// An API error returned by the Fingerprint backend.
case apiError(APIError)
/// A network error.
case networkError(Error)
/// A JSON parsing error that indicates the request body is malformed.
case jsonParsingError(Error)
/// An error that indicates the response is invalid.
case invalidResponseType
/// An error that indicates the client session timed out.
case clientTimeout
/// Unknown error.
case unknownError
}
/// A Fingerprint Server API error.
public struct APIError: Decodable, Sendable {
/// The API version.
public let version: String
/// The identifier that uniquely identifies a request.
public let requestId: String
/// The error details.
public let errorDetails: ErrorDetails?
}
public extension APIError {
/// The details about a reason that the API request failed.
struct ErrorDetails: Decodable {
/// The error code, as defined by ``APIErrorType`` enum.
public let code: APIError.Code?
/// A detailed error message.
public let message: String
}
/// An enumeration representing known Fingerprint API error codes.
enum Code: String, Decodable, Sendable {
/// The API token is missing.
case tokenRequired = "TokenRequired"
/// Invalid API token.
case tokenNotFound = "TokenNotFound"
/// The API token expired.
case tokenExpired = "TokenExpired"
/// Malformed request body or parameters.
case requestCannotBeParsed = "RequestCannotBeParsed"
/// Request failed.
case failed = "Failed"
/// A server connection timeout.
case requestTimeout = "RequestTimeout"
/// Request rate limit exceeded.
case tooManyRequests = "TooManyRequests"
/// The API key does not match a selected region.
case wrongRegion = "WrongRegion"
/// Workspace is not active for the provided API key.
case subscriptionNotActive = "SubsriptionNotActive"
/// This app is not authorized to make identification requests.
case packageNotAuthorized = "PackageNotAuthorized"
case originNotAvailable = "OriginNotAvailable"
case headerRestricted = "HeaderRestricted"
case notAvailableForCrawlBots = "NotAvailableForCrawlBots"
case notAvailableWithoutUA = "NotAvailableWithoutUA"
case unsupportedVersion = "UnsupportedVersion"
case installationMethodRestricted = "InstallationMethodRestricted"
case hostnameRestricted = "HostnameRestricted"
case invalidProxyIntegrationSecret = "InvalidProxyIntegrationSecret"
case invalidProxyIntegrationHeaders = "InvalidProxyIntegrationHeaders"
case proxyIntegrationSecretEnvironmentMismatch = "ProxyIntegrationSecretEnvironmentMismatch”
}
}
```
### IPLocation
```swift Swift theme={"theme":"github-dark-dimmed"}
/// The IP address information.
public struct IPLocation: Equatable, Codable, Sendable {
/// The city component of the IP address.
public let city: IPGeoInfo?
/// The country component of the IP address.
public let country: IPGeoInfo?
/// The continent component of the IP address.
public let continent: IPGeoInfo?
/// The longitude in degrees.
public let longitude: Float?
/// The latitude in degrees.
public let latitude: Float?
/// The postal code of the IP address.
public let postalCode: String?
/// The time zone of the IP address.
public let timezone: String?
/// The approximate accuracy radius in kilometers around the IP address location.
public let accuracyRadius: UInt?
/// The subdivisions (such as a county or other region) associated with the IP address.
public let subdivisions: [IPLocationSubdivision]?
}
/// A structure containing the location name and code.
/// It can represent a country, city or continent.
public struct IPGeoInfo: Equatable, Codable, Sendable {
/// The location name.
public let name: String
/// The area, country or continent code.
public let code: String?
}
/// A structure describing the location's subdivision.
public struct IPLocationSubdivision: Equatable, Codable, Sendable {
/// The ISO code.
let isoCode: String
/// The subdivision name.
let name: String
}
```
### Region
```swift Swift theme={"theme":"github-dark-dimmed"}
/// The Fingerprint Server API region.
public enum Region: Equatable, Sendable {
/// A default Global (US) region.
case global
/// A European (EU) region.
case eu
/// An Asia-Pacific (APAC) region.
case ap
/// A custom endpoint with optional fallback endpoints, as defined by the `domain` and `fallback`
/// associated values.
case custom(domain: String, fallback: [String] = [])
}
```
### Timestamp
```swift theme={"theme":"github-dark-dimmed"}
/// A data structure representing the timestamp.
public struct SeenAt: Equatable, Codable, Sendable {
/// The timestamp associated with the Fingerprint Server API region.
public let global: Date?
/// The timestamp associated with an active Fingerprint application.
public let subscription: Date?
}
```
# Java Server Quickstart
Source: https://docs.fingerprint.com/docs/java-server-quickstart
Get started using the Java Server SDK
## Overview
In this quickstart, you'll add Fingerprint to a [Java](https://www.oracle.com/java/) server using the [Spring Boot](https://spring.io/projects/spring-boot) framework to prevent fraudulent account creation.
The example use case in this quickstart is stopping new account fraud, where attackers create multiple fake accounts to abuse promotions, exploit systems, or evade bans. However, the steps you'll follow apply to most use cases. You can flag and block suspicious users by identifying the device behind each sign-up attempt, login, or transaction.
In this quickstart, you'll learn how to:
* Set up a Spring Boot server with a [Fingerprint integration](https://github.com/fingerprintjs/java-sdk)
* Retrieve visitor identification data using the Server API
* Block bots and suspicious devices
* Prevent multiple signups from the same device
This guide focuses on the backend integration and must be completed after identifying a visitor and generating a request ID. **Before starting this quickstart, start with one of the [frontend](/docs/web-quickstarts-overview) or [mobile](/docs/mobile-quickstarts-overview) quickstarts to see how to identify a visitor in your frontend.**
> Estimated time: \< 10 minutes
## Prerequisites
Before you begin, make sure you have the following:
* **A completed frontend or mobile Fingerprint implementation (See the quickstarts)**
* [Java](https://www.oracle.com/java/) (v11, v17, v21, or v25) installed
* Your favorite code editor or IDE
* Basic knowledge of Java and Spring Boot
## 1. Get your secret API key
Before starting this quickstart, you should already have a frontend Fingerprint implementation
that sends the `requestId` to your server. If not, pause here and check out one of the
[frontend](/docs/web-quickstarts-overview) or [mobile](/docs/mobile-quickstarts-overview)
quickstarts first.
If you're ready:
1. Sign in and go to the [**API keys**](https://dashboard.fingerprint.com/api-keys) page in the Fingerprint dashboard.
2. Create a new **secret API key**.
3. Copy it somewhere safe so you can use it to retrieve full visitor identification data from the Server API.
## 2. Set up your project
To get started, you need a basic Spring Boot server. If you already have a project, skip to the next step.
1. Go to [**start.spring.io**](https://start.spring.io/)
2. Configure the project:
* **Project:** Maven (or Gradle if you prefer)
* **Language:** Java
* **Spring Boot:** 3.x latest version
* **Dependencies:**
* Spring Web
* Spring Data JPA
* *Everything else can remain as default*
3. Click **Generate** to download the starter project and unzip it.
4. Open the project in your IDE and add these dependencies to your build file:
**Maven (pom.xml):**
```xml pom.xml theme={"theme":"github-dark-dimmed"}
jitpack.iohttps://jitpack.ioorg.xerialsqlite-jdbc3.44.1.0org.hibernate.ormhibernate-community-dialectscom.github.fingerprintjsjava-sdkv8.4.0
```
**Gradle (build.gradle):**
```groovy build.gradle theme={"theme":"github-dark-dimmed"}
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'org.xerial:sqlite-jdbc:3.44.1.0'
implementation 'org.hibernate.orm:hibernate-community-dialects'
implementation 'com.github.fingerprintjs:java-sdk:v8.4.0'
}
```
*Note: This quickstart is written for version 8.x of the Fingerprint Java Server SDK. Additional installation methods are detailed in the [SDK documentation](/reference/java-server-sdk).*
You can run the project with:
* Maven:
```bash Terminal theme={"theme":"github-dark-dimmed"}
./mvnw spring-boot:run
```
* Gradle:
```bash Terminal theme={"theme":"github-dark-dimmed"}
./gradlew bootRun
```
5. Update the file at `src/main/resources/application.properties` with the following content:
```properties application.properties theme={"theme":"github-dark-dimmed"}
spring.application.name=demo
server.port=3000
spring.datasource.url=jdbc:sqlite:database.db
spring.datasource.driver-class-name=org.sqlite.JDBC
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
spring.jpa.hibernate.ddl-auto=create-drop
```
*Note: `spring.jpa.hibernate.ddl-auto=create-drop` is for demo purposes to reset the database each time the app starts.*
6. Create a basic controller `src/main/java/com/example/demo/AccountController.java`:
```java AccountController.java theme={"theme":"github-dark-dimmed"}
package com.example.demo;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class AccountController {
@PostMapping("/create-account")
public ResponseEntity