pixellint

Pixels · PMs · marketers · engineers

A pixel is an HTTP request with a job

Marketing calls it a pixel. Engineering calls it a GET or POST. The vendor calls it a tag, a beacon, or a collector. Same object: a request that says something happened, with enough parameters that a system on the other end can count it. The 1x1 GIF is one transport. sendBeacon and fetch are the others. The job (impression, click, page, conversion) is independent of the transport.

What it is not

It is not a screenshot, not a creative, and not the ad itself. The 1x1 GIF is an implementation leftover from when the only thing a page could load without JavaScript was an image. Modern pixels are often fetch or sendBeacon, still named pixel because the budget line did not change. A 300x250 IAB unit that happens to contain a tracking URL is a creative plus a pixel, not a pixel that is 300 pixels wide.

It is also not proof the user saw the ad. An impression pixel fires when the tag runs, which can be before the creative is in view, after it was replaced, or on a player that never painted a frame. Viewability, if you have it, is a different request from a different vendor, often with a different host and a quartile or IAB viewable flag. Do not treat a 200 on the impression URL as a human who watched.

1x1, sendBeacon, and fetch

The classic pixel is an img whose src is the collector URL. The browser issues a GET, often with cookies for that host, and paints a transparent 1x1. There is no request body. The collector infers the event from the path and query: ev=PageView on facebook.com/tr, en=page_view on google-analytics.com/g/collect, src/type/cat on doubleclick.net. If the GET is cached, you undercount; that is why ord and similar cache busters exist.

navigator.sendBeacon(url, body) is a POST that the browser queues to outlive the page. It is built for unload: tab close, in-app webview teardown, SPA route change that destroys the document. You cannot read the status code. Typical payload caps sit around 64 KiB. fetch with keepalive: true is the programmable relative: you pick method, headers, and credentials, you can POST application/json, and on a living page you can await the response. A tag that uses img for PageView and fetch for Purchase is two transports for two jobs, not a bug, as long as both URLs are the ones the vendor documents.

Pick the transport the vendor actually supports. Meta's browser pixel is facebook.com/tr as a GET from fbq or from an img. Google Ads image fallback is a GET with script=0. Floodlight activity tags are still image GETs. Inventing a POST to those hosts because fetch feels modern will 405 or silently drop.

Impression, click, page, conversion

Four jobs, four failure modes. Impression: the creative loaded in the player or on the page. Click: the user left for the landing page, almost always via a 302 chain that stamps gclid, fbclid, or an MMP id. Page: they arrived, or they hit a later route. Conversion: they did the thing you pay for (purchase, lead, app install, or a custom event you registered). One vendor may use one endpoint for all four, with an event name in the query. Another uses a different host per job.

Read the host and the event field together before you decide what broke. facebook.com/tr?ev=PageView is a page job. The same host with ev=Purchase is a conversion job and needs cd[value] and cd[currency] as an ISO 4217 code. google-analytics.com/g/collect?v=2&en=page_view is a page job; en=purchase is a conversion job. An impression beacon on a VAST Impression element is not a page view on your site. Pasting one URL into the other slot is how you bill the wrong event.

Email image pixels

An ESP open pixel is an img in the HTML body. The job is page-like (the message rendered) but the environment is not a browser you control. Gmail routes images through googleusercontent.com proxies. Apple Mail Privacy Protection prefetches images on receipt, which mints opens with no human. Outlook and some corporate gateways block remote images until the user clicks Display Images. None of those clients run your page JavaScript, so there is no fbq, no gtag, no sendBeacon.

Cache busting still matters: if every subscriber hits the same img src, a proxy cache collapses thousands of opens into one GET. Put a unique id in the path or query, not an email address. A raw email in the open URL lands in the ESP's CDN logs and in the next hop's Referer if a tracked link is clicked. Hash or use an opaque send id. Do not traffic a site conversion pixel as an email footer image; the collector will log a Purchase from mail.google.com.

VAST impression beacons

In a VAST document the Impression element is a URL the player GETs when it considers the ad started. Tracking elements cover start, firstQuartile, midpoint, thirdQuartile, complete, and click. Those are still pixels: HTTP requests with a job. They often carry ad-server macros ([CACHEBUSTER], ${AUCTION_ID}, {{PLAYER_WIDTH}}) that must expand before the player fires. A live beacon that still contains [CACHEBUSTER] never busted a cache and never identified an impression.

VAST impression URLs should return 200 with a 1x1 or 204 No Content. They must not 302 to a landing page. The player is not a user click. A wrapper VAST that chains four Impression URLs will fire four GETs for one start; that is normal trafficking, not duplicate pageviews on your site. Validate the fired URL from a HAR of the player, not the template sitting in the ad server, or you will fail good macros and pass dead tokens.

Check the artifact

Paste the pixel URL or JSON body into the playground. Same engine as pixellint validate. Nothing leaves the tab.