All posts
Privacy

Attribution Without an Identity Graph

You can attribute revenue to an AI assistant without building a profile of the person who arrived. Here is the architecture, and the line worth refusing to cross.

Jay Patel10 min read
revenue joined

Every attribution vendor eventually meets the same temptation: the easiest way to connect a visit to a purchase is to know who the visitor is. Once you know that, everything becomes simple. Join on the person. Follow them across devices, across sites, across time. Attribution stops being an inference problem and becomes a lookup.

That path is well-trodden and it ends somewhere specific. It ends with a company that started out measuring marketing and now maintains a profile of a person who never agreed to it. The tooling that gets you there is the same either way — the difference is entirely in what you choose to store, and in whether you drew the line before or after you needed it.

This is where that line goes, and why it holds.

The join you cannot avoid

Start with what is genuinely necessary, because it is easy to over-correct into a system that cannot do its job.

An AI-referred visitor arrives anonymously. They have no account, they have not logged in, and the only thing you know about them is an opaque identifier in a first-party cookie. They browse, they leave, and — days or weeks later — they come back, sign in, and buy something.

The discovery touch belongs to the anonymous identifier. The purchase belongs to an account. If those two are never connected, the AI touch is orphaned from the revenue it produced, and the one thing this entire category exists to demonstrate cannot be demonstrated.

So a join is required. The question is not whether to connect the anonymous identity to the authenticated one. It is what you store in order to do it, and what that stored thing then enables.

The question is never whether to make the join. It is what the stored join makes possible afterwards.

Where attribution ends and profiling begins

The boundary is not the join itself. It is four properties of how the join is stored.

The same mechanism, two very different systems

PropertyAttribution joinIdentity graph
ScopeOne site. The mapping means nothing elsewhereCross-site. That is the entire point
ContentTwo opaque identifiers, nothing elseAttributes: email, device, location, behaviour
EnrichmentNone. Nothing is appended over timeContinuous. The profile accretes
PurposeJoin sessions to conversionsTargeting, segmentation, resale
DeletionTrivial — one row per visitor per siteHard — the graph is the value

Read down the last column. Every property that makes an identity graph valuable is a property that makes it dangerous, and none of them is required for attribution.

The four constraints follow directly:

Per-site scope. The mapping is meaningful only within one customer's site. The same person visiting two different sites produces two unrelated mappings with no key connecting them. This is the constraint that does the most work — it makes cross-site identity resolution not merely prohibited but structurally impossible, which is a much stronger guarantee than a policy.

Hashes only. What is stored is an opaque identifier derived from the application's account identifier. Not an email address, not a hashed email address that could be matched against another dataset, not anything that means something outside the system.

No profile attributes. The record holds the mapping and nothing else. No name, no location, no device list, no behavioural summary. There is nowhere for a profile to accumulate because no field exists to hold one.

Attribution-only use. The mapping is read for exactly one purpose: joining sessions to conversions. Not for segmentation, not for lookalike audiences, not for enrichment, and never sold.

Write the constraint down before you need it

The pressure to relax these arrives later, from a real customer with a reasonable-sounding request, at a moment when revenue depends on saying yes. A constraint documented in advance — with the reasoning, not just the rule — is the only thing that survives that conversation. A constraint held informally does not.

IP addresses: the one to get right first

IP addresses are the most consequential piece of data in an ingestion pipeline, and the most casually handled.

An IP address is personal data under GDPR. It is also genuinely useful — for geography, for rate limiting, for bot detection. The temptation to keep it is constant and the reasons are always good.

The correct handling is to hash it at the moment of ingestion, with a per-site salt, and never write the raw value anywhere. Not to the analytics store, not to a log line, not to a debug trace, not to disk at any point.

The details matter more than they look:

Hash at the edge, not downstream. If the raw IP reaches your analytics store before being hashed, it has already been persisted, and a hash applied afterwards protects nothing. The transformation belongs at the earliest point the request is handled.

Salt per site, not globally. A single global salt means the same visitor produces the same hash across every customer's site — which quietly reconstructs a cross-site identifier out of data you were careful not to store. Per-site salting closes that.

Watch the logs. This is where raw IPs actually leak. Not from the data pipeline, which somebody designed carefully, but from an error log that dumps a request object, an access log that was on by default, or a third-party error reporter capturing request context. The pipeline gets reviewed; the logs do not.

Where the raw IP is allowed to exist

The window is deliberately narrow: from arrival to hashing, in memory, and nowhere else.

01

Request arrives

The raw address exists in memory at the edge. This is the only place it should ever exist.

02

Hash with per-site salt

A one-way transform, salted per site so hashes cannot be correlated across customers.

03

Discard the original

The raw value is never written to storage, logs, traces or error reports.

04

Downstream sees only the hash

Every subsequent stage works with the hashed value. Nothing downstream can recover the original.

Step three is marked as the failure point because it is the one that actually breaks in production. Everyone implements the hash. The raw value escapes through an observability path nobody was thinking about.

What you give up, and what you don't

Being honest about the trade-off, because there is one.

You give up cross-site measurement. You cannot tell a customer that a visitor who saw them on one site later converted on another. Some vendors sell exactly that. You cannot, and you should say so plainly rather than implying a capability you have deliberately declined.

You give up deterministic cross-device tracking. If someone discovers you on their phone and buys on their laptop without logging in on both, that connection is lost. You can model it in aggregate; you cannot follow the individual.

You give up enrichment. No appending firmographics, no inferring seniority, no third-party data joins.

What you keep is more than it sounds:

Attribution still works. The anonymous-to-authenticated join within a site covers the great majority of cases, because the great majority of conversions involve authentication on a device the person already used to browse.

Deletion is genuinely trivial. A GDPR erasure request means deleting rows keyed by visitor, within one site. There is no graph to untangle and no downstream system that absorbed a copy. This is not merely compliance theatre — a system where deletion is easy is one where deletion actually happens.

Data residency is achievable. Per-site scope means per-site placement. Keeping EU data in the EU is a routing decision rather than an architectural rewrite.

The breach surface is small. The worst case for a leaked mapping table is a set of opaque identifiers that mean nothing outside the system that generated them. Compare that with the worst case for an identity graph.

The failure mode is gradual

Nobody decides to build an identity graph. What happens is a sequence of individually reasonable additions: store the email hash to make debugging easier, keep the device type for a segmentation feature, retain location because a customer asked for a report. Each is defensible. The eleventh one arrives and you are running a profile business. This is why the constraint has to be on the schema and the stored fields, not on intentions.

Privacy as an engineering property

The framing that makes this stick is not compliance. Compliance is a floor, it is jurisdictional, and building to it produces systems that are technically defensible and still creepy.

The better framing is that retained data is a liability with a carrying cost. Every field you keep is something to secure, something to delete on request, something to explain in a DPA, something to migrate, something that can leak, and something that constrains where the system can run.

Under that framing the four constraints stop being ethical restraint and start being ordinary engineering discipline. Smaller schema, fewer joins, simpler deletion, easier residency, smaller blast radius. The privacy-respecting architecture is also the cheaper one to operate — not always, but here, and it is worth noticing when the incentives happen to align.

Where they do not align, the constraint has to hold anyway. That is what makes it a constraint rather than a preference.

Frequently asked

Yes, within one site. Attribution requires joining an anonymous pre-login session to a post-login conversion, which needs a mapping between the two identifiers. That mapping can be scoped to a single site, contain only opaque identifiers with no profile attributes, and be used exclusively for attribution. What it cannot then do is follow a person across sites — which is the capability that turns attribution into profiling.

Sources & further reading

  1. 01Regulation (EU) 2016/679 (GDPR), Article 4 — DefinitionsEUR-Lex
  2. 02What is personal data?Information Commissioner's Office (ICO)
  3. 03Judgment in Case C-582/14 (Breyer) — dynamic IP addresses as personal dataCourt of Justice of the European Union
Share