# Migrate from FingerprintJS Pro

> Move a FingerprintJS Pro integration to Fingerly: map get() to identify(), Smart Signals to signal groups, and their webhooks to Fingerly webhooks, without downtime.

Last updated: 2026-09-17

The two products share a shape: a client agent identifies the visitor and returns a request ID, and your server reads the result with a secret key. Most of a migration is renaming. This guide maps each part, then walks through switching over without a gap in protection.

> **Note:** FingerprintJS Pro (now Fingerprint) names below are those of its JavaScript agent v3, its React library and its Server API v3. If you use a newer version, the names may differ; the concepts map the same way. Fingerly is not affiliated with Fingerprint.

## At a glance

| Concept | FingerprintJS Pro | Fingerly |
| --- | --- | --- |
| Load the agent | `FingerprintJS.load({ apiKey })` | `load({ apiKey })` |
| Identify | `fp.get()` | `fingerly.identify()` |
| Your reference | `linkedId` and `tag` | One `tag` string |
| Visitor | `visitorId` | `visitorId`, scoped to your organization |
| Match confidence | `confidence.score`, 0 to 1 | `visitorConfidence`, 0 to 100 |
| Risk | Smart Signals, Suspect Score | Signal groups, `suspect_score` and `suspect_level` |
| Read one result | Server API `GET /events/{request_id}` | [`GET /api/v1/events/{request_id}`](https://docs.fingerly.io/reference/get-event) |
| Visitor history | Server API `GET /visitors/{visitor_id}` | [`GET /api/v1/events?visitor=…`](https://docs.fingerly.io/reference/list-events), 30 days |
| Webhooks | One per identification, with Smart Signals | [`identification.completed`](https://docs.fingerly.io/reference/webhooks/identification-completed), and [`visitor.suspect`](https://docs.fingerly.io/reference/webhooks/visitor-suspect) with signals |
| Region | `region` load option | Part of the key |
| First-party requests | Custom subdomain or cloud proxy integrations | [Proxy integrations](https://docs.fingerly.io/docs/proxy-integrations) |

## Plan the switch

### Step 1: Create Fingerly keys

Create a public key with your site's origins and a secret key, first in **development**, then in **production**. Unlike FingerprintJS Pro's optional request filtering, allowed origins are required: a public key with none refuses every browser request. See [API keys](https://docs.fingerly.io/docs/api-keys).

### Step 2: Run both agents side by side

Call Fingerly next to FingerprintJS Pro at the same moments and send both request IDs to your server. Keep deciding with FingerprintJS Pro, and log what Fingerly would have decided.

### Step 3: Move server checks

When the two agree on the sessions you care about, switch your decisions to the Fingerly event. Keep reading FingerprintJS Pro results for a few days as a fallback.

### Step 4: Move webhooks

Register Fingerly endpoints, verify their signatures, and point your consumers at them.

### Step 5: Remove the old agent

Delete the FingerprintJS Pro agent, its proxy routes and its keys.

> **Warning:** Visitor IDs do not carry over. Fingerly issues its own, so a device you knew under a FingerprintJS Pro visitor ID starts as a new visitor. Store both IDs while you run side by side, and expect more "new device" decisions for a while. See [identities do not carry over](#identities-do-not-carry-over).

## Replace the JavaScript agent

```ts FingerprintJS Pro
import * as FingerprintJS from '@fingerprintjs/fingerprintjs-pro'

const fp = await FingerprintJS.load({
  apiKey: '<public api key>',
  region: 'eu',
  endpoint: ['https://metrics.example.com', FingerprintJS.defaultEndpoint],
})

const { requestId, visitorId } = await fp.get({ linkedId: user.id, tag: { action: 'login' } })
```

```ts Fingerly
import { load } from '@fingerly/web-js'

const fingerly = await load({
  apiKey: 'fly_pk_us_production_…',
  endpoints: '/metrics',
  fallbackToDefaultEndpoint: true,
})

const { requestId, visitorId } = await fingerly.identify({ tag: 'login' })
```

### Load options

| FingerprintJS Pro | Fingerly | Notes |
| --- | --- | --- |
| `apiKey` | `apiKey` | A Fingerly public key, `fly_pk_…`. |
| `region` | None | The region is part of the key, and the SDK routes by it. |
| `endpoint` | `endpoints` | Paths or URLs for a [proxy](https://docs.fingerly.io/docs/proxy-integrations). `/api/v1/identify` is appended. |
| `FingerprintJS.defaultEndpoint` in the list | `fallbackToDefaultEndpoint: true` | Try the regional API after your own endpoints fail. |
| `scriptUrlPattern` | None | The SDK is bundled into your application. Nothing is downloaded at runtime, so there is no script to proxy. |

### get() options and result

| FingerprintJS Pro | Fingerly | Notes |
| --- | --- | --- |
| `linkedId` | Part of `tag` | Fingerly has one reference field. Put the account or order in it, such as `login` or `checkout:8412`. |
| `tag` (any JSON) | `tag` (a string) | Encode what you need as a string. Your server compares it before trusting the result. |
| `extendedResult` | None | Network and device details are on the stored event your server reads. |
| `timeout` | `signal` | Pass an `AbortSignal`. Collection is already capped at 300 ms, and each attempt at 5 seconds. |
| `requestId` | `requestId` | Send it to your server with the action. |
| `visitorId` | `visitorId` | Twenty letters and digits, scoped to your organization. |
| `confidence.score` | `visitorConfidence` | 0 to 100 rather than 0 to 1. See [visitor confidence](https://docs.fingerly.io/docs/visitor-identification#visitor-confidence). |
| `visitorFound` | `visitorIsNew` | The opposite sense. |
| `sealedResult` | None | Read the event on your server with a secret key instead. |

Fingerly also returns the score, the level and the signals in the same response, and local [verdicts](https://docs.fingerly.io/docs/client-verdicts) computed in the browser. Use them to adapt the interface; decide on your server.

## Replace the React library

```tsx FingerprintJS Pro
import { FpjsProvider, useVisitorData } from '@fingerprintjs/fingerprintjs-pro-react'

<FpjsProvider loadOptions={{ apiKey: '<public api key>' }}>
  <App />
</FpjsProvider>

const { getData, isLoading } = useVisitorData({ extendedResult: true }, { immediate: false })
const { requestId } = await getData({ ignoreCache: true })
```

```tsx Fingerly
import { FingerlyProvider, useIdentify } from '@fingerly/react'

<FingerlyProvider apiKey="fly_pk_us_production_…">
  <App />
</FingerlyProvider>

const { identify, isLoading } = useIdentify({ tag: 'login' })
const { requestId } = await identify({ force: true })
```

`useIdentify({ immediate: true })` replaces `immediate: true`, and `refresh()` replaces `getData({ ignoreCache: true })`. Vue, Svelte, Angular and Next.js have the same shape: see [the SDKs](https://docs.fingerly.io/docs/sdks).

### Mobile

On iOS and Android, replace the FingerprintJS Pro client and its visitor ID calls with `Fingerly.load(apiKey:)` and `identify(tag:)`. The result has the same fields as on the web. See [iOS](https://docs.fingerly.io/docs/sdks/ios) and [Android](https://docs.fingerly.io/docs/sdks/android), or [React Native](https://docs.fingerly.io/docs/sdks/react-native) and [Flutter](https://docs.fingerly.io/docs/sdks/flutter).

## Replace server reads

```ts FingerprintJS Pro
import { FingerprintJsServerApiClient, Region } from '@fingerprintjs/fingerprintjs-pro-server-api'

const client = new FingerprintJsServerApiClient({ apiKey: process.env.FP_SECRET_API_KEY!, region: Region.EU })

const event = await client.getEvent(requestId)
const { visitorId, linkedId, timestamp } = event.products.identification.data
const isBot = event.products.botd?.data?.bot.result === 'bad'
const usesVpn = event.products.vpn?.data?.result === true
```

```ts Fingerly
import { load } from '@fingerly/node'

const fingerly = load({ secretKey: process.env.FINGERLY_SECRET_KEY! })
const fired = (event: { triggers: Array<{ signal: string }> }, group: string) =>
  event.triggers.some((trigger) => trigger.signal === group)

const event = await fingerly.events.get(requestId)
const { visitor_id, tag, occurred_at } = event
const isBot = fired(event, 'bot')
const usesVpn = fired(event, 'vpn')
```

| FingerprintJS Pro | Fingerly event |
| --- | --- |
| `products.identification.data.visitorId` | `visitor_id` |
| `products.identification.data.requestId` | `request_id` |
| `products.identification.data.linkedId`, `tag` | `tag` |
| `products.identification.data.timestamp` (milliseconds) | `occurred_at` (RFC 3339) |
| `products.identification.data.ip` | `ip_address` |
| `ipLocation` country | `country_code`, `country_name` |
| `products.ipInfo` network | `asn`, `asn_name`, `anonymity_network` |
| `browserDetails` | `agent`, `agent_version`, `os`, `device_kind` |
| `products.suspectScore.data.result` | `suspect_score`, with `suspect_level` from your threshold |
| Each Smart Signal's `result` | A group in `triggers`, with its `weight` and `confidence` |

An event lists only the signal groups that fired. A group that is absent did not fire. Server libraries for [Python, Go, Java, .NET, PHP, Ruby and Rust](https://docs.fingerly.io/docs/sdks#server) make the same calls.

## Smart Signals and signal groups

| Smart Signal | Fingerly group | Notes |
| --- | --- | --- |
| Bot detection, `bad` | `bot` | Automation, and clients that say they are bots. |
| Bot detection, `good` | None | Read `visitor_kind`: `search_crawler` or `ai_bot`. |
| VPN detection | `vpn` | Weighed by how it was recognised or by confidence. See [weighting modes](https://docs.fingerly.io/docs/risk-weights#weighting-modes). |
| Tor | `tor` |  |
| Proxy detection | `datacenter_proxy`, `residential_proxy` | Two groups, weighted separately. |
| IP blocklist | `ip_blocklist` |  |
| Incognito mode | `incognito_mode` |  |
| Browser tampering | `browser_tampering` |  |
| Virtual machine | `virtual_machine` |  |
| Privacy-focused settings | `privacy_settings` | Also `fingerprint_suppressed` when too little can be read to identify anyone. |
| Developer tools | `developer_tools` |  |
| High-activity device | `high_activity` |  |
| Geolocation spoofing | `location_spoofing` |  |
| Remote control tools | `remote_control` | Browsers only. |
| Root apps (Android) | `rooted_device` |  |
| Emulator (Android) | `android_emulator` | The iOS Simulator is `ios_simulator`. |
| Cloned app (Android) | `cloned_app` |  |
| Jailbroken device (iOS) | `jailbroken_device` |  |
| Frida detection | `frida_detected` |  |
| MITM attack | `mitm_attack` |  |
| Factory reset | `device_farm` | A signal that the device looks freshly reset or mass-provisioned, not a reset timestamp. |
| Velocity signals | None | Count per `visitor_id` in your own store. `high_activity` covers devices seen far more often than usual. |
| Raw device attributes | None | Fingerly does not expose what it collects from a device. |

Every group has a weight you can change, per organization or per key, and the score is their sum. See [signals](https://docs.fingerly.io/docs/signals) and [risk weights](https://docs.fingerly.io/docs/risk-weights).

## Replace webhooks

|  | FingerprintJS Pro | Fingerly |
| --- | --- | --- |
| What is sent | One request per identification, with Smart Signals | `identification.completed` per identification; `visitor.suspect` with every signal when the level is `high` |
| Signature header | `FPJS-Event-Signature: v1=…` | `X-Fingerly-Signature: sha256=…`, with `X-Fingerly-Timestamp` |
| Signed content | The body | The timestamp, a full stop, and the body |
| Replay protection | None in the signature | Reject timestamps more than five minutes old |
| Deduplication | By `requestId` | By the envelope `id`, also in `X-Fingerly-Event-ID` |

```ts FingerprintJS Pro
import { isValidWebhookSignature } from '@fingerprintjs/fingerprintjs-pro-server-api'

const valid = isValidWebhookSignature({
  header: req.get('fpjs-event-signature'),
  data: rawBody,
  secret: process.env.FP_WEBHOOK_SECRET!,
})
const visit = JSON.parse(rawBody.toString())
```

```ts Fingerly
import { verifyWebhook } from '@fingerly/node'

const valid = verifyWebhook({
  secret: process.env.FINGERLY_WEBHOOK_SECRET!,
  payload: rawBody,
  timestamp: req.get('x-fingerly-timestamp'),
  signature: req.get('x-fingerly-signature'),
})
const { type, data } = JSON.parse(rawBody.toString())
```

If your consumer needs every signal for every identification, read the event by `data.request_id` when `identification.completed` arrives. See [webhooks](https://docs.fingerly.io/docs/webhooks).

## Proxy integrations

FingerprintJS Pro's proxy integrations forward both the agent download and its requests. Fingerly's SDK is part of your bundle, so a Fingerly proxy only forwards two API routes. Use the [Cloudflare Worker](https://docs.fingerly.io/docs/sdks/cloudflare-worker), [`createProxy`](https://docs.fingerly.io/docs/sdks/node#serve-the-browser-sdk-from-your-domain) for Node.js runtimes, or [your own proxy](https://docs.fingerly.io/docs/proxy-integrations#build-your-own-proxy). Choose a new path rather than reusing the old one, so both can run during the switch.

## Identities do not carry over

- Store the Fingerly `visitor_id` next to the FingerprintJS Pro one on your accounts, orders and devices while both run.
- Rules such as "known device for this account" start empty. Loosen new-device friction during the switch, or keep consulting the old IDs until customers have returned once.
- Counters per visitor, such as sign-ups per device, restart from zero. Keep old limits running on the old IDs until the window they cover has passed.

## Other differences

- **Environments.** Keys belong to development, staging or production. Development and staging run the same detection and are free. See [environments](https://docs.fingerly.io/docs/api-keys#environments).
- **Pricing.** Prepaid, $0.003 per production identification, with no plans or minimums. See [billing](https://docs.fingerly.io/docs/billing).
- **Retention.** Events are readable for 30 days. Keep your own copy for longer. See [data retention](https://docs.fingerly.io/docs/data-retention).
- **Regions.** The United States region is available; the European Union region is coming soon. See [regions](https://docs.fingerly.io/docs/regions).
- **Consent.** Every SDK takes a `consent` option and collects nothing until it is `granted`. See [consent tools](https://docs.fingerly.io/docs/consent-tools).

> **Tip:** Stuck on something this page does not map? Email [support@fingerly.io](mailto:support@fingerly.io) with the FingerprintJS Pro feature you use.

- [Quick start](https://docs.fingerly.io/docs/quick-start): The Fingerly flow end to end.
- [Use cases](https://docs.fingerly.io/docs/use-cases): Recipes for the policies you are moving.
