To track the Facebook ad ID in your CRM, make the click carry it and the form keep it:
- Add
{{ad.id}},{{adset.id}}and{{campaign.id}}to the ad’s URL parameters. - Copy them from the URL into hidden form fields on the landing page.
- Save them on the lead in your CRM.
- Join leads to spend by Ad ID: CPL per ad.
Store IDs, not names. A name is a label anyone can edit; the ID is the key Meta uses when it reports spend per ad.
Step 1: put the Facebook ad ID in the URL
In Ads Manager, open the ad, scroll to Tracking and paste this into the URL parameters field:
ad_id={{ad.id}}&adset_id={{adset.id}}&campaign_id={{campaign.id}}
All three are among Meta’s documented dynamic URL parameters. Meta fills them in with the IDs of the ad, ad set and campaign, so each click arrives carrying its own. The Facebook URL parameters guide has the full list and a UTM version of the template.
If your form tool or CRM already captures UTM fields, reuse them instead of adding new ones:
utm_source={{site_source_name}}&utm_medium=paid&utm_campaign={{campaign.id}}&utm_term={{adset.id}}&utm_content={{ad.id}}
utm_content={{ad.id}} puts the ad ID into a field your stack already stores. Whatever keys you pick, use the same ones on every ad.
Step 2: copy the IDs into hidden form fields
The parameters sit in the address bar; the form has to pick them up. Add one hidden input per value and a few lines of script that fill them from the query string:
<form method="post" action="/lead">
<!-- visible fields: name, email, phone -->
<input type="hidden" name="ad_id">
<input type="hidden" name="adset_id">
<input type="hidden" name="campaign_id">
<input type="hidden" name="fbclid">
<button type="submit">Send</button>
</form>
<script>
const keys = ['ad_id', 'adset_id', 'campaign_id', 'fbclid'];
const params = new URLSearchParams(window.location.search);
for (const key of keys) {
const fromUrl = params.get(key);
if (fromUrl) sessionStorage.setItem(key, fromUrl);
const value = fromUrl || sessionStorage.getItem(key);
const field = document.querySelector(`input[name="${key}"]`);
if (field && value) field.value = value;
}
</script>
A few things that decide whether this works:
- The script comes after the form in the HTML, so the inputs exist when it looks for them.
- Name the inputs exactly like the CRM properties they map to.
sessionStoragekeeps the values when a multi-step form moves to a second URL in the same tab. It ends when the tab closes, so it won’t rescue a return visit days later.- A form embedded in an iframe from another domain can’t read the landing page’s address bar. Pass the values into the iframe’s URL, or use your form tool’s own hidden-field option.
fbclidisn’t needed for the join, but keep it for the Conversions API; the fbclid glossary entry explains what it can and can’t do.
Step 3: store the Facebook ad ID in your CRM
- Create custom fields for ad ID, ad set ID and campaign ID, as text. An 18-digit ID stored as a number gets rounded in some systems, and a rounded ID matches nothing; the ad_id glossary entry explains why.
- Map each hidden input to its field in your form tool, integration or webhook.
- Decide what a repeat submission does. My rule: the ad that created the lead keeps it. Later submissions from the same contact are logged as activity, with their own ad ID, and never overwrite the original.
- If leads reach your CRM by webhook, check that the IDs are in the payload. DropCPL’s lead routing sends the Meta ad ID, the landing page, the click ID and the UTM values with every lead.
Step 4: join leads to spend by ad ID
Spend comes from Ads Manager, per ad:
- Open the Ads tab in Ads Manager.
- Go to Columns, Customize columns and add Ad ID, which Meta lists in the Settings category of columns. Keep Amount spent.
- Set the same date range as your CRM export, then export the table as a CSV.
- In a spreadsheet, count the CRM leads per ad ID (a
COUNTIFon the ID column does it) and divide each ad’s spend by its count.
Example, one week:
| Ad ID (example) | Spend | Leads in CRM | CPL |
|---|---|---|---|
| …0033 | €800 | 40 | €20.00 |
| …0044 | €650 | 13 | €50.00 |
| …0055 | €300 | 0 | no leads yet |
| no ad ID | none | 9 | unattributed |
The last row is your tracking loss. Nine leads arrived without an ad ID, so something between the click and the CRM dropped the parameters; the last section lists where to look. Watch that row over time. If it grows, fix it before you trust any CPL above it.
Two cautions from measuring CPL by creative: spend and leads must cover the same window, and the current day’s spend isn’t final. Ads Manager’s own cost per result per ad uses Meta’s attributed count, so it rarely matches this table; Meta attribution settings explains the gap.
DropCPL does this join for you, because the ads are created from DropCPL and the ad_id is logged at the click. The click lands on the tracking domain that serves your landing page, spend per ad is synced from the Meta Ads API, and each lead reaches your CRM by webhook with its ad_id, landing page and click ID attached. Start free →
What about Instant Forms leads?
Lead ads with Instant Forms skip the landing page: the lead is created on Meta’s side, with the ad already attached. According to Meta’s documentation on retrieving leads:
- The leadgen webhook Meta sends when a lead is created includes
ad_id, together withleadgen_id,form_id,page_idandcreated_time. - Reading ad-specific fields such as
ad_idorcampaign_idthrough the API requires a token from someone who can advertise on the ad account and on the Facebook Page, plus permissions such asads_management. - When entries come without ad IDs, Meta lists three causes: the lead came from organic reach (
is_organicshows 1), it was submitted from an ad preview, or whoever pulled the leads lacks advertiser privileges on the ad account.
So a CRM integration that reads leads through Meta’s API can store the ad ID without any URL parameter. Check that yours maps it to a field, and step 4 works the same way. Whether Instant Forms or a landing page suits your offer is a separate decision, covered in Facebook lead ads vs landing page.
Where the Facebook ad ID gets lost
Multi-step forms
Step two loads on a new URL without the query string, and the values are gone unless step one saved them. The sessionStorage lines in the snippet cover this; so does carrying the parameters into the step-two URL. A form that reloads after a validation error has the same problem.
Redirects
A redirect between the ad and the landing page (a link shortener, an http-to-https rule, a consent or geo redirect) can rebuild the URL without its query string. Check what arrives after a real click on a live ad, not what you typed in Ads Manager.
Ad names instead of IDs
Meta fills name parameters with the name the ad had when it was first published. Rename the ad later and your CRM keeps the old name while the spend export shows the new one, so the join fails on every renamed ad.
Duplicated ads
A duplicate is a new ad. Meta’s API reference for ad copies describes copying as creating an ad, returned with its own copied_ad_id. With {{ad.id}} in the template, each copy reports its own ID and joins to its own spend. With a hard-coded value, say utm_content=video_a typed by hand, ten duplicates of a winning ad all arrive as video_a, and copies that keep the original name merge the same way if you join on names.
Return visits without the parameters
Meta’s help article on URL parameters says they apply to the visit that came from the ad: someone who leaves and comes back later on their own arrives without them. sessionStorage won’t help, because it ends with the tab. Only an identifier stored on the first visit, in a first-party cookie, ties the return to the original click, and the tracking identifiers entry explains why it should be set server-side.