← Blog
Attribution windows, cookie expiry, and click stitching: how OpenPartner survives Safari and a 60-day signup gap
attributiontechnical

Attribution windows, cookie expiry, and click stitching: how OpenPartner survives Safari and a 60-day signup gap

Three things actually decide whether a partner gets credit: how long the attribution window is, how long the cookie lives, and what happens when the cookie is gone before the user converts. Here's how OpenPartner handles each.

Most “how does affiliate tracking work” articles stop at the cookie. They describe the click → cookie → conversion → commission flow, conclude that’s the trick, and move on.

That description was accurate in 2014. Today every layer of it has a foot-gun:

  • The browser strips referer headers, blocks third-party cookies, and time-bombs first-party cookies it dislikes.
  • The attribution window the brand wants to enforce isn’t the same as the cookie window the browser allows.
  • Conversions happen across devices, weeks after the click, by users who clear cookies.

This post walks through the three pieces that actually decide whether a partner gets credit in a modern attribution stack — and how OpenPartner handles each. It’s the technical companion to the cookies + cref doc.

Piece 1: the attribution window

The attribution window is the brand’s policy: “we’ll credit a click for up to N days after it happens.” It’s a commercial decision, not a technical one.

OpenPartner defaults to 60 days, configurable per campaign up to 90 days. The number you should pick depends on your buyer journey:

  • Short-cycle SaaS or ecommerce (signup in the same session): 30 days is plenty.
  • Standard SaaS with a 14-day trial: 60 days catches trial-to-paid conversions without paying for stale clicks.
  • Long-cycle SaaS or B2B with sales-led closes: 90 days is the platform max.

The window applies to qualifying clicks at attribution time. The query looks roughly like this:

select c.partnerId
from "Event" e
left join "Identity" i on i.userId = e.userId
left join "Click" c on c.id = i.clickId
where e.id = $eventId
and c.ts > now() - interval '60 days';

Click older than the window? Not eligible. Doesn’t matter what the cookie said.

The cookie window is what the browser will actually let you keep. These are different numbers.

OpenPartner’s _cref first-party cookie has max-age=90 * 24 * 60 * 60 — 90 days, the platform max. Three reasons:

  1. The cookie should outlive the longest reasonable attribution window so the brand’s policy, not the cookie, is the binding constraint.
  2. Even after the attribution window closes, the click record is still useful for analytics — “we saw this user click before, even though the commission’s expired.”
  3. Safari ITP only enforces the 7-day cap on cookies the brand sets via JavaScript on pages classified as cross-site. With Identity stitching (next section), the cookie’s only required once, so the 7-day cap rarely bites in practice.

The mismatch matters. Platforms that set their cookie to match their attribution window have a foot-gun: any cookie clear, any cross-device sign-in, any Safari ITP timeout inside the window kills attribution. OpenPartner’s defaults assume the cookie is one input among several, not the source of truth.

This is the load-bearing piece. The cookie is the first anchor, not the only one.

When a user converts and the SDK calls identify(userId), OpenPartner writes an Identity row:

Identity { userId: 'usr_42', clickId: '01ARZ3NDEKTSV4RRFFQ69G5FAV', stitchedAt: ... }

That row is the cross-device, cross-session, cross-cookie anchor. Every future event from the same userId attributes via Identity → Click, even with no cookie present at all.

Three concrete scenarios this fixes:

Scenario A: Safari, 30-day trial

Day 1: user clicks partner link on Safari. Cref cookie set on the brand’s domain. Day 7: Safari ITP times the cookie out (cap on first-party cookies it classifies cross-site). Day 8: user signs up. Cref cookie is gone, but identify() runs at signup. The signup came from a session that had the cookie at landing — the brand’s session captured the cref before ITP killed it. Identity row written. Attribution credited. Day 35: user converts to paid. Cookie still gone. Identity row drives attribution. Done.

Scenario B: cross-device

Day 1: user clicks on phone. Cookie set on phone. Day 1: user signs up on phone. Identity row written. Day 14: user signs in on laptop. identify() runs again with same userId. No new Identity row (we dedup on userId). Day 30: user converts on laptop. Laptop has no cref cookie ever. Attribution flows through the existing Identity row. Done.

Day 1: user clicks, cookie set. Day 5: user clears all cookies. Day 30: user signs up — the cookie is gone, the cref query param isn’t in the URL anymore, the click is invisible to the SDK at signup. No Identity row gets written. Day 60: conversion happens. Nothing to attribute against. Click is lost.

This last case is unrecoverable without server-side fingerprinting (which we don’t do — it’s a privacy and accuracy disaster). It’s also the rarest of the three. The first two are the common cases and they’re handled cleanly.

Multi-touch attribution rides on the same primitive

For attribution models other than last-click — first-click, linear, position-based — the engine needs every click in the window. Identity is the join key:

select c.* from "Click" c
where c.id in (
select i.clickId from "Identity" i where i.userId = $userId
)
and c.ts > now() - interval '60 days';

That returns every click any device has ever logged for this user via the Identity table. The model decides who gets what share. Last-click takes the most recent; linear splits evenly; position-based does 40/40/20.

You can switch models and re-run history under the new model, because the raw events are still there. This is what the attribution-models doc means by “attribution is a query.”

What this means for your program

The takeaways for picking or running a partner program:

  1. Attribution window > cookie window. Set the window based on your buyer journey, not the technical limit of any single browser cookie.
  2. Identify early. The earlier identify() runs after the cref lands on the brand domain, the more attribution survives downstream — Safari, cross-device, cookie clears.
  3. Pick a platform that decouples cookie from attribution. If the platform’s attribution model is “the cookie said so,” you’re losing conversions you can’t see.
  4. Audit your funnel for identify() placement. The most common attribution leak in any program is the SDK not running identify() until the user is deep into onboarding. Move it to the signup completion event.

The full data-model walkthrough lives at Identity stitching and Cookies + cref. Both are worth reading once before you set your program’s attribution window.