Clean ad-level matching

Platforms count clicks per ad. X3 can only show visits, leads and cost per lead per ad when the landing URL tells it which ad was clicked. When the link only names the campaign or the ad set, the visits land in the row „Anzeige nicht erkennbar“ of the campaign's ad drill-down – and the ads themselves show clicks but 0 visits.

Why visits stay above ad level

X3 records the reason for every paid visit it could not match down to one ad (session_ad_match.gap_reason; GET /v1/projects/{project}/ads/unmatched?level=ad, MCP ads_unmatched with level: "ad"):

ReasonWhat happenedFix
ambiguousutm_content fits several ads – typically the same readable URL parameters in several ad sets (a duplicated ad set copies them), and the link names no ad setput the ad id into the link (template below), or at least utm_term={{adset.id}}
content_not_adutm_content is neither an ad id nor the URL tag or name of an ad of the campaigntemplate below
no_ad_paramthe link carries campaign / ad set values but nothing about the adtemplate below
campaign_unknownthe UTM values name no synced campaigntemplate, a mapping rule
ids_unknownthe ids belong to an ad account X3 is not connected toconnect the account or name the campaign
no_paramsonly the platform is known (click id or source alias)template below

Before giving up, X3 compares utm_content (and on Meta, LinkedIn and TikTok also utm_term) with the ads of the matched campaign – within the ad set when the link names it: the ad's own URL tags, then the slug of its name (k3-r8-08a-kunden-fragen fits „K3 · R8-08A Kunden fragen · Bild 4x5“), then its distinctive codes („K3“, „R8-08A“). Only a unique hit counts (method name_slug, confidence high, or medium for codes only); id-based matches always win, placement values like Facebook_Mobile_Feed are never read as ids or names, and an ambiguous hit stays at campaign / ad set level with reason ambiguous.

Visits without any campaign („Werbebesuche ohne Kampagne“)

A paid visit that matches no campaign at all (only the platform is known) gets a cause on top of Quellen & Kampagnen, with the one action that fixes it (GET /v1/projects/{project}/ads/unmatched/diagnosis, MCP ads_unmatched_diagnosis; troubleshooting article Paid visits have no campaign):

Cause (kind)What happenedAction
click_sync_pendingGoogle click ids are there, the click sync of that day has not seen them yetnone: X3 fetches days with waiting click ids every hour (today included) and re-matches the visits; the hint names the expected time
connected_afterthe visits came before the ad account was (re)connectednone: the catch-up fetches clicks up to 90 days back
url_params_missingno campaign values in the link: iPhone click ids (wbraid/gbraid, never in click_view), no click id at all, or a click id no connected account knowsGoogle: URL-Suffix in Google Ads setzen (one click, preview first, MCP ads_url_suffix_set); Meta, LinkedIn, TikTok: copy the URL parameters below
not_connectedthe platform's ad account is not connectedconnect it
campaign_unknownthe link names a campaign no connected account hasname the campaign or connect that account

Click sync. Google's click_view answers one day per query, at most 90 days back, and lists a click within about an hour (measured: a sync at 16:52 returned the clicks up to 16:18). X3 therefore syncs, besides D-1 and D-2 every day, every hour the days that still have click ids without a campaign (ads.clicks_pending, at most 10 days per account and run), and on connect the last 30 days including today. Every click sync re-matches the visits of its clicks right away. Meta (fbclid), LinkedIn (li_fat_id) and TikTok (ttclid) have no API that resolves a click id to a campaign; there the URL parameters are the only way, and their metrics sync hourly anyway.

URL suffix. X3 sets the account-level final URL suffix through the project's Google Ads login (CustomerService.MutateCustomer, update mask final_url_suffix only): an existing suffix is merged (its parameters stay, missing ones are added), never overwritten; campaigns with their own suffix are listed because the account suffix does not apply there; X3 reads the setting back afterwards. No campaign, budget or ad is changed. Earlier visits stay without a campaign, new clicks match.

Task „Werbebesuche ohne Kampagne“ (type ad_campaign_gap, one per platform): opens when on one day (today or yesterday) at least 20 % of a platform's paid visits (at least 10) have no campaign and no sync is pending, links to the hint (Google: straight into the URL suffix preview) and resolves itself below 10 %. Visits a sync will still match also do not count for the recommendation „Ordne bezahlte Besuche einer Anzeige zu“.

Early warning: „Anzeigen nicht zuordenbar“

The task „Anzeigen nicht zuordenbar“ (type ad_match_gap, one per platform) opens when, in the last 7 days,

  • at least 20 % of the paid visits of campaigns with synced ads (at least 20 visits) reach only campaign or ad set level, or the same share of the clicked ads' spend sits on ads without any matched visit, or
  • an ad has 20 or more clicks but no matched visit while its campaign has visits without an ad.

It names the detected cause and the template for the platform, links to the affected campaign's ads and resolves itself once the share drops below 10 % (after new links, or after X3 re-matched old visits). Google's utm_content carries the ad group by template, so there only ambiguous and content_not_ad count.

The ad drill-down shows the reason breakdown in the „Anzeige nicht erkennbar“ row and, per ad, the column „Klicks vs. Besuche“ with the hint „Besuche fehlen“ when an ad has at least 20 clicks and fewer matched visits than a fifth of them.

URL parameter templates

Set them once per ad (Meta, TikTok) or per account / campaign (Google, LinkedIn); every new ad then matches exactly. The same strings: dashboard Einstellungen → Werbekonten, GET /v1/projects/{project}/ads/utm-templates, MCP ads_utm_templates, and UTM templates.

Meta Ads – Werbeanzeigenmanager → Anzeige → Tracking → URL-Parameter (select all ads of a campaign and edit them together):

utm_source=meta&utm_medium=paid&utm_campaign={{campaign.id}}&utm_content={{ad.id}}&utm_term={{adset.id}}&utm_id={{campaign.id}}&x3_place={{placement}}

Prefer readable names in Google Analytics? Keep utm_campaign={{campaign.name}} – X3 matches by utm_id, utm_term and utm_content. Keep utm_content={{ad.id}}: a readable slug there is what makes duplicated ad sets ambiguous.

Google Ads – Kontoeinstellungen → Tracking → Suffix der finalen URL; keep auto-tagging on (the gclid matches the exact ad):

utm_source=google&utm_medium=cpc&utm_campaign={campaignid}&utm_content={adgroupid}&utm_term={keyword}&utm_id={campaignid}&x3_ad={creative}

LinkedIn Ads and TikTok Ads: see UTM templates.

New visits match right away. Old visits keep their link; X3 re-matches the last 90 days by itself whenever entities, URL tags, rules or the matcher change (ads.match job). Operators can re-match a project explicitly and see the before/after share with the ops script ads:rematch (dry run by default). Re-matching changes attribution only – conversions already reported to the platforms are not sent again.