Evergreen Growth & Marketing - B2B LinkedIn Ads Agency
    Tracking · Build guide

    First-touch capture that survives a nine-month sales cycle

    Someone clicks your ad in February, reads two pages, disappears, and books a call in September after searching your name. Read the URL at the moment they submit and that deal belongs to Organic Search forever. Store the first source you ever saw and it belongs to the campaign that actually found them. This is the build, end to end.

    11 min read• James Green• Updated 2026
    90 days
    Storage window
    First
    Touch, not last
    Hidden
    Fields on every form
    Consent
    Aware by default

    The short answer

    On the first visit, read the UTM parameters and the referrer, write them to local storage with a timestamp, and don't overwrite them for 90 days. On every form submit, copy that stored object into hidden fields. On every outbound booking link, append it to the URL before the click. Map the fields to your CRM once and leave it alone.

    First visit
    Capture
    90 days
    Store
    Forms + links
    Attach
    CRM fields
    Land

    Why first touch, and why 90 days

    Last touch is the default everywhere and it's the wrong default for B2B. In a six to nine month cycle the last touch is almost always branded search or a direct visit, because by then they know who you are. Crediting that is like crediting the taxi that took you to the airport with the holiday.

    First touch has the opposite flaw, so don't throw the rest away. Store the first touch as the thing that found them, keep the latest touch alongside it, and use both. First touch tells you where demand comes from. Last touch tells you what closes it. Retargeting will look terrible on the first and brilliant on the second, and neither read is the truth on its own.

    "Last touch credits the taxi to the airport with the holiday."

    Ninety days is the window we default to because it covers the realistic gap between an ad click and a first conversation in most B2B markets, without keeping identifiers around longer than you can justify. Longer cycles can push it further, but be able to explain the choice.

    01
    The lever

    Capture and store on the first visit

    Run this as early as you can on the first page load. If the URL has UTMs, use them. If it doesn't, infer something sensible from the referrer so you're not filing genuine organic search under Direct.

    // Runs once, on the first page of the first visit.
    const KEY = 'eg_attr';
    const NINETY_DAYS = 90 * 24 * 60 * 60 * 1000;
    
    function captureFirstTouch() {
      const existing = JSON.parse(localStorage.getItem(KEY) || 'null');
      if (existing && Date.now() - existing.ts < NINETY_DAYS) return existing;
    
      const p = new URLSearchParams(window.location.search);
      const attr = {
        source: p.get('utm_source') || inferSource(document.referrer),
        medium: p.get('utm_medium') || '',
        campaign: p.get('utm_campaign') || '',
        content: p.get('utm_content') || '',
        landing: window.location.pathname,
        ts: Date.now(),
      };
      localStorage.setItem(KEY, JSON.stringify(attr));
      return attr;
    }
    Details that catch people out
    • Store the landing page as well as the source. It tells you which content did the finding, which is the bit that actually informs next month's plan.
    • Keep a separate latest-touch object that you do overwrite on every visit. Two fields, two questions answered.
    • Local storage survives tab closes and browser restarts, which cookies with short lifetimes often don't. It won't survive a different device or a cleared browser, and nothing will.
    Screw-up to avoid
    Overwriting the stored source on every visit
    We see this constantly: someone builds the capture correctly and then forgets the early return, so every subsequent visit rewrites it. Three months later every lead in the CRM says Direct and the tracking looks broken when it is only misconfigured by one line.
    02
    The lever

    Keep it legal and consent-aware

    You're storing campaign parameters and a timestamp, not a name or an email. That's a low-risk payload, but it still sits behind your consent setup and your privacy policy needs to say it exists.

    • Gate the write behind analytics consent where your policy requires it, and accept that you'll lose the source for people who decline. That loss is real and it is heaviest in DACH.
    • Never write personal data into the attribution object. No emails, no names, nothing from a form field.
    • Add a blocklist so query strings containing anything that looks like an email address or a phone number never make it into storage or into your analytics.
    • Expire the record. Ninety days, then it clears itself. Indefinite storage is harder to defend and adds nothing useful.
    Want this run for you?

    Want this built and wired into your CRM properly?

    Book a 30 minute call. Tell us what stack you are on and we will be straight about what it takes.

    03
    The lever

    Write it into every form

    Storage does nothing on its own. The value only appears when it lands on a record a human can read months later.

    The fields to add
    • First touch source, medium, campaign. Hidden, populated from storage on submit.
    • First landing page and first touch date. The date is what lets you see how long the gap really is between ad and conversation.
    • Latest touch source. Separate fields, overwritten each visit.
    • How did you hear about us. Visible, optional, free text or a short list. Unfashionable and the only thing that survives everything else failing.

    Every form. Contact, download, demo request, newsletter. Miss one and it becomes the one that generates your best lead of the quarter with an empty source field.

    04
    The lever

    Carry it across to the booking tool

    The handoff to Calendly, HubSpot Meetings or whatever you use is where most setups quietly fail. New domain, new session, source gone. Fix it by putting the attribution into the URL before the visitor leaves.

    // Add the stored attribution to an outbound booking link
    function appendAttribution(url) {
      const a = JSON.parse(localStorage.getItem('eg_attr') || 'null');
      if (!a) return url;
      const u = new URL(url);
      u.searchParams.set('utm_source', a.source);
      u.searchParams.set('utm_medium', a.medium);
      u.searchParams.set('utm_campaign', a.campaign);
      return u.toString();
    }
    • Wrap every booking and external form link in that function. One helper, used everywhere, rather than hardcoded URLs scattered across components.
    • Add the booking domain to GA4 cross-domain measurement and to the unwanted referrals list.
    • Map the incoming UTM parameters to fields inside the booking tool so they reach the CRM record rather than sitting in the scheduler.
    • Fire a CTA click event before the redirect. You then have a funnel step between page view and booking, which is where the drop-off you cannot currently see is hiding.

    What the CRM side needs

    Create the custom fields once and protect them. The single most common way this breaks after launch is a well-meaning workflow that overwrites first touch when a contact re-converts.

    • First touch fields set on create only, never on update. Lock them if your CRM allows it.
    • Latest touch fields updated freely.
    • A report that groups closed-won deals by first-touch source, with the first-touch date next to the close date so the true cycle length is visible.
    • An "unknown source" count on the dashboard. When that number climbs, something in the chain has broken and you want to know that week, not at the quarterly review.

    Test it like a buyer would

    Not like a developer. Buyers arrive on phones and come back on laptops.

    1
    Tap a live ad on your phone

    Confirm the parameters get stored, not just present in the URL.

    2
    Close everything and come back the next day

    Type the domain in directly, then submit a form. It should still report the ad.

    3
    Book through the scheduler

    Check the source landed on the CRM record, not just in the booking tool.

    4
    Decline the cookie banner and repeat

    Confirm you fail cleanly rather than storing something you shouldn't.

    5
    Wait past the window

    Set a short expiry temporarily and confirm the record actually clears.

    Once it holds up under all five, you have a source on most of your records and a defensible answer to where demand comes from. What it still won't do is capture the buyer who saw four ads and never clicked one, and that gap is what company-level influence measurement is for.

    Read next

    Common questions

    Typical engagement: Starter (£1,250/mo) for tracking work, Growth with campaigns

    If you want the whole attribution chain built and tested

    • First-touch and latest-touch capture built, consent-gated and expiring on a defined window.
    • Hidden fields on every form, mapped to protected CRM fields that workflows cannot overwrite.
    • Booking and external form handoffs fixed so the source survives the redirect.
    • CTA click and form start events so you can see drop-off between intent and submission.
    • A closed-won by first-touch-source report, with true cycle length visible next to it.
    Pricing

    Starter £975/mo, Growth £1,725/mo, Scale £4,025+/mo. Usually two to three weeks of work inside the retainer. Standalone tracking rebuilds are quoted on the stack involved.