| utm_source | chatgpt |
| utm_medium | cpc |
| utm_campaign | {campaign_id} |
| utm_id | {campaign_id} |
| utm_term | {ad_id} |
| utm_content | {ad_group_id} |
ChatGPT Ads is OpenAI’s self-serve platform for placing ads inside ChatGPT conversations. You buy through Ads Manager at ads.openai.com, currently in beta and rolling out to approved advertisers by country. The account structure is the familiar three-level stack — campaign, ad group, ad — and OpenAI has added the measurement plumbing an advertiser expects: a query-parameter field for your own tracking, dynamic macros, a click reference, a Pixel, and a Conversions API.
This is a fundamentally different situation from ChatGPT’s organic citations. When ChatGPT cites your page in an answer, the link is your canonical URL and you cannot tag it (see ChatGPT ). An ad is a link you author: you control the destination and the query string, so you tag it the way you tag Google Ads or Meta.
The platform is young and the macro list is short. That makes setup quick and the mistakes easy to name: putting oppref in the template, expecting name macros that don’t exist, and setting parameters at two levels without realizing one overrides the other.
What ChatGPT Ads calls UTMs
Ads Manager has no “tracking template” or “URL parameters” object in the Google or Meta sense. It has a single free-text field called Landing page query parameters. Whatever you type there is appended to the landing URL as a query string when the ad serves, with the macros substituted.
The field takes key=value pairs joined with &, no leading ?. It accepts your own static text alongside the supported macros.
Where to put the parameters
- In Ads Manager, open the Campaigns page.
- Click the three-dot menu on the campaign and choose Edit campaign.
- Paste the template into Landing page query parameters.
- Save.
The same field exists on the Ad Groups page (Edit ad group) and the Ads page (Edit ad). It also exists, implicitly, on the ad’s own destination URL: anything already in the ad URL’s query string is honored first.
The recommended UTM template
| Parameter | Value | Why |
|---|---|---|
utm_source | chatgpt | Names the surface the ad appeared on. Sits next to google and bing in source/medium reports. |
utm_medium | cpc | Matches GA4’s paid-traffic regex and the convention used across your other paid channels. |
utm_campaign | {campaign_id} | There is no name macro. Swap for a static label if you need readable campaign names (see below). |
utm_id | {campaign_id} | Always the numeric ID — rename-safe, and the join key for GA4 cost-data imports. |
utm_term | {ad_id} | Creative-level dimension. The ad is the smallest unit OpenAI exposes. |
utm_content | {ad_group_id} | Audience or theme differentiation within a campaign. |
Pasted into the field, it looks like this:
utm_source=chatgpt&utm_medium=cpc&utm_campaign={campaign_id}&utm_id={campaign_id}&utm_term={ad_id}&utm_content={ad_group_id}
This is the template the builder hands you when you pick “ChatGPT Ads.” It deliberately omits oppref — OpenAI adds that itself.
IDs, not names
Every macro returns a numeric ID. OpenAI does not offer {campaign_name}, {ad_group_name}, or {ad_name}. Two ways to live with that:
- Dynamic IDs everywhere (recommended). One template, pasted into every campaign, never edited. Reports show IDs; you match them to names in Ads Manager or a CSV export. IDs survive renames, so historical joins never break.
- Static label in
utm_campaign. Replace{campaign_id}withspring-saleand keep{campaign_id}inutm_id. Reports read well, but each campaign needs its own template and you maintain the labels by hand. Leave{ad_group_id}and{ad_id}dynamic either way.
Dynamic macros
OpenAI’s macro catalog is four entries plus the click reference. All are lowercase, single-brace, underscore-separated — {campaign_id}, not {campaignid} (Google) and not {{campaign.id}} (Meta). Case matters: {Campaign_ID} outputs literally.
There are no conditional macros, no device or placement tokens, and no keyword token. If you need to distinguish surfaces or devices, split the campaign rather than templating.
The full list, with what each returns, is in the token table below.
Auto-tagging: oppref
OpenAI appends its own click reference to every landing page URL, in this shape:
https://example.com/landing?utm_source=chatgpt&...&oppref=gAAAAAb123
oppref plays the role gclid plays for Google Ads and fbclid for Meta. It is how OpenAI ties a conversion back to the click:
- The OpenAI Pixel (browser-side) captures
oppreffrom the URL on landing and stores it in a first-party cookie named__oppref, so later page views and conversion events on your site can reuse it. - The Conversions API (server-side) does not collect
oppreffor you. OpenAI’s guidance is to preserve the value exactly as it arrived and send it with each server-side event.
The {oppref} entry in the field’s template list can look like an invitation to add it yourself. Don’t. It is already appended.
As with every auto-appended click ID, oppref is only as durable as the path to your page. Link shorteners, server-side redirects that drop the query string, and app deep links all strip it. Keep the ad’s destination URL pointed at the final page.
Pixel + Conversions API
UTMs tell your analytics where a visit came from. They do not tell OpenAI that the visit converted. For that you need OpenAI’s own measurement, set up under a data source in Ads Manager:
- OpenAI Pixel — a JavaScript tag on your site. Captures
oppref, sets the__opprefcookie, and fires conversion events from the browser. - Conversions API — the server-side channel for the same events. Send it CRM conversions, offline events, and anything the Pixel misses to ad blockers or Safari’s cookie limits, with
opprefattached so OpenAI can attribute it.
Run both, deduplicated, for the same reasons you run Pixel plus CAPI on Meta. Details of event names, payloads, and deduplication are in OpenAI’s conversion-measurement docs linked below.
For how the receiving side classifies the visit — and why paid and organic ChatGPT traffic must stay separate — see Google Analytics .
Limitations and gotchas
- Beta behavior changes. OpenAI is adding measurement features over time. If a macro stops filling or the field moves, check the Measure results article before assuming your template broke.
- No name macros. Every macro is an ID. Plan your reporting around that or maintain static labels.
- No device, placement, or keyword macros. Split campaigns if you need those dimensions.
- GA4 files
chatgpt / cpcunder Paid Other. ChatGPT is not on Google’s list of search or social sites, so the default channel grouping can’t call it Paid Search. A custom channel group with asource matches chatgptrule fixes it. - Don’t reuse the organic
utm_source=chatgptwarning in reverse. Tagging organic citations is wrong because you can’t control the link. Tagging ads withutm_source=chatgptis right because you can. The source value is the same; the situation is not. - Lower-level fields override silently. There is no indicator on the campaign that an ad group or ad below it carries its own parameters. Audit all three levels when numbers look off.
Verification
- Click your own live ad. Land on your page and read the address bar. You should see your UTMs with real numeric IDs substituted, plus
oppref=with a long opaque value. Literal{campaign_id}means a typo or wrong casing in the macro. - Check the cookie. With the Pixel installed, open your browser’s storage inspector and confirm a
__opprefcookie exists with the same value as the URL parameter. - Confirm in GA4 realtime. Filter by
utm_source=chatgpt. The session should show sourcechatgpt, mediumcpc, and your campaign ID under Campaign ID. - Check Ads Manager’s data source. Fire a test conversion and confirm it appears against the right event, and that server-side events carry the same
opprefas the Pixel.
Common problems
- My UTMs come out as literal
{campaign_id}. Wrong casing or a typo. The macros are lowercase with underscores —{campaign_id}, not{CampaignID}or{campaignid}. - One campaign reports fine, one ad in it reports something else. That ad, or its ad group, has its own Landing page query parameters overriding the campaign template. Clear the lower level or paste the full template there.
opprefappears twice on my landing URL. You added it to the template. Remove it; OpenAI appends it.- Ads Manager shows no conversions but GA4 does. UTMs don’t report conversions to OpenAI. Install the Pixel, or send events through the Conversions API with
opprefattached. - Conversions API events aren’t attributed. The
opprefvalue was missing, truncated, or altered before it reached your server. Store it exactly as it arrived and pass it through unchanged. - GA4 puts the traffic in Paid Other. Expected. Build a custom channel group if you want it beside Paid Search.
| Token | Description | Substituted at |
|---|---|---|
| {campaign_id} | Numeric ID of the campaign that served the ad. Rename-safe — the recommended value for utm_id and, unless you need readable names, for utm_campaign too. | Click time |
| {ad_group_id} | Numeric ID of the ad group. | Click time |
| {ad_id} | Numeric ID of the individual ad the visitor clicked. | Click time |
| {ad_account_id} | Numeric ID of the ad account. Useful for agencies running several accounts through one analytics property. Carry it as a custom parameter (e.g. ad_account_id={ad_account_id}), not inside a utm_ field. | Click time |
| {oppref} | OpenAI's per-click reference. Listed among the field's template values, but OpenAI already appends oppref to the landing URL on every click. Do not add it to your template — you would put oppref on the URL twice. The OpenAI Pixel reads the auto-appended value. | Click time |
