[{"data":1,"prerenderedAt":430},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fmigrate-from-fingerprintjs":3},{"page":4,"toc":405,"updated":429},{"path":5,"title":6,"seoTitle":7,"description":8,"blocks":9},"\u002Fdocs\u002Fmigrate-from-fingerprintjs","Migrate from FingerprintJS Pro","Migrate from FingerprintJS Pro to Fingerly","Move a FingerprintJS Pro integration to Fingerly: map get() to identify(), Smart Signals to signal groups, and their webhooks to Fingerly webhooks, without downtime.",[10,13,17,22,73,76,104,107,110,118,122,144,147,181,183,186,193,195,198,200,203,209,243,245,248,330,332,335,358,364,366,369,371,374,380,383,390,393],{"type":11,"text":12},"p","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.",{"type":14,"tone":15,"text":16},"callout","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.",{"type":18,"level":19,"text":20,"id":21},"heading",2,"At a glance","at-a-glance",{"type":23,"columns":24,"rows":28},"table",[25,26,27],"Concept","FingerprintJS Pro","Fingerly",[29,33,37,41,45,49,53,57,61,65,69],[30,31,32],"Load the agent","`FingerprintJS.load({ apiKey })`","`load({ apiKey })`",[34,35,36],"Identify","`fp.get()`","`fingerly.identify()`",[38,39,40],"Your reference","`linkedId` and `tag`","One `tag` string",[42,43,44],"Visitor","`visitorId`","`visitorId`, scoped to your organization",[46,47,48],"Match confidence","`confidence.score`, 0 to 1","`visitorConfidence`, 0 to 100",[50,51,52],"Risk","Smart Signals, Suspect Score","Signal groups, `suspect_score` and `suspect_level`",[54,55,56],"Read one result","Server API `GET \u002Fevents\u002F{request_id}`","[`GET \u002Fapi\u002Fv1\u002Fevents\u002F{request_id}`](\u002Freference\u002Fget-event)",[58,59,60],"Visitor history","Server API `GET \u002Fvisitors\u002F{visitor_id}`","[`GET \u002Fapi\u002Fv1\u002Fevents?visitor=…`](\u002Freference\u002Flist-events), 30 days",[62,63,64],"Webhooks","One per identification, with Smart Signals","[`identification.completed`](\u002Freference\u002Fwebhooks\u002Fidentification-completed), and [`visitor.suspect`](\u002Freference\u002Fwebhooks\u002Fvisitor-suspect) with signals",[66,67,68],"Region","`region` load option","Part of the key",[70,71,72],"First-party requests","Custom subdomain or cloud proxy integrations","[Proxy integrations](\u002Fdocs\u002Fproxy-integrations)",{"type":18,"level":19,"text":74,"id":75},"Plan the switch","plan-the-switch",{"type":77,"steps":78},"steps",[79,84,89,94,99],{"title":80,"blocks":81},"Create Fingerly keys",[82],{"type":11,"text":83},"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](\u002Fdocs\u002Fapi-keys).",{"title":85,"blocks":86},"Run both agents side by side",[87],{"type":11,"text":88},"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.",{"title":90,"blocks":91},"Move server checks",[92],{"type":11,"text":93},"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.",{"title":95,"blocks":96},"Move webhooks",[97],{"type":11,"text":98},"Register Fingerly endpoints, verify their signatures, and point your consumers at them.",{"title":100,"blocks":101},"Remove the old agent",[102],{"type":11,"text":103},"Delete the FingerprintJS Pro agent, its proxy routes and its keys.",{"type":14,"tone":105,"text":106},"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).",{"type":18,"level":19,"text":108,"id":109},"Replace the JavaScript agent","replace-the-javascript-agent",{"type":111,"samples":112},"code",[113,116],{"label":26,"lang":114,"code":115},"ts","import * as FingerprintJS from '@fingerprintjs\u002Ffingerprintjs-pro'\n\nconst fp = await FingerprintJS.load({\n  apiKey: '\u003Cpublic api key>',\n  region: 'eu',\n  endpoint: ['https:\u002F\u002Fmetrics.example.com', FingerprintJS.defaultEndpoint],\n})\n\nconst { requestId, visitorId } = await fp.get({ linkedId: user.id, tag: { action: 'login' } })",{"label":27,"lang":114,"code":117},"import { load } from '@fingerly\u002Fweb-js'\n\nconst fingerly = await load({\n  apiKey: 'fly_pk_us_production_…',\n  endpoints: '\u002Fmetrics',\n  fallbackToDefaultEndpoint: true,\n})\n\nconst { requestId, visitorId } = await fingerly.identify({ tag: 'login' })",{"type":18,"level":119,"text":120,"id":121},3,"Load options","load-options",{"type":23,"columns":123,"rows":125},[26,27,124],"Notes",[126,129,133,137,141],[127,127,128],"`apiKey`","A Fingerly public key, `fly_pk_…`.",[130,131,132],"`region`","None","The region is part of the key, and the SDK routes by it.",[134,135,136],"`endpoint`","`endpoints`","Paths or URLs for a [proxy](\u002Fdocs\u002Fproxy-integrations). `\u002Fapi\u002Fv1\u002Fidentify` is appended.",[138,139,140],"`FingerprintJS.defaultEndpoint` in the list","`fallbackToDefaultEndpoint: true`","Try the regional API after your own endpoints fail.",[142,131,143],"`scriptUrlPattern`","The SDK is bundled into your application. Nothing is downloaded at runtime, so there is no script to proxy.",{"type":18,"level":119,"text":145,"id":146},"get() options and result","get-options-and-result",{"type":23,"columns":148,"rows":149},[26,27,124],[150,154,158,161,165,168,170,174,178],[151,152,153],"`linkedId`","Part of `tag`","Fingerly has one reference field. Put the account or order in it, such as `login` or `checkout:8412`.",[155,156,157],"`tag` (any JSON)","`tag` (a string)","Encode what you need as a string. Your server compares it before trusting the result.",[159,131,160],"`extendedResult`","Network and device details are on the stored event your server reads.",[162,163,164],"`timeout`","`signal`","Pass an `AbortSignal`. Collection is already capped at 300 ms, and each attempt at 5 seconds.",[166,166,167],"`requestId`","Send it to your server with the action.",[43,43,169],"Twenty letters and digits, scoped to your organization.",[171,172,173],"`confidence.score`","`visitorConfidence`","0 to 100 rather than 0 to 1. See [visitor confidence](\u002Fdocs\u002Fvisitor-identification#visitor-confidence).",[175,176,177],"`visitorFound`","`visitorIsNew`","The opposite sense.",[179,131,180],"`sealedResult`","Read the event on your server with a secret key instead.",{"type":11,"text":182},"Fingerly also returns the score, the level and the signals in the same response, and local [verdicts](\u002Fdocs\u002Fclient-verdicts) computed in the browser. Use them to adapt the interface; decide on your server.",{"type":18,"level":19,"text":184,"id":185},"Replace the React library","replace-the-react-library",{"type":111,"samples":187},[188,191],{"label":26,"lang":189,"code":190},"tsx","import { FpjsProvider, useVisitorData } from '@fingerprintjs\u002Ffingerprintjs-pro-react'\n\n\u003CFpjsProvider loadOptions={{ apiKey: '\u003Cpublic api key>' }}>\n  \u003CApp \u002F>\n\u003C\u002FFpjsProvider>\n\nconst { getData, isLoading } = useVisitorData({ extendedResult: true }, { immediate: false })\nconst { requestId } = await getData({ ignoreCache: true })",{"label":27,"lang":189,"code":192},"import { FingerlyProvider, useIdentify } from '@fingerly\u002Freact'\n\n\u003CFingerlyProvider apiKey=\"fly_pk_us_production_…\">\n  \u003CApp \u002F>\n\u003C\u002FFingerlyProvider>\n\nconst { identify, isLoading } = useIdentify({ tag: 'login' })\nconst { requestId } = await identify({ force: true })",{"type":11,"text":194},"`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](\u002Fdocs\u002Fsdks).",{"type":18,"level":119,"text":196,"id":197},"Mobile","mobile",{"type":11,"text":199},"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](\u002Fdocs\u002Fsdks\u002Fios) and [Android](\u002Fdocs\u002Fsdks\u002Fandroid), or [React Native](\u002Fdocs\u002Fsdks\u002Freact-native) and [Flutter](\u002Fdocs\u002Fsdks\u002Fflutter).",{"type":18,"level":19,"text":201,"id":202},"Replace server reads","replace-server-reads",{"type":111,"samples":204},[205,207],{"label":26,"lang":114,"code":206},"import { FingerprintJsServerApiClient, Region } from '@fingerprintjs\u002Ffingerprintjs-pro-server-api'\n\nconst client = new FingerprintJsServerApiClient({ apiKey: process.env.FP_SECRET_API_KEY!, region: Region.EU })\n\nconst event = await client.getEvent(requestId)\nconst { visitorId, linkedId, timestamp } = event.products.identification.data\nconst isBot = event.products.botd?.data?.bot.result === 'bad'\nconst usesVpn = event.products.vpn?.data?.result === true",{"label":27,"lang":114,"code":208},"import { load } from '@fingerly\u002Fnode'\n\nconst fingerly = load({ secretKey: process.env.FINGERLY_SECRET_KEY! })\nconst fired = (event: { triggers: Array\u003C{ signal: string }> }, group: string) =>\n  event.triggers.some((trigger) => trigger.signal === group)\n\nconst event = await fingerly.events.get(requestId)\nconst { visitor_id, tag, occurred_at } = event\nconst isBot = fired(event, 'bot')\nconst usesVpn = fired(event, 'vpn')",{"type":23,"columns":210,"rows":212},[26,211],"Fingerly event",[213,216,219,222,225,228,231,234,237,240],[214,215],"`products.identification.data.visitorId`","`visitor_id`",[217,218],"`products.identification.data.requestId`","`request_id`",[220,221],"`products.identification.data.linkedId`, `tag`","`tag`",[223,224],"`products.identification.data.timestamp` (milliseconds)","`occurred_at` (RFC 3339)",[226,227],"`products.identification.data.ip`","`ip_address`",[229,230],"`ipLocation` country","`country_code`, `country_name`",[232,233],"`products.ipInfo` network","`asn`, `asn_name`, `anonymity_network`",[235,236],"`browserDetails`","`agent`, `agent_version`, `os`, `device_kind`",[238,239],"`products.suspectScore.data.result`","`suspect_score`, with `suspect_level` from your threshold",[241,242],"Each Smart Signal's `result`","A group in `triggers`, with its `weight` and `confidence`",{"type":11,"text":244},"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](\u002Fdocs\u002Fsdks#server) make the same calls.",{"type":18,"level":19,"text":246,"id":247},"Smart Signals and signal groups","smart-signals-and-signal-groups",{"type":23,"columns":249,"rows":252},[250,251,124],"Smart Signal","Fingerly group",[253,257,260,264,268,272,275,278,281,284,288,291,294,297,301,304,308,311,314,317,320,324,327],[254,255,256],"Bot detection, `bad`","`bot`","Automation, and clients that say they are bots.",[258,131,259],"Bot detection, `good`","Read `visitor_kind`: `search_crawler` or `ai_bot`.",[261,262,263],"VPN detection","`vpn`","Weighed by how it was recognised or by confidence. See [weighting modes](\u002Fdocs\u002Frisk-weights#weighting-modes).",[265,266,267],"Tor","`tor`","",[269,270,271],"Proxy detection","`datacenter_proxy`, `residential_proxy`","Two groups, weighted separately.",[273,274,267],"IP blocklist","`ip_blocklist`",[276,277,267],"Incognito mode","`incognito_mode`",[279,280,267],"Browser tampering","`browser_tampering`",[282,283,267],"Virtual machine","`virtual_machine`",[285,286,287],"Privacy-focused settings","`privacy_settings`","Also `fingerprint_suppressed` when too little can be read to identify anyone.",[289,290,267],"Developer tools","`developer_tools`",[292,293,267],"High-activity device","`high_activity`",[295,296,267],"Geolocation spoofing","`location_spoofing`",[298,299,300],"Remote control tools","`remote_control`","Browsers only.",[302,303,267],"Root apps (Android)","`rooted_device`",[305,306,307],"Emulator (Android)","`android_emulator`","The iOS Simulator is `ios_simulator`.",[309,310,267],"Cloned app (Android)","`cloned_app`",[312,313,267],"Jailbroken device (iOS)","`jailbroken_device`",[315,316,267],"Frida detection","`frida_detected`",[318,319,267],"MITM attack","`mitm_attack`",[321,322,323],"Factory reset","`device_farm`","A signal that the device looks freshly reset or mass-provisioned, not a reset timestamp.",[325,131,326],"Velocity signals","Count per `visitor_id` in your own store. `high_activity` covers devices seen far more often than usual.",[328,131,329],"Raw device attributes","Fingerly does not expose what it collects from a device.",{"type":11,"text":331},"Every group has a weight you can change, per organization or per key, and the score is their sum. See [signals](\u002Fdocs\u002Fsignals) and [risk weights](\u002Fdocs\u002Frisk-weights).",{"type":18,"level":19,"text":333,"id":334},"Replace webhooks","replace-webhooks",{"type":23,"columns":336,"rows":337},[267,26,27],[338,342,346,350,354],[339,340,341],"What is sent","One request per identification, with Smart Signals","`identification.completed` per identification; `visitor.suspect` with every signal when the level is `high`",[343,344,345],"Signature header","`FPJS-Event-Signature: v1=…`","`X-Fingerly-Signature: sha256=…`, with `X-Fingerly-Timestamp`",[347,348,349],"Signed content","The body","The timestamp, a full stop, and the body",[351,352,353],"Replay protection","None in the signature","Reject timestamps more than five minutes old",[355,356,357],"Deduplication","By `requestId`","By the envelope `id`, also in `X-Fingerly-Event-ID`",{"type":111,"samples":359},[360,362],{"label":26,"lang":114,"code":361},"import { isValidWebhookSignature } from '@fingerprintjs\u002Ffingerprintjs-pro-server-api'\n\nconst valid = isValidWebhookSignature({\n  header: req.get('fpjs-event-signature'),\n  data: rawBody,\n  secret: process.env.FP_WEBHOOK_SECRET!,\n})\nconst visit = JSON.parse(rawBody.toString())",{"label":27,"lang":114,"code":363},"import { verifyWebhook } from '@fingerly\u002Fnode'\n\nconst valid = verifyWebhook({\n  secret: process.env.FINGERLY_WEBHOOK_SECRET!,\n  payload: rawBody,\n  timestamp: req.get('x-fingerly-timestamp'),\n  signature: req.get('x-fingerly-signature'),\n})\nconst { type, data } = JSON.parse(rawBody.toString())",{"type":11,"text":365},"If your consumer needs every signal for every identification, read the event by `data.request_id` when `identification.completed` arrives. See [webhooks](\u002Fdocs\u002Fwebhooks).",{"type":18,"level":19,"text":367,"id":368},"Proxy integrations","proxy-integrations",{"type":11,"text":370},"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](\u002Fdocs\u002Fsdks\u002Fcloudflare-worker), [`createProxy`](\u002Fdocs\u002Fsdks\u002Fnode#serve-the-browser-sdk-from-your-domain) for Node.js runtimes, or [your own proxy](\u002Fdocs\u002Fproxy-integrations#build-your-own-proxy). Choose a new path rather than reusing the old one, so both can run during the switch.",{"type":18,"level":19,"text":372,"id":373},"Identities do not carry over","identities-do-not-carry-over",{"type":375,"items":376},"list",[377,378,379],"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.",{"type":18,"level":19,"text":381,"id":382},"Other differences","other-differences",{"type":375,"items":384},[385,386,387,388,389],"**Environments.** Keys belong to development, staging or production. Development and staging run the same detection and are free. See [environments](\u002Fdocs\u002Fapi-keys#environments).","**Pricing.** Prepaid, $0.003 per production identification, with no plans or minimums. See [billing](\u002Fdocs\u002Fbilling).","**Retention.** Events are readable for 30 days. Keep your own copy for longer. See [data retention](\u002Fdocs\u002Fdata-retention).","**Regions.** The United States region is available; the European Union region is coming soon. See [regions](\u002Fdocs\u002Fregions).","**Consent.** Every SDK takes a `consent` option and collects nothing until it is `granted`. See [consent tools](\u002Fdocs\u002Fconsent-tools).",{"type":14,"tone":391,"text":392},"tip","Stuck on something this page does not map? Email [support@fingerly.io](mailto:support@fingerly.io) with the FingerprintJS Pro feature you use.",{"type":394,"columns":19,"cards":395},"cards",[396,401],{"title":397,"text":398,"href":399,"icon":400},"Quick start","The Fingerly flow end to end.","\u002Fdocs\u002Fquick-start","bolt",{"title":402,"text":403,"href":404,"icon":375},"Use cases","Recipes for the policies you are moving.","\u002Fdocs\u002Fuse-cases",[406,407,408,410,412,414,416,418,419,420,421,422,423,424,425,426,427,428],{"id":21,"text":20,"level":19},{"id":75,"text":74,"level":19},{"id":409,"text":80,"level":119},"step-create-fingerly-keys",{"id":411,"text":85,"level":119},"step-run-both-agents-side-by-side",{"id":413,"text":90,"level":119},"step-move-server-checks",{"id":415,"text":95,"level":119},"step-move-webhooks",{"id":417,"text":100,"level":119},"step-remove-the-old-agent",{"id":109,"text":108,"level":19},{"id":121,"text":120,"level":119},{"id":146,"text":145,"level":119},{"id":185,"text":184,"level":19},{"id":197,"text":196,"level":119},{"id":202,"text":201,"level":19},{"id":247,"text":246,"level":19},{"id":334,"text":333,"level":19},{"id":368,"text":367,"level":19},{"id":373,"text":372,"level":19},{"id":382,"text":381,"level":19},"2026-09-17T16:58:12.000Z",1789667797513]