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
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.
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.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.
Step 4: Point the SDK at the path
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
/k7x2qm4aexactly, for both calls. OmitfallbackToDefaultEndpointto keep every request first-party.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.
| Option | Runs in | Best for |
|---|---|---|
| Cloudflare Worker | Your Cloudflare account | Sites already on Cloudflare. No application changes. |
| Vercel | Routing Middleware in your Vercel project | Any framework deployed on Vercel. |
| Netlify | An Edge Function on your Netlify site | Any site on Netlify. |
| AWS CloudFront | A CloudFront Function and cache behavior | Sites served by a CloudFront distribution. No code to host. |
| Google Cloud Run | A Cloud Run service | Sites behind an external Application Load Balancer on Google Cloud. |
| nginx | A location block on your server | Sites nginx already serves. |
| Caddy | A handle block in your Caddyfile | Sites Caddy already serves. |
| Traefik | A router to the proxy container | Docker, file provider or Kubernetes setups routed by Traefik. |
| Container | A small container you run | Docker, Kubernetes, HAProxy, Envoy, or anything that can route a path to a container. |
Node.js createProxy | Your application | Next.js, Remix, Hono, SvelteKit, Nuxt and any runtime with web Request and Response. |
Python FingerlyProxy | Your application | FastAPI, Starlette, Django, Flask and any other ASGI or WSGI app. |
Go NewProxy | Your application | Any Go router: it is an http.Handler. |
Java FingerlyProxy | Your application | Spring Boot 2 and 3, WebFlux, and any servlet container. |
.NET FingerlyProxy | Your application | ASP.NET Core, as routed endpoints or middleware. |
PHP Fingerly\Proxy | Your application | Laravel, Symfony, any PSR-15 stack, or a bare index.php. |
Ruby Fingerly::Proxy | Your application | Rails, Sinatra, Hanami and any other Rack app. |
Rust FingerlyProxy | Your application | axum, hyper, warp and anything else that takes a tower::Service. |
| Your own proxy | Anywhere | Other 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:
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 send | Value |
|---|---|
x-api-key | The incoming x-api-key (the public key), unchanged. |
x-fingerly-proxy-key | Your proxy key. |
x-fingerly-client-ip | The visitor's IP address, from your load balancer or CDN. |
x-fingerly-origin | The incoming Origin. |
x-fingerly-user-agent | The incoming User-Agent. |
Idempotency-Key | The incoming value, when present. |
Content-Type | application/json. |
x-fingerly-proxy-version | Optional. <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:
| Relay | Why |
|---|---|
content-type | The browser SDK reads the body as JSON. |
x-request-id | The request ID, for support and logs. |
ratelimit-limit, ratelimit-remaining, retry-after | So the SDK backs off when asked to. |
- Do not forward cookies or
Authorizationheaders. - Answer
404to every other path and405to 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.
- In Integration > Proxy keys, edit the proxy and take a new random path, or type one.
- Deploy your proxy at the new path.
- Update
endpointsin 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.