One identifier that survives a reload

Session stitching is where most event pipelines quietly lose people. What breaks, why the usual fixes make it worse, and the narrow approach we settled on.
A visitor loads a page, reads for a minute, opens a second tab, comes back, refreshes, and buys something. That is one person having one experience. In a naive pipeline it is three or four sessions, and every funnel built on top of it is wrong in the same direction: it overcounts entries and undercounts completion.
Why the obvious fixes make it worse
The first instinct is a longer session timeout. This turns two genuine visits into one and hides the return you actually wanted to measure.
The second is device fingerprinting. Leave aside that it is a legal problem in several jurisdictions we operate in. It is also a correctness problem: fingerprints collide across similar devices on the same network, so you get the opposite failure, merging two people into one. A pipeline that occasionally invents a person is worse than one that occasionally splits one.
What we settled on
A first-party identifier, set server-side, scoped to the site, with an explicit lifetime. It survives a reload and a new tab. It does not survive a new browser, a new device, or a cleared store, and we do not pretend otherwise. When a visitor signs in we merge the anonymous series into the account, and we keep the seam visible in the data rather than smoothing it away.
The rule underneath
Never merge two identities on a probabilistic match. If the join is not deterministic, leave the records apart and say so in the interface. An analytics tool that guesses will guess wrong in the cases you care about most, and it will not tell you which ones those were.