Theme

Start for free
DocsServing it from your domain

04 · Tracking

Serve the script from your own domain

Two paths on your site, forwarded to Tally, keep the script and its events first-party.

01Why serve it yourself

Ad blockers block analytics by the address it loads from. Served from your own domain, the script and its events aren't on their lists. The visitor cookie doesn't depend on it: the script sets it on your page's domain either way.

02Two paths to forward

On your siteForwards to
/js/script.jshttps://t.tallybyoutpost.com/script.js
/api/eventhttps://t.tallybyoutpost.com/api/event

Then load the script from your site. It sends events to /api/event on its own address, so nothing else changes:

HTML
<script defer data-site="k3xq2mfa7twp" src="/js/script.js"></script>

If your site already has an /api/event, forward another path and point the script at it with data-api="/your/path".

03What every proxy must do

  • Pass the user agent through: it's how bots are filtered out.
  • Forward the visitor's IP address in X-Forwarded-For: it finds their city (and, on a cookieless site, counts them), and is then dropped. A server that forwards its visitors gets a proxy's larger allowance of events, so they don't share one browser's.
  • Don't forward cookies: they aren't read.
  • Answer the browser at once if you like (202): the script ignores every response.

04On your server

Show the code for

// next.config.js
module.exports = {
  async rewrites() {
    return [
      { source: '/js/script.js', destination: 'https://t.tallybyoutpost.com/script.js' },
      { source: '/api/event', destination: 'https://t.tallybyoutpost.com/api/event' },
    ];
  },
};

On Vercel the rewrite carries X-Forwarded-For. An /api/event route of the app's own takes precedence: use another path and data-api.

{
  "rewrites": [
    { "source": "/js/script.js", "destination": "https://t.tallybyoutpost.com/script.js" },
    { "source": "/api/event", "destination": "https://t.tallybyoutpost.com/api/event" }
  ]
}

vercel.json, for a site on Vercel without Next.js.

# netlify.toml
[[redirects]]
  from = "/js/script.js"
  to = "https://t.tallybyoutpost.com/script.js"
  status = 200
  force = true

[[redirects]]
  from = "/api/event"
  to = "https://t.tallybyoutpost.com/api/event"
  status = 200
  force = true

location = /js/script.js {
    proxy_pass https://t.tallybyoutpost.com/script.js;
    proxy_set_header Host t.tallybyoutpost.com;
    proxy_ssl_server_name on;
}

location = /api/event {
    proxy_pass https://t.tallybyoutpost.com/api/event;
    proxy_set_header Host t.tallybyoutpost.com;
    proxy_ssl_server_name on;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Cookie "";
}

yoursite.com {
    handle /js/script.js {
        rewrite * /script.js
        reverse_proxy https://t.tallybyoutpost.com {
            header_up Host {upstream_hostport}
        }
    }
    handle /api/event {
        reverse_proxy https://t.tallybyoutpost.com {
            header_up Host {upstream_hostport}
            header_up -Cookie
        }
    }
    # … the site's own handlers
}

Caddy sets X-Forwarded-For itself.

// Routed on yoursite.com/js/script.js and yoursite.com/api/event.
const UPSTREAM = {
  '/js/script.js': 'https://t.tallybyoutpost.com/script.js',
  '/api/event': 'https://t.tallybyoutpost.com/api/event',
};

export default {
  async fetch(request) {
    const upstream = UPSTREAM[new URL(request.url).pathname];
    if (!upstream) return fetch(request);
    const headers = new Headers(request.headers);
    headers.set('X-Forwarded-For', request.headers.get('CF-Connecting-IP') || '');
    headers.delete('Cookie');
    return fetch(upstream, { method: request.method, headers, body: request.body });
  },
};

// Node 18 or later.
const express = require('express');

const UPSTREAM = 'https://t.tallybyoutpost.com';
const app = express();
app.set('trust proxy', true); // when Express runs behind a proxy, so req.ip is the visitor's

app.get('/js/script.js', async (req, res) => {
  const upstream = await fetch(UPSTREAM + '/script.js');
  res.set('Content-Type', 'application/javascript; charset=utf-8');
  res.set('Cache-Control', upstream.headers.get('cache-control') || 'public, max-age=3600');
  res.send(await upstream.text());
});

app.post('/api/event', express.text({ type: '*/*', limit: '8kb' }), (req, res) => {
  fetch(UPSTREAM + '/api/event', {
    method: 'POST',
    headers: {
      'Content-Type': 'text/plain',
      'User-Agent': req.get('user-agent') || '',
      'X-Forwarded-For': req.ip,
    },
    body: req.body,
  }).catch(() => {});
  res.status(202).end();
});

05Check it worked

Something missing, or not what you see? Write to support@tallybyoutpost.com and say which page.