Skip to content
Learn · Measure & improve

Server-side vs client-side tracking

Client-side tracking fires events from the browser and sees context — page, device, UTM, referrer — while server-side tracking sends events from your back end and sees truth. Ad blockers and Safari's seven-day caps make the client side lossy; the server side is blind to context. The practical answer is both, split by event class.

Updated 7 Aug 20267 min readBy fromHello
Key takeaways
  • Client-side tracking captures attribution context — UTM, referrer, device — but per GWI data for Q2 2025, 29.5% of internet users use an ad blocker at least sometimes, so a share of browser events never arrives.
  • Safari's ITP caps JavaScript-set cookies to a seven-day expiry and deletes other script-writable storage — localStorage, IndexedDB — after seven days of Safari use without interaction on your site, so returning Safari visitors look new.
  • Revenue and subscription events belong server-side: subscription_started happens in your back end, out of any blocker's reach.
  • Track on both sides, joined by one user ID — client for context, server for truth — and accept a persistent gap between the two counts.

What's the difference in one line?

Client-side tracking sees context; server-side tracking sees truth. The browser knows the UTM parameters, the referrer, the device, and the page — and loses events to blockers. Your back end knows what actually happened — a subscription started, a payment failed — and knows nothing about how the user got there. This guide covers where to instrument. For what to track — naming and the starter event list — see the event tracking plan for startups; for why the data should be first-party at all, see first-party data and tracking. Both halves belong in one tracking plan.

Four terms that frame the client-versus-server decision.

What does client-side tracking see — and miss?

Client-side is where context lives. The SDK in the browser captures UTM parameters, the referrer, the landing page, the device, and session behavior — everything attribution and funnel analysis need. What it misses is whole users: per GWI data aggregated by Backlinko, 29.5% of internet users worldwide used an ad blocker at least sometimes in Q2 2025 — roughly 1.77 billion people. Many blockers stop analytics scripts along with ads, so a measurable share of your client-side events is never sent at all.

Even without a blocker, Safari makes client-side tracking lossy. Per WebKit's ITP documentation, Safari caps JavaScript-set cookies to a seven-day expiry and deletes all other script-writable storage — localStorage, IndexedDB — after seven days of Safari use without interaction on your site. Once that seven-days-of-use window passes, a returning visitor looks brand new, and identity breaks. The loss is measurable: when Plausible compared its own counts against Google Analytics on a site trending on Hacker News and Reddit in a late-August 2021 test, Google Analytics missed 58.67% of visitors — an extreme, tech-heavy case; Plausible cites earlier research putting blockage under 10% on lifestyle sites and above 25% on tech-focused ones.

What does server-side tracking see — and miss?

Server-side tracking is your back end reporting state changes to the events API: subscription_started, payment_failed, plan_changed. These events happen on your servers, not in the browser — recording them there is not a workaround, it is the honest source. No blocker sits in the path, no storage cap expires them, and the count matches your database because it comes from your database. A server-side event is authoritative because it records what your system did, not what a browser managed to report. The blind spot is context: your back end has no idea which campaign, referrer, or device brought the user in — that context only ever existed client-side.

Client vs server: which events go where?

Event classTrack itWhy
Page views & campaign attributionClientUTM, referrer, and landing page only exist in the browser.
In-product feature usageClient (server too if critical)UI context matters; add a server copy when a metric depends on it.
Signup & loginBothThe identity join point — the same user ID on both paths.
Revenue & subscription stateServerMoney events happen in your back end; the count must match your database.
Email, webhook & integration eventsServerThey arrive as webhooks from your providers — nothing client-side to capture.

How should a small team split it?

  • Anything money — trials, subscriptions, payments, refunds — goes server-side. When churn or MRR must be right, the server is the source of truth.
  • Anything attribution — page views, campaigns, referrers — stays client-side. That context cannot be reconstructed later.
  • Identify on both paths with the same user ID, so funnels stitch and A/B tests with low traffic assign each user to one variant across devices.
  • Accept the gap. Client counts run low by design; use them for direction, and server events for anything you report.

Why do the two numbers never match?

Run both and the counts will disagree — permanently. The client number runs low because blockers and ITP eat events; the server number lacks the context to say where users came from. This is not a bug to fix but a property to name: expect a persistent gap between client analytics and server truth, know its rough size for your audience, and stop chasing it. Report each number from its honest side — attribution and funnel direction from client data; anything that reaches an investor deck or an invoice — churn rate, MRR, active subscriptions — computed from server events. Engagement platforms like Iterable and Ortto accept both sources for the same reason.

How does fromHello handle it?

fromHello treats the split as the default. The JS/TS SDK tracks client-side and keeps an offline queue backed by localStorage — events fired during a connection loss persist and are retried when the browser comes back online — while the same events API accepts server-side sends, so subscription_started comes straight from your back end with an API key. Both paths write to one profile: the attribution the SDK captured and the revenue events your server sent describe the same person. Segments, journeys, and analytics read from that single profile — you split the instrumentation, not the identity.

FAQ

Common questions

  • Should I switch everything to server-side tracking?

    No. Server-side events have no UTM, referrer, or device context, so moving everything there trades loss for blindness. Keep attribution and in-product context client-side, move money and lifecycle state server-side, and join the two with one user ID. The split by event class beats either extreme.

  • Does server-side tracking bypass ad blockers?

    Framed honestly: your own first-party events sent from your back end never traverse the browser, so there is nothing for a blocker to block. That is not a loophole against users — your product's own events are not ad tech, and consent still applies either way. Under GDPR, consent is about the person and the purpose, not the transport the event took.

  • Which events should move server-side first?

    Revenue and subscription lifecycle: subscription_started, payment_failed, plan_changed, cancellations, refunds. They already happen in your back end, they drive churn and MRR math, and they are the events where an undercount costs you real decisions.

  • How do client and server events join up?

    Through a shared user ID. Give the SDK in the browser the same ID your back end sends to the events API, and both streams land on one profile. Funnels, experiments, and segments then read one person instead of two half-people.

See the platform the team runs.

Related guides
Early access

Put your growth teamon autopilot.

Early access opens gradually, so the team tunes to real use cases. Small teams with big ambitions go first.

Not ready to share an email? It's open source. Run it yourself today. View on GitHub

No spam. One email when your spot opens. Unsubscribe at any time.