Devoured - April 29, 2026
How ChatGPT serves ads (5 minute read)

How ChatGPT serves ads (5 minute read)

Tech Read original

A reverse-engineered breakdown reveals how ChatGPT injects contextual ads into conversations and tracks user clicks through merchant sites using encrypted attribution tokens.

What: OpenAI's ad platform injects structured ad units into ChatGPT's server-sent event stream during responses, then tracks conversions via a JavaScript SDK (OAIQ) on merchant sites that reads encrypted click tokens from URLs and stores them in 30-day browser cookies.
Why it matters: This is the first detailed technical documentation of how AI-native advertising works at the protocol level, revealing OpenAI's dual-sided attribution model and showing developers exactly what tracking happens when users click ChatGPT-recommended products.
Takeaway: Block ChatGPT ad tracking by filtering bzrcdn.openai.com and bzr.openai.com domains, or inspect __oppref and __oaiq_domain_probe cookies after clicking ChatGPT product recommendations.
Deep dive
  • OpenAI injects ads as single_advertiser_ad_unit typed objects directly into the SSE response stream at /backend-api/f/conversation, not through the language model itself
  • Each ad contains four separate Fernet-encrypted (AES-128-CBC + HMAC-SHA256) tokens for different parts of the attribution chain, all signed server-side so merchants cannot forge attribution
  • Ad targeting is contextual to the current conversation topic—the same account received six different advertisers (Grubhub, GetYourGuide, Axel, Gametime, Aritzia, Canva) when asking about six different topics (Beijing travel, NBA, fashion, productivity)
  • The oppref token travels from click URL to merchant site, where the OAIQ SDK stores it in a first-party cookie with 720-hour (30-day) TTL for cross-session attribution
  • Ads open in ChatGPT's in-app webview by default (open_externally: false), letting OpenAI observe post-click navigation before any pixel fires
  • The merchant-side SDK (oaiq.min.js v0.1.3) automatically instruments contents_viewed events and POSTs them back to bzr.openai.com with the attribution token
  • OpenAI hosts all advertiser creative assets (images, favicons) on bzrcdn.openai.com rather than letting merchants serve them directly
  • Fernet tokens embed creation timestamps in cleartext (first 9 bytes), so anyone can verify ad mint time without OpenAI's key—one observed ad had 95-second click latency
  • Each advertiser gets a stable account ID in the format adacct_<32-hex>, visible in the ad payload and presumably used for billing reconciliation
  • The schema naming (single_advertiser_ad_unit) implies OpenAI plans multi-advertiser carousel formats in future iterations
Decoder
  • SSE (Server-Sent Events): HTTP streaming protocol that pushes real-time updates from server to browser over a single long-lived connection, used here to deliver both model tokens and ad units
  • Fernet encryption: Symmetric encryption spec using AES-128-CBC with HMAC-SHA256 authentication, designed for securely passing time-limited tokens between services
  • OAIQ SDK: OpenAI's first-party JavaScript tracking library (v0.1.3) that merchants embed to report conversion events back to the ad platform
  • Attribution token: Encrypted identifier linking a click event to a specific ad impression, allowing the platform to credit conversions to the right campaign without exposing raw user data
  • First-party cookie: Cookie set by the merchant's own domain (not a third-party tracker), which bypasses some browser privacy protections and lasts across sessions
Original article

OpenAI's ad platform has two halves. On the ChatGPT side, the backend injects structured single_advertiser_ad_unit objects into the conversation SSE stream while the model is responding. On the merchant side, a tracking SDK called OAIQ runs in the visitor's browser and reports product views back to OpenAI. The two are tied together by Fernet-encrypted click tokens, four of them per ad.

I captured both halves on a consented mobile-traffic research fleet. Everything below comes from observed traffic.

How an ad gets into a conversation

When you send a message to ChatGPT, the backend opens an SSE response at chatgpt.com/backend-api/f/conversation. Most events in that stream are model-output. Some are ad units. They look like this:

event: delta
data: {
  "type":           "single_advertiser_ad_unit",
  "ads_request_id": "069e89b3-c038-7764-8000-6e5a193e5f69",
  "ads_spam_integrity_payload": "gAAAAABp6Js_<...redacted...>",
  "preamble":       "",
  "advertiser_brand": {
    "name":        "Grubhub",
    "url":         "www.grubhub.com",
    "favicon_url": "https://bzrcdn.openai.com/cabfae7ead26b03d.png",
    "id":          "adacct_6984ed0ba55481a29894bb192f7773b4"
  },
  "carousel_cards": [{
    "title":     "Get Chinese Food Delivered",
    "body":      "Satisfy Your Cravings with Grubhub Delivery.",
    "image_url": "https://bzrcdn.openai.com/cabfae7ead26b03d.png",
    "target": {
      "type":  "url",
      "value": "https://www.grubhub.com/?utm_source=chatgptpilot&utm_medium=paid&utm_campaign=diner_gh_search_chatgpt_kw_traffic_nb_x_nat_x&utm_content=nbchinese&oppref=gAAAA<...>&olref=gAAAA<...>",
      "open_externally": false
    },
    "ad_data_token": "eyJwYXlsb<...>"
  }]
}

Notes:

  • single_advertiser_ad_unit is a typed schema. The naming implies siblings (multi-advertiser, etc.).
  • advertiser_brand.id is adacct_<32-hex> — a stable per-merchant account identifier.
  • Brand favicon and ad image both load from bzrcdn.openai.com. OpenAI hosts the advertiser's creative, not the merchant.
  • target.open_externally: false opens the link in ChatGPT's in-app webview, so OpenAI observes the post-click navigation on top of any pixel signal.
  • Four Fernet tokens per ad: ads_spam_integrity_payload, oppref, olref, and a base64-wrapped ad_data_token. Each is AES-128-CBC under a server-only key with HMAC-SHA256 integrity.

How ads get selected

A single account in the panel received six different ads across six conversations on six different topics. The targeting is contextual to the chat:

Conversation topic Advertiser delivered
Beijing trip planning (Great Wall, Forbidden City) Grubhub — "Get Chinese Food Delivered"
Beijing tour bookings GetYourGuide — Great Wall tour, ad_id=beijing003
Beijing flights Axel — utm_term=vflight_beijing_03
NBA playoffs Gametime — utm_campaign=nba&utm_content=playoffs
Spring fashion/trends Aritzia — utm_campaign=chatgptpilot_trav3
Productivity / slides Canva — utm_campaign=…link-clicks_products

Same account, different topic, different brand. I didn't find evidence one way or the other on whether targeting also incorporates prior conversation history.

The four-token attribution chain

Every ad ships with four distinct Fernet-encrypted blobs. Their roles, based on where they appear:

  1. ads_spam_integrity_payload sent inside the SSE data, never on the click URL. Server-side integrity check against forged ad clicks.
  2. oppref present on the click URL and copied verbatim by the OAIQ pixel into the cookie __oppref (TTL 720 hours / 30 days). The forward attribution token. Travels with every subsequent merchant pixel event.
  3. olref paired with oppref on the click URL but not stored by the SDK we observed. Likely impression-side / outbound-link-reference logging on OpenAI's servers.
  4. ad_data_token base64-wrapped JSON containing yet another Fernet token. Carried in the SSE payload, presumably reconciled server-side at click time.

Fernet's first nine bytes are public: version byte 0x80 plus an 8-byte big-endian Unix timestamp. So the mint time of any of these tokens is recoverable without OpenAI's key:

import base64, struct, datetime
b = base64.urlsafe_b64decode("gAAAAABp7fdA" + "==")
print(datetime.datetime.utcfromtimestamp(struct.unpack(">Q", b[1:9])[0]))
# → 2026-04-26 11:30:08 UTC

The Home Depot click URL I captured was minted at 11:30:08; the browser fetched the merchant page at 11:31:43. Click latency: 95 seconds.

How the loop closes on the merchant side

User taps the card. Browser opens:

https://www.grubhub.com/?utm_source=chatgptpilot&...
                       &oppref=gAAAA<...>
                       &olref=gAAAA<...>

The merchant page loads the OAIQ SDK:

<script src="https://bzrcdn.openai.com/sdk/oaiq.min.js"></script>
<script>
  oaiq('init',    { pid: '<merchant pixel ID>' });
  oaiq('measure', 'contents_viewed', { ... });
</script>

oaiq.min.js is at version 0.1.3. On init it reads ?oppref= from window.location, writes it into the first-party cookie __oppref with a 720-hour TTL, and sets a probe cookie __oaiq_domain_probe. Every subsequent measure call POSTs JSON to:

POST https://bzr.openai.com/v1/sdk/events?pid=<merchant>&st=oaiq-web&sv=0.1.3

Two domains to add to your filter list if you want to block ChatGPT ad events: bzrcdn.openai.com, bzr.openai.com. Two cookie names to inspect after any ChatGPT-recommended click: __oppref, __oaiq_domain_probe.