Skip to content

Draft — awaiting review

How page caching silently breaks your pixel

If a plugin writes the event ID into your HTML, the cache freezes one ID for everybody — and the platform folds thousands of conversions into one.

TODO(owner): review before publishing — this article is written from the plugin project files but has not been signed off. Until it is, it carries noindex and stays out of the sitemap and the RSS feed. Flip status to published in its frontmatter to release it.

A page cache and a per-visitor event ID cannot coexist. If your tracking plugin writes the event ID into the HTML, the cache stores that page — ID and all — and serves the identical copy to every subsequent visitor. The platform then sees thousands of conversions carrying one event ID, decides they are all the same event, and deduplicates them down to one.

This is not a hypothetical failure mode. It is the specific bug that forced a re-architecture of this plugin, and it is worth understanding because the symptom is invisible: nothing errors, the pixel appears to fire, and your numbers just quietly go wrong.

What the event ID is actually for

Server-side tracking sends every conversion twice — once from the browser, once from your server — and both copies carry the same event ID so the platform can merge them into a single conversion. That ID has to be unique per visitor, per event. It is the only thing telling the platform “these two requests are one sale, not two.”

Which means an event ID that is the same for two different people is worse than no ID at all.

The trap: an ID baked into the HTML

The obvious way to build this is to generate the ID in PHP while rendering the page, print it into the pixel snippet, and queue the server copy at the same moment. It works perfectly on a site with no cache.

Turn a page cache on and it fails in two directions at once:

The browser half freezes. The cache stores the rendered HTML with that one event ID in it. Visitor two, visitor three and visitor nine hundred all receive the same page, so they all fire the pixel with the same event ID. The platform treats them as repeat deliveries of one event.

The server half nearly stops. The server copy was enqueued during the PHP render — but a cached page hit does not run PHP. So the server event only fires on a cache miss. On a well-cached site, that is a small minority of visits.

The result is a setup that looks installed, reports something, and is wrong in a way no error log will ever mention.

The fix: never put a per-visitor ID in cacheable HTML

The architecture that survives a cache inverts the order:

  1. The HTML contains only the static pixel snippet — nothing per-visitor, nothing that changes between two people. It is safe to cache because there is nothing in it worth caching wrongly.
  2. The tracker JavaScript generates a fresh event ID at runtime, in the browser, on every real page load — including loads served entirely from cache.
  3. It fires the browser event with that ID, and posts the same ID to the plugin’s own endpoint, which builds and queues the server copy carrying it.

Now the cache can serve byte-identical HTML forever and every visitor still produces their own unique matched pair. That is not a design claim; it is the test: byte-identical cached pages were verified to still produce unique matched pairs, and a full logged-out pass on a live WordPress store with WooCommerce and a page cache active showed PageView, ViewContent, AddToCart, InitiateCheckout and Purchase all matched on both sides.

The second cache trap: cached REST responses

There is a related failure with a completely different symptom, and it is worth knowing because it looks like a broken plugin rather than a broken cache.

You change a setting. A success message appears. You refresh, and the field is empty. You save again, same thing. Then five or ten minutes later the correct values appear on their own, without you touching anything.

That timing is the tell. Nothing genuinely failing to save fixes itself on a timer — but a cache expiring does. The save worked every time; what you were reading afterwards was a cached copy of the old settings, because the cache plugin was caching REST API responses as well as pages. The fix is for the plugin to mark its own admin responses as non-cacheable, which is what happens now. The long version is in LiteSpeed Cache and REST caching.

How to check your own site

Two things, in order:

Purge your page cache after every plugin update. A cache does not know you updated anything. It keeps serving the HTML it already has, which does not contain your new tracker. This is the second most common cause of “I installed it and nothing happened.”

Then look for a frozen ID. Open your store in two different private windows, look at the pixel request in each browser’s network tab, and compare the eid parameter. Two different visitors must produce two different IDs. If they match, your event ID is baked into cached HTML and every conversion after the first is being deduplicated into it.

If that check is beyond you or you would rather not do it by hand, the free tracking checker reads your public page and reports the cache headers your server returned along with the pixels it finds — including the same account ID being initialised more than once, which is the other common route to broken numbers.

Why this matters beyond one bug

Caching is not an edge case. Nearly every WordPress store worth optimising runs one, and the plugin has to be correct with the cache on, not correct in a clean test environment and broken in production.

That is the reasoning behind the cache-proof architecture: no per-visitor event ID in cacheable HTML, ever, and a multi-layer bypass so the REST endpoints the plugin depends on cannot be served stale either.

TODO(owner) — the author box needs your real details — name, one-line role, two or three sentences of bio in the first person, and optionally a real photo. They go in src/data/author.ts. Until then this post is attributed to PixelCapi in its structured data rather than to an invented person, and no byline is shown.

Where to go next

Related reading

Find out what your own site is sending.

The free Tracking Checker reads your page the way a visitor does and reports what it finds — read-only, no signup.