Skip to main content

The script

Serving the script from your own domain

Ad blockers block by hostname.

Ad blockers block by hostname. Every request to truestat.io from a visitor running one is lost before it leaves their browser. If you serve the script and the collector from your own domain instead, those requests look like requests to your own site, and most of them get through.

This is the single most effective thing you can do about undercounting.

Read this first: forward the visitor's IP

A proxy that does not pass the visitor's IP address through does not degrade your numbers. It destroys them.

Here is the mechanism. Your visitors' page views arrive at your server, and your server forwards them to us. Without extra configuration, the address we see is your server's, on every single request. Every visitor then looks identical, so:

  • Every visitor collapses into one visitor. Your unique visitor count drops to approximately 1 and stays there.

  • Every visitor geolocates to your server's data centre. Your world map turns into one dot.

  • Page views keep counting correctly, so the failure is not obvious — you get a dashboard that looks half-working.

Every configuration below includes the IP forwarding. Do not omit it, and if you adapt one of these to a setup not listed here, that is the line to carry over.

The signature to watch for after you set this up: all your visitors suddenly in one city, and your unique visitor count flat near 1. If you see that, your proxy is dropping the IP.

screenshot coming

The dashboard as it looks with a misconfigured proxy: unique visitors reading 1 or 2 against thousands of views, and the world map showing a single country. Annotated with a callout arrow. Light theme. This is a "what wrong looks like" figure — capture it deliberately against a test site.

What gets proxied

Two paths, always both:

PathWhat it is
/js/t.jsThe tracking script. Cacheable for a long time.
/api/eventsWhere events are sent. Never cache this.

How the script knows

If the script's own src is not on truestat.io, it sends events to /api/events on your page's origin automatically. So for a same-origin proxy you change one thing:

<script
  defer
  data-website-id="ts_a1b2c3d4e5f6"
  data-domain="example.com"
  src="/js/t.js"
></script>

No data-api-url needed.

If you want to send events somewhere else, data-api-url accepts:

  • A full URLhttps://a.example.com/api/events — used exactly as given.

  • A relative path/collect — resolved against your page's origin, not against the script's. This catches people out: if your script is served from a.example.com but your page is example.com, a relative data-api-url sends events to example.com, not to a.example.com.

  • An unparseable value falls back to the default collector rather than silently sending nothing. A typo in one attribute should not zero your analytics.

If you are proxying through a separate subdomain, always use a full URL.

Choosing a subdomain

If you proxy through a subdomain rather than a path on your main site, the name matters. Blocklists target names that describe analytics.

Good: a.example.com, cdn.example.com, s.example.com Bad: analytics.example.com, tracking.example.com, stats.example.com

Configurations

Nginx

location = /js/t.js {
    proxy_pass https://truestat.io/js/t.js;
    proxy_set_header Host truestat.io;
    proxy_cache_valid 200 1y;
}

location = /api/events {
    proxy_pass https://truestat.io/api/events;
    proxy_set_header Host              truestat.io;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

The three X- headers on /api/events are the IP forwarding. Without them, read the warning at the top of this page again.

Caddy

example.com {
    handle /js/t.js {
        reverse_proxy https://truestat.io {
            header_up Host truestat.io
        }
    }

    handle /api/events {
        reverse_proxy https://truestat.io {
            header_up Host      truestat.io
            header_up X-Real-IP {remote_host}
        }
    }
}

Caddy sets X-Forwarded-For on its own; X-Real-IP is added explicitly.

Express

const proxy = require("express-http-proxy");

// REQUIRED. Without it, req.ip is the proxy's address and every visitor
// arrives from the same place.
app.set("trust proxy", true);

app.use(
  "/js/t.js",
  proxy("https://truestat.io", {
    proxyReqPathResolver: () => "/js/t.js",
  })
);

app.use(
  "/api/events",
  proxy("https://truestat.io", {
    proxyReqPathResolver: () => "/api/events",
    proxyReqOptDecorator: (opts, req) => {
      opts.headers["X-Real-IP"] = req.ip;
      opts.headers["X-Forwarded-For"] = req.ip;
      return opts;
    },
  })
);

app.set('trust proxy', true) is the line people leave out.

Cloudflare Workers, Vercel Edge and similar

Any edge runtime that lets you fetch an upstream works. Forward the client address explicitly:

const upstream = new Request("https://truestat.io/api/events", request);
upstream.headers.set("X-Real-IP", request.headers.get("CF-Connecting-IP"));

Use whichever header your platform provides for the real client address.

Platforms that cannot do this

Firebase Hosting cannot rewrite to an external destination. It needs a Cloud Function running a proxy, wired in through firebase.json. It is the most awkward setup in this list and rarely worth it.

Webflow, Framer, Squarespace, Wix, Ghost, Bubble have no way to proxy — you do not control a server. Use the default hosted script; you will lose the blocked share of your traffic, as every analytics product on those platforms does.

After you switch

  1. Change the tag's src to /js/t.js (or your subdomain URL) and republish.

  2. Load a page and confirm in DevTools that /js/t.js returns 200 from your domain and POST /api/events returns 202.

  3. Wait a day, then check your unique visitor count and your world map. This is the IP-forwarding check and it is the one that matters. Views working proves nothing.

Content Security Policy gets simpler

When the script is same-origin, 'self' covers both script-src and connect-src and you need no TrueStat entry in your CSP at all. If you were blocked by a CSP, proxying is often the shorter path to fixing it than editing headers. See Content Security Policy.

If you proxy through a separate subdomain, that subdomain must be allowed in both directives — 'self' does not cover it.

Not available yet

A managed proxy — where you point a CNAME at us and we handle the certificate and the forwarding — is not built. Everything on this page is self-hosted, meaning you run the proxy.

Was this page helpful?

Last updated August 28, 2026