pixellint

Pixels · engineers

The pixel ran in the wrong frame

Checkout providers, CMPs, and ad creatives all isolate you in a nested browsing context. document.location is not the merchant URL. Cookies you set on the parent are not visible. A Purchase that fires inside Stripe's iframe tells Meta the conversion happened on stripe.com.

Nested browsing context

An iframe has its own document, its own location, its own cookie jar for that origin, and its own CSP. window.parent is a different origin if the frame is Stripe, PayPal, Adyen, or a CMP. document.cookie in the frame is not _fbp from the merchant. location.href is https://js.stripe.com/... or https://www.paypal.com/..., not https://shop.example/checkout.

Third-party iframe cookies are the first thing browsers kill. Do not put the conversion tag only inside the payment frame. It will not see your click ids, it may not be allowed to set cookies, and the collector will log the frame origin. The pixel URL can still be a valid facebook.com/tr with a numeric id. The identity payload is empty.

Referrer becomes stripe.com

The collector may log the iframe origin as the page URL, or the Referer header as stripe.com. Meta's event_source_url, if you copy location.href from inside the frame, is stripe.com. Domain verification and EMQ both suffer. GA4 dl set from the frame is the same miss. You will see a slice of conversions attributed to a payment host you do not own.

Referrer-Policy on the frame or the parent can strip even that. Then you get a blank source instead of the wrong source. Neither is your thank-you page. Set event_source_url on the parent, explicitly, to the merchant URL where the user paid, or set it on the server from the order's checkout URL.

postMessage from the frame

Payment SDKs emit events to the parent when the charge succeeds: a postMessage, a callback, or a redirect back to your return_url. Fire the browser pixel from the parent handler, not from inside the iframe. Pass the order id, value, and currency in the message payload you control. Do not trust an origin you did not allowlist; a compromised script in another frame can postMessage a fake Purchase.

If the SDK redirects the top window to /thank-you, you are back to a normal thank-you pixel. If it keeps the user on /checkout and shows a success modal, you do not have a page trigger. You need the callback. If 3-D Secure opens a new window, the parent may never see success; the webhook will. That is a CAPI event, not an iframe pixel.

Creative iframes are not landing pages

Display ads already run in iframes (SafeFrame, MRAID webviews, GAM friendly frames). Impression beacons from the creative should hit the ad server, not your site pixel. If you trafficked a facebook.com/tr PageView into a 1x1 creative slot, you are measuring the ad iframe, not the landing page. Referrer is the publisher. Cookies are the publisher's, or none.

ClickThrough should leave the iframe and land on your site as a top-level navigation. Then your site pixel runs in the right document. If a vendor serves a click pixel as an img inside the creative instead of a 302 on click, you get a click count without a landing hit. That is an impression-shaped click. Fix the slot, not the site tag.

CMP and tag-manager frames

Some CMPs inject an iframe. GTM noscript is an iframe to googletagmanager.com/ns.html?id=GTM-XXXX. Those frames load vendor code with a different cookie context. Tags that fire only inside ns.html will not see parent cookies unless they are first-party on a parent path the iframe can read, which they usually cannot. Prefer parent-page tags for identity, and treat ns.html as a no-JS container load, not as a conversion surface.

A pixel that works in the parent and 0-fills in the CMP preview iframe is often this context bug. Preview on the real page. Confirm document.location.hostname is yours. Confirm _fbp is readable. Then look at the collector URL. Core will still require an absolute URL with a host, http or https, no userinfo. It will not tell you the frame was wrong.

Check the artifact

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