Reverse-proxy tracking: how to beat ITP without breaking anything
Your backend receives the events and forwards them server-to-server. An hour of work, no disguises, and the visitor id stops expiring after seven days.
your origin receives → your server sets the cookie → you forward server-to-server
A reverse proxy makes the tracking request genuinely first-party — your own backend, your own code, your own domain — and lets your server set the visitor id in an HTTP response, where Safari's 7-day cap does not apply.
This is not CNAME cloaking. Nothing is disguised, because nothing needs to be.
If you install any analytics snippet directly, the browser sees a request from your page to somebody else’s domain. That is third-party, and two things follow: blockers can drop it, and any identity written from JavaScript is capped at seven days in Safari.
A reverse proxy fixes both. It takes about an hour.
The idea
Instead of the browser talking to the analytics vendor, the browser talks to you, and you talk to the vendor.
Before: browser ──→ flowsk.com/api/events
After: browser ──→ yoursite.com/api/events ──→ flowsk.com/api/events
(your code, your origin) (server-to-server)
Three consequences follow immediately:
- Nothing on your page is third-party. There is no hostname for a blocklist to contain.
- Your server sees the request, so it can read the cookie itself — the browser cannot lie about which visitor this is.
- Your server can set the cookie, in a
Set-Cookieresponse header, which is exempt from Safari’s seven-day cap on JavaScript-written cookies.
Point three is the one that actually matters. The blocking immunity is nice; the durable identity is the reason to do this.
The three pieces
Mint the id, on every request, before rendering:
// middleware.ts (Next.js — the same idea works in any framework)
export function middleware(request: NextRequest) {
const response = NextResponse.next()
if (!request.cookies.get("_fsk_id")) {
response.cookies.set("_fsk_id", crypto.randomUUID(), {
maxAge: 60 * 60 * 24 * 730, sameSite: "lax", path: "/"
})
}
return response
}
Receive events on your origin, read the id from the cookie header, forward, return immediately:
export async function POST(request: Request) {
const { events } = await request.json()
const anonymousId = request.headers.get("cookie")?.match(/_fsk_id=([^;]+)/)?.[1]
fetch("https://flowsk.com/api/events", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ write_key: process.env.FLOWSK_WRITE_KEY, anonymous_id: anonymousId, events })
}).catch(() => {})
return new Response(null, { status: 204 }) // do not make the visitor wait
}
Point the snippet at yourself:
<script async src="https://flowsk.com/flowsk.js"
data-write-key="pk_live_…" data-endpoint="/api/events"></script>
Why this is not cloaking
The distinction matters, and it is not a semantic one.
CNAME cloaking points analytics.yoursite.com at a vendor’s servers. The vendor’s infrastructure answers, sets its own cookie, and the browser is told a third-party interaction is first-party. Safari detects it and caps the cookie at seven days. Blocklists have started catching the pattern. It is a disguise with a known expiry date.
A reverse proxy means your server answers. Your code runs. Your cookie. You then make a separate server-to-server call, which no browser is party to and none has an opinion about.
The difference is not how it looks to a browser — it is that in one case nothing is being hidden. Which is also why it does not stop working when the next browser release ships.
Details that catch people
Do not block on the forward. Return 204 immediately. Enqueue if your framework makes it easy. The visitor’s request should end at your server.
Read the id server-side. Take it from the cookie header, not from the request body. It stays correct even if the snippet failed to load, and it cannot be spoofed by a client.
Exclude assets from the middleware matcher. Otherwise you mint ids for image requests and inflate your visitor count.
Watch full-page caching. A cached response can carry someone else’s Set-Cookie or have it stripped entirely. Test by loading twice in separate private windows and confirming two different ids.
Handle the failure path silently. If the forward fails, log it and move on. Measurement must never break a page.
Verifying it worked
- Load your site in a private window.
- Check the response headers for
Set-Cookie: _fsk_id=…with a longMax-Age. - Run the cookie inspector — the cookie should show Survives ITP: yes.
- Open a browser with an ad blocker on and confirm events still arrive.
That last check is the satisfying one.
The full step-by-step for Rails, Next.js, WordPress and edge runtimes is in the proxy setup docs.
Frequently asked questions
How is this different from CNAME cloaking?
CNAME cloaking points a subdomain's DNS at a third-party host so their cookie appears first-party. Browsers detect the pattern and Safari caps those cookies anyway. In a reverse proxy, your backend genuinely receives the request and forwards it — there is nothing to detect.
Does this add latency for my visitors?
It should not. Return a 204 immediately and forward asynchronously, or enqueue. The visitor's request ends at your server.
Do I need a dedicated server?
No. Edge middleware works fine — Next.js middleware, Cloudflare Workers, Netlify Edge Functions, or a small route in whatever backend you already run.
What if my site is fully static?
Edge functions still run per request. Any layer on your domain that can set a response header can mint the cookie.
Is this against any ad platform's terms?
This is about your own first-party measurement, not about how you send data to ad platforms. Their conversion APIs are a separate integration with their own rules, which this does not touch.
Verify the durable cookie exists
Run the cookie inspector against your site after setting this up. A server-set first-party id should appear with Survives ITP: yes.
Stop guessing which ad made the sale.
Flowsk Signals stitches the anonymous click to the email to the purchase — first-party, server-side, de-duplicated. One snippet, $29/mo, and every conversion comes with a receipt you can inspect.