Docs menu · Your own domain

Docs › Developers

Through your own domain

Serve the script and send its events through your own domain, so blockers that know nviv.io count your visitors too, with the right address and country.

Normally the script loads from nviv.io and sends its events there. Most blockers let that through: they block by name, and their generic rules do not apply to the way the script sends (a request that also leaves when the page closes, not a beacon). A blocker that does list nviv.io stops it. With a small proxy on your own domain, the script and its events go through your site instead, for example example.com/stats/, and those visitors count too.

The proxy also tells Nvivio who the visitor is and, where your platform knows it, where they are. Without that, Nvivio would see your server instead of your visitor: every visit from the same address and the same country, and your hosting’s network, which the bot filter treats as a datacenter. So the proxy signs its requests with your site’s key, and only signed requests may say who the visitor is.

Set it up

  1. Make your site’s key in Settings › Bots via your server (/app/<your-domain>/settings#server-bots), if you have none yet. You see it once; it is the same key your server uses to forward bots. Keep it on your server, never in a page.
  2. Add the proxy for your platform below, on the path /stats/ of your site.
  3. Load the script through it, with your own site code:
<script defer src="/stats/js/NV-XXXXXXX.js"></script>

The script sends its events next to itself, to /stats/e; there is nothing else to set. The objection box can go through the proxy too: /stats/optout.js.

What the proxy sends

Whatever your platform, the proxy forwards /stats/<path> to https://nviv.io/<path>, with the same method and body, and these headers:

Header What it is
User-Agent, Accept-Language, Sec-GPC, Content-Type The visitor’s own, unchanged: the browser, the language, Global Privacy Control.
Authorization Bearer and your site’s key.
X-Nvivio-Ip The visitor’s IP address. Nvivio uses it in memory to recognise the visitor for one day, as without a proxy, and never stores it.
X-Nvivio-Country, X-Nvivio-Region, X-Nvivio-City Where the visitor is, if your platform knows: a two-letter country code, and region and city names (URL-encoded when they have accents). Leave them out otherwise; the place then shows as unknown.

A request that says who the visitor is without the right key is refused with status 401, so a proxy that is set up wrong shows at once.

Cloudflare Worker

For a site behind Cloudflare. Make a Worker with this code, add the variable NVIVIO_KEY (as a secret) with your key, and give it the route example.com/stats/*.

// Nvivio through your own domain: example.com/stats/* goes to nviv.io.
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const headers = new Headers();
    for (const name of ['user-agent', 'accept-language', 'sec-gpc', 'content-type']) {
      const value = request.headers.get(name);
      if (value) headers.set(name, value);
    }
    if (request.method === 'POST') {
      headers.set('authorization', `Bearer ${env.NVIVIO_KEY}`);
      headers.set('x-nvivio-ip', request.headers.get('cf-connecting-ip') ?? '');
      if (request.cf?.country) headers.set('x-nvivio-country', request.cf.country);
      if (request.cf?.region) headers.set('x-nvivio-region', encodeURIComponent(request.cf.region));
      if (request.cf?.city) headers.set('x-nvivio-city', encodeURIComponent(request.cf.city));
    }
    return fetch('https://nviv.io' + url.pathname.replace(/^\/stats/, '') + url.search, {
      method: request.method,
      headers,
      body: request.method === 'POST' ? await request.text() : undefined
    });
  }
};

Next.js on Vercel

A route handler at app/stats/[...path]/route.ts, with NVIVIO_KEY in your environment variables. Vercel says the country, region and city; elsewhere the place stays unknown.

// Nvivio through your own domain: /stats/* goes to nviv.io.
import type { NextRequest } from 'next/server';

type Context = { params: Promise<{ path: string[] }> };

async function forward(request: NextRequest, { params }: Context) {
  const headers = new Headers();
  for (const name of ['user-agent', 'accept-language', 'sec-gpc', 'content-type']) {
    const value = request.headers.get(name);
    if (value) headers.set(name, value);
  }
  if (request.method === 'POST') {
    headers.set('authorization', `Bearer ${process.env.NVIVIO_KEY}`);
    headers.set('x-nvivio-ip', (request.headers.get('x-forwarded-for') ?? '').split(',')[0]!.trim());
    for (const [from, to] of [['x-vercel-ip-country', 'x-nvivio-country'], ['x-vercel-ip-country-region', 'x-nvivio-region'], ['x-vercel-ip-city', 'x-nvivio-city']]) {
      const value = request.headers.get(from!);
      if (value) headers.set(to!, value);
    }
  }
  const answer = await fetch(`https://nviv.io/${(await params).path.join('/')}`, {
    method: request.method,
    headers,
    body: request.method === 'POST' ? await request.text() : undefined,
    cache: 'no-store'
  });
  return new Response(answer.body, {
    status: answer.status,
    headers: { 'content-type': answer.headers.get('content-type') ?? 'text/plain', 'cache-control': answer.headers.get('cache-control') ?? 'no-store' }
  });
}

export { forward as GET, forward as POST };

nginx

nginx knows the visitor’s address but not their country, unless you use a GeoIP module.

location /stats/ {
  proxy_pass https://nviv.io/;
  proxy_ssl_server_name on;
  proxy_set_header Host nviv.io;
  proxy_set_header Authorization "Bearer YOUR_KEY";
  proxy_set_header X-Nvivio-Ip $remote_addr;
  # Behind Cloudflare: the visitor's address and country come from Cloudflare.
  # proxy_set_header X-Nvivio-Ip $http_cf_connecting_ip;
  # proxy_set_header X-Nvivio-Country $http_cf_ipcountry;
}

Check that it works

Open your site, then the installation page: it switches to “It works” when the first visit arrives. On Live, the visit shows your visitor’s country (if your platform knows it), not your server’s. The browser’s network tab shows the script and /stats/e on your own domain, with status 204.

What changes with a proxy

  • The bot filter decides by the user agent alone: Cloudflare’s own bot signals describe your server, not the visitor. Bots that pretend to be a browser are then less often caught.
  • The network (internet provider) of the visitor is not known; it stays empty.
  • Your server handles every event once (a pageview, the time on the page, a click), each a request of a few hundred bytes.
  • Privacy is the same: the IP address is still used only in memory, for one day, and never stored. Global Privacy Control and the objection box work as without a proxy.