Almost every affiliate tool advertises a cookie window: 30 days, 60 days, 90. The implication is that if someone clicks an affiliate link today and buys seven weeks later, the affiliate still gets credit. On Chrome, mostly true. On Safari, that number is fiction.
Safari's Intelligent Tracking Prevention caps client-side, script-set cookies at 7 days, and in some cases 24 hours. So if you set your affiliate cookie with JavaScript, a huge slice of your traffic loses its attribution a week after the click, silently. Firefox and Brave block a lot of third-party tracking outright. Your 60-day promise quietly becomes a 7-day promise for anyone on the privacy-forward half of the internet.
The fix is to stop trusting the cookie to carry attribution for 60 days. Use it only to bridge one gap: from the click to the signup. After that, attribution lives in your database, where no browser can expire it.
Capture the referral the moment they land
On the landing hit, read the ref and store it. Set a first-party cookie (from your own domain, server-side where you can), but treat it as short-lived glue, not the source of truth.
// GET /?ref=aff_123 (your own server, first-party)
app.get('/', (req, res) => {
const ref = req.query.ref;
if (ref) {
res.cookie('aff_ref', ref, {
maxAge: 30 * 24 * 60 * 60 * 1000, // best effort; Safari may shorten it
httpOnly: false,
sameSite: 'lax',
});
// also stash it against the anonymous session server-side
req.session.aff_ref = req.session.aff_ref || ref;
}
res.render('landing');
});
Notice the cookie is first-party (your domain), not a third-party tracker. That already survives far better than a script injected from an affiliate vendor's domain. But you still do not rely on it lasting 60 days.
Move attribution to your database at signup
The instant someone creates an account, promote the referral from the fragile cookie/session into a durable record. This is the step that actually makes the window real.
async function onSignup(user, req) {
const ref = req.session.aff_ref || req.cookies.aff_ref;
if (ref) {
await db.users.update(user.id, {
affiliate_id: ref,
attributed_at: new Date(), // the window starts HERE, server-side
});
// and onto the Stripe customer, so billing events carry it forever
await stripe.customers.update(user.stripe_customer_id, {
metadata: { affiliate_id: ref },
});
}
}
Now the affiliate link only has to survive from click to signup, which is usually minutes to days, well inside even Safari's 24-hour floor for the tightest cases. The 60-day window is measured from attributed_at in your own database, not from a cookie the browser is free to delete.
Measure the window where it cannot be tampered with
When a payment comes in, you check the window against your stored timestamp, not against whether a cookie still exists.
// on invoice.paid
const affiliateId = invoice.customer_metadata?.affiliate_id;
const attributedAt = await db.attribution.getTimestamp(invoice.customer);
if (affiliateId && withinDays(attributedAt, 60)) {
await ledger.credit({ affiliate_id: affiliateId, amount: commissionOn(invoice.amount_paid) });
}
The mental shift is the whole thing: the cookie is a bridge, not a vault. It carries attribution across a single gap and then hands it to your database, which honors the full window regardless of browser. Anyone still leaning on a 60-day JavaScript cookie is under-crediting their Safari affiliates and does not know it.
We handle this in Referralful (affiliate software on Stripe, which I help build), so the 60-day cookie our marketing page mentions is actually a 60-day server-side window, not a client-side hope. If you roll your own, the one rule to keep: never let a browser be the thing that remembers who referred a paying customer.
Top comments (0)