Server-side tracking: what it is, what it fixes and what it can't
Server-side tracking records clicks and payments on your own server. What it fixes in Safari and ad blockers, what Cloud Run costs and a 6-step SaaS setup.
Muzahid Maruf, FounderUpdated
On this page
Explore with AI
Opens this article inside the chosen assistant with a ready-made prompt.
Server-side tracking means a server you control records the event, such as a click or a payment, instead of JavaScript in the visitor's browser.
The browser can still send the request, but the log entry, the cookie and any forwarding to Google Analytics 4, Google Ads or Meta happen on your infrastructure.
That removes several losses: blocked scripts, the 7-day limit Safari Intelligent Tracking Prevention puts on script-written cookies, and beacons that die with the tab. Consent and cross-device identity stay as they were.
Key takeaways
- Safari caps JavaScript cookies at 7 days since ITP 2.1, or 1 day after link decoration since ITP 2.2, but a cookie set in an HTTP response keeps its expiry unless a third-party CNAME serves it, which Safari 14 caps at 7 days.
- A Google Cloud Run server container costs about $45 per server a month with 2 recommended, while a redirect and a Stripe webhook run inside the app you already host.
- EDPB Guidelines 2/2023 place tracking links and pixels under Article 5(3) of the ePrivacy Directive, so consent stays on the table even when a server does the recording.
How do browser tags and server records differ?
| Question | Client-side tag | Server-side record |
|---|---|---|
| Where the code runs | The visitor's browser | A machine you operate |
| How the cookie is set | JavaScript, through document.cookie | An HTTP Set-Cookie header |
| Cookie lifetime in Safari | 7 days (ITP 2.1), or 1 day after link decoration (ITP 2.2) | The expiry you set on your own domain, such as 365 days; 7 days when a third-party CNAME serves a subresource |
| Ad blockers | Drop a listed script or beacon host | Drop only a listed tracking URL |
| Scroll depth and form fields | Visible | Invisible |
| Stripe payment | Seen only if the buyer reaches your thank-you page | Seen through a webhook, retried for up to 3 days |
Sources: WebKit and Stripe documentation.
A server-side record is an event that a machine you operate logs, and a client-side tag is JavaScript running in the visitor's browser.
The two differ in 3 ways: where the code runs, how long the cookie lasts and what each side can see. Cookie lifetime decides the most for a SaaS product.
Safari Intelligent Tracking Prevention (ITP 2.1, shipped in Safari 12.1 on February 21, 2019) caps a JavaScript cookie at 7 days, so a trial that pays on day 14 keeps its source only if something outlasted that cap.
A cookie set in a redirect response keeps the 365-day expiry you chose. The server never sees scroll depth or form fields, and the browser never sees the Stripe charge unless the buyer reaches the thank-you page.
Which four setups share the name?
| Setup | What the server records | Still in the browser | Documented figures |
|---|---|---|---|
| Redirect click log | The click, plus the cookie in the redirect response | Nothing until the landing page loads | A 301 may be reused from cache without reaching you, and a 302 may not (RFC 9110) |
| Server container (server-side Google Tag Manager) | Events the container receives, forwarded to Google Analytics 4, Google Ads or Meta | The web tag that feeds the container, unless your own web server does | About $45 a month per Cloud Run server, 2 recommended (Google) |
| Conversion APIs (Meta Conversions API, GA4 Measurement Protocol) | Conversions your backend sends | The Meta Pixel or Google tag, for event IDs and client IDs, since Measurement Protocol is meant to augment gtag or Tag Manager | Meta deduplicates events arriving within 48 hours; Measurement Protocol takes 25 events and 130 kB per request, with timestamps up to 72 hours back (Google) |
| Payment webhook (Stripe) | The charge, pushed to your endpoint, the same server-to-server pattern as an affiliate postback URL | Nothing | Retries for up to 3 days in live mode; resend for 15 days in the Stripe Dashboard, 30 with the Stripe CLI (Stripe) |
Do server-set cookies still meet Safari's rules?
A cookie set in a server response escapes the 7-day script cap, but Safari applies other rules to servers, CNAMEs and link decoration.
Statcounter's September 2026 data puts Safari at 46.38% of US mobile usage, and 18.27% worldwide, so a Safari cap touches a large share of mobile traffic in the US.
| Date | Release | Rule | Cookie your server sets in a response |
|---|---|---|---|
| Feb 21, 2019 | ITP 2.1, Safari 12.1, iOS 12.2 | Cookies created with document.cookie capped at 7 days | Not affected, since only document.cookie cookies are covered |
| Apr 24, 2019 | ITP 2.2, iOS 12.3 | document.cookie cookies capped at 1 day after link decoration from a classified domain | Not affected |
| Mar 24, 2020 | Safari 13.1, iOS 13.4 | Third-party cookies blocked; script-writable storage deleted after 7 days of Safari use without interaction | Not affected on your own domain |
| Nov 12, 2020 | Safari 14 | Cookies set in responses to third-party CNAME-cloaked subresource requests capped at 7 days | Capped when a subresource on your site, such as a tagging subdomain, CNAMEs to a third party |
WebKit's November 2020 post cites a study that found 1,762 websites using CNAME cloaking for 56 trackers. WebKit's tracking prevention page adds that ITP detects third-party IP address cloaking too, with the same 7-day cap, and leaves that term undefined.
A CNAME to another subdomain of your site is not capped. Google's custom domain guide, last updated on June 27, 2025, says a server container left on its default provider domain can set JavaScript cookies only.
Stape's Cookie Keeper power-up, per its documentation, restores cookies that Safari 16.4 and later expire after 7 days.
Does recording on a server remove the need for consent?
Consent is required in many setups, because a server does not remove the legal question. The EDPB's Guidelines 2/2023 reached version 2.0 on 7 October 2024, after a consultation that ran from 16 November 2023 to 18 January 2024.
They treat tracking links and tracking pixels as falling under Article 5(3) of the ePrivacy Directive (2002/58/EC). Sending the link to the device counts as storage, and collecting the identifier counts as gaining access.
Being covered does not automatically require consent, since exemptions are assessed case by case. UK and US state rules differ, so take legal advice on your own setup.
TrackRev's pixel waits for grantConsent() when data-requires-consent is set, and in our setup the redirect itself runs no script.
What does a server container cost?
A Google Cloud Run server container costs about $45 per server a month, and Google recommends 2 servers, so the floor is $90 a month or $1,080 a year.
Stape Pro costs $17 a month on yearly billing for 500K requests, and Stape Free covers 10K requests.
| Option | Monthly figure | Annual cost | Capacity |
|---|---|---|---|
| Cloud Run, 2 servers | 2 x $45 = $90 | $1,080 | Google's minimum recommendation |
| Cloud Run, 10 servers | 10 x $45 = $450 | $5,400 | 35 to 350 requests per second across 2 to 10 servers |
| Stape Free | $0 | $0 | 10K requests |
| Stape Pro | $17 | $200 | 500K requests |
| Stape Business | $83 | $1,000 | 5M requests |
| Stape Enterprise | $167 | $2,000 | 20M requests |
| Redirect and webhook in your app | No separate bill | $0 extra | Your existing hosting plan |
Prices from each vendor's pricing page, October 2026. Google's figure comes from its setup guide, and Stape's monthly figures assume yearly billing.
Two Cloud Run servers cost $880 a year more than Stape Pro at $200 a year, per Stape's pricing page.
For source-to-payment attribution alone in our experience building TrackRev I would skip the container at first, since the redirect and the webhook cover the click and the charge, the events that decide the answer.
When is browser-side tracking enough?
Browser-side tracking is enough when the payment lands within 7 days of the click and no partner is paid from the result.
Safari 12.1 and later cap script-written cookies at 7 days, or 1 day after link decoration under ITP 2.2 (April 24, 2019).
For example, a trial that converts on day 14 needs a response cookie, because Google Analytics 4 files the charge under Direct.
Meta Ads and Google Ads optimization needs both sides: the tag captures the session and the Stripe backend sends the conversion.
| Situation | Browser tag enough? | Reason |
|---|---|---|
| Payment 1 to 7 days after the click | Usually | A script cookie lasts 7 days, unless link decoration cuts it to 1 |
| Payment 8 to 30 days after the click | No | The cookie can expire first, and the charge lands under Direct |
| Partner commissions | No | A lost source pays the wrong partner, or none |
| Optimizing Meta or Google Ads on conversions | Both | The tag captures the session and your backend sends the conversion |
How do you log the click and set the cookie?
A logged click is a row your own server writes before the landing page loads, and it takes 2 of the 6 steps. First (step 1), put a redirect on a domain you own.
Share links like go.example.com/spring-launch, and tag every one, since an untagged click logs a blank channel. The route writes a click row with visitor ID, link, timestamp, user agent and referer, then answers 302.
Node.js crypto.randomUUID() returns a version 4 UUID for the visitor ID. A cache may reuse a 301 without asking again, so a repeat click behind a 301 can skip your server.
Second (step 2), send the visitor cookie on the redirect with SameSite=Lax, Secure and a Domain on your registrable domain, so subdomains can read it.
MDN's Set-Cookie reference says Lax also sends it on cross-site top-level navigations such as an email click, and Strict does not. Append the ID to the destination as vid and accept only UUID-shaped values on the landing page.
import { randomUUID } from "node:crypto"; app.get("/:slug", async (req, res) => { const link = await db.links.findBySlug(req.params.slug); if (!link) return res.redirect(302, "https://www.example.com"); const vid = req.cookies.vid ?? randomUUID(); await db.clicks.insert({ linkId: link.id, visitorId: vid, userAgent: req.get("user-agent"), referer: req.get("referer"), at: new Date(), }); res.cookie("vid", vid, { maxAge: 365 * 24 * 60 * 60 * 1000, // Express takes milliseconds domain: ".example.com", sameSite: "lax", secure: true, httpOnly: true, }); const target = new URL(link.destination); target.searchParams.set("vid", vid); res.redirect(302, target.toString());});How do you bind the ID to the signup and the payment?
A bound ID is a visitor ID saved on the user row and on the Stripe payment, and it takes 2 more of the 6 steps.
Third (step 3), read the ID in the request that creates the account and save it beside the email, since a script cookie may be gone when someone signs up 8 days after the click.
For example, a user who clicked on May 1 and signed up on May 9 still carries the May 1 visitor ID. A phone click and a laptop purchase share no ID until a login or email joins them.
Fourth (step 4), pass the ID to Stripe Checkout as client_reference_id or in metadata. Both fields come back on the Stripe Checkout Session in the checkout.session.completed event, the same event Stripe retries for up to 3 days.
The Stripe metadata attribution setup post has the field mapping.
How do you read charges and test the result in Safari?
Fifth (step 5), read charges through a Stripe webhook. Verify the Stripe-Signature header against the raw request body and return a 2xx before any heavy work. Stripe retries a failed delivery for up to 3 days in live mode.
Stripe's libraries reject a timestamp more than 5 minutes old by default.
Store each Stripe event ID, since the same event can arrive twice and out of order, and serve the endpoint over TLS 1.2 or 1.3, the only versions Stripe supports.
Sixth and finally (step 6), test in Safari. Click a tracked link in Safari 17 or later and read the cookie's expiry on the Storage tab of Safari Web Inspector.
A date 365 days out means the response cookie survived, and a date 7 days out means something capped it. The Safari ITP attribution post covers the remaining rules.
How does TrackRev record clicks on the server?
TrackRev is SaaS affiliate software that logs every click on our own redirect server, and we built TrackRev with link tracking and revenue attribution inside it.
A visit to a tracked link reaches TrackRev's redirect first, which logs the click, sets a 365-day visitor-ID cookie and forwards the visitor with a 302 response. A pixel on the landing page then reads the _vid parameter.
Revenue attribution starts at $29 a month on Indie, billed monthly for 1 site. Payments sync from Stripe, Paddle Billing, Polar, Lemon Squeezy, Creem and Dodo Payments. A team that needs only a click log can use a standalone tracker.
| Step | What TrackRev does | Detail |
|---|---|---|
| Click | Logs each click at its own redirect before your page loads | 302 response with a visitor-ID cookie (Lax, Secure, 365 days); UTM parameters and _vid added to the destination. See first-party tracking |
| Record | Timestamp, IP address, location, device, browser, OS, referrer, bot flag | Crawler and empty user agents and prefetches flagged; click.created webhook for humans only. See bot filtering |
| Identity | A pixel reads _vid and writes its own JavaScript cookie | Safari's 7-day cap applies to that copy; window.trk.identify binds the email on the server |
| Payment | Stripe read hourly with a read-only restricted key | Optional webhook; match order is metadata.vid, client_reference_id, email. See revenue attribution |
| Consent | Pixel waits for grantConsent() when data-requires-consent is set | The redirect itself runs no script |
| Plans | Free: 50 links and 1,000 tracked events a month | Revenue attribution starts at $29 a month on Indie, per the pricing page |
| Not included | A tag manager, or forwarding to ad platforms | A server container or the Meta Conversions API does that job |
Found this useful? Share it.
Frequently asked questions
- Tracking where your own server records the event instead of a browser script, such as a redirect click log, a Meta Conversions API call or a Stripe webhook.
- WebKit's definition covers subresource requests, which Safari 14 capped at 7 days on November 12, 2020. A short link domain that CNAMEs to a vendor is a top-level navigation, and I found nothing from WebKit on how ITP treats its cookie, so check the expiry in Safari.
- Yes when the event never depended on a browser script, like a redirect click or a Stripe webhook. A tracking URL on a filter list can still be blocked.
- Possibly. The EDPB's Guidelines 2/2023, version 2.0 from October 2024, cover tracking links and pixels, and consent is needed unless an exemption fits your use.
- Reload with Chrome DevTools or Safari Web Inspector open. A server-set cookie appears in a response's Set-Cookie header on the Network tab, and a checked HttpOnly column usually points to the server.
- No. Server-side Google Tag Manager is one product that implements it, running on Google Cloud Run at about $45 per server a month. A redirect log or a webhook receiver counts too and needs no container.
- Three carry most of the weight: checkout.session.completed for the first payment of a $49 plan, invoice.paid for each of the 11 renewals in a 12-month year and charge.refunded for returns. Save the visitor ID from event 1 and reuse it for events 2 and 3, so one $49 customer keeps a single source across all 12 payments.

Written by
Founder, TrackRev.io & Contant.io
Muzahid Maruf founded TrackRev.io, SaaS affiliate software with no limit on tracked revenue, and Contant.io. He writes about affiliate programs.
Writes about Marketing attribution · Link tracking · Revenue analytics · SaaS growth
Run your affiliate program without a revenue cap.
Affiliate software for indie hackers and small SaaS teams: $29 a month flat, 0% fees, link tracking and revenue attribution built in. Free to start, no card.