Save the first touch against the person, not against the browser session. When a visitor lands from an ad, the click id, the campaign and the landing page have to be written to a record that outlives the cookie that happened to be present. When the purchase arrives weeks later — in a different session, on a different device, or straight into your payment processor with no browser involved — it gets joined back to that record by an identifier the buyer supplied, normally an email address or a phone number.
Once the join is made on a person, the gap stops mattering. Two weeks, six weeks, a quarter: the arithmetic is the same.
What breaks is never the length of the gap. It is that everything proving the click happened lives in the first session, and nothing carries it forward.
Why does a purchase two weeks after the click show up as Direct?
Because the evidence of the click and the record of the sale are two separate things, and nothing connects them. The click id arrived in a landing-page URL that the visitor navigated away from. The campaign went into a cookie that has since been shortened or cleared. The purchase arrives in a fresh session with no parameters on it at all — so the only honest label left is “no source visible”.
That label is usually printed as Direct, which is where the confusion starts. Direct is not a channel. It is a missing value wearing a channel’s name, and it is why ad conversions show up as Direct far more often than any media buyer’s instinct allows for.
The longer your sales cycle, the worse the distortion gets. A business selling something considered — a service, a high-ticket product, a B2B subscription — routinely sees a gap of days or weeks between the ad click and the money. Every one of those gaps is an opportunity for the link to break, and the breakages are not distributed evenly. They concentrate in exactly the campaigns that produce considered purchases, which means the channels doing the patient work look worst in the report.
Then the budget follows the report. The campaign that started the journey gets cut because the journey finished somewhere it could not be seen.
What actually expires between the click and the purchase?
Six things decay, roughly in this order, and each one removes a different piece of evidence.
- The URL parameters.
gclid,fbclid,msclkid,ttclidand your UTM tags exist in the landing-page URL and nowhere else. One internal navigation later they are gone unless something deliberately copied them into storage on arrival. - Script-written cookies. A cookie written by JavaScript has a shorter effective life than most marketers assume, because Safari’s Intelligent Tracking Prevention (ITP) caps it, and privacy extensions delete it outright. A visitor returning after a gap can arrive as a stranger without anything having been deleted by hand.
- The device. The ad gets clicked on a phone during a commute; the purchase happens on a laptop that evening or that fortnight. Two devices, two storage buckets, no shared identifier unless one was deliberately created — which is why tracking leads across devices is a separate problem from tracking sessions.
- The domain. Checkout on a payment provider’s page, a booking tool, or a subdomain that does not share cookies with the landing page. The buyer never left your funnel; your storage did.
- The browser, entirely. Subscription renewals, invoices paid by finance, trials that convert on a schedule — plenty of purchases are created by a webhook from Stripe or a billing system with no browser session attached to them at all.
- The platform’s own window. Even with perfect data on your side, an ad platform only credits a conversion that lands inside the attribution window configured on the ad set. Past it, the platform’s report will not show the conversion no matter what your database knows.

Notice that only the last item is the platform’s fault. The other five are storage problems, and storage problems are fixable on your side.
How long will the ad platforms still credit the click?
Two different clocks get confused here, and separating them removes most of the anxiety about long sales cycles.
The first clock is the attribution window — how far back a platform will look from a conversion to find a click of its own. It is configured per ad set, it is measured in days, and it governs what appears in the platform’s reporting. A conversion outside it is not counted there, and no amount of server-side work changes that.
The second clock is the event upload window — how old an event itself may be when you send it server-side. For Meta’s Conversions API this is the event_time parameter, and Meta’s server-event documentation sets the limit at seven days: an event sent with a timestamp older than that is outside what the API accepts (Meta for Developers, Conversions API server event parameters).
Here is the part that trips people up: the age of the click is not the age of the event. A purchase that happens today, from a click fourteen days ago, is a today event. Its event_time is the moment of the purchase, so it sits comfortably inside the seven-day upload window, and the Conversions API will take it. The fourteen-day-old thing is the attribution evidence you attach to it — the click id, the campaign, the first touch — and that has no expiry at all on your own servers.
The seven-day limit only bites when you try to backfill: sending a batch of conversions that happened weeks ago. Those deliver with a clamped timestamp rather than their true one, which is a good reason to dispatch conversions as they occur rather than reconciling monthly.
Google Ads applies its own cut-off to offline conversion imports, and it is enforced against the click, not the upload. Check the current limit in Google’s own documentation before you design a workflow that depends on a long backfill — it is the constraint most likely to quietly drop the oldest and most valuable conversions in your file.
How do you keep the first touch without buying anything?
You persist it yourself, at three points: on arrival, at lead creation, and at purchase. None of this needs a vendor, and all of it is worth doing regardless of what tooling you eventually run.
- Capture on arrival. On every landing page, read
gclid,gbraid,wbraid,fbclid,msclkid,ttclid, the UTM parameters, the referrer and the landing path, and write them into first-party storage immediately. Do it before the visitor clicks anything, because after the first navigation the URL is gone. - Write first touch once, and never overwrite it. Keep two records: the first touch, which is set once and frozen, and the last touch, which updates on every visit. Systems that keep only one field almost always keep the last one, which is how the retargeting ad gets credit for a sale that a cold prospecting campaign created.
- Push it into the form. Hidden fields on the form carry the stored attribution into your CRM with the lead, so the contact record itself holds the click id and campaign rather than a cookie.
- Stamp the order. When the purchase is created, copy the stored click id and campaign onto the order record. This is what makes an offline conversion import possible later.
- Set your own cookie server-side. A cookie written by your server, rather than by JavaScript, survives the browser storage restrictions that shorten script-written cookies — the single highest-leverage change on this list for long sales cycles.
Each of these works. The ceiling on all of them is the same, and it is worth being precise about it: every one of these fixes starts working at submit time. They depend on the visitor completing a form so that an email exists to hang the record on. If the visitor types an email, abandons, and comes back a fortnight later on a different device to buy, there is no contact record to match against — the DIY chain never started. They also cannot merge two devices, because nothing links a phone’s storage to a laptop’s storage.
That ceiling is what the rest of this article is about.
How does PartialLeads attribute a purchase weeks after the ad click?
By resolving the sessions into one person first, then matching the purchase to that person rather than to a session. The mechanism has three parts, and each has a surface in the dashboard that proves it worked.
The identity cluster. PartialLeads maintains an identity graph per account and unions a person’s sessions across six tiers: the first-party visitor id, normalised email, normalised phone, IP plus user-agent within a tight window, a device fingerprint with a bounded lookback, and a click id seen on more than one session. The visitor id itself is set by the server rather than by script, so it survives the storage caps that shorten JavaScript-written cookies. That is the layer that makes a fortnight-old click reachable, and it is described in full in how visitor identity resolution works.
First touch and last touch, both stored. Every matched purchase writes two attribution rows, not one — a first-touch row and a last-touch row, each snapshotting the UTMs, click ids, referrer, landing page and the time-to-purchase from that touch. Nothing overwrites the first touch. The display layer uses last-non-direct with a 28-day decay window, deliberately matching the standard ad platforms report on, so the numbers argue with Ads Manager less often.
Purchase matching, tiered by how much it proves. Purchases arrive from Shopify, WooCommerce, GoHighLevel, Stripe or a universal webhook, and each one is matched to a session in confidence order: a returned visitor id first, then normalised email, then phone, then IP. A Stripe invoice that pays weeks after signup carries an email and a customer id, and that is enough — no browser required. Purchases that match nothing still land, anonymously, and become attributable later when an identifier arrives; there is a manual re-match for the rest.

The proving surfaces are the point. The Attribution report shows first touch, last touch and the resolved model side by side with their own purchase counts, so you can see the three views disagree instead of being handed one number — the walkthrough is in how to read an attribution report. Its Time to Purchase panel buckets journeys from under an hour to over 28 days, which tells you whether your long-lag conversions are a rounding error or the business. Its Recovered Attribution block itemises revenue that a raw last-click view loses, separated by the mechanism that recovered it. On the Leads list, the Journey column renders each touch as a badge, so a row reads as a sentence — ad click, return visit, purchase — without opening anything. The same purchase-to-click join underpins attributing ecommerce revenue to the ad that started it.
Two honest constraints. First, matched conversions are dispatched server-side to Meta, Pinterest, TikTok and Google Ads, but dispatch and matching are different things: every order your store records can produce a conversion event, and whether the platform then ties that event to a user and a click depends on its own graph — consent, opt-outs, unresolvable people. No vendor controls that half, and anyone claiming otherwise is selling. Second, server-side match quality still leans on the platform cookies captured client-side, so keep your base pixel installed. PartialLeads reconstructs Meta’s _fbc from an fbclid in the landing URL; it does not invent _fbp out of nothing.
| What breaks | The mechanism | Where you see it in the dashboard |
|---|---|---|
| Click id only ever existed in the first landing URL | Click ids and UTMs captured on arrival and stored against the session, _fbc reconstructed from fbclid |
Session attribution on the lead, CAPI activity payload |
| The cookie expired before the buyer returned | Server-set first-party visitor id, durable against script-cookie caps | Returning visitors stitched to the same person |
| Buyer clicked on a phone, purchased on a laptop | Six-tier identity cluster joining sessions on email, phone, click id and visitor id | Journey column on the Leads list, journey timeline |
| Purchase arrived with no browser session (Stripe, billing) | Purchase matching ladder: visitor id, then email, then phone, then IP | Purchases ledger, matched versus unmatched |
| Retargeting stole credit from the campaign that started it | First-touch and last-touch rows written per purchase, first touch never overwritten | Attribution report, first versus last versus resolved |
| Long-lag sales look like a data problem, not a sales cycle | Time-to-purchase buckets from under an hour to over 28 days | Attribution report, Time to Purchase |
| Revenue a last-click view drops entirely | Recovered attribution, itemised by the mechanism that recovered it | Attribution report, Recovered Attribution |
| The ad platform stopped optimising for slow buyers | Matched purchases dispatched server-side with deterministic event ids | CAPI activity log, delivery health |
The reframe worth keeping: a two-week gap is not a tracking failure, it is a sales cycle. The failure is storing evidence in a place that expires faster than your customers decide.
Tell us what's broken. We'll fix your tracking — free.
Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.
Sources
- Meta for Developers — Conversions API, server event parameters: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/server-event
- Meta for Developers — Conversions API overview: https://developers.facebook.com/docs/marketing-api/conversions-api
- Meta for Developers — Deduplicate Pixel and Conversions API events: https://developers.facebook.com/docs/marketing-api/conversions-api/deduplicate-pixel-and-server-events