Hacker News — AI on Front Page · · 6 min read

ChatGPT now knows what you do on other websites via ad collector

Mirrored from Hacker News — AI on Front Page for archival readability. Support the source by reading on the original site.

204 pts · 105 comments on Hacker News

OpenAI's ad collector at bzr.openai.com sets a cookie called __obi, scoped to .openai.com. The value is while you are on ChatGPT and tied to your ChatGPT account. __obi is then sent to OpenAI from ordinary websites you visit.

Any company that buys ads on ChatGPT installs a small piece of OpenAI code on its own site, the same way retailers already install Meta and Google tracking code. Loading that code, sends __obi to OpenAI along with data about the page you are browsing. This includes products you are searching for, articles you are reading, and purchase behaviors.

The bottom line is that OpenAI can connect what you do on those sites to your ChatGPT account.

I reproduced the full mechanism on my own phone, verified with two independent capture methods, and cross-checked against several months of observed traffic covering 936 distinct advertiser pixels across 1,029 hostnames.

How it works

Step 1. ChatGPT creates an identifier and signs it.

On chatgpt.com, the client generates 16 random bytes and calls POST /backend-api/bazaar/obi/sync-token (or /backend-anon/ when signed out). The backend returns an RS256 JWT:

{
  "iss": "chatgpt-wadi",
  "aud": "bzr.openai.com",
  "purpose": "obi_sync",
  "operation": "set",
  "consent_decision": "analytics_allowed",
  "consent_policy_version": "user_granular_consent_v1",
  "sub": "«redacted: 64-hex account subject»",
  "subject_type": "account_user",
  "obi": "«redacted: 22-char identifier»",
  "exp": "«iat + 60s»"
}

sub is the account. obi is the identifier. The token binds them, is scoped to the collector, and expires in 60 seconds. bzr stands for bazaar, OpenAI's internal name for the ads platform; wadi is the issuing service.

Step 2. The identifier becomes a cookie on OpenAI's domain.

The client POSTs {"token": "«JWT»"} cross-site to bzr.openai.com/v1/obi/sync. The response:

Set-Cookie: __obi=«redacted»; Domain=.openai.com; HttpOnly;
            Max-Age=31536000; Path=/; SameSite=none; Secure

SameSite=none with Secure is the configuration a cookie needs to be sent on cross-site requests. Max-Age is one year. The obi value in the JWT and the value in the cookie are identical.

Step 3. Advertiser sites send it back.

Three request classes go from an advertiser's page to OpenAI's hosts. On a phone with __obi in the jar, all three carried it:

Request Carried __obi Notes
GET bzrcdn.openai.com/sdk/oaiq.min.js yes the script load itself
POST bzr.openai.com/v1/sdk/events with obref yes conversion events
POST bzr.openai.com/v1/sdk/events, bare body yes the SDK's "no credentials" path
GET bzrcdn.openai.com/pixel-config/… no cookie header at all control

The first row is particularly interesting. The pixel SDK has a code path that omits credentials, and it does not help: the browser attaches cookies to the <script src> request that loads the SDK before any of OpenAI's code runs. By the virtue of loading the tag the identifier is disclosed.

What travels with it

The same SDK also collects identity from the advertiser's page. The payload separates four sources, labelled by OpenAI itself: in for values the advertiser passes deliberately, and fm, ht, js for values the SDK scrapes from form fields, rendered page text, and the tag-manager bus. In observed traffic, scraped identity outnumbered advertiser-supplied identity 685 events to 255.

The tag-manager bus is the largest source of email. The SDK replaces window.dataLayer.push with its own function, also reads adobeDataLayer, and locates renamed GTM layers by parsing the l= parameter off the gtm.js script tag. Current versions take email and phone from it. Version 0.1.31 also took names and geography before the scope was narrowed on 27 August.

Email, phone, first and last name are SHA-256 hashed before transmission. Country, region, city and postal code are sent in the clear. Postal code was the most-harvested form field, 100 events across 28 sites.

URLs are reduced to origin plus path before sending; none of 23,929 observed carried a query string. Paths survive, and paths reaching the collector included a medical condition, a debt-solutions funnel and a litigation intake form.

Automatic matching was enabled for 638 of 881 pixels with a known setting, including every credit and lending advertiser observed. It is controlled from OpenAI's Ads Manager. A denylist excludes passwords, one-time codes, card numbers, SSN, date of birth, medical history, diagnosis and court fields.

The cookie is built to cross sites

On the same advertiser-page requests, every other OpenAI cookie was blocked by the browser:

Cookie Outcome
oai-did, oaicom-stable-id blocked, SameSite=Lax
oai-client-auth-info, session cookies blocked, domain mismatch
__obi sent

__obi is the only OpenAI identifier configured with SameSite=None.

Observed reach

On my device, one __obi value was sent to OpenAI from 12 commercial websites under 13 distinct pixel IDs, including Chewy, Wayfair, ThriftBooks, Eventbrite, HelloFresh, Coursera and SeatGeek. Every request was accepted with 202.

In the broader traffic, 12 of 30 distinct __obi values appeared under more than one advertiser, one under ten.

It works when you are logged out

Across 932 decoded sync tokens, 736 carried subject_type: account_user and 196 carried anonymous. The anonymous subject is as stable as the account subject: one per device, persisting at least 27 days.

What OpenAI's cookie policy says

OpenAI's cookie policy lists __obi under Analytics cookies, one year, on chatgpt.com and openai.com. It is the only entry in that section. The policy describes analytics cookies as helping OpenAI understand how its services perform and are used.

OpenAI runs analytics and marketing as two separate consent choices, oai_consent_analytics and oai_consent_marketing, and every sync token I decoded carried consent_decision: analytics_allowed. Someone who allows analytics and refuses marketing gets this.

OpenAI's response

I sent the mechanism and two questions to [email protected] and [email protected] on 14 September: why __obi is classified as an analytics cookie, and whether a user who grants analytics consent and refuses marketing consent still receives it. The reply came from OpenAI Support. It acknowledged the inquiry, said the observations would be shared internally for review, and did not answer either question. The script-load observation above was made after the inquiry was sent. I will update this post if OpenAI responds.

Limits

Browsers. Observed on Chrome for Android. Safari's Intelligent Tracking Prevention blocks all third-party cookies, and Chrome on iOS runs on WebKit, so the mechanism does not operate on any iOS browser. Desktop Chrome is untested.

Gating. Roughly one ChatGPT session in five produced a sync token. ChatGPT's mobile web client serves ads without syncing at all. Someone following the steps below may see the pixel fire with no cookie attached.

The join is not observed. 202 means the collector accepted the event with the cookie attached. That OpenAI resolves it to the account server-side follows from the design; I did not watch it happen.

Meta built the structural equivalent years ago. A logged-in account, third-party cookies on pixel fires, off-site conversions resolved to a profile. The mechanism is standard adtech. What has no precedent is running it on an AI chat product. People tell these products things they would not put on a social network, and these products increasingly act on their behalf.

The pixel's other cookie does not do this. __obref is set on the advertiser's own domain. Each site gets a different value and no site can see another's. Of 2,860 values observed, 2,828 appeared under exactly one advertiser.

Advertisers cannot see this. __obi belongs to a domain their scripts cannot read. They installed a conversion pixel and have no way to know their visitors are being resolved to a ChatGPT identity.

Discussion (0)

Sign in to join the discussion. Free account, 30 seconds — email code or GitHub.

Sign in →

No comments yet. Sign in and be the first to say something.

More from Hacker News — AI on Front Page