Author: Michael Strauss
Disclosure: I work on Venbit Intelligence, which is mentioned below.
If you ship a marketing site with a contact form and a tel: link, pageview analytics will tell you that someone arrived. It will not tell you which ad, keyword or search produced the lead. The conversion lives in the inbox or the phone log, and unless you join that event to the first visit, the trail stops at the landing page.
This is a tracking problem, not a dashboard-taste problem. The pattern is straightforward: persist the first-touch source, then attach it when the form is submitted or a click-to-call link is tapped.
How attribution works in the browser
Four signals do most of the work.
- UTM parameters (
utm_source,utm_medium,utm_campaign, and the rest) are the labels you put on the URL for mailers, posts and ads you control. -
document.referreris the previous page, when the browser still sends it. Organic search, some social visits and many sites show up here. A lot of AI assistants and in-app browsers send a blank referrer, so "direct" is often a bucket of unknowns unless you have another clue. - Click IDs such as
gclid(Google Ads) andfbclid(Meta) arrive on the landing URL when someone clicks an ad. Store them on the first hit. They are how you join a later lead back to the ad platform. - The conversion events themselves are
submiton a form and a click ona[href^="tel:"]. Those are the moments you stamp the stored source onto the lead.
First touch is the usual rule for "which campaign originally brought this person." Capture it once, on the first landing, and do not overwrite it if they come back later through a reminder email.
A small first-touch pattern looks like this:
const KEY = "first_touch"; function firstTouch() { const saved = localStorage.getItem(KEY); if (saved) return JSON.parse(saved); const q = new URLSearchParams(location.search); const touch = { landing: location.pathname + location.search, referrer: document.referrer || "", utm_source: q.get("utm_source") || "", utm_medium: q.get("utm_medium") || "", utm_campaign: q.get("utm_campaign") || "", gclid: q.get("gclid") || "", fbclid: q.get("fbclid") || "", at: new Date().toISOString(), }; localStorage.setItem(KEY, JSON.stringify(touch)); return touch; } function attachHiddenFields(form, touch) { for (const [key, value] of Object.entries(touch)) { const name = "attr_" + key; let input = form.elements[name]; if (!input) { input = document.createElement("input"); input.type = "hidden"; input.name = name; form.appendChild(input); } input.value = value; } } firstTouch(); document.querySelectorAll("form").forEach((form) => { form.addEventListener("submit", () => attachHiddenFields(form, firstTouch())); }); document.addEventListener("click", (event) => { const link = event.target.closest('a[href^="tel:"]'); if (!link) return; const body = JSON.stringify({ type: "tel", href: link.href, ...firstTouch() }); if (navigator.sendBeacon) navigator.sendBeacon("/lead-tap", body); }); This is a teaching sketch, not a production library. You still need a server that stores the payload with the lead, a plan for consent in your jurisdiction, and a way to recognise ChatGPT and other AI-assistant referrals when the referrer is missing or generic. Do not treat localStorage as a full identity graph, and do not log phone numbers into a public endpoint.
Why a lot of analytics tools still miss the lead
Revenue-first tools such as DataFast are built to join website traffic to a payment in Stripe or a similar processor. That is the right job when the conversion is a checkout.
It is the wrong job when the conversion is a form send or a phone tap. Those events never hit the payment provider. A privacy-first traffic counter in the Plausible mold will show you sessions and sources, and still leave the call and the form unjoined. A chatbot that only holds a conversation has the same hole: the lead never carries the campaign.
Local businesses add one more requirement. Map-pack rankings have to be checked from the city the business serves. A national rank is the wrong number for a company that wins work within a few miles.
What to look for, or what to build
If you are wiring this yourself, the checklist is short. Persist first-touch UTMs, referrer and click IDs. Stamp them onto form submits and tel: taps. Keep AI-assistant traffic out of a vague "direct" bucket when you can identify it. If you also need local rank checks or an on-site agent that captures the lead, that is product work on top of the snippet above.
If you would rather use a product that already does the lead-side job, Venbit Intelligence ties each form send and phone tap to the ad, keyword or search that produced it, across twelve ad platforms. It shows traffic from AI assistants such as ChatGPT, checks Google rankings (map pack included) from the city the business serves, and includes Site Agent, an AI chatbot that answers by chat or voice and sends the lead to the inbox.
It is not a checkout-revenue tool. Use it, or a tool like it, when the event you care about is the lead.
A plain way to choose
If you sell online and the question is which channel produced the payment, start with a revenue-attribution product.
If the site converts on a call or a form, attribute those events. The snippet above is the core idea. Judge any tool on whether it actually joins them, covers the ad platforms you buy, and keeps AI traffic visible.
Pick the conversion you actually have, then measure that.
About the author: Michael Strauss
![Robot Hand and Dexterous Grasping — Ch.09: Force and Torque Control for Hands [EN]](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyh837tb5vp99ae1scr4c.png)
![Robot Hand and Dexterous Grasping — Ch.16: Checklists and Templates [EN]](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fowhozp6neqiwwv1y561k.png)