How ChatGPT serves ads

Published: (April 28, 2026 at 07:54 PM EDT)
4 min read

Source: Hacker News

OpenAI’s ad platform consists of 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.

Example SSE payload

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

{
  "type": "single_advertiser_ad_unit",
  "ads_request_id": "069e89b3-c038-7764-8000-6e5a193e5f69",
  "ads_spam_integrity_payload": "gAAAAABp6Js_",
  "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": "eyJwYXlsb2"
    }
  ]
}

Key observations

  • single_advertiser_ad_unit is a typed schema; the name suggests sibling types (e.g., multi‑advertiser).
  • advertiser_brand.id follows the pattern adacct_ and is a stable per‑merchant identifier.
  • Both the brand favicon and the ad image are served from bzrcdn.openai.com; OpenAI hosts the creative assets.
  • target.open_externally: false causes the link to open in ChatGPT’s in‑app webview, allowing OpenAI to observe post‑click navigation in addition to any pixel signal.
  • Each ad carries four Fernet tokens: ads_spam_integrity_payload, oppref, olref, and a base64‑wrapped ad_data_token. All are AES‑128‑CBC encrypted with a server‑only key and HMAC‑SHA256 for integrity.

How ads get selected

A single account in the OpenAI panel received six different ads across six conversations on six distinct topics. Targeting appears to be contextual to the current chat:

Conversation topicAdvertiser delivered
Beijing trip planning (Great Wall, Forbidden City)Grubhub – “Get Chinese Food Delivered”
Beijing tour bookingsGetYourGuide – Great Wall tour (ad_id=beijing003)
Beijing flightsAxel – utm_term=vflight_beijing_03
NBA playoffsGametime – utm_campaign=nba&utm_content=playoffs
Spring fashion/trendsAritzia – utm_campaign=chatgptpilot_trav3
Productivity / slidesCanva – utm_campaign=…link-clicks_products

The same account saw a different brand for each topic. No clear evidence was found that prior conversation history influences targeting.

The four‑token attribution chain

Each ad includes four distinct Fernet‑encrypted blobs. Their roles, based on where they appear, are:

TokenWhere it appearsPurpose
ads_spam_integrity_payloadInside the SSE data (never on the click URL)Server‑side integrity check against forged ad clicks
opprefOn the click URL; copied verbatim by the OAIQ pixel into the cookie __oppref (TTL 720 hours / 30 days)Forward attribution token; travels with every subsequent merchant pixel event
olrefOn the click URL (not stored by the observed SDK)Likely impression‑side / outbound‑link‑reference logging on OpenAI’s servers
ad_data_tokenBase64‑wrapped JSON in the SSE payloadContains another Fernet token; reconciled server‑side at click time

Fernet’s first nine bytes are public: a version byte (0x80) followed by an 8‑byte big‑endian Unix timestamp. The mint time of any token can be recovered without the key:

import base64, struct, datetime

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

In a captured click to Home Depot, the token was minted at 11:30:08 UTC, while the browser fetched the merchant page at 11:31:43 UTC—a click latency of 95 seconds.

How the loop closes on the merchant side

When a user taps an ad card, the browser opens a URL such as:

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

The merchant page loads the OAIQ SDK:


  oaiq('init', { pid: '' });
  oaiq('measure', 'contents_viewed', { /* … */ });
  • oaiq.min.js is version 0.1.3.
  • On init, the SDK reads the oppref query parameter, 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=&st=oaiq-web&sv=0.1.3

Blocking tip: To stop ChatGPT ad events, add the following domains to your filter list:

  • bzrcdn.openai.com
  • bzr.openai.com

Inspect the cookies __oppref and __oaiq_domain_probe after any ChatGPT‑recommended click for additional insight.

0 views
Back to Blog

Related posts

Read more »