Lead capture for ad landing pages
Every lead row carries its click ID and utm set, captured on arrival and persisted until submit. The sheet doubles as your offline conversion source.
The workflow
Paid traffic has a specific failure mode: you know what a click cost, and you have no idea what it produced. The click ID that would connect the two - gclid for Google Ads, fbclid for Meta, msclkid for Microsoft - arrives in the landing page URL and is gone the moment the visitor navigates, unless something saves it.
Here, something does. The sl.js embed reads utm_source through utm_content plus gclid, wbraid, gbraid, fbclid, and msclkid from the URL the moment the page loads, and persists them in localStorage. The visitor can wander to your pricing page, come back tomorrow on the same browser, and submit - the attribution travels with the POST and lands in columns beside the lead's name and email.
The mechanics are covered step by step in capture gclid in forms; the wider pipeline is on how it works.
Suggested columns
This is the attribution-heavy sheet in the catalog. Header row:
- Name, Email, Phone - the lead itself.
- utm_source, utm_medium, utm_campaign, utm_term, utm_content - the campaign taxonomy you set in your ad URLs, one column each.
- gclid, wbraid, gbraid - Google's click identifiers; wbraid and gbraid are the consent-era variants you will see from iOS traffic.
- fbclid, msclkid - Meta and Microsoft Advertising equivalents.
- Submitted At - the conversion timestamp, which the import formats want.
- Status, Value, Converted On - working columns your sales process fills in. These turn the sheet into an offline-conversion source, below.
The form markup
No hidden attribution inputs needed - sl.js appends the captured parameters automatically to any form it binds (forms marked data-sheetlink, or whose action points at /f/):
<form action="https://sheetlinkforms.com/f/your_token" method="POST">
<input name="name" placeholder="Name" required>
<input name="email" type="email" placeholder="Work email" required>
<input name="phone" type="tel" placeholder="Phone">
<input name="_slhp" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px">
<button type="submit">Get the quote</button>
</form>
<script src="https://sheetlinkforms.com/sl.js" data-token="your_token"></script>The script also adds a timing signal and works inside single-page apps via MutationObserver, so landing pages built in React or a page builder behave the same as static HTML. Keep the honeypot input in the markup regardless - it covers visitors with JavaScript disabled. Full embed reference in the docs.
Closing the loop: offline conversions
Google Ads accepts offline conversion imports keyed on the click ID: gclid, a conversion time, and optionally a value. Your sheet already holds two of the three in the gclid and Submitted At columns. When a lead actually closes, write the amount into Value and the date into Converted On, filter to converted rows, and export - that file is your import. Smart Bidding then optimizes toward leads that became revenue instead of leads that filled a form.
The fbclid column is the same raw material on the Meta side, and msclkid for Microsoft Advertising. The point of keeping them as plain columns is that nothing is locked in a vendor's reporting UI - the join between spend and outcome sits in a spreadsheet you own.
Spam considerations
Paid landing pages get a distinctive mix: bot clicks on the ad itself, and form-stuffing bots that find the page later. Two settings matter more here than anywhere else.
First, set the per-form origin allowlist to your landing domain, so the endpoint rejects posts from anywhere else - a token visible in page source is useless to a scraper. Second, remember why quarantine-not-drop matters on this page specifically: you paid for the click behind every submission, so a false positive that silently vanished would be a lead you bought and never saw. Suspicious rows wait for one-click review instead. For high-spend campaigns, enabling Cloudflare Turnstile on the form is cheap insurance; rate limits per form and per IP handle the floods either way.
FAQ
Does attribution survive a multi-page visit?
Yes. sl.js captures the parameters on the first page load and persists them in localStorage, so a visitor who lands on the ad page, reads three other pages, and submits from a contact page still carries the original gclid and utm set.
What lands in the columns when there is no click ID?
They stay empty for that row. Organic and direct visitors simply have blank gclid/fbclid cells, which is itself useful - filtering on empty click-ID columns splits paid from unpaid leads in one step.
Do I need to add hidden fields for gclid myself?
No. The embed appends captured parameters automatically at submit time. Hidden inputs are only needed if you refuse all JavaScript, in which case the gclid guide shows the manual pattern.
Can I import these rows into Google Ads?
Yes - Google's offline conversion import wants gclid, conversion time, and value, all of which are columns in this sheet. Export the converted rows and upload. wbraid and gbraid rows follow the same shape for iOS-consent traffic.
Does this work on landing pages built in a SPA framework?
Yes. sl.js watches the DOM with a MutationObserver and binds forms whenever they render, so client-side routed pages and page builders work without extra wiring.
Related: Quote requests your team can actually work · A waitlist that runs itself · Newsletter signups you own