SDKs

nginx proxy

A location block for the nginx server that already serves your site: it answers the one path the dashboard issued and forwards the browser SDK's calls to Fingerly.

A location block for the nginx server that already serves your site. It answers one path the dashboard issued, on your own host, and forwards only the browser SDK's submissions to Fingerly. It is not an open proxy: the upstream is fixed in the configuration, and every other path and method is refused. Version 1.0.0.

Set up

  1. Step 1: Create the proxy

    In the dashboard, open Integration > Proxy keys and choose New proxy. Pick the environment of the public key it will forward, and nginx under How it runs. Copy the proxy key it shows once, and note the path it issued, such as /k7x2qm4a.

    The dashboard then prints the steps below, filled in with your proxy's own path and key. They stay under Show setup in the proxy's menu, without the key, which is never shown again.

  2. Step 2: Add the proxy location

    Save this as /etc/nginx/snippets/fingerly.conf, with your path in place of the example.

    /etc/nginx/snippets/fingerly.conf
    location = /k7x2qm4a {
        # One line, readable only by root: set $fingerly_proxy_key "fly_px_...";
        include /etc/nginx/fingerly-proxy-key.conf;
    
        if ($request_method != POST) {
            add_header Allow "POST" always;
            add_header Cache-Control "no-store" always;
            return 405;
        }
        if ($fingerly_proxy_key !~ "^fly_px_(production|staging|development)_[A-Za-z0-9_-]+$") {
            add_header Cache-Control "no-store" always;
            return 503;
        }
    
        set $fingerly_origin $http_origin;
        if ($fingerly_origin = "") {
            set $fingerly_origin "$scheme://$http_host";
        }
    
        client_max_body_size 1m;
    
        proxy_pass https://api.fingerly.io/api/v1/ingest;
        proxy_http_version 1.1;
        proxy_ssl_server_name on;
        proxy_ssl_verify on;
        proxy_ssl_verify_depth 3;
        proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
        proxy_redirect off;
        proxy_connect_timeout 5s;
        proxy_send_timeout 5s;
        proxy_read_timeout 5s;
    
        proxy_pass_request_headers off;
        proxy_set_header Host api.fingerly.io;
        proxy_set_header Content-Type $content_type;
        proxy_set_header X-Api-Key $http_x_api_key;
        proxy_set_header Idempotency-Key $http_idempotency_key;
        proxy_set_header X-Fingerly-Proxy-Key $fingerly_proxy_key;
        proxy_set_header X-Fingerly-Client-Ip $remote_addr;
        proxy_set_header X-Fingerly-Origin $fingerly_origin;
        proxy_set_header X-Fingerly-User-Agent $http_user_agent;
        proxy_set_header X-Fingerly-Proxy-Version "nginx/1.0.0";
    
        proxy_hide_header Set-Cookie;
        proxy_hide_header Cache-Control;
        add_header Cache-Control "no-store" always;
    }
    

    proxy_ssl_trusted_certificate points at the usual system CA bundle. Where your system keeps it elsewhere, such as /etc/pki/tls/certs/ca-bundle.crt, change the path.

  3. Step 3: Store the proxy key

    In its own file, readable only by root. If /etc/nginx is in a repository, add the file to its .gitignore.

    Terminal
    sudo install -m 600 -o root -g root /dev/null /etc/nginx/fingerly-proxy-key.conf
    echo 'set $fingerly_proxy_key "fly_px_production_…";' | sudo tee /etc/nginx/fingerly-proxy-key.conf >/dev/null
    
  4. Step 4: Include it in your site

    Inside the server block that serves your pages.

    nginx.conf
    server {
        server_name www.example.com;
        include /etc/nginx/snippets/fingerly.conf;
        # ...
    }
    
  5. Step 5: Reload nginx

    Behind a load balancer, set up the realip module first (below), so the visitor's address is right.

    Terminal
    sudo nginx -t && sudo systemctl reload nginx
    

    nginx resolves api.fingerly.io when it loads the configuration, so a reload also picks up address changes.

  6. Step 6: Point the browser SDK at the path

    fingerly.ts
    import { load } from '@fingerly/web-js'
    
    const fingerly = await load({
      apiKey: 'fly_pk_production_…',
      endpoints: '/k7x2qm4a',
    })
    

    The SDK posts both of its calls to the path exactly as given. Once pages use it, the proxy's row in Proxy keys shows it last seen, running nginx/1.0.0, with its requests over the last 30 days.

How the visitor's address is trusted

The proxy sends $remote_addr, the address of the connection nginx accepted, as x-fingerly-client-ip. Nothing the visitor sends can change it. When visitors reach nginx directly, that is the visitor.

Behind a load balancer or CDN, $remote_addr is the load balancer. Tell the realip module which addresses to trust and which header they write, in the http or server block:

nginx.conf
set_real_ip_from 10.0.0.0/8;          # your load balancer's addresses
real_ip_header X-Forwarded-For;
real_ip_recursive on;

With real_ip_recursive on, nginx walks X-Forwarded-For from the right and takes the first address outside the trusted ranges, so entries a visitor adds on the left are ignored.

What it sends and relays

Upstream, to https://api.fingerly.io/api/v1/ingest only: content-type, x-api-key, Idempotency-Key when present, and the x-fingerly-* headers the proxy sets itself (the proxy key, the visitor's address, the page origin, the user agent and x-fingerly-proxy-version: nginx/1.0.0). proxy_pass_request_headers off makes this an allowlist: cookies, Authorization and any x-fingerly-* header a visitor forges never leave your server.

Bodies over 1 MiB are refused with 413, redirects are not followed, and a key that is not shaped like a proxy key answers 503. Other methods answer 405 with Allow: POST. If you prefer limit_except POST { deny all; } to the if block, they answer 403 instead; the browser SDK only posts, so either works.

Back to the page: the API's status and body, with Set-Cookie dropped and Cache-Control: no-store. The API sends nothing naming Fingerly to proxied callers, so dropping headers here is defense in depth.