# Proxy with Cloudflare Workers

> Serve the MetricRaven script and events from a Cloudflare Worker on your own domain.

Source: https://metricraven.com/docs/proxy-cloudflare-workers  
Markdown: https://metricraven.com/docs/proxy-cloudflare-workers.md  
Section: Proxy

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**

   ```javascript title="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:

   ```toml title="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"
   ```

   ```bash
   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.

   ```html title="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](https://metricraven.com/docs/proxy).

## 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.
