Guides

Proxy integrations

Route the browser SDK through a random path on your own site so content blockers do not stop identification, without losing the visitor's real network signals.

Content blockers and privacy extensions often refuse requests to third-party APIs. When they refuse Fingerly's, the visitor is simply not identified. A proxy integration sends the SDK's requests to a path on your own site instead, where they look like the rest of your traffic.

Why a Fingerly proxy is different

A naive reverse proxy would make every visitor arrive from your server's address, and every network signal would describe your server rather than the visitor. A Fingerly proxy authenticates with a proxy key, and only then may it tell Fingerly the visitor's real address, origin and user agent. A public key alone can never do that, so nobody can spoof those details with your public key.

Why each proxy has its own random path

Blockers refuse requests by URL: a third-party host, a path they already know, or a word such as fingerprint or tracking. A path that every Fingerly customer shared, or anything the SDK appended to your path, would be one pattern a single filter rule could match on every site at once.

So the dashboard issues each proxy its own random path, such as /k7x2qm4a, and the SDK posts to it exactly as given, with nothing appended. Both of its calls, the identification and the deferred report after it, go to that one path. Nothing in the response names Fingerly either. There is no pattern shared with any other site for a filter list to match.

Set up a proxy

  1. Step 1: Create a proxy

    In the dashboard, open Integration > Proxy keys and choose New proxy. Name it, pick the environment of the public key it will forward, and pick how it runs: the Cloudflare Worker, your application through a server SDK, one of the ready-made templates such as Vercel or nginx, or your own proxy.

  2. Step 2: Copy the key and the path

    The dashboard shows the proxy key once, with the path it issued, such as /k7x2qm4a, and prints the setup for how it runs, filled in with that path and key. Store the key as a server secret: it is never shown again. The setup stays under Show setup in the proxy's menu, without the key.

  3. Step 3: Deploy the proxy at that path

    Deploy the Cloudflare Worker or a template, mount a server SDK's proxy (see the options below), or follow the contract in your own. The proxy answers that one path on your site's own host and nothing else.

  4. Step 4: Point the SDK at the path

    fingerly.ts
    import { load } from '@fingerly/web-js'
    
    const fingerly = await load({
      apiKey: 'fly_pk_production_…',
      endpoints: '/k7x2qm4a',               // the path your proxy was issued
      fallbackToDefaultEndpoint: true,      // optional: try Fingerly directly if the proxy fails
    })
    

    The SDK posts to /k7x2qm4a exactly, for both calls. Omit fallbackToDefaultEndpoint to keep every request first-party.

  5. Step 5: Watch it arrive

    The proxy's row shows when it was last seen, the version it reports, such as cloudflare-worker/0.2.0, the page origin it last served, and its requests over the last 30 days. The counts are refreshed every 30 seconds or so.

Options

Every option answers one path on your own host and forwards to the same place. Pick the one that runs where your site already runs.

OptionRuns inBest for
Cloudflare WorkerYour Cloudflare accountSites already on Cloudflare. No application changes.
VercelRouting Middleware in your Vercel projectAny framework deployed on Vercel.
NetlifyAn Edge Function on your Netlify siteAny site on Netlify.
AWS CloudFrontA CloudFront Function and cache behaviorSites served by a CloudFront distribution. No code to host.
Google Cloud RunA Cloud Run serviceSites behind an external Application Load Balancer on Google Cloud.
nginxA location block on your serverSites nginx already serves.
CaddyA handle block in your CaddyfileSites Caddy already serves.
TraefikA router to the proxy containerDocker, file provider or Kubernetes setups routed by Traefik.
ContainerA small container you runDocker, Kubernetes, HAProxy, Envoy, or anything that can route a path to a container.
Node.js createProxyYour applicationNext.js, Remix, Hono, SvelteKit, Nuxt and any runtime with web Request and Response.
Python FingerlyProxyYour applicationFastAPI, Starlette, Django, Flask and any other ASGI or WSGI app.
Go NewProxyYour applicationAny Go router: it is an http.Handler.
Java FingerlyProxyYour applicationSpring Boot 2 and 3, WebFlux, and any servlet container.
.NET FingerlyProxyYour applicationASP.NET Core, as routed endpoints or middleware.
PHP Fingerly\ProxyYour applicationLaravel, Symfony, any PSR-15 stack, or a bare index.php.
Ruby Fingerly::ProxyYour applicationRails, Sinatra, Hanami and any other Rack app.
Rust FingerlyProxyYour applicationaxum, hyper, warp and anything else that takes a tower::Service.
Your own proxyAnywhereOther stacks. Follow the contract below.

Every server SDK's proxy takes an optional path, the issued path, matched exactly. Leave it out where your router already sends only that path to the proxy; set it where the proxy sees other requests too, such as middleware.

Each template reports itself as its own kind, such as nginx/1.0.0, and resolves the visitor's address from a source the visitor cannot write: the connection itself, or a header your platform overwrites. Its page says which, and what to change when something sits in front of it.

Proxy keys

Proxy keys look like fly_px_production_…. Owners and admins issue them for one environment in Integration > Proxy keys, and each key is shown once, when it is issued. Only owners and admins can see, edit and revoke them. A proxy key only forwards client requests: it cannot read events, and it must match the public key's organization and environment. The management API does the same from code.

Build your own proxy

Accept POST at your one issued path, and forward every request it receives, whatever it is, to one URL:

Upstream
https://api.fingerly.io/api/v1/ingest

Fingerly tells the two calls apart by the body: a deferred report is answered 202 with request_id and duplicate, and an identification 200 with the verdict. Your proxy does not need to know which is which. Replace the visitor headers with values you resolved yourself:

Header to sendValue
x-api-keyThe incoming x-api-key (the public key), unchanged.
x-fingerly-proxy-keyYour proxy key.
x-fingerly-client-ipThe visitor's IP address, from your load balancer or CDN.
x-fingerly-originThe incoming Origin.
x-fingerly-user-agentThe incoming User-Agent.
Idempotency-KeyThe incoming value, when present.
Content-Typeapplication/json.
x-fingerly-proxy-versionOptional. <name>/<version>, such as my-proxy/1.4.0, shown for the proxy in the dashboard.

If you forget the key, the API refuses rather than recording your proxy's address. A request that carries x-fingerly-client-ip, x-fingerly-origin or x-fingerly-user-agent without a proxy key is answered 401 with the code proxy_key_missing, so a proxy that lost its key fails loudly the first time, instead of every visitor quietly arriving from your server.

Relay back only these response headers, and set cache-control: no-store yourself:

RelayWhy
content-typeThe browser SDK reads the body as JSON.
x-request-idThe request ID, for support and logs.
ratelimit-limit, ratelimit-remaining, retry-afterSo the SDK backs off when asked to.
  • Do not forward cookies or Authorization headers.
  • Answer 404 to every other path and 405 to every other method, and cap request bodies at 1 MiB.
  • Do not follow redirects from the upstream.
  • Serve the path on your site's own host. If it has to live on another host, answer CORS preflights for your page origins only.

Change the path

If a path ever starts being blocked, or you want a new one, move the proxy. The key stays the same.

  1. In Integration > Proxy keys, edit the proxy and take a new random path, or type one.
  2. Deploy your proxy at the new path.
  3. Update endpoints in the browser SDK to the new path, and release.

Where the data goes

A proxy never changes that. The Cloudflare Worker and every template forward to api.fingerly.io and nowhere else, because the upstream is a constant in their source or configuration rather than a setting.