How ChatGPT serves ads
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_unitis a typed schema; the name suggests sibling types (e.g., multi‑advertiser).advertiser_brand.idfollows the patternadacct_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: falsecauses 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‑wrappedad_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 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 |
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:
| Token | Where it appears | Purpose |
|---|---|---|
ads_spam_integrity_payload | Inside the SSE data (never on the click URL) | Server‑side integrity check against forged ad clicks |
oppref | On 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 |
olref | On the click URL (not stored by the observed SDK) | Likely impression‑side / outbound‑link‑reference logging on OpenAI’s servers |
ad_data_token | Base64‑wrapped JSON in the SSE payload | Contains 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.jsis version 0.1.3.- On
init, the SDK reads theopprefquery parameter, writes it into the first‑party cookie__opprefwith a 720‑hour TTL, and sets a probe cookie__oaiq_domain_probe. - Every subsequent
measurecall 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.combzr.openai.com
Inspect the cookies __oppref and __oaiq_domain_probe after any ChatGPT‑recommended click for additional insight.