메뉴
HN
Hacker News 32일 전

ChatGPT 광고의 모든 것: 완벽한 어트리뷰션 추적 원리

IMP
8/10
핵심 요약

해커뉴스에 공개된 분석 자료에 따르면, ChatGPT가 대화 중에 광고를 노출하고 사용자의 클릭과 상호작용을 매우 정밀하게 추적합니다. 대화 문맥에 맞춰 타겟팅된 광고가 SSE 스트림을 통해 전달되며, 클릭 시 4개의 Fernet 암호화 토큰을 통해 서버에서 조작되지 않은 유효한 클릭인지 검증하고 앞으로의 이벤트까지 추적합니다. 이는 AI 플랫폼이 본격적인 프리미엄 광고 네트워크로 발돋움하고 있음을 시사하며, 광고 효과 측정 및 개인정보 추적에 대한 명확한 기술적 이해가 필요합니다.

번역된 본문

OpenAI의 광고 플랫폼은 크게 두 부분으로 나뉩니다. ChatGPT 측면에서는 모델이 응답하는 동안 백엔드가 구조화된 single_advertiser_ad_unit 객체를 대화 SSE(Server-Sent Events) 스트림에 주입합니다. 가맹점(광고주) 측면에서는 방문자의 브라우저에서 OAIQ라는 추적 SDK가 실행되어 제품 조회 내역을 OpenAI로 다시 보고합니다. 이 두 부분은 광고당 4개씩 부여되는 Fernet 암호화 클릭 토큰으로 연결됩니다. 저는 사용자 동의를 얻은 모바일 트래픽 연구 플릿(fleet)에서 이 두 과정을 모두 캡처했습니다. 아래의 모든 내용은 실제 관찰된 트래픽에서 얻은 것입니다.

광고가 대화에 포함되는 방식 사용자가 ChatGPT에 메시지를 보내면, 백엔드는 chatgpt.com/backend-api/f/conversation에서 SSE 응답을 엽니다. 해당 스트림의 이벤트는 대부분 모델의 출력입니다. 하지만 일부는 광고 단위(ad units)입니다. 그 형태는 다음과 같습니다:

event: delta data: { "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<...>" }] }

참고 사항:

  • single_advertiser_ad_unit은 타입화된 스키마입니다. 이 네이밍은 다른 형제 타입(예: multi-advertiser 등)이 존재함을 암시합니다.
  • advertiser_brand.idadacct_<32-hex> 형태로, 가맹점 계정마다 고유하게 부여되는 안정적인 식별자입니다.
  • 브랜드 파비콘과 광고 이미지는 모두 bzrcdn.openai.com에서 로드됩니다. 즉, OpenAI가 가맹점의 서버가 아닌 자사 CDN에서 광고 소재를 호스팅합니다.
  • target.open_externally: false는 링크를 ChatGPT의 인앱 웹뷰(In-app webview)에서 열도록 설정합니다. 따라서 OpenAI는 어떠한 픽셀 신호 외에도 클릭 후 이루어지는 탐색(navigation) 기록까지도 추적할 수 있습니다.
  • 광고당 4개의 Fernet 토큰이 존재합니다: ads_spam_integrity_payload, oppref, olref, 그리고 base64로 래핑된 ad_data_token입니다. 각 토큰은 HMAC-SHA256 무결성 검사와 함께 서버 전용 키를 통한 AES-128-CBC 방식으로 암호화되어 있습니다.

광고 타겟팅 선정 방식 패널에 있는 단일 계정은 6개의 다른 주제에 대한 6번의 대화에서 각각 다른 6개의 광고를 받았습니다. 타겟팅은 대화의 문맥(Context)에 맞춰서 이루어집니다:

  • 베이징 여행 계획(만리장성, 자금성) → Grubhub — "Get Chinese Food Delivered"
  • 베이징 투어 예약 → GetYourGuide — 만리장성 투어, ad_id=beijing003
  • 베이징 항공권 → Axel — utm_term=vflight_beijing_03
  • NBA 플레이오프 → Gametime — utm_campaign=nba&utm_content=playoffs
  • 봄 패션/트렌드 → Aritzia — utm_campaign=chatgptpilot_trav3
  • 생산성 / 슬라이드 → Canva — utm_campaign=…link-clicks_products

동일한 계정이지만 주제가 다르면 노출되는 브랜드도 달랐습니다. 이전 대화 기록이 타겟팅에도 반영되는지에 대해서는 확인된 바가 없습니다.

4개의 토큰으로 이루어진 어트리뷰션(Attribution) 체인 모든 광고에는 4개의 서로 다른 Fernet 암호화 블롭(blobs)이 포함되어 있습니다. 이들이 나타나는 위치를 바탕으로 한 각각의 역할은 다음과 같습니다:

  • ads_spam_integrity_payload: SSE 데이터 내부에 전송되며, 클릭 URL에는 절대 포함되지 않습니다. 위조된 광고 클릭을 방지하기 위한 서버 측 무결성 검사용도입니다.
  • oppref: 클릭 URL에 존재하며, OAIQ 픽셀에 의해 쿠키 __oppref(유효기간 720시간 / 30일)에 그대로 복사됩니다. 정방향 어트리뷰션 토큰(Forward attribution token) 역할을 하며, 이후 발생하는 모든 가맹점 픽셀 이벤트와 함께 전송됩니다.
  • olref: 클릭 URL에서 oppref와 짝을 이루지만, 관찰된 SDK에는 저장되지 않습니다. OpenAI 서버 측에서의 노출(Impression) 및 아웃바운드 링크 참조 로깅용으로 추정됩니다.
  • ad_data_token: Base64로 래핑된 JSON 형태로, 또 다른 Fernet 토큰을 포함하고 있습니다. SSE 페이로드에 담겨 전달되며, 클릭 시점에 서버 측에서 데이터를 매칭(Reconcile)하는 데 사용되는 것으로 보입니다.

Fernet 토큰의 처음 9바이트는 공개되어 있습니다: 버전 바이트 0x80과 8바이트의 빅엔디안(Big-endian) 유닉스 타임스탬프입니다. 따라서 OpenAI의 키 없이도 이러한 토큰들이 생성된 시간을 알아낼 수 있습니다:

import base64, struct, datetime b = base64.urlsafe_b64decod

원문 보기
원문 보기 (영어)
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": "eyJwYXlsb2<...>" }] } 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: ads_spam_integrity_payload sent inside the SSE data, never on the click URL. Server-side integrity check against forged ad clicks. 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. 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. 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 we 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 .
관련 소식