> For the complete documentation index, see [llms.txt](https://docs.adpage.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.adpage.io/integrations-marketingtools/chatgpt-ads/utm-parameters-and-dynamic-tracking-for-chatgpt-ads.md).

# UTM parameters & dynamic tracking for ChatGPT Ads

How to structure UTM parameters and OpenAI's dynamic ad tokens for ChatGPT Ads attribution

The main ChatGPT Ads guide covers sending conversions back to OpenAI through the Conversions API. This page covers the other direction: getting clean, joinable attribution data out of ChatGPT Ads clicks and into GA4, the client's CRM and their data warehouse, by structuring the URL parameters correctly.

{% hint style="warning" %}
**This is beta, account-gated behaviour.** OpenAI's public Help Center still states that dynamic macros in landing URLs are unsupported. Some Ads Manager accounts nonetheless expose a "Landing page query parameters" field with dynamic tokens described below. Always confirm in the specific client's account whether this field is visible before building on it, and keep a static-UTM fallback ready in case it isn't (or stops resolving).
{% endhint %}

### The two-layer model

Don't put OpenAI's object IDs into `utm_source` or `utm_medium` — that fragments GA4's channel grouping into one row per campaign. Instead, use two separate layers:

* **Marketing taxonomy** (`utm_source`, `utm_medium`, `utm_campaign`, ...) — stable, human-readable values you set once per campaign. This is what GA4 channel reporting groups on.
* **Technical join keys** (`oai_*` parameters, built from OpenAI's dynamic tokens) — the platform's own object IDs, used to join spend/click data back to sessions, leads and orders in the CRM or warehouse. Names are editable in Ads Manager after launch, IDs are not, so use IDs as the historical join key wherever possible.

### OpenAI's dynamic tokens

When the "Landing page query parameters" field is available for a campaign, these four tokens can be added to it and are resolved into the destination URL at click time:

| Token             | Represents                   | Example value | Recommended target parameter           |
| ----------------- | ---------------------------- | ------------- | -------------------------------------- |
| `{campaign_id}`   | Top-level campaign object    | `cmpn_101`    | `utm_id` (and/or `oai_campaign_id`)    |
| `{ad_group_id}`   | Ad group within the campaign | `adgrp_301`   | `oai_ad_group_id` — **not** `utm_term` |
| `{ad_id}`         | Individual ad                | `ad_501`      | `utm_content` (and/or `oai_ad_id`)     |
| `{ad_account_id}` | OpenAI Ads account           | `adacct_123`  | `oai_account_id`                       |

{% hint style="info" %}
There is no `{product_id}` token for product-feed campaigns. Add the SKU or item ID as a static parameter on each product's landing URL instead, and confirm during QA that it survives when the same URL is reused across products.
{% endhint %}

### Recommended parameter string

For a standard campaign where the query-parameter field is available, append this to the landing page query template in Ads Manager:

```
utm_source=chatgpt&utm_medium=paid_ai&utm_campaign=<static-campaign-slug>&utm_id={campaign_id}&utm_content={ad_id}&utm_source_platform=openai_ads&oai_account_id={ad_account_id}&oai_campaign_id={campaign_id}&oai_ad_group_id={ad_group_id}&oai_ad_id={ad_id}
```

Which, once OpenAI resolves the tokens on an eligible click, arrives on the landing page as something like:

```
https://www.client-site.com/landing?utm_source=chatgpt&utm_medium=paid_ai&utm_campaign=q3-demo-push-emea&utm_id=cmpn_101&utm_content=ad_501&utm_source_platform=openai_ads&oai_account_id=adacct_123&oai_campaign_id=cmpn_101&oai_ad_group_id=adgrp_301&oai_ad_id=ad_501&oppref=gAAAAAb123
```

Keep `utm_source`, `utm_medium`, `utm_campaign` and `utm_source_platform` **static** — type them out per campaign, don't template them. Only the object IDs go through the dynamic tokens; they're tedious to type correctly by hand and that's exactly where a join key needs to be exact.

{% hint style="info" %}
Use one lowercase naming convention throughout (`chatgpt`, not `ChatGPT` or `Chatgpt`). Mixed casing is the most common cause of a campaign showing up as multiple rows in GA4's channel report.
{% endhint %}

### `oppref`: leave it alone

You'll see an `oppref` parameter appended automatically to eligible landing URLs, on top of whatever UTMs you configured. This is OpenAI's own click reference, used for its Conversions API attribution matching — it's unrelated to UTMs and is the same click ID stored in the first-party `__oppref` cookie described in Step 3 of the main setup guide. Never manually set, overwrite or strip `oppref` in a redirect — doing so breaks OpenAI-side attribution even if your own GA4/CRM tracking is unaffected.

### Support varies by how the campaign is built

| Campaign type                                | Confidence the dynamic tokens resolve                                                    | What to do                                                                                                                                                   |
| -------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Standard Ads Manager campaign (CPM/CPC/oCPC) | Medium — works when the "Landing page query parameters" field is visible in that account | Use the dynamic tokens; verify with a controlled test click                                                                                                  |
| Product-feed campaigns                       | Low — no product token exists                                                            | Add SKU/item ID statically per product URL                                                                                                                   |
| Bulk CSV creation                            | Low — no documented query-template column                                                | Use fully static UTMs in the landing URL, or set the query-parameter field manually after upload                                                             |
| Public Advertiser API                        | Low — schema has no landing-query-suffix field                                           | Build static query strings from the object IDs already in the API response                                                                                   |
| MMP links (AppsFlyer, Adjust, ...)           | Depends on the MMP                                                                       | Never replace an MMP link with a plain URL — only use that partner's supported custom parameters, and confirm the resolved values survive the redirect chain |

### Capturing the parameters in GTM and GA4

On the client's web container:

1. Make sure the GA4 configuration tag (or your GA4 event tags) picks up `utm_id`, `utm_content` and the other standard UTMs the way it already does for other paid channels — no extra work needed there.
2. For the `oai_*` parameters, they won't populate GA4's built-in traffic-source dimensions automatically. Read them from the URL with a Data Layer or URL variable, send them as an event parameter on your key events, and register matching GA4 custom dimensions if the client wants to report on them (e.g. for creative-level analysis by `oai_ad_id`).
3. If the client has lead forms, parse the landing URL on first page load and write the values into hidden form fields so they land in the CRM: original source/medium, the static campaign slug, `oai_campaign_id`/`utm_id`, `oai_ad_group_id`, `oai_ad_id`/`utm_content`, `oai_account_id`, `oppref`, and the full raw landing URL plus a timestamp (useful for forensic QA later).

{% hint style="warning" %}
Don't put personal data (email, phone, name) into query strings for this or any capture step — it ends up in browser history, server logs and third-party tools.
{% endhint %}

For GA4 channel reporting, set up a custom channel group once per client:

```
Custom channel: Paid AI
Source exactly matches: chatgpt
AND
Medium exactly matches: paid_ai
```

### Troubleshooting

| Symptom                                                      | Likely cause                                                                                                             | Fix                                                                                                                                       |
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| Landing URL contains the literal text `{campaign_id}`        | This account/campaign type doesn't support token resolution                                                              | Confirm the token picker is available in that exact account; fall back to static values                                                   |
| Only the static UTMs show up, the `oai_*`/dynamic ones don't | Field wasn't saved, or the campaign type doesn't support it                                                              | Re-check the saved field value; test with a fresh eligible click                                                                          |
| Duplicate query keys in the final URL                        | The same key is set both in the ad's destination URL and in the campaign's query-parameter field                         | Give each parameter a single owner — either the ad URL or the campaign suffix, never both                                                 |
| One ChatGPT Ads campaign shows as several rows in GA4        | Inconsistent casing or a campaign-name change (mutable) used as the join key                                             | Normalize source/medium casing; use a stable static slug instead of the live campaign name                                                |
| Ads Manager clicks noticeably exceed GA4 sessions            | Expected gap — page-load failures, consent, redirects, ad blockers and attribution windows all differ from a GA4 session | Compare like-for-like dates/timezones before treating it as a tracking bug                                                                |
| OpenAI-side conversions are missing despite GA4 showing them | `oppref` was stripped somewhere, or the Pixel/Conversions API event IDs don't match                                      | Confirm `oppref` survives every redirect; check that client-side and server-side events share the same event ID (see deduplication guide) |

### QA checklist before launch

* [ ] "Landing page query parameters" field confirmed visible in this exact client account and campaign editor
* [ ] Token values copied from OpenAI's UI, not retyped by hand
* [ ] No leading `?` in the campaign query field
* [ ] One lowercase naming convention used throughout
* [ ] Each parameter is owned by exactly one layer (ad URL **or** campaign suffix, not both)
* [ ] Tested against a destination URL with no existing query string
* [ ] Tested against a destination URL that already has unrelated query parameters
* [ ] Tested against a destination URL with a fragment (e.g. `#pricing`)
* [ ] Followed every redirect and checked the final browser URL — no literal `{...}` braces left anywhere
* [ ] `oppref` present on an eligible test click and confirmed to survive redirects
* [ ] Source, medium, campaign, `utm_id` and `utm_content` verified in GA4 DebugView or Realtime
* [ ] `oai_*` values confirmed to reach the website/CRM/server logs on a test lead or purchase
* [ ] Repeat the test after any campaign edit, URL change, bulk update, feed refresh or MMP-link change — this is a beta feature and can change without notice


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.adpage.io/integrations-marketingtools/chatgpt-ads/utm-parameters-and-dynamic-tracking-for-chatgpt-ads.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
