Affiliate tracking without third-party cookies: 8 steps for Stripe SaaS

Google kept third-party cookies in Chrome while Safari, Firefox and Brave restrict them. Build first-party affiliate tracking in 8 steps with Stripe and email.

Muzahid Maruf — Founder of TrackRev.io

Muzahid MarufUpdated

Affiliate programs · 8 min read
On this page
  1. 01Which browsers block third-party cookies in 2026?
  2. 02Why do third-party cookies fail affiliate tracking, and what replaces them?
  3. 03How do you set up affiliate tracking without third-party cookies in 8 steps?
  4. 04What happens when a trial pays on day 16?
  5. 05What does a first-party setup still miss?
  6. 06What does TrackRev do for first-party affiliate tracking?

Explore with AI

Opens this article inside the chosen assistant with a ready-made prompt.

Affiliate tracking without third-party cookies keeps the proof of each sale on systems you control: a click log on your own domain, a visitor ID in a first-party cookie, and that ID on the Stripe payment.

A server matches payment to click and credits the partner. Chrome, at 66.36% of worldwide usage, still allows third-party cookies, but Safari has blocked them since March 24, 2020 and Firefox has partitioned them since version 103.

The method has 4 parts: a redirect that logs the click, a visitor ID, a payment reference and a server job that matches them.

According to Statcounter, Safari holds 18.27% of worldwide usage, so the cookie rules reach a large share of buyers.

Key takeaways

  • No Chrome removal date exists after Google's two 2025 announcements. Safari's block dates from 2020 and Firefox's partitioning from version 103, so those buyers are the ones a cookie-dependent tracker loses.
  • Statcounter's September 2026 data gives Safari 18.27% of worldwide browser usage and 46.38% of US mobile.
  • Four pieces do the work: a redirect that logs the click, a visitor ID, a payment reference and a server job that matches them.
  • Stripe takes the ID in client_reference_id (200 characters) or metadata (50 keys, 500-character values).
  • Save the visitor ID on the user row at signup and read it from there at checkout, so a Safari visitor whose script cookie was deleted after 7 days still resolves.

Which browsers block third-party cookies in 2026?

DateBrowser (share)What changed
Feb 21, 2019Safari (18.27%)Intelligent Tracking Prevention 2.1 caps persistent cookies created through document.cookie at 7 days (Safari 12.1)
Mar 24, 2020Safari (18.27%)Safari 13.1 blocks cookies for every cross-site resource by default
Firefox 103Firefox (2.8%)State partitioning is on by default for all users, so Firefox stores a third-party cookie separately for each top-level site
Default settingBrave (not reported)Shields block cross-site cookie tracking
Apr 22, 2025Chrome (66.36%)Google will not roll out a standalone prompt for third-party cookies; users choose in settings, and Incognito windows block them
Oct 17, 2025Chrome (66.36%)Chrome maintains its current approach to cookie choice, and 10 Privacy Sandbox technologies are retired, including the Attribution Reporting API

Shares are Statcounter's worldwide figures for all platforms, September 2026.

No Chrome cutoff is coming, which leaves Safari as the largest of the blockers. Statcounter's US mobile data for September 2026 puts Safari at 46.38%, 1.06 points behind Chrome's 47.44%, with Samsung Internet at 3.28% and Firefox at 1.29%.

Worldwide across all platforms, Edge holds 6.04% and Samsung Internet 2.31%. Split paying customers by browser to find your own mix. Safari and Firefox together hold 21.07% of worldwide usage, so blocking affects about 1 visitor in 5.

Third-party cookie blocking means a browser refuses cookies set by a site other than the one the visitor is on.

BrowserWorldwide, all platformsUS mobile
Chrome66.36%47.44%
Safari18.27%46.38%
Edge6.04%Not listed
Firefox2.8%1.29%
Samsung Internet2.31%3.28%

Statcounter, September 2026.

Why do third-party cookies fail affiliate tracking, and what replaces them?

A network-style link such as network.example/click?aff=123 sets a cookie on network.example and redirects to your site. A pixel on your confirmation page then fires at network.example, which matches the sale only if the browser attaches that cookie.

Safari 13.1 blocks that cookie by default. Firefox 103 looks only in the jar partitioned under your site, which is empty. Content blockers can drop the pixel request too.

WebKit's tracking prevention page says ITP deletes script-created cookies after 7 days without user interaction and caps response cookies at 7 days for third-party CNAME or IP address cloaking.

Nothing on that page limits a cookie your server sets on your own domain, so the redirect carries the identity. The Safari ITP post has the full rule table.

Per WebKit's page, checked in 2026, no rule limits a server cookie on your own domain.

How do you set up affiliate tracking without third-party cookies in 8 steps?

StepWhere the ID livesNumber to remember
2Click row and a first-party cookieMax-Age=31536000, 365 days
3Cookie on the registrable domain7-day Safari cap on script copies
4User row beside the emailStill present on day 9 in the example
5Stripe Checkout Session200 characters, and a UUID uses 36
6Click lookup by ID, then by email45-day lookback
7Coupon code on the invoice1 unique code per partner
8Three test browsersSafari, Firefox, Chrome Incognito

Steps 1 to 8 at a glance. Step 1 is the partner link itself.

A first-party link is a partner URL served from a domain the merchant owns. Give each partner a link such as go.example.com/anna, backed by a redirect you run.

If the host CNAMEs to a vendor, read the cookie's expiry in Safari after a test click, because WebKit's Intelligent Tracking Prevention caps response cookies at 7 days when it detects CNAME cloaking.

A 7-day cap on a 60-day cookie window cuts the window by about 88%. A branded subdomain also lets you change vendors later without asking partners to rewrite links, because every link already carries your own name.

Step 2: how do you log the click and mint a visitor ID?

The redirect is the component that records each click before the visitor reaches the site. The redirect writes one click row and skips bots and link-preview fetchers.

A 302 is a temporary redirect, and the response carries both the destination with _vid on the URL and the ID in a 365-day cookie.

One row per click gives the later match a timestamp, a partner and a user agent to work with.

According to WebKit's tracking prevention page, the 7-day rule targets JavaScript-written cookies, so the 365-day cookie from the redirect is not on that list. The route below stores five fields and returns the redirect in a single response.

A redirect route on your own domain
// GET go.example.com/:partnerexport async function GET(req: Request, { params }: { params: { partner: string } }) {  const ua = req.headers.get("user-agent") ?? "";  const existing = /(?:^|; )vid=([^;]+)/.exec(req.headers.get("cookie") ?? "")?.[1];  const vid = existing ?? crypto.randomUUID();   // Partner, timestamp, user agent, referrer and visitor ID in one row  if (!isBot(ua)) {    await db.clicks.insert({      vid,      partner: params.partner,      userAgent: ua,      referrer: req.headers.get("referer"),      at: new Date(),    });  }   const dest = new URL("https://example.com/pricing");  dest.searchParams.set("_vid", vid);   return new Response(null, {    status: 302,    headers: {      Location: dest.toString(),      "Set-Cookie": "vid=" + vid + "; Max-Age=31536000; Path=/; Domain=example.com; Secure; SameSite=Lax",    },  });}

Step 3: how do you keep the ID on your own site?

A registrable domain is the part of a hostname a site owner can register, such as example.com. A cookie set with Domain=example.com works on every subdomain, so go.example.com and app.example.com share one visitor ID.

If the redirect shares your registrable domain, step 2's cookie is already on your site.

A redirect on another domain hands over only _vid, so read it on the landing page and write the cookie yourself; Safari deletes script-written cookies after 7 days without interaction.

Check each hop to the signup form (http to https, a trailing slash, an /en/ prefix) for a dropped query string.

Runs on every landing page when the redirect lives on another domain
const vid = new URLSearchParams(location.search).get("_vid");
if (vid && !/(?:^|; )vid=/.test(document.cookie)) {
  document.cookie = "vid=" + vid + "; Max-Age=31536000; Path=/; Domain=example.com; Secure; SameSite=Lax";
}

Step 4: how do you bind the ID to an email at signup?

A signup bind is the step that copies the visitor ID onto the user record. Store the visitor ID on the user row beside the email, since a script-written cookie can disappear after 7 days.

An email survives a deleted cookie, a new browser and a second device, and the email sits on the Stripe customer too. The bind costs one extra column and one line in the signup handler, shown below.

In the day-16 example later in the post, the script cookie is gone on day 9, 7 days before payment, and the user row still holds the ID.

In the signup handler
const vid = req.cookies.get("vid")?.value ?? null;
await db.users.insert({ email, vid, createdAt: new Date() });

Step 5: how do you write the ID onto the payment?

Writing the ID onto the payment is the step that lets Stripe return the visitor ID with the webhook. A UUID uses 36 characters, 18% of the 200-character limit on client_reference_id.

Read the ID from the user row when you create the Stripe Checkout Session in your backend. A visitor whose cookie vanished on day 9 then still pays on day 16 with the ID attached.

A Payment Link, Buy Button or pricing table has no backend step, so the cookie value goes into its URL parameter or attribute and the email route covers the rest.

Checkout pathWhere the ID goesLimit
Checkout Session from your backendclient_reference_id200 characters
Subscription created by Checkoutsubscription_data.metadata50 keys, keys to 40 characters, values to 500
Payment Linkclient_reference_id URL parameterAlphanumeric, hyphen and _ characters, up to 200
Buy Button or pricing tableclient-reference-id attributeSame characters, up to 200
PaymentIntent you create yourselfmetadata on the PaymentIntentSame 50, 40 and 500 limits, copied once to the Charge

Limits from Stripe's Checkout Sessions, Payment Links, Buy Button and pricing table documentation.

A UUID is 36 characters, inside every limit above. Stripe's metadata documentation lists a few exceptions and otherwise says metadata does not copy between objects, so session metadata reaches the Subscription only through subscription_data.metadata.

The Payment Links page covers the URL parameter, the Buy Button and pricing table pages cover the attribute, and the Stripe metadata attribution setup post maps every field.

A Checkout Session that carries the visitor ID from the user row
const user = await db.users.find(currentUserId); const checkout = await stripe.checkout.sessions.create({  mode: "subscription",  line_items: [{ price: "price_123", quantity: 1 }],  customer_email: user.email,  client_reference_id: user.vid,  subscription_data: { metadata: { vid: user.vid } },  success_url: "https://example.com/welcome",  cancel_url: "https://example.com/pricing",});

Step 6: how do you match payments to clicks on the server?

A matching job is a server routine that joins each payment to a click. Listen for checkout.session.completed, which carries the session's client_reference_id and metadata.

Look up the click by visitor ID, then by the customer's email, within 45 days of the payment (a 14-day trial plus 30 days to decide, rounded up). Copy the credit onto each renewal.

The credit rule matters when a visitor clicked 3 partners, say A on day 1, B on day 10 and C on day 20, then paid $79 on day 21 (the attribution models comparison covers the trade-offs):

RuleCreditedCommission at 20%
Last clickPartner C, $79$15.80 to C
First clickPartner A, $79$15.80 to A
SharedA, B and C, $26.33 each$5.27 each
The webhook handler, after you verify Stripe's signature
if (event.type === "checkout.session.completed") {  const s = event.data.object;  const vid = s.client_reference_id ?? s.metadata?.vid ?? null;   const click =    (vid && (await db.clicks.latestForVisitor(vid, { withinDays: 45 }))) ||    (await db.clicks.latestForEmail(s.customer_details?.email, { withinDays: 45 }));   if (click) {    await db.commissions.createOnce({ sessionId: s.id, partner: click.partner });  }}

Step 7: how do you cover sales that have no click?

Give podcast, YouTube and Instagram partners a personal coupon code and credit the sale when it shows on the invoice, as the coupon code tracking guide describes. A code shared by all partners cannot tell them apart.

A coupon fallback is the credit rule for sales that arrive without any recorded click, such as a podcast listener who types a code at checkout.

Unique codes also expose abuse, because a code posted on a coupon site appears on invoices with no partner click behind it.

Step 8: how do you test in Safari, Firefox and a private window?

A test run is a proof of the whole chain, not one component. Safari on an iPhone exercises the 7-day script-cookie rule, Firefox exercises state partitioning, and a private window exercises the no-cookie path.

In Stripe test mode, click a partner link in Safari on an iPhone, sign up, delete the site's cookies and pay. The click row, user row and payment should carry one visitor ID.

Repeat in Firefox and Chrome Incognito, then pay once with the ID stripped to prove the email route.

What happens when a trial pays on day 16?

The numbers here are invented for illustration. A visitor clicks a partner link in Safari on day 0. The redirect runs on a short domain, so the landing page writes the cookie with JavaScript.

The visitor signs up on day 2 for a 14-day trial and pays $79 on day 16, and the partner earns 20% recurring.

DayEventScript cookie in SafariWhat the match uses
0Click on the partner linkNone yetClick row and visitor ID from the redirect
2Signup, trial startsWrittenVisitor ID saved on the user row with the email
97 days without a visitDeletedNothing changes; the user row still holds the ID
16Trial converts, $79 chargeAbsentID read from the user row into Stripe; email as fallback

At 20%, the first payment earns the partner $15.80 ($79 × 0.20). A customer who stays 12 months earns 12 × $15.80 = $189.60.

Without the signup bind, the day-16 payment arrives with no source and the partner loses that whole stream, since every renewal inherits the first payment's credit.

Months the customer staysRevenue at $79Partner earns at 20%
1$79$15.80
3$237$47.40
6$474$94.80
12$948$189.60
24$1,896$379.20
36$2,844$568.80

What the signup bind protects, using the same invented figures.

What does a first-party setup still miss?

A first-party setup is a strong default but not a complete one. The table lists 5 gaps and the step that covers each, for example a $79 purchase made on a laptop after a click on a phone.

Cross-device purchases fall to the email match in step 4, bare-URL shares fall to coupon codes in step 7, and consent rules sit outside the engineering fix. Review the gaps every quarter against your real refund and direct-sale counts.

The first-party link tracking post covers the cookie-free variant, and the affiliate tracking explainer maps the same chain.

GapWhy it happensCovered by
Click on a phone, purchase on a laptopEach browser holds its own cookieStep 4
Partner shares the bare site URLNo click reaches your redirectStep 7
A redirect drops the query stringThe landing page never sees _vidStep 3
A content blocker stops your scriptNo script cookie and no pageviewSteps 2 and 4
Visitors in regions that require consentThe UK ICO says online advertising storage and cross-site tracking need consentCounsel, plus the FTC and GDPR guide

What does TrackRev do for first-party affiliate tracking?

TrackRev is SaaS affiliate software for indie hackers and small SaaS teams. TrackRev covers the click log, the visitor ID and the payment match; you add the p.js pixel and one trk.identify(email) call at signup.

Each partner link in the redirect we built is a server-side redirect that logs the click and sends the visitor to your page with the ID in _vid, on a TrackRev-hosted subdomain, a custom domain with 1 CNAME record, or the shared short domain.

At payment, TrackRev checks 4 places in our lookup order:

OrderWhere TrackRev looks
1Charge metadata, key vid
2Checkout Session client_reference_id
3Checkout Session metadata, key vid
4The customer's email, matched to the visitor bound by identify

The pixel adds the ID to Stripe Payment Links, Buy Buttons and pricing tables itself.

One workspace-wide rule sets credit (last click, first click or shared) over a lookback window of 1 to 365 days, and renewals copy the first sale's credit.

Coupon-code commissions run alongside, a refund reverses its commission, and each program can hold commission for 0 to 180 days, with 0 the default in our own settings. With data-requires-consent="true" the pixel writes no cookie until your code calls window.trk.grantConsent().

Partner programs need a paid plan, and in our pricing the entry point is Indie.

PlanPriceWorkspacesPartner programCommission tracked
Free$01Locked; link tracking only, 50 links and 1,000 eventsNone
Indie$29 a month1UnlockedNo cap
Growth$299 a yearUp to 10UnlockedNo cap

From the pricing page; the affiliate program page covers links and payouts. Indie bills monthly ($348 over 12 months) and Growth bills yearly ($299, or about $25 a month).

Skip the switch if your current tool already writes a first-party cookie and sends the referral ID to Stripe; step 8 will tell you.

A network whose contract makes its pixel the record for disputes keeps that pixel, with first-party logging beside it. TrackRev reads revenue from Stripe, Paddle Billing, Polar, Lemon Squeezy, Creem and Dodo Payments.

Found this useful? Share it.

PostLinkedIn

Frequently asked questions

Muzahid Maruf — Founder of TrackRev.io

Written by

Muzahid Maruf

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.