Aller au contenu
wheremyflow
Fonctionnalités Tarifs Comparatif Manifeste Aide Audit Contact Démo Connexion

Self-host the tracker (anti-blocking)

Chrome 121+, uBlock Origin, Brave Shields, AdGuard and most ad blockers now block by default any request to a third-party domain labelled "analytics", even when the service does no advertising tracking (which is our case).

Without action, you currently lose 5–20% of sessions, and that share will rise to 30–50% within 12–24 months as Chrome rolls _Tracking Protection_ out to all sessions (not just incognito mode).

The solution: serve the tracker and the API from your own domain. The browser then sees a "1st-party" request, indistinguishable from an image or stylesheet load. No blocker blocks that.

This page documents how to do it using strictly EU-sovereign tools. None of the recipes below depend on a US Cloud-Act-bound provider.


How it works — high-level diagram

┌──────────────────────────────────────────────────────────────────────────┐
│                                                                          │
│   BEFORE (3rd-party, blocked)                                            │
│   ───────────────────────                                                │
│                                                                          │
│   browser ──► <script src="https://wheremyflow.com/w.js">                │
│               ❌ ERR_BLOCKED_BY_CLIENT                                    │
│                                                                          │
│   browser ──► POST https://wheremyflow.com/api/event                     │
│               ❌ ERR_BLOCKED_BY_CLIENT                                    │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────────────────────┐
│                                                                          │
│   AFTER (1st-party via your proxy, never blocked)                        │
│   ───────────────────────────────────────────                            │
│                                                                          │
│   browser ──► <script src="/js/flow.js"> on yoursite.com                 │
│               ✅ served by your proxy from wheremyflow.com               │
│                                                                          │
│   browser ──► POST yoursite.com/api/event                                │
│               ✅ relayed by your proxy to wheremyflow.com                │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

You proxy 3 endpoints from your domain to wheremyflow.com:

| Path on your domain | Target on wheremyflow | Role | | ------------------- | --------------------------- | -------------------------- | | /js/flow.js | wheremyflow.com/w.js | Tracker script | | /api/event | wheremyflow.com/api/event | Pageviews + custom events | | /api/ping | wheremyflow.com/api/ping | "Live" heartbeat every 30s | | /api/zone | wheremyflow.com/api/zone | Scroll attention zones |

The snippet you put in <head> becomes:

<script defer src="/js/flow.js" data-site="yoursite.com"></script>

All paths are 1st-party; nothing leaves to a third-party domain from the browser's perspective. Server-side, your proxy bridges to wheremyflow.com transparently.

Important — preserve the visitor IP: your proxy must forward X-Forwarded-For correctly, otherwise wheremyflow's geolocation will resolve the proxy's IP instead of the visitor's. Each recipe below includes this explicitly.

Recipe 1 — Nginx (most universal)

Works on: any Linux VPS (OVH, Scaleway, Hetzner, Clever Cloud with Nginx runtime, IONOS, Infomaniak…).

Sovereignty: neutral (depends on your hosting provider — choose EU).

Add this block inside the server { ... } that serves your site (typically /etc/nginx/sites-available/yoursite.conf):

# wheremyflow — 1st-party proxy (anti-blocking)
location = /js/flow.js {
    proxy_pass        https://wheremyflow.com/w.js;
    proxy_set_header  Host wheremyflow.com;
    proxy_ssl_server_name on;
    proxy_set_header  X-Forwarded-For $remote_addr;
    proxy_set_header  X-Real-IP       $remote_addr;
    proxy_set_header  User-Agent      $http_user_agent;
    proxy_set_header  Accept-Language $http_accept_language;
    proxy_hide_header Set-Cookie;
    proxy_read_timeout 10s;
}

location ~ ^/api/(event|ping|zone)$ {
    proxy_pass        https://wheremyflow.com$request_uri;
    proxy_set_header  Host wheremyflow.com;
    proxy_ssl_server_name on;
    proxy_set_header  X-Forwarded-For $remote_addr;
    proxy_set_header  X-Real-IP       $remote_addr;
    proxy_set_header  User-Agent      $http_user_agent;
    proxy_set_header  Accept-Language $http_accept_language;
    proxy_set_header  Origin          $http_origin;
    proxy_hide_header Set-Cookie;
    proxy_read_timeout 10s;
}

Then:

sudo nginx -t && sudo systemctl reload nginx

Verification: open https://yoursite.com/js/flow.js in your browser — you should see the minified tracker code. If you get a 502, check that proxy_ssl_server_name on; is set (required for SNI to wheremyflow.com).


Recipe 2 — Caddy (simplest)

Works on: any host running Caddy (auto-TLS included).

Sovereignty: neutral.

In your Caddyfile, inside your site block:

yoursite.com {
    # ... your existing config ...

    # wheremyflow — 1st-party proxy (anti-blocking)
    @wmfApi path /api/event /api/ping /api/zone

    handle_path /js/flow.js {
        rewrite * /w.js
        reverse_proxy https://wheremyflow.com {
            header_up Host wheremyflow.com
            header_down -Set-Cookie
        }
    }

    handle @wmfApi {
        reverse_proxy https://wheremyflow.com {
            header_up Host wheremyflow.com
            header_down -Set-Cookie
        }
    }
}

Then:

caddy reload --config /etc/caddy/Caddyfile
Caddy 2.6+ syntax: use a named matcher (@wmfApi path …) to apply handle to multiple paths. The form handle /a /b /c { … } is not accepted by the parser and fails caddy validate.
Caddy injects X-Forwarded-For and X-Real-IP automatically.

Recipe 3 — Apache (mod_proxy)

Works on: shared hosting (OVH, Infomaniak, IONOS) and any Apache server with mod_proxy enabled. Useful for WordPress on cPanel without root access.

Sovereignty: neutral.

In your .htaccess (site root) or <VirtualHost>:

# wheremyflow — 1st-party proxy (anti-blocking)
SSLProxyEngine On

# Tracker script
RewriteEngine On
RewriteRule ^js/flow\.js$ https://wheremyflow.com/w.js [P,L]

# API
RewriteRule ^api/(event|ping|zone)$ https://wheremyflow.com/api/$1 [P,L]

# Preserve visitor IP (setifempty avoids overwriting an upstream XFF
# if you sit behind another LB/reverse-proxy that already sets one)
ProxyPreserveHost Off
RequestHeader setifempty X-Forwarded-For "%{REMOTE_ADDR}s"
RequestHeader setifempty X-Real-IP       "%{REMOTE_ADDR}s"

# Never forward visitor-side session cookies
Header always unset Set-Cookie

Required modules (typically already enabled on serious EU shared hosts): mod_proxy, mod_proxy_http, mod_ssl, mod_rewrite, mod_headers. On OVH shared, ask support to enable if missing — it's free and standard.


Recipe 4 — Bunny.net Edge Scripting 🇸🇮 (EU-sovereign CDN)

For whom: high-traffic sites that want a CDN edge closer to the visitor (latency win + origin offload).

Sovereignty: 🇸🇮 Slovenia / European Union. Bunny.net (BunnyWay d.o.o., HQ in Maribor) is GDPR-bound. Not subject to the US Cloud Act. Slovenian team, 119+ PoPs including 30+ in EU.

Pricing: ~€0.01 per million Edge Script requests + ~€0.005 per GB CDN bandwidth. For 1M pageviews/month, count ~€5 total. No minimum, pay-as-you-go.

Steps:

  1. Create an account on bunny.net (credit card, ~€5 free initial credit).
  2. Create a "Pull Zone":
  • _CDN_ tab → _Add Pull Zone_
  • Name: mysite-wmf (free choice)
  • Origin URL: https://wheremyflow.com
  • Pricing tier: Standard
  • Pricing zones: uncheck Asia + Americas + Oceania, keep only Europe (sovereignty + lower cost)
  1. Connect your domain via CNAME:
  • _Hostnames_ tab → _Add Hostname_ → flow.yoursite.com
  • At your DNS registrar (Gandi, OVH, etc.): add CNAME flow.yoursite.com → mysite-wmf.b-cdn.net
  • Wait 5 min, return to Bunny → click _Generate Free SSL Certificate_ (auto Let's Encrypt)
  1. Map the path:
  • _Edge Rules_ tab → _Add Edge Rule_
  • Action: _Override URL_
  • Match: Request URL contains "/js/flow.js"
  • Override URL: https://wheremyflow.com/w.js
  1. Final snippet:
<script defer src="https://flow.yoursite.com/js/flow.js" data-site="yoursite.com"></script>

All /api/* paths automatically pass through to wheremyflow.com/api/* via the Pull Zone.

IP note: Bunny adds X-Forwarded-For correctly by default. Verify in the wheremyflow dashboard _Audience > Geo_ that countries resolve correctly within 1–2 hours.

Recipe 5 — Next.js / Nuxt rewrites

For whom: modern JS-stack sites deployed on EU-sovereign hosting.

⚠️ Hosting: do not deploy on Vercel, Netlify, AWS, or Fastly (US companies, Cloud Act). Prefer:

  • Clever Cloud 🇫🇷 (Paris)
  • Scaleway Serverless Functions 🇫🇷 (Paris/Amsterdam)
  • OVHcloud 🇫🇷
  • Render.com — Frankfurt region 🇩🇪 (US HQ, less safe than the above — verify your legal needs)
  • Self-hosted Docker on an EU VPS

Next.js — in next.config.js:

module.exports = {
  async rewrites() {
    return [
      { source: '/js/flow.js', destination: 'https://wheremyflow.com/w.js' },
      { source: '/api/event', destination: 'https://wheremyflow.com/api/event' },
      { source: '/api/ping', destination: 'https://wheremyflow.com/api/ping' },
      { source: '/api/zone', destination: 'https://wheremyflow.com/api/zone' },
    ];
  },
};

Nuxt 3 — in nuxt.config.ts:

export default defineNuxtConfig({
  routeRules: {
    '/js/flow.js': { proxy: 'https://wheremyflow.com/w.js' },
    '/api/event': { proxy: 'https://wheremyflow.com/api/event' },
    '/api/ping': { proxy: 'https://wheremyflow.com/api/ping' },
    '/api/zone': { proxy: 'https://wheremyflow.com/api/zone' },
  },
});
Next/Nuxt rewrites preserve X-Forwarded-For and HTTP method automatically. POST events go through correctly.

To stay consistent with our EU-sovereign commitment, do not use:

| Service | Country / HQ | Why not | | ---------------------- | ---------------- | -------------------------------------------------------- | | Cloudflare Workers | 🇺🇸 San Francisco | US Cloud Act, obligation to cooperate with US government | | Vercel | 🇺🇸 San Francisco | Idem, infra mostly AWS US | | Netlify | 🇺🇸 San Francisco | Idem | | AWS CloudFront | 🇺🇸 Seattle | Cloud Act, FISA 702 | | Fastly | 🇺🇸 San Francisco | Idem | | Akamai | 🇺🇸 Cambridge MA | Idem |

These services can be compelled by US court order to hand over proxied data to US authorities, even when the physical server is in Europe (post-Schrems II / Privacy Shield invalidation, July 2020). For a product sold as "strictly EU-sovereign", this would defeat your commercial argument.


Verify it works

  1. Script loads — open https://yoursite.com/js/flow.js in your browser, you should see the minified tracker code (starts with !function()...).
  2. Events leave — open DevTools (F12) → _Network_ tab, click around your site. You should see POST /api/event returning 204 No Content.
  3. Geolocation works — connect to the wheremyflow dashboard, wait 1–2 minutes, check _Audience > Geo_. If all visitors resolve from your proxy's country instead of their real one, X-Forwarded-For is not being forwarded. Review your proxy config.
  4. Tracker tab in dashboard — the HTTP probe "Test installation" auto-detects self-host mode and shows _"1st-party proxy detected"_ in the result.

FAQ

Does the proxy slow down my site? No, or marginally. The tracker flow.js is 2.21 KB gzip (1.96 KB brotli) and stays browser-cached. Events leave via non-blocking sendBeacon, so invisible to visitors even if the proxy adds 50 ms of latency.

What if my proxy goes down? Events are lost during the outage (no offline queue, by doctrine §25 TDDDG / GDPR minimization). Your site keeps working normally — only audience measurement stops. Same as any other third-party service.

Can I proxy only the script and not the API? Yes, with the data-api attribute:

<script
  defer
  src="/js/flow.js"
  data-site="yoursite.com"
  data-api="https://wheremyflow.com"
></script>

The script is served 1st-party (bypasses filename-based blockers) but events go to wheremyflow.com directly (re-blocked by domain-based blockers). Less effective than the full recipe; documented here for edge cases only.

What if I switch analytics provider later? The proxy structure stays the same, just change the targets. The <script src="/js/flow.js" data-site="..."> snippet is universal.


Need help?

  • 🛠️ Dashboard _Integration_ tab → _Test installation_ button (auto HTTP probe)
  • 📧 DPO / support contact: see _Compliance_ tab in the dashboard
  • 📚 Full docs: /docs