Power Law Performance Subscribe

ResourcesImplementing Signals and Attribution for Power Law Businesses

Capture every lead

Name every door a lead comes through, tag every link from one vocabulary, save everything at the form, and attribute web leads without cookies.

Chapter 3 of 7 · 12 min read · A guide by Broad Eye Solutions · Version 1.0 · Updated November 3, 2026

Get the whole guide as a PDF

Chapter 3 of 7All chapters
  1. 1 Where the thread breaks
  2. 2 One ID and the funnel
  3. 3 Capture every lead
  4. 4 Reports run on PRO, never on raw data
  5. 5 One question that sorts
  6. 6 Tell the platforms
  7. 7 Close the loop
  8. Sources
The guide’s overview

Every door gets a name

Ask a CRM how a lead got into it and most can’t say. Yet how a lead got in is one of the earliest predictors of what it’s worth, whatever brought it there. A call converts differently from a form fill. A lead a rep keyed in after an event converts differently again, and so does a name from a list someone loaded.

So every contact carries its conversion point, the door it came through, and it’s required: a contact without one is a gap to fix, not a lead with no door. A conversion point has a type, and each type has its capture methods. Nobody types the capture method. Each web form writes web_form, hard-coded into the form. The chat writes chat. The call-tracking integration writes phone_call, the integration that delivers a platform’s lead form or a partner’s feed writes api, the import writes import, and a rep’s entry screen writes manual. A CRM that already holds “Inbound Phone” or “Zapier” in some field doesn’t need its records changed. A map translates whatever is there into one vocabulary:

Conversion pointCapture methodWhat it isEnters the funnel as
OnlineWeb FormA form on your site, with its web form IDConversion
OnlineLive ChatChat on your site, with its chat IDConversion
API / Third Party DirectThird Party APIDelivered by an integration straight into the CRM: an ad platform’s own lead form, such as Meta’s instant form, or a partner’s feedLead when the person asked to be contacted, as on a platform’s lead form. Otherwise Prospect
Manual EntryInbound CallA call, logged by call tracking or keyed in by salesLead
Manual EntryReferralA partner, affiliate or customer sent itLead
Manual EntryManual EntryA rep keyed it in: an event, a business card, a walk-inProspect, or Lead when the person asked to be contacted
ListListA list loaded into the CRM: bought, rented, a trade show’s scansProspect

The conversion points roll up into two groups. Online is Web, and the other three are Non-Web. An ad platform’s lead form is filled in online, but not on your website, and it arrives through the platform’s integration, so it’s Third Party API, and Non-Web.

Every door also gets an ID of its own. A web form’s is its web form ID, the WFID (some builds call the hidden field the conversion ID), and every tracking number, chat widget, partner feed and import gets one too. A small table, the form map, says what each one is: the brand it belongs to, the page it lives on, the offer, and the traffic it was built to receive. A form that only runs in Meta ads says so in its row, and that turns out to be one of the most useful facts in the whole build.

A new form gets its row before it goes live. A form whose purpose changes gets a new ID, so old leads keep the meaning they arrived with.

What’s missing is evidence too

Attribution isn’t only what lands in the source, medium and campaign fields. It’s everything known about a lead, and everything known to be missing.

The capture URL is the clearest case. If every web form and chat submission writes the page it was sent from into the CRM, without exception, then a lead without a capture URL didn’t come from the website. It came from a call, a referral, a rep, a list or an integration. That one rule turns a field nobody looks at into a cohort: Web and Non-Web. Non-Web leads stop hiding inside “unknown,” and their own conversion rates show up beside the website’s.

The rule only holds if the capture is diligent. One web form that skips the capture URL turns its leads into Non-Web by mistake. So when a record says it came from a web form and has no capture URL, it’s still filed as Web, with a flag that says the form has a gap to fix.

Brand comes first

Most businesses have more than one brand by the time they look hard at attribution: a sub-brand launched a few years in, a second site, a company bought with its own forms and its own CRM records. Mixed together, each one’s numbers hide the others’. The main brand’s strong close rate props up a sub-brand that’s struggling, or a sub-brand’s cheap leads make the main brand’s cost per lead look fine.

So every form writes its brand too, hard-coded the same way, and the first cut on every report is Brand against Non-Brand: the main brand on one side, and every sub-brand, other brand and other site on the other. The capture URL’s domain backs it up, so a lead from the second site still lands in Non-Brand when a form carries no brand code, or one the map doesn’t know.

Every ad’s link should carry the same parameter names, filled in by the platform at the moment of the click, from one shared vocabulary. Then “meta / instagram” means the same thing in every report, and the platform fills in every name it can. Google can’t fill in a campaign’s name, so it’s typed once per campaign, and the campaign ID never goes stale.

The set the blueprint uses:

ParameterWhat it holds
utm_sourceThe platform: meta, google, microsoft, linkedin, newsletter
utm_mediumThe first cut of the platform’s spend. Meta: fb, ig, msg, an. Google: search, display, video, performance_max, demand_gen, shopping. LinkedIn: sponsored_updates, sponsored_inmails, text_ad, dynamic
utm_campaignThe campaign name
utm_termThe keyword on search, the ad set or ad group name elsewhere
utm_contentThe ad name
utm_campaignidThe campaign ID
utm_adgroupidThe ad set or ad group ID
utm_adidThe ad ID

The medium is where consistency pays off. It’s the first cut under the source, the first layer a channel’s spend breaks into when you dig into it, so it has to be a cut the platform’s own spend report makes too. PRO Medium then takes the exact value the platform’s report uses, so leads join to spend with no translation: Meta’s Platform column reads facebook, instagram, messenger and audience_network, and Google’s Campaign type column reads Search, Display, Video, Shopping, Performance Max and Demand Gen. The tags on the links stay short, and the medium map turns each one into its report value. On Meta, the cut is where the ad was seen, and Meta fills it in for you. The blueprint’s template puts utm_medium={{site_source_name}} in each ad’s URL parameters, so every click arrives as fb, ig, msg or an without anyone typing it. Check the values against Meta’s URL parameter builder before the ads go live. On Google, it’s the campaign type. Google’s {network} parameter says which network the click came from, not which kind of campaign bought it, so each campaign gets a custom parameter (the blueprint calls it channel, set to search, performance_max, display and so on) and the account’s final URL suffix writes it into the medium as {_channel}. Google fills in the campaign, ad group and ad IDs itself with {campaignid}, {adgroupid} and {creative}. On LinkedIn, it’s the campaign type: Sponsored Content in the feed, Message and Conversation Ads in the inbox, Text and Dynamic Ads in the right rail. Its links have no dynamic value for it, so each ad set’s tracking parameters set the medium as a fixed value. LinkedIn’s reporting also splits Sponsored Content spend by whether it served on LinkedIn or off it, on the Audience Network, but its links can’t, so give the Audience Network its own campaigns and its leads and spend stay on the same line.

Names go in the traditional fields, and every ID gets a field of its own. Names get edited, and a campaign renamed in March splits into two lines in every report that groups on the name. IDs don’t change, and they’re what the platforms’ own reports and spend exports carry, so your reports join to the platform’s one to one. With the ad ID on every lead, a lead resolves to its ad, ad set and campaign even after someone renames all three. On Meta, the URL parameters fill all six in for you: {{campaign.name}}, {{adset.name}}, {{ad.name}}, {{campaign.id}}, {{adset.id}} and {{ad.id}}. On Google, {campaignid}, {adgroupid}, {creative} and {keyword} fill in the IDs and the keyword, and the campaign name goes in as a custom parameter, the same way as the medium.

A few rules make the vocabulary hold:

  • Lowercase, with hyphens or underscores, and never spaces.
  • Values from the vocabulary only. A new value goes in the vocabulary before the first link that uses it.
  • Leave auto-tagging on, so the click IDs arrive too.
  • Nothing personal and no form answers in any address. Not in an ad’s link, not in a redirect, not in the thank-you page.
  • Every new campaign gets one test click before it spends, to check that every parameter arrives filled in.

Links you place yourself follow the same pattern: the newsletter tags its own links as newsletter / email, your own posts on a platform that aren’t ads as <platform> / content (meta / content for a Facebook or Instagram post), a partner’s link with the partner’s ID as the medium, and a QR code on a printed piece as offline. Nothing is ever tagged organic. A visit with no tags takes its source from the referrer, and PRO sets its medium to organic. One cost to know: GA4’s default channel groups count a medium as paid only when it says cpc, ppc or paid, so Meta, LinkedIn and TikTok ad traffic tagged this way lands outside GA4’s paid groups. If your team reads GA4, give it a custom channel group built from the vocabulary, instead of adding “paid” to the tags.

Capture everything at the form

The form is where an anonymous click becomes a lead. Everything the visit carried has to land on the lead record here, with the form’s own ID, before the page moves on. Anything that doesn’t make it onto the record at this moment is gone.

A small script on every page keeps the tracking tags from the tagged arrival that started the visit (a new tagged arrival replaces them), so a visitor who lands on a blog post from an ad and fills in the form three pages later still carries the ad’s tags. At submit it writes them into the form as hidden fields, along with the click IDs, the landing page, the full referral URL, the page the form was sent from, the web form ID, the capture method, a session ID and the event ID. Only the listed parameters are read, so a name or email that ends up in an address is never stored.

A few fields make a web lead provably a web lead, and carry most of its attribution: the capture URL (the page the form was sent from), the full referral URL, the IP address, the user agent and the click IDs, alongside the web form ID. Store them on every web submission without exception. They’re also what lets you prove a lead didn’t come from the website. And two of them, the capture URL and the user agent, carry the attribution on their own, as the next section shows. After those come the traditional tracking tags, and the blueprint’s capture fields list (templates/capture_fields.csv) has the full set worth keeping, from the must-haves to the optional extras.

Save first, then everything else

Order matters. The blueprint’s endpoint works in this order.

  1. The page creates the event ID and fills in the hidden fields.
  2. The form posts to your own endpoint, which scores the answers, saves the submission with its codes to the submission store, and waits for the save before it answers.
  3. If the save failed, the visitor sees an error and can send again. Nothing else fires.
  4. The endpoint forwards the lead to the CRM. If the CRM is down, the store still has it.
  5. When the answers clear the line, the endpoint sends the server-side conversion.
  6. The page moves on to the thank-you page the server picked: one for the leads whose answers clear the line, one for everyone else. The pages are for the visitor, so their addresses are neutral and carry nothing.
  7. That page pushes the events to the tag manager, once, for the lead the form just saved. A reload or a direct visit pushes nothing.

A save fired from the browser as the page is leaving races the redirect, and when the redirect wins, the lead is never saved. It happens on slow phones and busy networks, so the leads it loses aren’t random. Save first.

A submission store, and one path into the CRM. The store, a Google Sheet or a web-connected database or form system, keeps every web submission exactly as it arrived, and nothing works leads from it. The CRM gets each lead once, forwarded by the endpoint or read from the store by one integration. Two systems that each create the lead in the CRM, such as a workflow tool and a direct integration, create duplicates, or each fails on its own. Run that path on an integration user that belongs to no individual, so it doesn’t stop working the day the person who set it up leaves.

Multi-step forms

Step one creates the lead. Step two updates the same lead, found by its lead ID. Carry the lead ID to step two, never the name, email or phone in the address. An address with personal details ends up in browser history, server logs and every analytics tool on the next page. And step two isn’t a new lead. Count it as the next step of the same lead (form_step_completed, step 2), never as a second lead. The other order puts the answers first: step one saves the answers, and the record becomes a lead when step two adds the contact details.

If sales will call or text, the form asks with a checkbox, and the lead record keeps the exact wording the visitor saw, the answer and the time. A paragraph under the button isn’t a checkbox. Write the wording so it covers what the form sends along with the answers, the page it was sent from and the browser, and have counsel approve it for your business.

Store the raw user agent too. Social apps open links in their own built-in browser, and the browser names itself in the user agent. A visitor in Instagram’s browser came from Instagram, whether or not the tags survived. Treat it as a hint, because the strings change, and check it against your own leads now and then.

Attribution without cookies

Most teams miss this part. Two pieces of evidence arrive with every web submission, whatever the visitor chose about tracking: the page the form was sent from, and the browser that sent it. The capture URL and the user agent. With those two alone, most teams can get their web attribution working at a level that third-party tools and add-ons can’t match.

Take a visitor who clicks an ad and opts out of tracking on your site. Their wish is respected. No cookies are set, nothing follows them from page to page, no analytics records their visit, and nothing about them is sent to the ad platforms. Then they fill in the form on the page the ad sent them to, and tick the box agreeing to the use of what they’re submitting. The page’s own address is part of that submission, and it still carries the ad’s tags: the source, the medium, the campaign ID and the ad ID, exactly as the platform filled them in at the click. The browser names itself too, and Instagram’s or Facebook’s built-in browser says so.

Store the capture URL with every submission, with its tracking parameters and nothing else from the address, and that lead is attributed to its ad in your own reports without a single cookie. That’s why the accuracy on web submissions should be far higher than what most teams accept today. In the blueprint, the site’s consent tool tells the capture script the visitor opted out. The script then keeps nothing between pages and marks the submission, and the server leaves that lead out of everything it sends to the platforms.

So the model is cookieless by design.

  • Primary: what every submission carries. The capture URL, the user agent, the form ID and the hard-coded capture method and brand.
  • Backup: tags kept in cookies or browser storage from the page the visit started on, for visitors who allowed tracking and browsed to a different page before they submitted.

When the stored tags are there, PRO reads them first, because they came from the arrival itself. The model doesn’t depend on them.

In your own data

See it in your own book.

ABPRO_ resolves every lead's PRO fields from all of its raw capture through your own map files and runs every report on them. It joins each lead to the account it became and reads every source, form, cohort and answer by the share of above-average accounts it produces and the revenue they return.

Get the whole guide as a PDF

Every chapter, version 1.0, in one file, sent to your inbox. The newsletter follows with the articles that build on it.

One email a week after the PDF.