[{"data":1,"prerenderedAt":133},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fproxies\u002Fgoogle-cloud-run":3},{"page":4,"toc":113,"updated":132},{"path":5,"title":6,"seoTitle":7,"description":8,"blocks":9},"\u002Fdocs\u002Fproxies\u002Fgoogle-cloud-run","Google Cloud Run proxy","Google Cloud Run First-Party Proxy for the Browser SDK","A small Cloud Run service behind your external Application Load Balancer, answering the one path the dashboard issued and forwarding the browser SDK's calls.",[10,13,17,22,97,100,102,104,107,109,111],{"type":11,"text":12},"p","A small Cloud Run service that your external Application Load Balancer routes one path to. It answers the path the dashboard issued, on your own domain, and forwards only the browser SDK's submissions to Fingerly. It is not an open proxy: the upstream is a constant in the code, and every other path and method is refused. Version 1.0.0.",{"type":14,"tone":15,"text":16},"callout","warning","Firebase Hosting rewrites to Cloud Run are not supported. Firebase documents no header carrying the visitor's address to Cloud Run that the visitor cannot forge, so the proxy could not tell Fingerly who the visitor is. Use a load balancer, or a [hosted subdomain](\u002Fdocs\u002Fhosted-subdomain).",{"type":18,"level":19,"text":20,"id":21},"heading",2,"Set up","set-up",{"type":23,"steps":24},"steps",[25,32,46,57,66,75,86],{"title":26,"blocks":27},"Create the proxy",[28,30],{"type":11,"text":29},"In the dashboard, open **Integration > Proxy keys** and choose **New proxy**. Pick the environment of the public key it will forward, and **Google Cloud** under **How it runs**. Copy the [proxy key](\u002Fdocs\u002Fproxy-integrations#proxy-keys) it shows once, and note the path it issued, such as `\u002Fk7x2qm4a`.",{"type":11,"text":31},"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.",{"title":33,"blocks":34},"Save the service",[35,37,39],{"type":11,"text":36},"Save `server.mjs` in an empty folder, with its `package.json` beside it. Buildpacks build the service from `package.json`, so no Dockerfile is needed. It needs Node 20 or newer.",{"type":11,"text":38},"`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.",{"type":40,"samples":41},"code",[42],{"label":43,"lang":44,"code":45},"package.json","json","{\n  \"name\": \"first-party-relay\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"start\": \"node server.mjs\"\n  },\n  \"engines\": {\n    \"node\": \">=20\"\n  }\n}",{"title":47,"blocks":48},"Store the proxy key",[49,51],{"type":11,"text":50},"In Secret Manager, readable by the service's runtime service account.",{"type":40,"samples":52},[53],{"label":54,"lang":55,"code":56},"Terminal","bash","printf '%s' 'fly_px_production_…' | gcloud secrets create first-party-relay-key --data-file=-\ngcloud secrets add-iam-policy-binding first-party-relay-key \\\n  --member=serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com \\\n  --role=roles\u002Fsecretmanager.secretAccessor",{"title":58,"blocks":59},"Deploy",[60,62],{"type":11,"text":61},"From the folder. The ingress setting means only your load balancer can reach the service.",{"type":40,"samples":63},[64],{"label":54,"lang":55,"code":65},"gcloud run deploy site-relay --source . --region=europe-west1 \\\n  --ingress=internal-and-cloud-load-balancing --allow-unauthenticated \\\n  --set-env-vars=FINGERLY_PATH=\u002Fk7x2qm4a \\\n  --set-secrets=FINGERLY_PROXY_KEY=first-party-relay-key:latest",{"title":67,"blocks":68},"Add it to your load balancer",[69,71],{"type":11,"text":70},"A serverless network endpoint group and a backend service. Use the scheme your load balancer already uses: `EXTERNAL_MANAGED` for the global external Application Load Balancer, `EXTERNAL` for the classic one.",{"type":40,"samples":72},[73],{"label":54,"lang":55,"code":74},"gcloud compute network-endpoint-groups create site-relay-neg \\\n  --region=europe-west1 --network-endpoint-type=serverless \\\n  --cloud-run-service=site-relay\ngcloud compute backend-services create site-relay-backend \\\n  --global --load-balancing-scheme=EXTERNAL_MANAGED\ngcloud compute backend-services add-backend site-relay-backend \\\n  --global --network-endpoint-group=site-relay-neg \\\n  --network-endpoint-group-region=europe-west1",{"title":76,"blocks":77},"Route the path to it",[78,80,84],{"type":11,"text":79},"In the URL map that serves your site. If your host has no path matcher yet:",{"type":40,"samples":81},[82],{"label":54,"lang":55,"code":83},"gcloud compute url-maps add-path-matcher URL_MAP --global \\\n  --path-matcher-name=site --default-service=YOUR_SITE_BACKEND \\\n  --new-hosts=www.example.com --path-rules=\u002Fk7x2qm4a=site-relay-backend",{"type":11,"text":85},"If it has one, add a path rule for `\u002Fk7x2qm4a` with the backend `site-relay-backend` to it instead: export the map with `gcloud compute url-maps export`, add the rule and import it back, or use **Load balancing > your load balancer > Edit > Routing rules** in the console.",{"title":87,"blocks":88},"Point the browser SDK at the path",[89,95],{"type":40,"samples":90},[91],{"label":92,"lang":93,"code":94},"fingerly.ts","ts","import { load } from '@fingerly\u002Fweb-js'\n\nconst fingerly = await load({\n  apiKey: 'fly_pk_production_…',\n  endpoints: '\u002Fk7x2qm4a',\n})",{"type":11,"text":96},"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 `gcp-cloud-run\u002F1.0.0`, with its requests over the last 30 days.",{"type":18,"level":19,"text":98,"id":99},"How the visitor's address is trusted","how-the-visitor-s-address-is-trusted",{"type":11,"text":101},"The load balancer appends `\u003Cclient-ip>,\u003Cload-balancer-ip>` to `X-Forwarded-For`, and vouches for nothing before them. The service sends the second-to-last entry as `x-fingerly-client-ip`, so entries a visitor writes on the left are ignored. That is its default address source, `xff:2`.",{"type":11,"text":103},"It holds only while every request arrives through the load balancer, which is what `--ingress=internal-and-cloud-load-balancing` enforces. If you put another proxy in front of the load balancer, the second-to-last entry is that proxy: set `FINGERLY_CLIENT_IP_SOURCE` to match, as described for the [container](\u002Fdocs\u002Fproxies\u002Fcontainer#how-the-visitor-s-address-is-trusted).",{"type":18,"level":19,"text":105,"id":106},"What it sends and relays","what-it-sends-and-relays",{"type":11,"text":108},"Upstream, to `https:\u002F\u002Fapi.fingerly.io\u002Fapi\u002Fv1\u002Fingest` 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: gcp-cloud-run\u002F1.0.0`). Cookies, `Authorization` and any `x-fingerly-*` header a visitor sends never leave.",{"type":11,"text":110},"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. With `FINGERLY_PATH` unset, the service answers `404` to everything.",{"type":11,"text":112},"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.",[114,115,118,120,122,124,126,128,130,131],{"id":21,"text":20,"level":19},{"id":116,"text":26,"level":117},"step-create-the-proxy",3,{"id":119,"text":33,"level":117},"step-save-the-service",{"id":121,"text":47,"level":117},"step-store-the-proxy-key",{"id":123,"text":58,"level":117},"step-deploy",{"id":125,"text":67,"level":117},"step-add-it-to-your-load-balancer",{"id":127,"text":76,"level":117},"step-route-the-path-to-it",{"id":129,"text":87,"level":117},"step-point-the-browser-sdk-at-the-path",{"id":99,"text":98,"level":19},{"id":106,"text":105,"level":19},"2026-09-25T18:31:31.000Z",1793630453193]