Pixels · marketers · PMs · engineers
Impression and click are not the same URL
Ad servers have two (or three) tracker fields. Putting a click URL in the impression slot bills you for clicks that never happened, or worse, navigates the player on render. Impression wants 204 or a 1x1. Click wants 302 until the landing page. View-through is neither URL.
Impression: 200 and a 1x1, or 204
Fired on load, often as an image or a VAST Impression beacon. The player or the page issues GET. A well-behaved collector returns 204 No Content or 200 with a 1x1 GIF or PNG. Cache-busting (ord, random, cb, cbuster) exists so the network does not collapse a thousand impressions into one cached GET. A leftover literal ord=1 on a live Floodlight tag is a trafficking miss, not a unique impression.
Read the status on the impression URL itself, not on the page that contains it. A creative that returns 200 with HTML while the nested impression beacon 404s is a live ad with a dead counter. Core flags unsupported schemes and http for upgrade; it will not tell you that you pasted the click URL into the impression field. That is a slot error you catch by reading Location vs Content-Type.
Click: a 302 chain
Fired on click, almost always a redirect chain: publisher, ad server, vendor click id stamper, then the landing page. Each hop should return 302 or 301 with a Location that still carries the query. Hops append fbclid, gclid, ttclid, or an MMP click id. The last hop is a document, 200, on your site.
If the chain drops a parameter, downstream CAPI matching and paid search attribution both rot, and the dashboard still shows the click. A hop that returns 200 with an HTML interstitial is a broken click: the user never reaches the landing page, and the click id never lands in your first-party cookie. Debug with curl -sIL, not with a browser that only shows the last document.
Never 302 an impression to a landing page
An impression tracker that 302s to a landing page is a trafficking error. The player or the img will follow Location. Some players abort; some navigate the webview; some fire the landing page pixel as if a user arrived. You mint pageviews, consent banners, and sometimes Purchases from ads that were only rendered.
Impression URLs should not be your click-through URL with a different cache buster. They should not be the same GAM click tracker with &imp=1 hoped-for. If the vendor gives you one URL and two slots, read which status they return. If they 302, it is a click tracker. If they 204, it is an impression pixel. If you are unsure, curl -sI the URL from a machine that will not follow into a user session.
View-through is a report setting
Some vendors count a conversion after an impression with no click, inside an attribution window (1 day, 7 day, 28 day, whatever the UI offers). That is a report setting, not a third pixel you forgot to traffic. There is no view-through URL to paste next to impression and click. Confirm the window and the model (last click, data-driven, view-through included or excluded) before you declare the pixel broken.
A spike in view-through conversions with a dead click chain often means impressions still fire and clicks 200 mid-chain. Fix the click redirects. Do not add a second impression pixel to compensate. Do not fire Purchase on impression because the view-through report looks low; that is how you train the model on renders.
Three slots, three artifacts
Display in GAM: impression URL, click URL, sometimes a vendor backup. Video in VAST: Impression plus ClickThrough plus Tracking. Audio and native have the same split under different names. QA each as its own URL with its own expected status. pixellint validate url on the impression GET; walk the click chain with curl -sIL separately.
Macros differ by slot. A click URL may legally contain ${CLICK_URL} in a template. A fired impression that still contains [CACHEBUSTER] is a miss. Do not validate the click template as fired, and do not validate a HAR impression as a template. The three slots are three states and three contracts.
Check the artifact
Paste the pixel URL or JSON body into the
playground. Same engine as
pixellint validate. Nothing leaves the tab.