Sign up

Proxy

Proxy with Cloudflare Workers

On this page

If your domain is on Cloudflare, a Worker on one route serves the script and events and passes Cloudflare's own visitor location.

Set it up

  1. Create the Worker

    src/index.js
    // src/index.js · Cloudflare Worker on the route example.com/a/*
    export default {
      async fetch(request) {
        const { pathname } = new URL(request.url);
        const script = pathname === "/a/script" && request.method === "GET";
        const event = pathname === "/a/event" && request.method === "POST";
        if (!script && !event) return new Response("Not found", { status: 404 });
        const headers = new Headers();
        for (const name of ["content-type", "origin", "user-agent"]) {
          const value = request.headers.get(name);
          if (value) headers.set(name, value);
        }
        const ip = request.headers.get("cf-connecting-ip");
        if (ip) headers.set("x-visitor-ip", ip);
        // Cloudflare's location for this visitor. Names are URL-encoded so accented cities survive.
        const cf = request.cf || {};
        for (const [name, value] of [
          ["x-visitor-country", cf.country],
          ["x-visitor-region", cf.region],
          ["x-visitor-city", cf.city],
          ["x-visitor-latitude", cf.latitude],
          ["x-visitor-longitude", cf.longitude],
        ]) {
          if (ip && value) headers.set(name, encodeURIComponent(value));
        }
        const upstream = await fetch(script ? "https://metricraven.com/js/script.js" : "https://metricraven.com/api/v1/collect", {
          method: request.method,
          headers,
          body: event ? await request.arrayBuffer() : undefined,
        });
        return new Response(upstream.body, {
          status: upstream.status,
          headers: {
            "Content-Type": upstream.headers.get("content-type") || "application/octet-stream",
            "Cache-Control": script ? "public, max-age=3600" : "no-store",
          },
        });
      },
    };
  2. Route it to /a/* on your domain

    Replace both example.com values with your domain, then deploy:

    wrangler.toml
    # wrangler.toml
    name = "metricraven-proxy"
    main = "src/index.js"
    compatibility_date = "2026-10-01"
    
    [[routes]]
    pattern = "example.com/a/*"
    zone_name = "example.com"
    terminal
    npx wrangler deploy

    You can also add the route in the Cloudflare dashboard, under the Worker's Settings → Domains & Routes.

  3. Point your script tag at your own paths

    Replace your MetricRaven tag with this one. src loads the tracker from your domain and data-api sends its events there too.

    Your website's shared layout
    <script defer src="/a/script" data-api="/a/event" data-site-id="YOUR_SITE_ID"></script>

Verify it works

Open your live site. In the browser's Network panel, /a/script loads the tracker and /a/event answers with ok: true. Nothing goes to metricraven.com from the browser.

In Traffic, your own visit should show your real country and city. If every visit appears to come from your server's or host's location, the visitor IP isn't being forwarded.

Notes

  • The domain must be proxied through Cloudflare (orange cloud) for a Worker route to run on it.
  • cf-connecting-ip is set by Cloudflare and can't be faked by visitors. It must still be sent as x-visitor-ip: requests from your Worker reach MetricRaven with the Worker's address as the connecting IP.
  • If the route pattern is wider than /a/script and /a/event, the Worker answers other /a/ paths with 404. Pick a prefix your site doesn't use.

How the proxy works and which headers it must send: Use your own domain.

Details

  • The proxy forwards only Content-Type, Origin, User-Agent and the visitor's IP as x-visitor-ip. Your site's cookies and auth headers never leave your server.
  • Origin matters: MetricRaven checks it against your site's allowed domains. A missing or wrong origin is refused with domain_not_allowed.
  • MetricRaven allows about 120 events per minute per visitor IP. Without x-visitor-ip, all of your visitors share the proxy's IP, so busy sites lose events as well as locations.
  • A proxy doesn't guarantee that every blocker will allow tracking.