Ads in ChatGPT: reverse-engineering which ones actually get shown
On this page
OpenAI’s ad payload answers one question — what did ChatGPT serve? — and quietly hides another: what did the user actually see? Those are not the same number. Ads in ChatGPT are served as candidates in a result.ads[] array, but the chat interface renders at most one visible card per answer. Everything else is served-but-not-shown: paid inventory the model considered and dropped before it reached the screen.
This post reverse-engineers the gap from our analysis of cloro’s own data on ads in ChatGPT. We measured how often a served ad is actually rendered, how often “multiple ads” is really one ad re-emitted, how often OpenAI hides an ad, and — when two advertisers genuinely compete — which one the interface picks. The last question has a clean, content-based answer.
The figures come from cloro’s own measurements across millions of ChatGPT responses, not a census of all of ChatGPT: ads concentrate in the US, Canada, Australia, and Japan, and the prompt mix skews commercial.
Served is not shown
A ChatGPT response can carry more paid inventory than the user ever sees. The result.ads[] array is OpenAI’s serve decision — the ad candidates the auction returned for that prompt. The visible card under the answer is the show decision — a client-side render that keeps at most one ad and drops the rest.
For anyone measuring ads in ChatGPT, this matters. If you count the length of result.ads[], you are counting what was served, not what was seen. Impression-weighted exposure — the number an advertiser actually cares about — needs the show decision, and the show decision is not a field in the served payload. You have to recover it from the rendered DOM.
What “served but not shown” looks like
Say you ask ChatGPT, “How do I clean Nike Air Max sneakers without damaging them?” The API response comes back with a result.ads[] array that is not empty — OpenAI served an ad for a shoe-care brand:
"ads": [
{
"brand": { "name": "Wolf & Shepherd", "url": "https://wolfandshepherd.com/?utm_source=chatgpt.com" },
"cards": [{ "title": "Sneaker Shoe Care Kit", "image": "https://bzrcdn.openai.com/…webp" }],
"visibility": "allowed",
"adsResponseIndex": 0,
"rendered": false
}
]
The ad sits right there in the payload. But read the answer the user actually saw, and there is no Sponsored card in it — not for Wolf & Shepherd, not for anyone. The rendered: false flag says it plainly: OpenAI served the ad, the interface never drew it. Count the response as an ad served, not an ad seen.
This happens even when the ad fits the question. A shoe-care ad against a shoe-cleaning prompt is a close match, and it still didn’t show. Serving is the auction’s call; showing is a separate decision the interface makes as it builds the answer.
Now the opposite. Ask “what’s the best outbound sales platform right now?” and the response serves JustCall — and this time the answer displays its Sponsored card, so the ad carries rendered: true. Same result.ads[] shape, opposite outcome for the reader. The one field that tells the two apart is rendered.
How we read visibility and rendered
Two fields on each ad carry the story, and they come from different places.
visibility is OpenAI’s own call, passed straight through. Every served ad in the SSE stream carries a visibility.status of allowed or hidden — hidden meaning OpenAI’s integrity checks filtered the ad. cloro copies that value onto the ad; it does not decide it. In practice the value is almost always allowed.
rendered is cloro’s own measurement, because the payload never says which ad the page drew. cloro reads the rendered HTML for a data-testid="image-card-v2-elevated-brand" element — the tag that sits only on a real Sponsored card, not on the served-ad JSON embedded elsewhere in the page, and that holds whether the visible label reads “Sponsored” or “광고”. Its text is the advertiser name. An ad is rendered: true when its advertiser matches one of those cards, and false when it does not. The read is time-bounded, so a stalled page marks nothing rendered rather than hanging the scrape.
So visibility answers “did OpenAI allow the ad?” and rendered answers “did the user see it?” — separate questions, which is why an ad can be allowed and still rendered: false.
Finding 1 — served, but not always shown

Across 3.25M ad-bearing responses, about 87% rendered a visible ad card and roughly 13% were served but never shown. When a single ad is served — most cases — it renders ~87% of the time; the rest are counts no user ever saw. That ~13% gap is the single most important correction to make when you measure ads in ChatGPT: roughly one in eight served ads is never seen.

Finding 2 — the surface is single-ad, but not strictly
ChatGPT’s ad surface is single-slot by design, and the data agrees: only ~4.2% of ad-bearing responses serve more than one distinct advertiser. Three advertisers show up in about 1 in 4,000 responses, and four is a genuine rarity — we found four in a week of traffic.
“At most one shown” is the right intuition but slightly too strong. Among two-advertiser responses:
| Cards rendered | Share of two-advertiser responses |
|---|---|
| One card | 82.9% |
| Zero cards | 12.7% |
| Both cards | 4.4% |

So the interface usually shows one, sometimes shows none, and occasionally shows two. Treating result.ads[] as a one-per-response impression log is wrong in both directions — it can overstate (served, not shown) and, rarely, understate (two rendered).
Finding 3 — one ad, counted many times
Here is what makes naive ad counting wrong. ChatGPT re-emits the same ad several times as the answer streams, each copy carrying a fresh ads_response_index. A response that shows one ad can contain three identical type:"ads" events with indices 0, 1, and 2.
In cloro’s data, 5.1% of ad-bearing responses re-emit a single advertiser this way. If you de-duplicate on ads_response_index, you keep all three copies and report three ads where the user saw one.
This is a real trap. A parser that keys on ads_response_index — which differs across the re-emissions — reports three identical entries in result.ads[] for one placement. De-duplicate on the advertiser and creative content instead — the brand and its cards — and treat ads_response_index as a re-emission counter, not an ad identifier. A single ad, streamed a few times, is still a single ad.
Finding 4 — hidden ads barely exist in the stream
Each served ad carries a visibility.status. The intuition is that hidden — an ad OpenAI’s integrity checks filtered — might explain the served-but-not-shown gap. It doesn’t. Over 60 days, not one of 8,590 ad-bearing responses in cloro’s data carried a hidden ad event. Every served ad we saw was marked allowed.
That is an important negative result. The gap between served and shown is not OpenAI flagging ads as ineligible in the payload — those almost never reach the response. It is a rendering decision made after the ad is served and marked allowed. If you were planning to infer exposure from visibility.status, don’t — it is nearly always the same value.
Finding 5 — the shown ad is the one ChatGPT names
Now the interesting case. When ChatGPT serves two distinct advertisers and shows one, which does it pick? We tested every field in the payload — ad format (image_card_v2 vs product_card_v2), card count, image presence, visibility, even adsResponseIndex — and the strongest signal isn’t in the ad payload at all. It’s in the answer.
The rendered advertiser’s brand appears in ChatGPT’s generated answer 61% of the time; the dropped advertiser’s brand appears just 4% of the time. The interface shows the ad for the brand the model actually talked about.
| Served ad | Brand named in the answer |
|---|---|
| The one that rendered | 61% |
| The one that was dropped | 4% |

That resolves into a clean two-part rule:
- When the answer names exactly one of the two advertisers — 60% of two-ad responses — that ad renders 96% of the time (43 of 45).
- When it names neither (39%), the interface falls back to serving order: the later-served ad (higher
adsResponseIndex) renders 86% of the time.
The exceptions to the naive order rule give it away. In the ~10% of responses where the earlier-served ad wins, it is consistently the one the answer is about: Expedia rendered over a running-shoe brand for a “Starbucks in Seattle” location query; Gong.io rendered over a lead-gen tool for “best sales outreach tool”; a migration tool rendered for “how to move your Netflix profile.” Order didn’t pick those — relevance did.
So the selection process is contextual match between the ad and the generated answer, not a fixed slot. adsResponseIndex predicts the winner ~90% only because OpenAI tends to serve the answer-relevant ad later; it is a proxy, not the mechanism. When you need the true answer, the shown ad is the one whose advertiser the model surfaced in its own text — and the rendered DOM is the ground truth for the rest.
What it means for measuring ads in ChatGPT
Three rules for measurement
Three practical takeaways for anyone tracking ads in ChatGPT:
- Exposure is not
ads[].length. About 87% of ad-bearing responses render a card; the served array overstates what users saw by roughly 13%. Measure the rendered ad, not the served list. - De-duplicate on the advertiser, not the index. Re-emission — 5.1% of responses — inflates counts if you trust
ads_response_indexas an identifier. One placement is one advertiser plus one creative, however many times it streams. - The shown ad is the one the answer names. For the ~4% of responses with two advertisers, the ad whose brand appears in the generated answer renders 96% of the time when the answer names exactly one of them.
visibility, format, and card count show no effect; serving order (adsResponseIndex) is only a ~90% proxy for the underlying content match.
Why it matters to advertisers
For an advertiser, the distinction is money. A brand in result.ads[] won the ad auction; a brand in the rendered card was actually shown. When two advertisers share a response, the one served first is the one dropped about 90% of the time. Exposure counts that stop at the served array over-credit the ads that never rendered.
cloro bakes all three corrections into its ChatGPT results: result.ads[] is de-duplicated on the advertiser and creative, each ad carries its visibility and adsResponseIndex, and a rendered flag marks the ad the interface actually drew. That turns “what was served” into “what was seen” — the number that matters for exposure.
Winning the ad slot in ChatGPT
If the shown ad is the one ChatGPT names in its answer, the task for advertisers is clear: become the brand the model names for your target prompts. Two ads can win the auction for the same query, but only the one named in the answer is shown. So performance on ads in ChatGPT is a visibility problem, not just a bidding one.
Three things matter:
- Be worth citing for the query. The advertisers that render are the ones the model already treats as a good answer — brands with real authority on the topic and fresh, structured content that AI engines cite. If ChatGPT never names you on its own, buying the slot leaves the choice to serving order.
- Match the product to the intent, not the keyword. A charger brand served against a “best rural carrier” query loses to any advertiser the answer actually discusses. Relevance is judged against the generated answer, which is narrower than the raw prompt.
- Measure the impression, not the serve. Track the
renderedflag, notresult.ads[]length, and watch whether your brand appears in the answer text alongside the card. That overlap is the early sign that your ads in ChatGPT are being seen.
None of this shows up in a served-count report. It surfaces only when you join the ad payload to the answer content — which is the whole reason to measure ads in ChatGPT from the rendered response rather than the served list.
Reproduce it
Everything above about ads in ChatGPT is reproducible. The detection logic is public: check the result.ads[] array for served ads and the image-card-v2-elevated-brand card test id for the rendered one. The ChatGPT ads monitoring guide walks through the detection signals end to end. The ChatGPT ads penetration study tracks how often the surface fires by country. And if you want to place ads rather than measure them, see how to advertise on ChatGPT.

About the author
Ricardo Batista
Founder, cloro
Ricardo is one of the founders and engineers behind its SERP and AI-search scraping infrastructure. Before cloro he scaled a financial comparison site to $7M ARR and ran the full-country operations of a unicorn to $65M ARR, then went back to building. He writes about search engine scraping, generative-engine optimization, and turning live search and AI-answer data into something teams can act on.
Frequently asked questions
Does ChatGPT show every ad it serves?+
No. Ads in ChatGPT are served in the `result.ads[]` array, but the interface renders at most one visible card most of the time. Across 3M+ responses, about 87% of ad-bearing responses actually rendered a card; the other ~13% were served but never shown. Counting the length of `ads[]` overstates real user exposure.
How many ads does ChatGPT show at once?+
Usually one. Among two-advertiser responses, 83% show exactly one card, 13% show none, and about 4% show both. ChatGPT serves more than one advertiser in only ~4% of ad-bearing responses (rarely three, almost never four), so the practical answer is one ad per response.
When ChatGPT serves two ads, which one does it show?+
The one it talks about. The rendered advertiser's brand appears in ChatGPT's generated answer 61% of the time versus 4% for the dropped ad. When the answer names exactly one of the two advertisers, that ad renders 96% of the time; when it names neither, the later-served ad (higher `adsResponseIndex`) renders about 86%. Serving order predicts the winner ~90% as a proxy, but the real driver is contextual match to the answer.
Why does a ChatGPT response list the same ad multiple times?+
It re-emits one ad several times as the answer streams, each copy carrying a higher `ads_response_index`. In cloro's data, 5.1% of ad-bearing responses re-emit a single advertiser this way. De-duplicate on the advertiser and creative, not on `ads_response_index`, or you will double-count one placement.
How can I measure real ChatGPT ad exposure instead of served counts?+
Count the ad the interface actually rendered, not the length of `result.ads[]`. cloro detects the rendered card from the response DOM (the `image-card-v2-elevated-brand` card test id, which is locale-independent) and exposes a `rendered` flag per ad. See the technical monitoring guide for the detection logic.
Related reading

ChatGPT Ads Penetration Study: 51% in the US
ChatGPT ads over the last 7 days measure 51.0% of US responses, 53.6% CA, 49.8% AU, and — new since May — 18.8% JP. Everywhere else remains near zero. The surface reverted then re-spiked in June and now looks structural, not transient.

How to Monitor ChatGPT Ads (Technical Guide, 2026)
ChatGPT ads render as a `response.result.ads[]` array on the response, served from `bzrcdn.openai.com` and tagged `utm_source=chatgpt.com&utm_medium=src`. Here's how to detect them with cloro's API and build a real monitoring pipeline.

How to Advertise on ChatGPT in 2026: The Honest Guide
ChatGPT ads are sold direct through OpenAI's sales team at a $200K minimum commitment, with CPC bids of $3-5 general and $8-18 for software and finance. No self-serve manager exists yet. Here's how to actually get placements and what to do if you can't.