← The Complete Playbook
Varritech
The Complete Digital Product Playbook

Analytics &
Conversion Systems.

Instrument the truth before you optimise it.

📊
Almost every conversion problem we've diagnosed had two layers: a real product or checkout defect, and an instrumentation gap that made the defect invisible or, worse, made a broken funnel look like a slow week. This guide is the analytics discipline behind our own product updates — how we know an event actually reached the ad platform, how we pick the signal worth optimising on, and how we found (and honestly reported) a 55%-to-10% checkout collapse that a single well-intentioned commit caused.

Inside this guide you'll find the ten-second check that catches a pixel silently missing from every paid landing page, the discriminator that separates buyers from browsers at 24.8x lift, the regression that cut a real checkout's conversion rate by 82% overnight, and the honest arithmetic on why a countdown timer was the wrong thing to build next.


Phase 1: Instrument Before You Optimise

Phase 1 · Ten seconds

curl the route before debugging anything else

loops ~15s
landing-page   fbq:0   tr:0
checkout-page   fbq:1   tr:1
Static pre-renderbeforeInteractive script never runs — pixel silently stripped
Force-dynamic routesame tag, same env var, pixel fires every time
Same provider, same config, two different render modes. The route's render mode is the discriminator, and it's invisible from the page itself.

The most common analytics failure is not a wrong number. It is a number that looks plausible, isn't being generated at all, and nobody checks until a launch quietly underperforms for weeks.

The Ten-Second Check That Finds Real Bugs

Before debugging event names, deduplication, or consent logic, curl the actual routes and count the markup:

for u in <landing-url> <homepage> <checkout-url>; do
  printf "%s fbq:%s tr:%s\n" "$u" \
    "$(curl -s "$u" | grep -c 'fbq(')" "$(curl -s "$u" | grep -c 'facebook.com/tr')"
done

On one real site, every paid landing page returned 0 and 0 for a pixel that had been "installed" for weeks, while the checkout page — same tracking provider, same environment variable — returned 1 and 1. The ad platform's own reporting showed 587 link clicks against 21 landing-page views: individual ad rows showing 79 clicks driving 2 recorded views. That gap is not a slow week. It is a page silently failing to load its own tracking script.

The Root Cause, Once Found

In a modern React framework, a client hook reading URL search parameters, mounted inside a provider that also renders a tracking script meant to load before the page becomes interactive, can force the framework to render a placeholder into the static HTML of every pre-rendered route and defer the real content to the client. A script tag that depends on running before interactivity is only honoured while the initial document is being produced — so the tracking stub never executes, and any helper that checks "is the tracking function defined yet" before firing silently drops every call, forever, on that route.

⛔
The render mode of the route is the actual discriminator, and it is invisible from the page itself. A statically pre-rendered route strips the pixel. A route explicitly marked to render fresh on every request keeps it. Two routes can look identical in a browser — same visual design, same other tracking tags firing normally — while one silently has no ad pixel and the other does. Don't conclude "analytics is broken on this page" from one working tag; check the specific tag that matters, on the specific route that matters.

The fix is narrow: remove the URL-parameter hook from anything mounted beside a script that must load before interactivity, and test it by making that hook throw in a test environment — a page that still renders its tracking script under that condition is decoupled correctly; a page that breaks is coupled to exactly the thing that caused the production bug.


Phase 2: The Deduplication Discipline

Phase 2 · Dedup

One event ID, two senders, one honest gap

loops ~15s
Browser pixel + Server CAPI → Same event_id → one counted sale
Found, not fixed: 12 raw purchase events for 4 real orders — exactly 3 per order. Dedup held at the reporting layer, but the root cause of the triple-send is still untraced.
Report what's verified and what isn't. "Dedup is holding today" is not the same claim as "this is fixed."

Firing a conversion event from both the browser and your own server is the correct redundancy pattern — buyers close tabs before a redirect finishes, ad blockers silently drop 30-35% of client-side pixel calls — but only if both sends agree on the same deduplication key. Get this wrong and you either lose real conversions or, worse, count one sale as several.

The Pattern That Works

Case Study: An Unsolved 3x, Reported Honestly

Reconciling four real sales against an ad platform's own reporting once turned up something stranger than a missing event: the pixel's raw event stream showed three purchase events fired for every one real order — twelve events for four orders, exactly three per order, consistently, across a second measurement a week later. The platform's own attributed reporting was still correct — deduplication was holding at the reporting layer, so nothing downstream had actually broken yet — but three server-side sends were happening where there should have been one.

🔍
This was reported as unresolved, not fixed, because it was unresolved. The honest state was: dedup is holding today, the root cause of the triple-send is untraced, and if the dedup key ever drifts — a handler stops passing it, or two handlers compute it differently — reporting and the platform's own optimisation signal both break silently, with no warning before it happens. Writing "found and fixed" about a problem you've only confirmed isn't currently causing visible harm is the single most common way a real defect gets closed as resolved and then resurfaces months later with no one left who remembers investigating it.

The lesson generalises beyond this one bug: verify a fix against the actual failure mode you found, not against "the number looks right today." A number that looks right can be one silent architecture change away from looking very wrong.


Phase 3: Choosing the Right Signal to Optimise On

Phase 3 · Choosing a signal

Lift vs. usable weekly volume

2,161 sessions · loops ~15s
Added payment info
90x / 10wk
8+ clicks, 120s dwell
48x / 12wk
6+ page loads
35x / 14wk
3+ clicks, 2+ loads
25x / 36wk
Shipped as the optimisation event: the signal that caught 9 of 9 known buyers while clearing enough weekly volume to actually feed a learning algorithm — not the single highest-lift candidate.
The best discriminator and the best optimisation event are frequently different signals.

Once events are reliably reaching your analytics platform, the next decision — which signal to actually optimise ad delivery on — matters as much as the instrumentation itself. The obvious answer ("optimise on purchase") is right in principle and frequently wrong in practice at low volume, because a rare event gives an ad platform's algorithm almost nothing to learn from.

Measuring the Real Discriminators

On one product's landing page, across 2,161 sessions with a 0.42% baseline buy rate, matching real purchases (via payment-processor timestamps, since no purchase event had ever actually fired inside the analytics tool) back onto session data surfaced a ranked table of candidate signals:

SignalSessions matchingBuyers caughtBuy rateLiftWeekly volume
Added payment info24937.5%90.0x10/week
8+ clicks and 120s+ dwell30620.0%48.0x12/week
6+ page loads34514.7%35.3x14/week
3+ clicks and 2+ page loads87910.3%24.8x36/week
Initiated checkout10099.0%21.6x41/week
Session 120s+19384.2%10.0x80/week

The strongest single discriminator (added payment info) catches too few sessions per week to give an ad platform's learning phase enough volume. The signal actually shipped as the optimisation event was the one that caught every single buyer in the dataset (9 of 9) while clearing enough weekly volume (36) to actually feed a learning algorithm — a deliberate trade of some precision for enough scale to be usable at all.

🎯
The best discriminator and the best optimisation event are frequently different signals. A rare, high-lift event (added payment info at 90x lift) proves buyer intent almost perfectly but starves an optimizer of volume. A slightly noisier, much more common event that still catches every real buyer gives the algorithm something to actually learn from. Choose the signal that maximizes usable volume among candidates that catch (ideally) all of your known buyers — not the single highest-lift signal in isolation.

One channel-specific finding worth checking on any consumer product: sessions arriving through an in-app browser (a social platform's embedded webview rather than a real mobile browser) accounted for a quarter of all landing-page traffic on this product and converted at a hard zero — not a low rate, zero purchases across 545 sessions. Every session now carries a browser-class tag specifically so that traffic can be excluded from lookalike audiences and reported separately, rather than silently dragging down every blended conversion number on the account.


Phase 4: Session-Scoped, Not Page-Scoped

Phase 4 · Counter scope

The buyers who went backward

3 of 9 buyers · loops ~16s
Page-scoped counter
Visitor clicks 3 times on the LP
Clicks "back to product" from checkout
Counter resets to 0 — signal erased
Session-scoped counter
Same 3 clicks recorded to sessionStorage
Navigation happens; state persists
Signal survives — buyer still counted
The undercounted sessions are disproportionately your real buyers — the ones engaged enough to leave and come back.
Mount the tracker once per session, not once per page.

A subtle instrumentation bug that only shows up once you're measuring engagement across multiple pages: a counter that resets every time the visitor loads a new page will systematically undercount real, multi-page engagement — and the sessions it undercounts are disproportionately your actual buyers.

The Buyers Who Went Backward

In the same discriminator study above, three of nine confirmed buyers used a "back to the product page" link from the checkout screen and returned to browsing before completing their purchase. A page-scoped click or dwell counter resets to zero the moment that navigation happens, silently erasing everything the session had already accumulated — which means a signal like "3+ clicks and 2+ page loads" would undercount exactly the buyers who most demonstrate genuine intent by leaving and coming back.

The fix: store engagement counters in session-scoped storage (with an in-memory mirror as a fallback for private browsing modes, where persistent storage can be unavailable or cleared), and mount the same tracker component on every page in the funnel rather than re-initialising per page. A visitor's session state should survive navigation within that session; only a genuinely new session should start the counters over.

⚠️
A UI change that removes something buyers were actually using can look harmless and cost real conversions. The same product later removed its "back to the product page" link from checkout as, in the team's own words, "an unnecessary escape hatch." Every observed use of that link in the discriminator data predated its removal, so causing direct harm from the removal is unproven — but a link real buyers used, removed on an assumption about what visitors "should" need, is exactly the kind of change that deserves a controlled test rather than a confident guess either way.

Phase 5: Building an A/B Testing System That Doesn't Lie to You

Phase 5 · Bucketing

A single hash pass looks fine in aggregate

10 bands, intended 10% each · loops ~15s
Band 1Band 2Band 3 — 8.8%Band 4Band 5
A murmur3 avalanche step after the initial hash fixes local density — the aggregate 50/50 split was never the problem, the band-level unevenness was.
Test bucketing for local evenness, not just the whole-range average.

Reliable experimentation infrastructure is a small number of concerns, done precisely, rather than a large framework. The two failure modes that matter most are uneven traffic splits that look fine in aggregate, and a system that reports success on a write that the read path silently ignores.

Deterministic Bucketing, Done Correctly

A visitor needs to land in the same test variant every time they return, without a database write — which means computing their bucket from a hash of their identifier. The naive version of this (a single-pass hash function) can pass an aggregate 50/50 split check while still being badly uneven within narrow bands: sequential-looking identifiers landed one band at 8.8% of traffic instead of an intended 10% — a deviation large enough to be a real, silent sample-ratio mismatch, on exactly the band a weight change would move first.

The fix is a second "avalanche" mixing step applied after the initial hash, specifically to fix local, band-level unevenness rather than only the whole-range average — and using an integer multiply operation that doesn't lose precision at the bit-width the hash needs, since a language's default multiplication can silently truncate at that scale.

Registry Wins on Shape, Runtime Wins on Weight

The cleanest architecture splits "what variants exist and what code branches handle them" (a versioned, code-reviewed registry) from "what weights and pauses are currently active" (a runtime store an admin dashboard can edit without a deploy). The runtime layer is allowed to re-weight or pause an experiment; it is never allowed to invent a variant the deployed code has no branch for. A write attempting to force an undeclared variant, or supply weights that don't sum to one, should be rejected loudly at write time — a system that silently accepts and discards invalid input will show a dashboard reporting success while every read continues serving the old, correct-but-unintended default. That gap — every layer of a system looking finished while the one piece that actually serves traffic still hardcodes the old behaviour — is worth explicitly testing for, because it does not throw an error anywhere; it just quietly never does what the dashboard implies it's doing.

Don't Bucket Traffic That Was Never Eligible

Two gates should run before any visitor is bucketed into a test at all: does this flag even apply to the page they're on, and does this visitor match the experiment's intended audience (excluding, for instance, the in-app-browser traffic from Phase 3 that structurally cannot convert regardless of what it sees). Bucketing an ineligible visitor doesn't add data, it dilutes the readout — a real effect gets dragged toward zero by noise from people who could never have converted either way.

🧪
Report exposure only for the flag a visitor's session actually reaches, not every flag that merely matched their URL path. A path-based match can include an experiment that's registered and routed but has no live consumer yet — recording an exposure for it pollutes the denominator with visitors nothing ever really tested, making the experiment's own reported sample size a lie about how many people actually saw a difference.

Phase 6: Reading a Dead Funnel From Raw Numbers

Phase 6 · Raw numbers

Add-to-cart, day by day

traffic kept arriving · loops ~16s
Day 3 — 5
Day 5 — 41
Day 6 — 0
Day 7 — 0
Not backfilled. A second, independent analytics tool showed the same zeroes — the events never happened, so replaying them would poison the exact signal ads optimise on.
A raw event table shows a cliff a smoothed weekly chart hides completely.

Sometimes the analytics question isn't "which variant won" — it's "did the funnel work at all this week," and the honest way to answer that is a raw event table, not a dashboard's smoothed summary.

The Table That Tells the Whole Story

DayPage viewsProduct viewsAdded to cartStarted checkoutPayment info addedPurchases
Day 125485102230
Day 2953308123950
Day 3103812652833
Day 5 (post-change)11144414156259
Day 68991100300
Day 7549580200
Day 8259380300

A funnel that was converting normally on Day 5 — 41 add-to-carts, 9 purchases — goes to zero add-to-carts for three straight days immediately after, while page views keep arriving in the hundreds. That is not a slow week or a soft market; that is a specific step in the funnel breaking completely while traffic acquisition keeps working exactly as before, which is precisely the pattern that a smoothed weekly or monthly chart hides.

What Not to Do With Missing Data

The tempting fix, once a gap like this is confirmed, is to "backfill" the missing conversion events into the ad platform so the campaign's own optimisation signal doesn't look broken. Don't. Cross-checking against an independent analytics tool that receives the same events through a different code path showed the exact same zeroes — the events were never silently dropped in transit, they never happened, because almost nobody actually reached the page that was supposed to fire them. Manufacturing events for sessions that never occurred would be fabricating data into the exact signal an ad platform's algorithm uses to decide who to show your ads to next — poisoning your own future targeting to make a retrospective chart look better.

🚫
The ethical and the technically correct answer were the same answer here. When conversion data is missing because a step in the funnel is broken, the fix is finding and repairing the break — not inventing plausible-looking replacement events. A platform's own optimisation depends on the honesty of what you feed it; a backfilled fake event trains the algorithm on something that never happened, which costs you real money on every future impression it influences.

Phase 7: A Checkout Regression, Diagnosed Honestly

Phase 7 · The regression

Card-fill to paid, before and after one commit

odds of chance: ~1 in 220 · loops ~16s
55%
10%
The honest limitation: instrumentation couldn't distinguish "never clicked pay" from "clicked pay, threw client-side" — the payment provider owned the iframe, so no button click was ever logged.
Ship the instrumentation fix before the next theory — without it, every explanation is unfalsifiable.

The most expensive analytics lesson in this guide: a single commit, made with good intentions, cut a real checkout's completed-purchase rate by more than 80% overnight — and the honest diagnosis required admitting, at first, that the instrumentation couldn't actually distinguish the two competing explanations.

The Collapse

Before a particular checkout rewrite: 11 people completed the card-entry step, 6 of them paid. A 55% completion rate. After the rewrite — which touched roughly 200 lines, removing a payment-method disclosure element, dropping some pre-payment add-on offers, and discounting a post-payment upsell: 10 people completed the card-entry step, 1 paid. A 10% completion rate. The odds of that swing happening by chance alone are roughly 1 in 220.

What was ruled out, checked rather than assumed: the checkout page itself rendered correctly — the payment form's fields displayed at their expected size, the pay button was enabled, nothing crashed visibly. And no payment attempt that day carried a recorded processor-side error — so failures, if they were happening, weren't reaching the payment processor at all.

🔴
The honest limitation, stated plainly rather than papered over: the instrumentation as it existed could not distinguish "the visitor never actually clicked pay" from "clicking pay threw a client-side error before the payment processor was ever contacted." The payment provider owned the entire card-entry iframe, so no button click was ever logged on that element — only the fact that the card fields had reached a valid, complete state was recorded, which proves someone finished typing, not that they then attempted to submit.

The fix that had to ship before re-diagnosing anything further: one explicit event fired the instant a submission handler starts, before any network call — and a second event on any submission error, carrying the actual error message. Only once that distinction exists in the data can the next investigation actually answer the question instead of guessing between two equally plausible stories.

The larger lesson: a regression this severe deserves an instrumentation fix as its first remediation step, before any hypothesis about root cause, because without the ability to distinguish "didn't try" from "tried and failed silently," every subsequent theory is unfalsifiable.


Phase 8: The Checkout Killers, Ranked

Phase 8 · Ranked

Six killers, 20% toward a realistic 50%

loops ~16s
1Lead-capture ask before any buy action
2Payment form remounts, wipes typed card
3Visible, empty discount-code field
4Tier still re-selectable at checkout
5No one-tap wallet payment
6Thin trust signalling at the pay moment
What was deliberately not added: fake urgency. Manufactured scarcity, once noticed, raised abandonment ~23% on a comparable checkout.

Separate from the regression above, a structured audit of the same checkout surface — ranked by estimated impact, all fixed inside the actual checkout component — found six specific friction sources holding conversion at roughly 20% against a realistic target near 50%.

  1. A lead-capture ask presented before any buy action. The checkout opened with an offer to have someone "reach out to help you pick the right plan before you buy" — telling an already-committed buyer to stop and reconsider, at the exact moment they'd decided to purchase. The single largest killer measured. Removed entirely.
  2. The payment form remounting on every price change. Selecting a different tier, entering a phone number, or applying a promo code recreated the entire payment session — wiping any card details the buyer had already typed and replacing the payment column with a loading state. Fixed by creating the payment session exactly once on page load and locking the tier and promo to URL parameters so nothing about the page ever re-prices after mount.
  3. A visible, empty discount-code field. An empty box invites 5-10% of buyers to leave the page hunting for a code that may not exist, and silently signals that other people are paying less than they are. The only real user of that field internally was an internal test code — moved behind a URL parameter instead.
  4. A tier selector still changeable at checkout, reopening a decision the visitor had already made on the product page — and every change re-triggered the remount problem above. Locked to the value chosen upstream, with a plain link back to reconsider rather than an in-place selector.
  5. No one-tap wallet payment option, the single biggest lever specifically for mobile completion (measured elsewhere at roughly +20% on mobile) — contingent on a one-time domain verification with the payment provider before it renders at all.
  6. Thin trust signalling at the exact moment of payment — a small "secured by" logo and nothing else. Added a guarantee block directly under the pay button (a shipping-speed promise, a money-back window, and delivery expectations) plus a price anchor comparing the cost to the value of building the equivalent from scratch.
⚠️
What was deliberately not added: a fake countdown or artificial scarcity. A prior test on a comparable checkout showed manufactured urgency ("2 left!" with no truth behind it) increasing abandonment by roughly 23% once buyers noticed it was fake. Genuine scarcity, honestly stated, is a legitimate lever — invented scarcity is a trust cost that outweighs whatever short-term nudge it provides.

Phase 9: Measure Before You Build the Next Feature

Phase 9 · Size it first

Two fixes, sized before either was built

from real session data · loops ~15s
2-3/wkBest-case lift from a countdown timer — most abandoners leave in single-digit seconds
11/wkEstimated lift from fixing the card-fill→paid instrumentation gap
~4x difference, found in an afternoon with data already on hand — before either feature was built.
Size a proposed fix against your own data before building it, not after.

The final discipline, and the one most teams skip under pressure to "just ship something": before building a proposed conversion improvement, size its actual ceiling from the data you already have, so effort goes to the change with the larger real payoff rather than the more appealing one.

Two Candidate Fixes, Sized Honestly Before Either Was Built

Asked whether a countdown timer would help a checkout, the answer came from the product's own session data rather than intuition:

📐
Size a proposed fix against your own data before building it, not after. A countdown timer is a more visually satisfying thing to ship than an instrumentation patch — it's a feature a stakeholder can see and approve in a screenshot. The session-length data made the actual comparison unambiguous: most visitors who abandon checkout are gone in single-digit seconds, long before any urgency mechanic could reach them, while the instrumentation gap was silently hiding a much larger and more fixable problem in the step right before it. The honest sizing exercise, done in an afternoon with data you likely already have, is frequently worth more than either feature would have been built blind.

This is the same discipline that runs through this whole guide: verify what's actually happening before optimising it, choose the signal that can actually be learned from, hold everything else constant while testing the one thing that matters, and report what you found honestly — including the parts that are still unresolved.


Bonus: The Conversion Instrumentation Audit Checklist

Run this before trusting any funnel number enough to act on it. Every unchecked box is a plausible-looking dashboard hiding a real gap.


Bonus: Tools and Resources Library

Analytics and Session Data

Conversion Attribution

Experimentation Infrastructure

Testing and Verification



What's Next?

You now have the discipline behind every real conversion number this guide cites: verify the tag is actually on the page before trusting anything downstream of it, choose an optimisation signal for its usable volume rather than its raw lift, treat "3x events for one order" as unresolved until it's actually traced, and size a proposed fix against real session data before you build it. The checkout collapse in Phase 7 and the killers in Phase 8 both came from the same habit — reading the raw numbers instead of the smoothed dashboard, and reporting exactly what was and wasn't yet known.

📞
Staring at a funnel number you don't fully trust? Book a free 30-minute strategy call. We'll go through your actual instrumentation — what's verified, what's assumed, and where the real gap probably is.

Email sales@varritech.com or visit https://varritech.com/booking to book.

No pitch. No pressure. Just two engineers talking shop.

Varritech Development Services — Bold ideas wait for no one.

</content>

Flagship · by application · 3 installations per quarter Scalewright Installation$15,000 Done-with-you. A six-week production build with 100% of the IP yours from day one, the four growth systems installed with it, and two working sessions a week inside your repo. We don't hand you a codebase and walk, we stay on it until you hit $10K MRR. Guaranteed. Book a consultation →
Claude Code · one time Skillsmith $47 · no subscription The 213 production skills behind this guide, plus one autonomous agent included. Ship $10,000 of agency-grade software by Sunday or we refund every dollar. Application-only Scalewright Circle™ Free · no card, ever The room where founders ship every week. Priority answers from our engineers, weekly office hours, blueprint pods, and vetted investor intros. Access worth $650+/mo.
Varritech

Nine guides in the playbook.
Product strategy, design systems, production engineering, security, testing, go-to-market, AI scaling, AI ad scaling, and analytics & conversion systems.