Sift Docs
Configuration

Proxying the script

Serve Sift from your own domain so blockers and strict CSP do not drop events.

Some ad blockers and DNS filters block known analytics hosts. If that is costing you data, serve the script and the event endpoint from your own domain.

Sift supports this with no server changes: the script reads data-api and sends events there instead.

What you are proxying

Two paths:

PathUpstream
/stats/js/sift.jshttps://sift.agentonboard.xyz/js/sift.js
/stats/api/eventhttps://sift.agentonboard.xyz/api/event

The names are yours to choose. Keep them stable, because the snippet embeds them.

The snippet

<script
  defer
  data-domain="example.com"
  data-api="/stats/api/event"
  src="/stats/js/sift.js"
></script>

Without data-api, the script derives the endpoint from its own src origin and posts to /api/event on your domain. Setting it explicitly is clearer and survives a path rename.

Cloudflare Worker

worker.js
export default {
  async fetch(request) {
    const url = new URL(request.url);

    if (url.pathname === "/stats/js/sift.js") {
      const upstream = await fetch("https://sift.agentonboard.xyz/js/sift.js", {
        cf: { cacheTtl: 86400 },
      });
      return new Response(upstream.body, {
        headers: {
          "content-type": "application/javascript; charset=utf-8",
          "cache-control": "public, max-age=86400",
        },
      });
    }

    if (url.pathname === "/stats/api/event" && request.method === "POST") {
      // Forward the visitor's identity inputs. Without them every visitor
      // looks like the proxy.
      const headers = new Headers({ "content-type": "text/plain" });
      const ip = request.headers.get("cf-connecting-ip");
      const ua = request.headers.get("user-agent");
      if (ip) headers.set("cf-connecting-ip", ip);
      if (ua) headers.set("user-agent", ua);

      return fetch("https://sift.agentonboard.xyz/api/event", {
        method: "POST",
        body: request.body,
        headers,
      });
    }

    return new Response("Not found", { status: 404 });
  },
};

Add a route for example.com/stats/* to this worker.

Next.js

next.config.ts
const nextConfig = {
  async rewrites() {
    return [
      { source: "/stats/js/sift.js", destination: "https://sift.agentonboard.xyz/js/sift.js" },
      { source: "/stats/api/event", destination: "https://sift.agentonboard.xyz/api/event" },
    ];
  },
};

Rewrites forward the method and body, so the POST survives.

Nginx

location = /stats/js/sift.js {
    proxy_pass https://sift.agentonboard.xyz/js/sift.js;
    proxy_set_header Host sift.agentonboard.xyz;
}

location = /stats/api/event {
    proxy_pass https://sift.agentonboard.xyz/api/event;
    proxy_set_header Host sift.agentonboard.xyz;
    proxy_set_header Content-Type text/plain;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

Rules the proxy must respect

  • Do not parse the body. Events are sent as text/plain with a JSON string inside. Pass it through untouched.
  • Forward the visitor's IP. Sift reads cf-connecting-ip first, then x-forwarded-for. If you strip both, every visitor on that path looks like your proxy's IP and unique counts collapse.
  • Forward the user agent. Bot filtering and browser reports depend on it.
  • Keep the POST method. sendBeacon sends POST. A redirect to GET loses the event.

That second point is the one that quietly breaks proxies: the script still sends, the endpoint still answers 202, and visitors silently merge into one.

Verifying

Load a page, watch the network panel, and confirm:

  1. The request goes to your domain, not sift.agentonboard.xyz.
  2. The response is 202.
  3. The dashboard shows a new pageview, from a new visitor.

If the pageview arrives but visitors stay at 1, check the IP headers.

On this page