SDKs

Traefik proxy

Route the one path the dashboard issued from Traefik to the small proxy container, with Docker labels, the file provider or a Kubernetes IngressRoute.

Traefik's configuration alone cannot copy the visitor's address into a header: its headers middleware only sets fixed values. So this template routes the one path the dashboard issued to the proxy container, which does the forwarding. The router answers exactly that path, on your own host. Version 1.0.0, reported as container/1.0.0 because the container runs it.

Set up

These steps use Traefik's Docker provider. The file provider and Kubernetes follow them.

  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 Traefik 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: Save the service

    Save server.mjs as site-relay/server.mjs beside your compose file, and its Dockerfile beside it as site-relay/Dockerfile. The image has no dependencies and runs as an unprivileged user.

    server.mjs is a small Node service with no dependencies: the built-in HTTP server and fetch. It reads FINGERLY_PROXY_KEY, FINGERLY_PATH, FINGERLY_CLIENT_IP_SOURCE and PORT (default 8080), and refuses to start when the address source is not one it knows. The dashboard prints it; copy it from there.

    site-relay/Dockerfile
    FROM node:24-alpine
    WORKDIR /app
    COPY server.mjs ./
    ENV NODE_ENV=production PORT=8080
    USER 1000:1000
    EXPOSE 8080
    CMD ["node", "server.mjs"]
    
  3. Step 3: Add it to your compose project

    In the project Traefik routes with its Docker provider, on a network Traefik can reach. If your other routers name entrypoints or tls in their labels, add the same to this one.

    docker-compose.yml
    services:
      site-relay:
        build: ./site-relay
        restart: unless-stopped
        environment:
          FINGERLY_PATH: /k7x2qm4a
          FINGERLY_CLIENT_IP_SOURCE: header:x-real-ip
          FINGERLY_PROXY_KEY: ${FINGERLY_PROXY_KEY:?set FINGERLY_PROXY_KEY}
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.site-relay.rule=Path(`/k7x2qm4a`)"
          - "traefik.http.routers.site-relay.priority=10000"
          - "traefik.http.services.site-relay.loadbalancer.server.port=8080"
    
  4. Step 4: Start it

    Take the key from your secret store, or an .env file kept out of version control.

    Terminal
    FINGERLY_PROXY_KEY='fly_px_production_…' docker compose up -d --build site-relay
    
  5. Step 5: 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 container/1.0.0, with its requests over the last 30 days.

File provider and Kubernetes

With the file provider, run the container as site-relay on a network Traefik can reach, and add this to your dynamic configuration:

dynamic.yml
http:
  routers:
    site-relay:
      rule: "Path(`/k7x2qm4a`)"
      priority: 10000
      service: site-relay
  services:
    site-relay:
      loadBalancer:
        servers:
          - url: "http://site-relay:8080"

On Kubernetes, push the image to your registry, create the key's Secret from your secret store, and apply a Deployment of it (FINGERLY_PATH, FINGERLY_CLIENT_IP_SOURCE=header:x-real-ip, and FINGERLY_PROXY_KEY from the Secret, running as non-root with a read-only root filesystem), a Service on port 8080, and an IngressRoute:

kubectl create secret generic site-relay --from-literal=proxy-key=fly_px_production_…

The dashboard prints the whole manifest, the Deployment and the Service included.

How the visitor's address is trusted

The container reads X-Real-Ip (FINGERLY_CLIENT_IP_SOURCE=header:x-real-ip). For a request from an address outside forwardedHeaders.trustedIPs, Traefik deletes every X-Forwarded-* and X-Real-Ip header the client sent and sets X-Real-Ip to the address of the connection it accepted, so a visitor cannot forge it. That is the default, with no trusted IPs configured.

  • Traefik behind a load balancer listed in forwardedHeaders.trustedIPs: Traefik keeps the X-Real-Ip the load balancer sent, and only sets it when there is none. Keep header:x-real-ip if the load balancer sets X-Real-Ip itself on every request. If it only appends to X-Forwarded-For, use xff:2 instead: the load balancer appends the visitor, then Traefik appends the load balancer.
  • Kubernetes: Traefik sees the visitor's address only when its own Service preserves it, with externalTrafficPolicy: Local or the PROXY protocol from the load balancer. Otherwise every request carries a node's address.

What it sends and relays

Upstream, to https://api.fingerly.io/api/v1/ingest and nowhere else, on headers built from scratch: 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: container/1.0.0). Cookies, Authorization and any x-fingerly-* header a visitor sends never leave.

Other methods answer 405 with Allow: POST, and other paths 404. Bodies over 1 MiB are refused with 413, redirects are not followed, and a missing or malformed key answers 503 rather than forwarding anything. Traefik adds nothing to the response.

Back to the page: the API's status and body, with only content-type, x-request-id, ratelimit-limit, ratelimit-remaining and retry-after, plus cache-control: no-store. Nothing relayed names Fingerly.