1. Lead form
2. What the hidden fields hold
First touch is saved once and never overwritten. Last touch updates on every visit that carries UTMs or a click ID. Both live in localStorage, so a lead who clicks an ad today and books next week still carries the source.
3. Payload sent to the n8n webhook
Submit the form to see the payload.
4. How it maps into GoHighLevel
| Form / script value | GHL contact custom field (key) | Where it is used |
|---|---|---|
| ft_source / ft_medium / ft_campaign | contact.first_touch_source / first_touch_medium / first_touch_campaign | Never overwritten. Answers "which ad first brought this person in". |
| lt_source / lt_medium / lt_campaign | contact.last_touch_source / last_touch_medium / last_touch_campaign | Updated on each form fill. Answers "what made them convert". |
| utm_content | contact.utm_content | Ad or post creative level reporting. |
| fbclid / fbc | contact.fbclid / contact.fbc | Sent back with Conversions API events to match Meta clicks. |
| referrer / landing_page | contact.referrer / contact.landing_page | Debugging and organic traffic. |
| lt_source + lt_campaign | Contact "source" (standard field) | Shows in the contact record and in GHL source reports. |
| first touch fields | Opportunity source + opportunity custom fields | A workflow copies the contact fields onto the opportunity when it is created (form, calendar booking or manual), so revenue reports by source work even after the lead moves stages. |
Key rule: calendars and later forms often create or update the contact without UTMs. Writing to dedicated first touch fields that workflows never overwrite is what stops the source from dropping.
5. UTM naming convention
| Parameter | Rule | Ads Manager example | Boosted post example |
|---|---|---|---|
| utm_source | Platform, lowercase | facebook or instagram | facebook or instagram |
| utm_medium | Type of traffic | paid_social | boosted_post |
| utm_campaign | yyyy-mm_offer_goal | 2026-11_coach-webinar_lead | 2026-11_boost_testimonial-post |
| utm_content | Creative or post ID | video-a_hook1 (or {{ad.name}}) | post-1103 |
| utm_term | Audience | lookalike-1pct (or {{adset.name}}) | followers |
Ads Manager supports dynamic URL parameters such as {{campaign.name}} and {{ad.name}} in the URL parameters box. Boosted posts usually do not give you the same control, so links in the post or button need the UTMs typed in by hand. When a boosted post sends people to Messenger, Instagram DM or an instant form, there is no page visit, so UTMs cannot be captured on a website and the source has to be set in the GHL workflow or integration instead.
6. The n8n side
The matching n8n workflow (in the public repo ghl-attribution-demo by 369Apps on GitHub) does this:
- Webhook receives the form payload above.
- Code node cleans the values and builds the GHL custom field list.
- HTTP node calls the GHL API contact upsert (by email or phone) with source and custom fields.
- First touch fields are only written when empty, so a returning lead keeps the original source.