[{"data":1,"prerenderedAt":194},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fsdks\u002Freact":3},{"page":4,"toc":183,"updated":193},{"path":5,"title":6,"seoTitle":7,"description":8,"blocks":9},"\u002Fdocs\u002Fsdks\u002Freact","React","React SDK: Identify Visitors with Hooks","A provider and two hooks for React 19. One shared identification per page, server-rendering safe, with the result as plain state.",[10,13,18,23,27,30,43,46,52,54,57,62,65,70,72,75,91,153,155,158,160,165,167,170,173,178,181],{"type":11,"text":12},"p","`@fingerly\u002Freact` wraps the [JavaScript SDK](\u002Fdocs\u002Fsdks\u002Fjavascript) in a provider and hooks. However many components ask, a page makes one identification and every component reads the same answer.",{"type":14,"level":15,"text":16,"id":17},"heading",2,"Requirements","requirements",{"type":19,"items":20},"list",[21,22],"React 19 or newer.","A [public key](\u002Fdocs\u002Fapi-keys) with your site in its allowed origins.",{"type":24,"tone":25,"text":26},"callout","note","Building with Next.js? Use [`@fingerly\u002Fnext`](\u002Fdocs\u002Fsdks\u002Fnextjs), which adds a Server Component friendly provider and helpers for Server Actions.",{"type":14,"level":15,"text":28,"id":29},"Install","install",{"type":31,"samples":32},"code",[33,37,40],{"label":34,"lang":35,"code":36},"npm","bash","npm install @fingerly\u002Freact",{"label":38,"lang":35,"code":39},"pnpm","pnpm add @fingerly\u002Freact",{"label":41,"lang":35,"code":42},"yarn","yarn add @fingerly\u002Freact",{"type":14,"level":15,"text":44,"id":45},"Add the provider","add-the-provider",{"type":31,"samples":47},[48],{"label":49,"lang":50,"code":51},"main.tsx","tsx","import { createRoot } from 'react-dom\u002Fclient'\nimport { FingerlyProvider } from '@fingerly\u002Freact'\n\ncreateRoot(document.getElementById('root')!).render(\n  \u003CFingerlyProvider apiKey=\"fly_pk_us_production_…\">\n    \u003CApp \u002F>\n  \u003C\u002FFingerlyProvider>,\n)",{"type":11,"text":53},"The provider collects nothing when it mounts. It accepts `apiKey`, `endpoint`, `endpoints`, `fallbackToDefaultEndpoint`, `submit` and `consent`, with the meanings the [JavaScript agent](\u002Freference\u002Fjavascript-agent#load) gives them. Changing `consent` takes effect without remounting, and `setConsent()` is also on the store from `useFingerly()`.",{"type":14,"level":15,"text":55,"id":56},"Identify on an action","identify-on-an-action",{"type":31,"samples":58},[59],{"label":60,"lang":50,"code":61},"Checkout.tsx","import { useIdentify } from '@fingerly\u002Freact'\n\nexport function Checkout({ orderId }: { orderId: string }) {\n  const { identify, isLoading, error } = useIdentify({ tag: () => 'checkout:' + orderId })\n\n  async function onSubmit() {\n    const { requestId } = await identify()\n    await submitOrder({ orderId, requestId })\n  }\n\n  return (\n    \u003C>\n      \u003Cbutton onClick={onSubmit} disabled={isLoading}>\n        {isLoading ? 'Checking…' : 'Pay now'}\n      \u003C\u002Fbutton>\n      {error && \u003Cp>{error.message}\u003C\u002Fp>}\n    \u003C\u002F>\n  )\n}",{"type":14,"level":15,"text":63,"id":64},"Identify on mount","identify-on-mount",{"type":31,"samples":66},[67],{"label":68,"lang":50,"code":69},"RiskBanner.tsx","const { suspectLevel, isReady } = useIdentify({ immediate: true })",{"type":11,"text":71},"`immediate` runs in an effect, so it never runs during server rendering. `suspend: true` suspends the component instead, for use inside `\u003CSuspense>`.",{"type":14,"level":15,"text":73,"id":74},"useIdentify","useidentify",{"type":76,"title":77,"fields":78},"fields","Options",[79,84,87],{"name":80,"type":81,"text":82,"default":83},"immediate","boolean","Identify when the component mounts.","false",{"name":85,"type":81,"text":86,"default":83},"suspend","Suspend during render until the identification finishes.",{"name":88,"type":89,"text":90},"tag","string | () => string | undefined","The tag to send. A function is read at the moment of submission. A `tag` passed to `identify()` overrides it.",{"type":76,"title":92,"fields":93},"Returns",[94,98,101,104,108,111,114,117,121,124,128,132,136,139,142,145,149],{"name":95,"type":96,"text":97},"requestId","string | null","Identifies this identification. Send it to your server with the action it protects.",{"name":99,"type":96,"text":100},"visitorId","The stable identifier the server resolved for this browser.",{"name":102,"type":81,"text":103},"visitorIsNew","Whether your organization is seeing this visitor for the first time.",{"name":105,"type":106,"text":107},"visitorConfidence","number","How sure the identification is, from 0 to 100.",{"name":109,"type":81,"text":110},"identifiable","`false` when the browser gave too little to identify anyone.",{"name":112,"type":81,"text":113},"duplicate","`true` when the server had already answered this request.",{"name":115,"type":96,"text":116},"state","`enriched`, or `unavailable` when the network lookup could not run.",{"name":118,"type":119,"text":120},"suspectScore","number | null","The server's weighted score. `null` when nothing was scored, which is not the same as `0`.",{"name":122,"type":96,"text":123},"suspectLevel","`low`, `medium` or `high`.",{"name":125,"type":126,"text":127},"triggers","IdentifyTrigger[]","The signals the server scored, heaviest first.",{"name":129,"type":130,"text":131},"verdicts","Verdicts | null","The local, advisory [client-side verdicts](\u002Fdocs\u002Fclient-verdicts).",{"name":133,"type":134,"text":135},"error","Error | null","The last failure, if the identification failed.",{"name":137,"type":81,"text":138},"isLoading","An identification is in flight.",{"name":140,"type":81,"text":141},"isReady","The identification has finished, successfully or not.",{"name":143,"type":81,"text":144},"isSuspicious","Any local verdict matched at `medium` confidence or above.",{"name":146,"type":147,"text":148},"identify","(options?: { tag?: string; force?: boolean }) => Promise\u003CIdentifyResult>","Identify, or return the page's existing identification. `force` collects afresh.",{"name":150,"type":151,"text":152},"refresh","(options?: { tag?: string }) => Promise\u003CIdentifyResult>","Identify again, ignoring the shared result.",{"type":11,"text":154},"`identify` and `refresh` are stable across renders. A failed identification is forgotten, so the next call tries again.",{"type":14,"level":15,"text":156,"id":157},"useVerdict","useverdict",{"type":11,"text":159},"Reads one local verdict, with a confidence floor. It never triggers identification itself.",{"type":31,"samples":161},[162],{"label":163,"lang":50,"code":164},"AutomationNotice.tsx","import { useVerdict } from '@fingerly\u002Freact'\n\nfunction AutomationNotice() {\n  const { matched, confidence } = useVerdict('automation', { min: 'high' })\n  if (!matched) return null\n  return \u003Caside>Automated access suspected ({confidence})\u003C\u002Faside>\n}",{"type":11,"text":166},"Returns `matched`, `verdict`, `confidence` and `reasons`. `min` defaults to `'medium'`. Verdict names are `incognito`, `shields`, `tor`, `emulator`, `automation` and `farm`.",{"type":24,"tone":168,"text":169},"warning","Local verdicts are advisory and can be edited by whoever controls the browser. Make decisions on your server with the [stored event](\u002Fdocs\u002Fserver-side-verification).",{"type":14,"level":15,"text":171,"id":172},"Server rendering","server-rendering",{"type":19,"items":174},[175,176,177],"On the server the hooks return the unidentified state, so hydration always matches.","Calling `identify()` during server rendering rejects with `FingerlyServerError`. Call it from an effect or an event handler.","For a server that renders many requests, create a store per request with `createFingerlyStore(options)` and pass it as `\u003CFingerlyProvider store={store}>`.",{"type":14,"level":15,"text":179,"id":180},"Errors","errors",{"type":11,"text":182},"Failures are stored in `error` and rethrown from `identify()`. `TransportError` is re-exported with its `status` and `retryable` fields. Using a hook outside a provider throws an error that says where to add one.",[184,185,186,187,188,189,190,191,192],{"id":17,"text":16,"level":15},{"id":29,"text":28,"level":15},{"id":45,"text":44,"level":15},{"id":56,"text":55,"level":15},{"id":64,"text":63,"level":15},{"id":74,"text":73,"level":15},{"id":157,"text":156,"level":15},{"id":172,"text":171,"level":15},{"id":180,"text":179,"level":15},"2026-09-17T16:57:29.000Z",1789667797788]