If you don't know where your leads are coming from, you can't make defensible decisions about where to spend your ad budget.
Attribution is the answer to "which of my ad campaigns is working?" Without it, you're spending money on the assumption that something is generating leads — but you can't prove which thing, and you can't stop spending on the things that aren't working.
Before Era 2, the Anchor build captured name, email, phone, and message. No UTM parameters. No referrer. No landing page. When the owner asked "is our Facebook ad campaign generating leads?" the honest answer was "we don't know." Every lead record was identical from a source perspective — a name and a phone number with no context about how they got there.
The ad budget was significant. Without attribution, there was no way to evaluate it. Pausing the Facebook campaign might cut leads in half or do nothing — there was genuinely no way to know.
UTM parameters appear in the URL when a visitor arrives from a tagged source: ?utm_source=facebook&utm_medium=cpc&utm_campaign=spring-sale. The problem: the visitor may browse through several pages before submitting a form. By the time they submit, the UTM parameters are gone from the URL. If you read UTM from the URL at form submission time, you get nothing.
The fix: capture UTM parameters into sessionStorage on the first page load, before the visitor navigates anywhere. sessionStorage persists across page navigation within the same browser tab until the tab is closed.
const utmKeys = [
'utm_source', 'utm_medium', 'utm_campaign',
'utm_term', 'utm_content'
];
const params = new URLSearchParams(window.location.search);
utmKeys.forEach(key => {
// Only write if a value exists in the URL (don't overwrite with blank)
if (params.has(key) && params.get(key)) {
sessionStorage.setItem(key, params.get(key));
}
});
The landing page — the first URL the visitor saw — is different from the page where they submitted the form. If they arrived on a specific listing and submitted from the contact page, you want to know about the listing, not just the contact page.
// Only set if not already set — captures first URL of session, not current page
if (!sessionStorage.getItem('landing_page')) {
sessionStorage.setItem(
'landing_page',
window.location.pathname + window.location.search
);
}
The HTTP referrer tells you which external site sent the visitor. document.referrer is only available on the first page load — subsequent page navigations have the same-origin referrer. Capture it alongside UTM at session entry.
if (!sessionStorage.getItem('referrer') && document.referrer) {
const referrerHost = new URL(document.referrer).hostname;
if (referrerHost !== window.location.hostname) {
sessionStorage.setItem('referrer', document.referrer);
}
}
At form submission, the hidden fields are populated from sessionStorage before the form data is sent. This is done in JavaScript, not server-side — the server receives the values as standard POST fields.
function populateAttributionFields(form) {
const attributionFields = [
'utm_source', 'utm_medium', 'utm_campaign',
'utm_term', 'utm_content', 'landing_page', 'referrer'
];
attributionFields.forEach(field => {
const input = form.querySelector(`input[name="${field}"]`);
if (input) {
input.value = sessionStorage.getItem(field) || '';
}
});
}
function [client]_get_attribution_data( array $post_data ): array {
return [
'utm_source' => sanitize_text_field( $post_data['utm_source'] ?? '' ),
'utm_medium' => sanitize_text_field( $post_data['utm_medium'] ?? '' ),
'utm_campaign' => sanitize_text_field( $post_data['utm_campaign'] ?? '' ),
'utm_term' => sanitize_text_field( $post_data['utm_term'] ?? '' ),
'utm_content' => sanitize_text_field( $post_data['utm_content'] ?? '' ),
'referrer' => esc_url_raw( $post_data['referrer'] ?? '' ),
'landing_page' => esc_url_raw( $post_data['landing_page'] ?? '' ),
];
}
Attribution makes ad spend defensible. When the owner asks "is our Facebook campaign working?" the answer becomes "Facebook-sourced leads are 18% of total volume and account for 31% of closed deals in the last 90 days." That's a different conversation than "we're spending $1,200/month on Facebook and we think it's helping."
It also surfaces problems. If Google Ads is generating 40% of traffic but only 8% of leads, either the landing pages are wrong, the ad targeting is wrong, or the traffic is low-intent. Attribution makes that visible. Without it, the ad account keeps spending and nobody knows why it's underperforming.
Attribution was added in Era 2 alongside the multi-touch capture surfaces. Within three months, the data showed that organic search was generating the highest-intent leads (unit inquiries converting to sales at 3×the rate of paid social), and that a specific Google Ads campaign was generating volume but almost zero conversions. The campaign was restructured around high-intent keywords. The lead quality from paid search improved measurably.
None of that analysis was possible before the attribution data was being captured.
sessionStorage, not a JavaScript variable. A JS variable is lost when the visitor navigates to a new page. sessionStorage persists across page navigation within the same tab — which is exactly the scope you need.sanitize_text_field() or esc_url_raw(). These values come from the URL — anyone can put anything in a UTM parameter. Sanitize before storing.Every lesson stays free — no account, no paywall, no email gate, ever. But if you’d rather have this system standing on your business than wire all 48 lessons yourself, leave your email. We’ll send you a direct line to a build — and you’ll be first to hear when we add new tools to the curriculum.
None of this gates a single lesson. The curriculum was free before you got here and it stays that way.
You came here to understand the system, and now you do. If you’d rather have it standing on your business than spend the next three months wiring it yourself, GAP Concierge is the same architecture from these lessons — a white-label AI agent that knows your catalog and captures your leads — set up for you, from $97/mo.
See GAP Concierge →