TikTok Ads UTM Tracking
Last updated · published
Put a macro template in every TikTok ad’s URL parameter field. That single habit is the whole tagging strategy, and it is the one most accounts skip.
TikTok does offer to tag links for you. Auto-attach fills in a source, a medium of Paid, plus campaign and ad identifiers. Useful as a floor, wrong as a convention: capital-P Paid matches nothing in your channel rules, and the names it inserts are whatever somebody typed in Ads Manager.
So override it deliberately rather than leaving it on and hoping.
TL;DR
- Auto-attach exists in Ads Manager and its defaults are not your taxonomy. Set your own parameters at the ad level.
- Nothing inherits. Campaign and ad group carry no parameter field, so every ad ships its own tagged URL.
- Macros resolve at click time:
__CAMPAIGN_NAME__,__AID_NAME__,__CID_NAME__,__PLACEMENT__and the ID variants, plus__ADID_V2__on upgraded Smart+ campaigns.__PLACEMENT__returns values likeTikTokandTikTok Pangle, not the inventedplacement_tiktokform older guides use.ttclidis the Events API’s click key, and your ad group’s attribution window caps what gets credited regardless of how the match was made.- TikTok reports more conversions than GA4 because of view-through credit and in-app browser isolation. Both numbers are real.
Where the Parameters Go
TikTok exposes URL parameters on the ad, below the Website URL field in the ad creation panel. There is no equivalent at campaign or ad group level, so every ad needs its own.
A correctly tagged destination looks like this:
https://example.com/landing?utm_source=tiktok&utm_medium=paid-social&utm_campaign=2026-summer-launch&utm_content=video-vertical-a
Do not put UTMs in the Tracking URLs field. That field fires third-party measurement beacons on a click or impression and has nothing to do with your destination URL.
The macros that resolve
Write the template once and let TikTok substitute at click time:
| Macro | Resolves to |
|---|---|
__CAMPAIGN_ID__, __CAMPAIGN_NAME__ | The campaign |
__AID__, __AID_NAME__ | The ad group |
__CID__, __CID_NAME__ | The ad |
__PLACEMENT__ | Where it served |
__ADID_V2__, __ADID_V2_NAME__ | The ad on upgraded Smart+ campaigns |
A usable template:
utm_source=tiktok&utm_medium=paid-social&utm_campaign=__CAMPAIGN_NAME__&utm_content=__CID_NAME__&utm_term=__PLACEMENT__
One correction worth making loudly, because it circulates widely. __PLACEMENT__ returns TikTok’s own placement labels, TikTok and TikTok Pangle. It does not return placement_tiktok or placement_pangle, and any example built on those strings produces values that never occur.
Name macros also inherit whatever naming exists in Ads Manager, spaces and capitals included. Use the ID macros for joins and a governed string for the campaign name your reports group on. Terminus, the marketing taxonomy governance platform, is where that string comes from, so the same campaign name appears across TikTok, Meta, search and email.
Placement: Source or Content?
TikTok inventory is not one surface. Your ad serves in the main feed and on Pangle, TikTok’s third-party network, and the two behave differently enough that you may want them separated.
Two workable conventions. Pick one for the whole account.
Placement in the source. Tag utm_source with the placement value itself. You see Pangle separately in every report without filtering, at the cost of more than one source value describing one ad account, plus channel rules for each.
Placement in the content, source stays clean. Keep utm_source=tiktok for everything and push placement into utm_content:
utm_source=tiktok&utm_medium=paid-social&utm_campaign=__CAMPAIGN_NAME__&utm_content=__CID_NAME__-__PLACEMENT__&utm_term=__AID_NAME__
One source, trivial channel grouping, and Pangle is still separable by filtering utm_content. This is the right default for most budgets.
What breaks either convention is two campaign owners choosing differently inside one account. One writes tiktok, another writes TikTok-Ads, and your source report splits. That is a governance problem, not a TikTok problem.
ttclid and the Attribution Window
ttclid is TikTok’s click identifier, appended to destination URLs. Treat the value as opaque: you store it and hand it back, you never parse it.
The window that matters is set on the ad group, not on the identifier. The default click-through window is 7 days, shorter than the 30 days older articles cite and shorter than Meta’s default, and view-through runs 1 day by default with engaged view-through available as a separate setting.
Two consequences follow, and one widely repeated claim does not.
A conversion outside the window is not credited in TikTok’s reporting, whatever identifiers you send. Late conversions are still yours to analyse in the warehouse, joined on the stored ttclid, but they will not appear in Ads Manager.
Hashed identifiers raise your match rate on every event, fresh or not. The thing they do not do is rescue a day-8 conversion. There is no documented fallback where ttclid expires and email quietly takes over; the window caps the attributed conversion regardless of which key matched it. Send email, phone and external_id because they improve matching, not because they extend time.
Practically: ttclid survives most ESP and short-link redirects. It does not appear on the community-reported lists of parameters iOS strips, though Apple publishes no list, so re-check if you depend on it. Strict tracking protection in some browsers can block the client-side pixel, which is what the Events API is for.
Spark Ads
Spark Ads promote an existing organic video, yours or a creator’s, so they look native. Tagging follows one rule: the destination URL and its parameters live on the ad, exactly as with any in-feed ad.
The failure to avoid is a configuration where the click goes somewhere you do not control, such as a profile or a bio link. Those visits arrive with no campaign attribution at all. Give every Spark Ad a call to action pointing at your tagged URL, and check the live click before the budget scales.
Events API
The Events API is the server-side companion to the pixel. Your server posts the conversion, TikTok matches it to the click, and the event feeds optimisation as well as reporting.
A minimal payload:
{
"event_source": "web",
"event_source_id": "<your-pixel-id>",
"data": [
{
"event": "CompletePayment",
"event_time": 1734614400,
"event_id": "<your-deduplication-id>",
"user": {
"ttclid": "E.C.P.AaAbCdEf...",
"email": "<sha256-hashed-email>",
"phone": "<sha256-hashed-phone>",
"external_id": "<sha256-hashed-customer-id>"
},
"properties": {
"value": 49.95,
"currency": "USD",
"utm_source": "tiktok",
"utm_medium": "paid-social",
"utm_campaign": "spring-launch"
}
}
]
}
Four things to get right:
Capture ttclid at landing and keep it. Write it to a first-party cookie and your session store, then include it on every conversion event.
Keep UTMs in properties. TikTok does not match on them. They are there for your warehouse and your CRM.
Hash identifiers properly. SHA-256, lowercase, phone numbers in E.164. Formatting mistakes fail silently, which is the worst way to fail.
Deduplicate with a shared event_id when the pixel fires the same event. Without it you double-count and bidding degrades.
Consent state travels here too. TikTok accepts a limited_data_use flag, so pipe your consent platform’s state through rather than deciding at the server.
Why Ads Manager and GA4 Disagree
They count different things. Once that is clear, the reconciliation stops being an argument.
View-through credit. TikTok counts conversions from people who saw the ad and never clicked. GA4 has no concept of this. For a fair comparison, filter Ads Manager to click-through only.
In-app browser isolation. Most TikTok clicks open inside TikTok’s own browser, whose cookies are sandboxed from Safari or Chrome. Convert later in a normal browser and GA4 sees a brand new person with no campaign. This is the single largest source of loss, and the Events API is what recovers it on TikTok’s side.
Attribution shape. TikTok credits the click that happened inside its window. GA4 credits the session, using last non-direct click, so a returning visitor who came back without clicking usually keeps the original TikTok attribution rather than falling to Direct. Where GA4 genuinely loses the thread is the cross-browser case above.
Invalid traffic filtering. TikTok removes some clicks before they count. GA4 records the session anyway.
Tagging mistakes. A medium of social rather than paid-social files paid spend under Organic Social. Note that GA4’s Paid Social rule already catches tiktok with a paid medium, so no custom channel group is needed once the tagging is right.
A weekly routine: pull click-through conversions by campaign from Ads Manager, pull GA4 conversions filtered to the TikTok source for the same dates, and track the ratio per campaign over time. The ratio itself matters less than its stability. A campaign whose ratio suddenly moves has a tagging problem worth sampling.
Both numbers are real. The warehouse view, joining stored ttclid values to customer records, is the third one and usually the closest to truth.
The In-App Browser
Worth its own section, because it explains most of what teams blame on TikTok.
A tap opens the destination in TikTok’s embedded browser, with its own cookie store, local storage and user agent. Nothing there is visible to the user’s normal browser.
- First-party cookies do not carry over, so a later visit in Safari is a new person.
- Some authentication flows stall, and users reopen the page elsewhere, arriving as Direct.
- Service workers and installed app state do not transfer.
What helps: the Events API to reconnect click and conversion server-side, capturing an identifier as early as the funnel allows so sessions can be stitched later, and treating in-app sessions as a distinct context in any attribution model rather than pretending they are continuous.
Android behaves slightly better, but TikTok’s app still defaults to an embedded view. Test on a real device, and retest after app updates.
FAQ
Does TikTok auto-tag like Google Ads?
Partly. Ads Manager has an Auto-attach setting that adds a source, a medium of Paid, and campaign and ad identifiers when an ad has no parameters of its own. It is a floor, not a taxonomy: the medium does not match GA4’s paid rules and the names come from Ads Manager. Set your own values.
Where do UTM parameters go?
On the ad, in the URL parameter field below the website URL. Not in Tracking URLs, which is for third-party measurement beacons.
Do parameters inherit from campaign or ad group?
No. Every ad carries its own. A macro template pasted at creation time is what keeps that manageable.
What does __PLACEMENT__ return?
TikTok’s placement labels, such as TikTok and TikTok Pangle. Examples built on placement_tiktok are wrong and will never match your data.
What is the attribution window for ttclid?
Your ad group’s window, which defaults to 7 days for click-through and 1 day for view-through, with engaged view-through configurable separately. The window caps the conversion regardless of which identifier matched it.
Does sending hashed email extend the window?
No. It improves match rates within the window. A conversion after the window closes is not credited in TikTok’s reporting either way.
Can I send UTMs to the Events API?
Yes, in properties. TikTok does not use them for matching, so they exist for your own analysis.
Why does TikTok report more conversions than GA4?
View-through credit that GA4 never counts, plus in-app browser isolation splitting one person into two. Fix tagging first, then accept that a structural gap remains.
Does iOS strip ttclid?
It does not appear on the community-reported lists, which name gclid, fbclid and msclkid. Apple publishes nothing official, so verify if you depend on it. Note that stripping applies to Mail, Messages and Private Browsing rather than to in-app browsers.
Should I run the pixel and the Events API together?
Yes, with a shared event_id. The pixel catches client-side richness, the API catches what blockers and embedded browsers break.
Every account starts with a 21-day trial, no credit card required.