ChatGPT 的广告采集器如何把你的购物浏览接回聊天账号:__obi cookie 全链路拆解

在 ChatGPT 里聊过几次购物话题之后,浏览器里的推荐内容开始变得更「懂」你,这种体验背后通常有一条数据链路。独立开发者 Tom Bušo 在自己的博客上发布了对 ChatGPT 广告系统的技术还原:OpenAI 的广告采集器部署在 bzr.openai.com,通过一个名为 __obi 的 cookie,把用户在第三方购物网站上的浏览、搜索和购买行为,接回到对应的 ChatGPT 账号。他用手机完整复现了这条链路,用两种独立方式抓包验证,并交叉核对了数月来积累的流量记录,覆盖 936 个广告主追踪像素和 1029 个主机名。这篇文章按链路顺序拆解它的每一步。

OpenAI 广告采集器两阶段时序图:种子阶段签发令牌并种下 cookie,回放阶段广告主网站触发时浏览器自动携带

一条 60 秒有效期的签名令牌

链路的第一步发生在 chatgpt.com。客户端生成 16 个随机字节,作为用户在广告系统中的标识符,然后调用 POST /backend-api/bazaar/obi/sync-token(未登录状态走 /backend-anon/ 路径)。后端返回一个 RS256 签名的 JWT,关键字段包括:

  • sub:64 位十六进制的账号主体
  • obi:22 字符的标识符,与账号绑定
  • subject_typeaccount_user(登录用户)或 anonymous(匿名设备)
  • consent_decision:用户的同意状态
  • exp:签发后 60 秒过期

令牌的签发方标记为 chatgpt-wadi,接收方(aud)是 bzr.openai.combzr 是 bazaar 的缩写,这是 OpenAI 广告平台的内部代号,wadi 是签发令牌的服务。令牌有效期只有 60 秒,这个设计让令牌本身无法被截获后长期重放,它的唯一用途是在签发后立刻完成下一步。

拿到令牌后,客户端立即向 bzr.openai.com/v1/obi/sync 发起跨站 POST,请求体就是那个 JWT。服务器的响应是写入 cookie:

text
Set-Cookie: __obi=<标识符>; Domain=.openai.com; HttpOnly;
            Max-Age=31536000; Path=/; SameSite=none; Secure

这行配置里有三处决定了后面发生的一切。Domain=.openai.com 让这个 cookie 对 openai.com 的所有子域生效,包括 bzr 和 bzrcdn;Max-Age=31536000 是一年有效期;SameSite=noneSecure 是浏览器允许 cookie 随跨站请求发送的唯一组合。令牌里的 obi 值与 cookie 值完全一致,等于把「账号—标识符」的绑定关系从 60 秒的令牌转移到了一年的 cookie 里。

广告主网站上的三类回传

__obi 离开 OpenAI 自己的域名,靠的是广告主网站上安装的追踪代码。在 ChatGPT 投放广告的广告主,会在自己的网站加载 OpenAI 的像素 SDK(bzrcdn.openai.com/sdk/oaiq.min.js),安装方式与零售网站加载 Meta 或 Google 的追踪代码相同。实测流量中,广告主页面到 OpenAI 主机之间有三类请求:

请求是否携带 __obi说明
GET bzrcdn.openai.com/sdk/oaiq.min.jsSDK 脚本本身的加载
POST bzr.openai.com/v1/sdk/events(带 obref转化事件上报
POST bzr.openai.com/v1/sdk/events(裸请求体)SDK 的「无凭据」路径
GET bzrcdn.openai.com/pixel-config/…配置拉取,完全不带 cookie

第一行最关键。SDK 有专门的代码路径刻意不带凭据,但这没有用:浏览器在执行 OpenAI 的任何代码之前,就要先向 bzrcdn 请求 <script> 标签指向的脚本文件,而这个加载请求自动附上了 cookie。只要网站装了 OpenAI 的追踪代码,标识符就在脚本加载这一步被送出去了,与用户在页面上做了什么无关。观察到的每一次携带 __obi 的请求,OpenAI 服务器都以 202 状态码接受。

SDK 在页面上收集的另一半数据

__obi 只是身份的一半,SDK 同时在广告主页面上收集行为数据。上报载荷把数据来源分成四类,分类标签来自 OpenAI 自己的实现:in 是广告主主动传入的值,fmhtjs 分别是 SDK 从表单字段、页面渲染文本和标签管理器总线抓取的值。在观察到的流量里,SDK 自己抓取的身份信息(685 条事件)远多于广告主主动提交的(255 条)。

标签管理器总线是邮箱地址的最大来源。SDK 会替换页面上的 window.dataLayer.push 函数,同时读取 adobeDataLayer,还通过解析 gtm.js 脚本标签上的 l= 参数来识别被改过名的 Google Tag Manager 数据层。当前版本从这些渠道取邮箱和电话;0.1.31 版本还收集姓名和地理位置,这个范围在 8 月 27 日被收窄。

敏感信息的处理是分层的。邮箱、电话、姓名在传输前做了 SHA-256 哈希,国家、地区、城市和邮编则明文发送。邮编是被抓取最多的表单字段,28 个站点上发生了 100 次采集。URL 在上报前被裁剪成域名加路径,观察到的 23929 条上报没有一个携带查询串,但路径本身保留了:到达采集器的路径里,出现了某项医疗状况的页面、一个债务解决方案的转化漏斗和一份诉讼委托表单。

自动化匹配默认开启。881 个设置已知的像素里有 638 个开了自动匹配,观察到的每一家信贷和借贷类广告主全部开启。这个开关由 OpenAI 的 Ads Manager 控制, denylist 排除了密码、一次性验证码、卡号、社保号、出生日期、病史、诊断和法院字段。

现代浏览器对第三方 cookie 的默认封锁是跨站追踪的最大障碍,__obi 的特殊性在于它是唯一穿过去的那个 OpenAI cookie。在同样的广告主页面请求里:

Cookie结果
oai-didoaicom-stable-id被浏览器拦截(SameSite=Lax)
oai-client-auth-info、会话 cookie被拦截(域名不匹配)
__obi正常发送

覆盖范围的实测数据:在作者自己的设备上,一个 __obi 值从 12 个商业网站、13 个像素 ID 下被发送到 OpenAI,涉及的网站包括 Chewy、Wayfair、ThriftBooks、Eventbrite、HelloFresh、Coursera 和 SeatGeek。更大的流量样本里,30 个不同的 __obi 值中有 12 个出现在不止一家广告主那里,其中一个出现在十家。

匿名用户也在覆盖范围内。932 个被解码的同步令牌中,736 个标记 account_user,196 个标记 anonymous。匿名主体和登录主体一样稳定:每台设备一个,至少持续 27 天。不登录 ChatGPT 不会让追踪失效,只是把身份从「账号」换成「设备」。

同意开关与官方回应

OpenAI 的 cookie 政策把 __obi 列在 Analytics(分析)类 cookie 下,有效期一年,生效域名是 chatgpt.com 和 openai.com,它是该分类下唯一的条目。政策原文对分析类 cookie 的描述是帮助 OpenAI 了解其服务的性能和使用情况。

OpenAI 同时维护两个独立的同意选项:oai_consent_analyticsoai_consent_marketing,用户可以分别允许或拒绝。但在作者解码的全部同步令牌里,consent_decision 字段的值清一色是 analytics_allowed。换句话说,允许分析、拒绝营销的用户,同样拿到了这个随跨站请求发送的标识符。

作者在 9 月 14 日向 [email protected][email protected] 发信,提出两个问题:为什么 __obi 被归类为分析类 cookie;允许分析同时拒绝营销的用户是否仍会收到它。OpenAI Support 回复确认已收到,表示会将观察结果转交内部审查,没有回答任何一个问题。「脚本加载即携带 cookie」的观察是在发信之后完成的,也就是说截至文章发布,该行为仍在持续。作者表示 OpenAI 若有回应会更新文章。

边界在哪里

这份还原的适用范围有明确限制。浏览器方面,全部观察在 Android 版 Chrome 上完成;Safari 的智能防跟踪(ITP)封锁所有第三方 cookie,iOS 上的所有浏览器(包括 Chrome)都运行 WebKit 内核,因此整套机制在 iOS 上不生效;桌面版 Chrome 未测试。触发频率方面,大约五个 ChatGPT 会话里只有一个产生同步令牌;ChatGPT 的移动网页版展示广告但完全不做同步。复现步骤中也可能遇到像素正常触发但没有附带 cookie 的情况。

严格来说,「采集器把 cookie 解析回账号」这一步是设计推论而非直接观测:202 状态码说明服务器收到了带有 cookie 的事件,服务端如何处理属于 OpenAI 内部实现。但整个体系的设计意图是清楚的,令牌绑定账号与标识符,cookie 承接绑定,事件携带 cookie 回流。

文章也指出了一个行业坐标:Meta 很多年前就搭好了结构上等价的体系,登录账号、像素请求上的第三方 cookie、站外转化解析到用户档案,这是广告技术的标准做法。没有先例的部分是载体:AI 聊天产品的用户会对它说出不会写在社交网络上的事,而这些产品越来越多地代替用户执行操作。与 __obi 相对,像素体系里还有另一个 cookie __obref,它设置在广告主自己的域名下,每个站点取值不同,2860 个观察值中 2828 个只出现在一家广告主,各站点之间互不可见。

广告主与用户各自的暴露面

对安装了 OpenAI 像素的广告主来说,这份还原揭示了一个它们无从知晓的事实:安装转化追踪代码是常规操作,但代码加载时浏览器自动带上的那个 cookie,把它们的访客解析成了 ChatGPT 身份。广告主自己看不到这个 cookie,它属于一个其脚本无权读取的域名。

对用户来说,可控点取决于浏览器和设置。iOS 用户在结构上不受影响;Android 版 Chrome 用户如果拒绝 ChatGPT 的分析类同意,按实测数据并不能阻止令牌签发;把 chatgpt.com 与日常购物浏览隔离到不同浏览器或浏览器配置文件,可以从机制上切断 cookie 关联。至于拒绝营销类同意是否有实际效果,正是作者向 OpenAI 提出而尚未得到回答的问题。

这条链路的每个环节都用的是行业里存在了十几年的标准件:JWT 签名、跨站 cookie、像素 SDK、标签管理器。Bušo 的贡献在于用 936 个像素、932 个解码令牌和两套独立抓包把这些标准件的组装方式完整摊开,让 ChatGPT 用户第一次能确切知道自己参与的广告系统长什么样。