Identity stitching
The hard part of partner attribution isn’t the click. It’s the gap between click and conversion. A user clicks a partner link on their phone, plays around for a week, signs up on their laptop a month later. Did the partner earn that?
OpenPartner says yes. Here’s how.
The core trick: link userId to clickId
Every successful conversion lands two records:
- Identity — a row that links a
userId(the brand’s internal user ID) to theclickId(the click that brought them in) - Event — the conversion itself, tagged with the userId
Once an Identity exists, every future event from that userId can be attributed back to the original click without needing a cookie or referer.
When the Identity gets written
The brand’s SDK calls identify() when a user signs up or first signs in:
window.openpartner.identify(user.id, { email: user.email });The SDK sends the userId + the _cref cookie value (if present) to the API. Server-side:
insert into "Identity" (id, userId, clickId, stitchedAt)values (...)on conflict do nothing;The cref came from the click; the userId came from the brand. Now they’re linked.
From this moment forward, any event() calls with the same userId are attributable, even
without a cookie:
// Months later, same user signs up on a different device — no cookiewindow.openpartner.identify(user.id, { email: user.email });window.openpartner.event('subscription_created', { amount: 4900 });// Server attributes via Identity.userId → original clickId → partnerSurviving Safari ITP
Safari’s Intelligent Tracking Prevention (ITP) caps first-party cookies at 7 days for domains it classifies as cross-site (anything you reached via a redirect, basically).
Without identity stitching, this would mean any conversion 8+ days after the click on Safari loses attribution. With Identity, the cookie is only needed once — at first identify. After that, attribution survives on the userId, which the brand controls server-side.
A practical impact: SaaS conversions with long trial periods (14-day, 30-day) work correctly on Safari. Same for ecommerce abandoned-cart flows where the user comes back weeks later.
Cross-device
User clicks on phone → cookie + click recorded. User signs up on phone → identify() runs, Identity row written linking phoneCookie’s clickId to the userId.
Same user signs in on laptop → identify() runs again with the same userId. No new Identity row (we dedup on userId). Future events from laptop attribute via Identity.userId → original clickId.
Works because identify() is the join key. If the brand consistently calls identify on
both devices (which they should — it’s how their own analytics work too), attribution
follows the user across devices.
When stitching breaks
A few cases:
- User clicks but never identifies — never converts → no commission. Expected.
- User uses two different brand accounts — separate userIds → separate Identity rows. If they convert on the second account, the click on the first doesn’t transfer.
- Brand calls
identify()with the user’s email instead of internal ID — works if email is unique per user, but breaks if anyone shares an email or the brand allows email changes. Use a stable internal ID. - Cookie + cref both wiped before first identify — first conversion can’t link back to the click. Subsequent conversions can’t either (no Identity row to anchor to). Lost.
The first-identify case is the load-bearing one. Brands need to call identify() as early
as possible — ideally on signup landing, before the user navigates anywhere. The longer the
gap between click and identify, the more chances the cookie has to be cleared.
What gets attributed via Identity vs Click
A simplified version of the attribution query:
select c.partnerIdfrom "Event" eleft join "Identity" i on i.userId = e.userIdleft join "Click" c on c.id = i.clickIdwhere e.id = $eventId and c.ts > now() - interval '60 days' -- the campaign's windowIf there’s an Identity, attribution flows through it. If not, attribution falls back to the cref cookie value sent with the event itself (the “first conversion” case before Identity exists).
After the first conversion, every subsequent event from that user uses Identity.
Multi-touch via Identity
For attribution models other than last-click (linear, position, first-click), the system needs all clicks the user made within the window. Identity gives us that:
select c.* from "Click" cwhere c.id in ( select i.clickId from "Identity" i where i.userId = $userId)and c.ts > now() - interval '60 days';Each click in that result is a touchpoint. The model decides who gets what share.
So Identity isn’t just for “did this convert” — it’s the cross-device, cross-session record of the user’s full attribution graph.
Privacy implications
Identity stores (userId, clickId). The userId is whatever the brand passes (typically
their internal database ID, not the user’s email). No PII unless the brand opts in via
identify(id, { email, name }) extras.
If a user requests deletion, the brand cascades — deleting their userId from their own DB
- telling our SDK to revoke their Identity. The Identity row stays linked to the (now orphaned) userId; we don’t have any other PII to delete.
For full GDPR detail, see the privacy page.