The stages, and the event for each
| Stage | Where it happens | Canonical event | Sent by |
|---|---|---|---|
| Form submitted | browser | generate_lead (lead_type, form_id) | SDK, hybrid with vendor pixels |
| Demo booked | booking tool | book_appointment | SDK on confirmation, or webhook |
| Contact request | browser | contact | SDK |
| Marketing qualified | marketing automation | qualified_lead (custom, declared) | server source |
| Sales qualified / opportunity | CRM | qualified_lead with stage | server source |
| Closed won | CRM | purchase with value, transaction_id | server source |
Each stage is its own event and, in the ad platforms, its own conversion action or rule. That is what prevents double counting: nothing is sent twice under the same name from two sources.
Step 1: the form
- Fire
generate_leadon successful submission, not on button click. For AJAX forms, on the success callback; for full-page redirects, on the thank-you page with a guard against reloads. - Include
form_idandlead_type(demo,whitepaper,contact), nothing free-text. - Call
identifywith the business e-mail after marketing consent. The SDK hashes it before it leaves the browser and attaches it asemfor the platforms that match on hashed e-mail (Meta, LinkedIn, Google Enhanced Conversions for Leads, Microsoft, TikTok).
Step 2: carry the click ids into the CRM
Copy the click-id parameters from the landing URL — gclid, msclkid, fbclid, li_fat_id, ttclid — into hidden form fields when marketing consent is granted, and store them on the lead record together with the landing page and the consent state at submission. Weeks later, these are what make an upload match deterministically instead of relying on e-mail hashing alone.
Step 3: qualified leads from the CRM
When a lead reaches a stage worth optimising for, the CRM sends a server event: qualified_lead with the stage, the original click ids, the hashed e-mail and the stage timestamp. Each platform receives it on its own conversion action:
- Google Ads:
uploadClickConversionswithgclidor hashed e-mail, conversion action "Qualified lead (CRM)". - LinkedIn: Conversions API with
SHA256_EMAILorli_fat_id, rule "Qualified lead", within 90 days. - Meta: Conversions API
Leadwithaction_source: system_generated, on a separate custom conversion so the pixelLeadstays separate. - Microsoft: CAPI with
msclkidor hashed e-mail, goal "Qualified lead".
Step 4: closed won as purchase
The deal value is real now. Send purchase with value, currency and the CRM opportunity id as transaction_id, from the server, with the close date as the event time. Platforms that support value-based bidding get the actual number; those that do not still count the conversion.
Consent across the stages
- The form and its
generate_leadneed marketing consent for advertising destinations; analytics-only consent still records the event for your own reporting. - Click ids are captured only with marketing consent. Without it, the CRM stage uploads can only use hashed e-mail, and only if the person consented to that use at submission — record the consent state on the lead.
- CRM uploads carry the consent state you recorded at submission; the policy engine routes accordingly, and an upload without consent information reaches no advertising destination. A lead that refused marketing at the form is not uploaded to advertising platforms later, however valuable.
Values without inventing them
For qualified_lead, either send no value or a documented expected value per stage configured as a constant in the destination mapping. For purchase, send the real deal value. Never backfill a value into generate_lead; the platforms will optimise toward whatever you send, and a fabricated number optimises toward fiction.
What to look at weekly
- Health score coverage:
generate_leadseen on every form id in the plan. - Destination monitor: upload acceptance per platform;
CLICK_NOT_FOUNDand out-of-window errors point at missing click ids or late uploads. - Consent page: share of leads with marketing consent — the ceiling for what advertising platforms can ever see.