Matching sessions to ads
Every session with paid attribution is assigned to the most specific ad entity 3x can prove. The
ads.match job evaluates new sessions every 15 minutes and re-evaluates the last 90 days whenever
entities, URL tags or rules change. The result (session_ad_match) stores the platform, account,
campaign, ad set, ad and the method that matched.
Methods (first match wins)
| # | Method | Evidence | Precision |
|---|---|---|---|
| 1 | click_id | gclid/gbraid/wbraid found in Google's click view | exact ad and keyword |
| 2 | utm_id | UTM values that are platform ids of imported campaigns/ad sets/ads (templates) | exact |
| 3 | utm_map | the literal UTM combination configured on an ad (Meta URL parameters, TikTok landing URLs, LinkedIn URLs, Google suffixes and templates) | exact, or the deepest common parent when several ads share it |
| 4 | name | normalized utm_campaign equals a campaign name (umlauts transliterated, punctuation → -) | campaign; only when unique per platform and account |
| 5 | manual | your mapping rules, highest priority first | as configured |
| 6 | platform | only the platform is known (click id or a source alias like fb, ig, li) | platform |
Platform-level matches still count for platform cost per session; they just cannot be broken down further.
Unmatched UTMs
Dashboard → Ads → Unmatched UTMs (GET /v1/projects/{project}/ads/unmatched, MCP ads_unmatched)
lists UTM combinations of paid sessions that matched only at platform level or not at all, with
session counts. Typical causes:
- ads without URL parameters (add the template),
- hand-written campaign names that differ from the platform name (
spring-salevs. "Spring Sale 2026 – DE"), - links from organic posts of the same platform,
- old campaigns that were deleted before the first sync (entities are only imported while they exist).
Mapping rules
A rule maps a UTM pattern to an entity:
{
"platform": "meta_ads",
"accountId": "act_1234567890",
"match": { "utm_source": "fb", "utm_campaign": "spring-sale" },
"campaignId": "120210000000000001",
"adsetId": null,
"adId": null,
"priority": 10,
"note": "Spring campaign used hand-written UTMs"
}matchtakes any ofutm_source,utm_medium,utm_campaign,utm_content,utm_term,utm_id; all given keys must be equal (case-insensitive).- Higher
prioritywins when several rules fit. - Create rules from the unmatched view with one click, via
POST /v1/projects/{project}/ads/mapping-rules(scopeads:write) or MCPads_mapping_rules_create. Existing sessions are re-matched on the nextads.matchrun.
Day alignment
Costs are stored per ad-account day, sessions per project day. With different time zones, a daily chart can shift spend by a few hours relative to sessions. Ranges of several days are practically unaffected. LinkedIn is always UTC.