Skip to content

UTM Tracking -- Full Setup

UTM tracking is the cornerstone of attribution in DURUM.ai. Without it, your leads and sales appear in the dashboard but aren't linked to the right ads. This guide walks you through the setup step by step, regardless of the form tool you use.


Why It's Essential

When a prospect clicks on a Meta ad, the destination URL contains UTM parameters that identify the campaign and ad. These parameters are the only way for DURUM.ai to know which ad generated which lead.

The problem: these parameters live in the URL. If your form doesn't capture them, the information is lost when the prospect submits the form. The lead arrives in DURUM.ai, but it's impossible to link it to the right ad.

The solution relies on three complementary layers that reinforce each other.


The Three Tracking Layers

LayerMethodReliabilityEffort
Tracking scriptDURUM.ai script on your landing pagesVery high1 line of code
Hidden form fieldsHidden fields in your CRM/form toolMaximumManual configuration
Server enrichmentDURUM.ai looks up UTMs from previous sessionsHigh (fallback)Automatic

Recommendation

The combination of layers 1 and 2 gives near-perfect coverage. Layer 3 automatically catches cases where the first two failed.


Step 1 -- URL Parameters in Meta Ads

In your Meta ads, add the following parameters in the URL Parameters field (at the ad or account level). Copy this block as-is:

utm_source=Meta&utm_medium=paid&utm_campaign={{campaign.name}}&utm_content={{ad.name}}&utm_term={{adset.name}}&utm_campaign_id={{campaign.id}}&utm_ad_id={{ad.id}}&utm_adset_id={{adset.id}}

The double curly braces (campaign.name, ad.name, etc.) are Meta macros that are automatically replaced with real values at click time. You don't need to do anything else on this side.

WARNING

Do not modify the parameter names (utm_source, utm_campaign, etc.). DURUM.ai expects these exact names. Changing utm_content to ad_name for example would prevent attribution.


Step 2 -- DURUM.ai Tracking Script

Add this line to the <head> of each of your landing pages. Replace YOUR_KEY with your DURUM.ai client_key.

html
<script src="https://app.durum.ai/api/track.js" data-client="YOUR_KEY"></script>

What the Script Does

The script is lightweight (under 5 KB) and automatically performs the following operations:

  1. Captures UTM parameters from the URL on page load
  2. Stores values in a cookie (30 days) to cover multi-page visits
  3. Publishes the values into standard cookies that third-party tools read (see below)
  4. Injects values into your form's hidden fields
  5. Decorates outbound booking links and widgets with your UTMs
  6. Detects form submission and sends UTMs to DURUM.ai

The script works with all form tools: GHL, Typeform, Calendly, Jotform, Tally, WordPress, Webflow, ClickFunnels, and any standard HTML form.

Handing attribution to third-party tools

A cookie never crosses a domain boundary. The tools you embed therefore do not all read attribution the same way, and the script covers all three cases:

CaseHow the tool receives attributionExamples
The tool runs JavaScript on your pageStandard cookies utm_source, utm_medium, utm_campaign, utm_term, utm_content and their first_utm_* counterpartsEmbedded iClosed widget
The tool renders a form inside your pageHidden fields filled automaticallyGHL, WordPress, Webflow
The tool lives on its own domainParameters appended to the link or iframe URLCalendly, Typeform, direct iClosed link

Cookies are set on your registrable domain, so they stay readable from any of your subdomains. A funnel whose opt-in lives on go.example.com and whose booking page lives on www.example.com works with no extra configuration.

Script placement

Load the script in the <head>, before any third-party widget. It then writes the cookies before the widget tries to read them.


Attribution model: channel versus ad

The script separates two pieces of information that do not have the same lifetime, because they answer different questions.

The channel (utm_source, utm_medium) answers "where did the last click come from?". It follows the last touch. If your prospect comes back through an email, the channel becomes email.

The ad (utm_campaign, utm_term, utm_content and the ad platform IDs) answers "which ad produced this lead?". It is only ever replaced by another ad. An email click, a link in bio or an organic share never overwrite it.

In practice: a prospect clicks your Meta ad on Monday, does nothing, comes back Tuesday through your nurturing sequence and books. The attribution handed over is utm_source=email together with the original Meta campaign, ad set and ad. You know both which ad paid for the lead and which channel triggered the booking.

A consequence worth knowing

The channel and the ad can come from two different clicks. In the dashboard, splitting revenue by source will credit the email, while splitting it by campaign will credit the ad. Those two views are therefore not two slices of the same total.

The first_utm_* fields keep the contact's very first known touch, for one year.

Tag your emails as owned channels

If you would rather keep the ad credited as the source, tag your email and SMS links with utm_channel=email instead of utm_source=email. The script then treats that click as an assist and does not touch ad attribution at all.

Smart Detection

The script detects form submissions via six different methods, allowing it to work even with the most exotic forms:

  • Native HTML submission (standard forms, GHL, WordPress)
  • Typeform iframe (via postMessage)
  • Calendly iframe (via postMessage)
  • AJAX and fetch calls (SPA forms, React, Vue)
  • Thank-you pages (detection via MutationObserver)
  • URL changes (SPA redirects to /thanks, /thank-you)

First-party domain (CNAME)

By default the script is served from app.durum.ai, a third-party domain. Ad-blockers and Safari/iOS tracking protection can then block part of the measurement, especially page views.

For maximum coverage (from about 70-85% to 90-95%), serve the pixel first-party from a subdomain of your own site. Two steps:

1. Add a DNS record at your registrar (where your domain is managed):

TypeNameValue
CNAMEtrkcname.vercel-dns.com

This creates trk.your-domain.com. DURUM adds the domain and issues the SSL certificate automatically.

2. Replace the script with its first-party version (no data-client needed, the domain resolves your account):

html
<script src="https://trk.your-domain.com/api/track.js"></script>

The script and beacons now live on a subdomain of your own site: they resist blockers that filter by third-party domain, and Safari/Firefox treat them as first-party.

What stays out of reach

Even first-party, no JavaScript pixel counts 100% of page views (JS disabled, signature-based blockers, consent declined). For an exhaustive total identical to your funnel platform's (which counts server-side), reconcile against the platform's server-side total. The pixel gives you the source of each page view.

Consent

The first-party pixel respects consent exactly like the standard version: no page view is collected if the visitor declined (Law 25 / GDPR). Getting past ad-blockers never authorizes bypassing consent.


Step 3 -- Hidden Fields in Your Forms

The tracking script covers most cases, but adding hidden fields to your forms provides an extra safety layer. It's also the only way to guarantee your CRM stores UTMs directly in the contact.

Naming Convention

Field names must be exactly as in this table. Respect lowercase and underscores.

Field NameDescriptionRequired
utm_sourceTraffic source (Meta, Google, etc.)Yes
utm_mediumTraffic type (paid, organic, email)Yes
utm_campaignCampaign nameYes
utm_contentAd nameYes
utm_termAd set nameYes
utm_campaign_idMeta campaign numeric IDRecommended
utm_ad_idMeta ad numeric IDRecommended
utm_adset_idAd set numeric IDRecommended

WARNING

Field names are case-sensitive. UTM_Source or utmSource won't be recognized. Always use the lowercase version with underscores.


Configuration by Platform

GoHighLevel (GHL)

  1. Go to Settings > Custom Fields > Contact
  2. Create each field from the table above as Single Line Text
  3. Name each field exactly as in the table (e.g., utm_source)
  4. In your form or capture page, add these fields in Hidden mode
  5. GHL supports native pre-fill via URL parameters: fields named utm_source, utm_campaign, etc. are automatically filled from the URL

TIP

With GHL, the DURUM.ai script and custom fields are complementary. The script fills hidden fields on page load, and GHL saves them in the contact at submission time.

Typeform

  1. In your form settings, go to Hidden Fields
  2. Add the following fields: utm_source, utm_medium, utm_campaign, utm_content, utm_term, utm_campaign_id, utm_ad_id, utm_adset_id, client_key
  3. Typeform automatically extracts values from URL parameters
  4. Set client_key to your DURUM.ai client key

Calendly

Calendly automatically captures standard UTM parameters (utm_source, utm_medium, utm_campaign, utm_content, utm_term) from the URL of the page where the embed is placed. No additional configuration needed.

The DURUM.ai script completes the tracking by capturing Meta identifiers (utm_ad_id, utm_campaign_id) that Calendly doesn't natively support.

Jotform

  1. Add Hidden Field elements to your form
  2. Set the Field Name to the exact name (e.g., utm_source)
  3. Check Prepopulate and set the Query Parameter Name to the same name

Tally

  1. In the form settings, add Hidden Fields
  2. Name each field exactly as in the table
  3. Tally automatically captures corresponding URL parameters

WordPress / Elementor

  1. Add Hidden type fields to your form
  2. Set the Default Value to URL Parameter and specify the name (e.g., utm_source)

Webflow

  1. Add <input type="hidden"> fields to your form
  2. Set the name attribute to the exact UTM name
  3. The DURUM.ai script will automatically fill these fields

Custom HTML

If you use a custom HTML form, add these hidden fields between the <form> tags:

html
<input type="hidden" name="utm_source">
<input type="hidden" name="utm_medium">
<input type="hidden" name="utm_campaign">
<input type="hidden" name="utm_content">
<input type="hidden" name="utm_term">
<input type="hidden" name="utm_campaign_id">
<input type="hidden" name="utm_ad_id">
<input type="hidden" name="utm_adset_id">
<input type="hidden" name="client_key" value="YOUR_KEY">

The DURUM.ai script will automatically inject values from the UTM cookie on page load.


Step 4 -- Test Your Configuration

DURUM.ai provides a test endpoint to verify your UTMs are being transmitted correctly.

Test via URL

Open this URL in your browser, replacing the values:

https://app.durum.ai/api/webhook/test-utm?client_key=YOUR_KEY&utm_source=Meta&utm_medium=paid&utm_campaign=test_campaign&utm_content=test_ad&utm_term=test_adset

Test via Webhook

Send a test webhook identical to what your form would send:

POST https://app.durum.ai/api/webhook/test-utm?client_key=YOUR_KEY
Content-Type: application/json

{
  "email": "test@example.com",
  "utm_source": "Meta",
  "utm_medium": "paid",
  "utm_campaign": "test_campaign",
  "utm_content": "test_ad",
  "utm_term": "test_adset"
}

Understanding the Response

The response contains a grade from A+ to F and a breakdown of what was found:

GradeMeaning
A+All required and recommended fields are present
AAll required fields are present
BMost required fields are present
CSome required fields are missing
D/FIncomplete configuration, attribution will be limited

The response also indicates missing fields and any warnings to correct your configuration.


Troubleshooting

My leads arrive but aren't attributed

  1. Verify UTM parameters are present in the destination URL of your Meta ads
  2. Verify the DURUM.ai script is properly installed on the landing page
  3. Test with the /api/webhook/test-utm endpoint to see which fields are received
  4. Verify the hidden field naming convention in your form

UTMs contain unresolved placeholders

If you see values like campaign_name or unresolved curly braces instead of real values, it means Meta macros weren't resolved. Verify UTM parameters are configured at the right level in Meta Ads Manager (at the ad level, not the account level).

The tracking script doesn't detect the submission

Some highly customized forms may escape automatic detection. In that case, hidden form fields take over. Make sure you've configured hidden fields as explained in your platform's section.

I use a tool that isn't listed

The DURUM.ai script works with any standard HTML form. If your tool generates a standard <form> or uses fetch/XMLHttpRequest to submit data, tracking will work. When in doubt, add HTML hidden fields and the script will handle the rest.


Summary

For complete attribution in DURUM.ai:

  1. Add UTM parameters to your Meta ads
  2. Install the tracking script on your landing pages (1 line)
  3. Add hidden fields to your forms (exact naming required)
  4. Test with the validation endpoint

These three steps ensure every lead, booking, and sale is correctly attributed to the ad that generated it.

Powered by DURUM.ai — ad attribution and operational intelligence