This guide explains what the monitoring product means by “value”—the dollar and percentage figures you see on customer views, ROI, and some emails. It is the detailed mechanics version: windows, formulas, suppression rules, edge cases.
Looking for a plain-English summary of each banner label? See Value banner glossary — one short entry per tier with worked examples.
For Klaviyo flow checks (live flows, competitors, exclusions), see Klaviyo flows. For ROI emails in Intercom, see Customer emails (Intercom).
Monitoring pulls together several signals from recent audits (Klaviyo, Meta, Google Ads, GA4 where connected). Each signal can produce a value tier—for example “extra Klaviyo revenue we can attribute” or “extra Google Ads conversion value from server-side tracking.”
The product then:
You may still see other tiers listed (for example Littledata Klaviyo flow revenue when flow exclusion rules are not yet in a passing state, or predicted incremental Klaviyo revenue from audience modelling). They answer different questions; the headline is the one the system thinks is the best single story for ROI.
Klaviyo contributes up to three different value ideas. They are not double-counting the same thing; they measure different things.
Self-serve validation: the “How is this calculated” explainers for the observed Klaviyo tiers link directly to the exact Klaviyo flow reports behind the figures (
https://www.klaviyo.com/flow/<flowId>/reports, built inklaviyoFlowReportsUrl), so a customer can check the revenue against Klaviyo’s own dashboard. The explainers deliberately show conclusions, not step-by-step formulas — the full method lives in the “How we calculated your Littledata revenue boost” modal.
What it is: Total Klaviyo-attributed Placed Order revenue from live flows triggered by Littledata abandonment metrics (viewed product, added to cart, checkout started), summed across all such flows. When flow exclusion rules pass, the Littledata flows are set up to exclude subscribers who already entered the native Klaviyo abandonment flows, so this revenue is fully incremental—it would not be generated by the native flows for the same shoppers.
When it appears: Only if flow exclusion rules are in a passing state and there is positive attributed revenue in either the 30-day or 90-day window on those Littledata-triggered flows. The window matches Littledata Klaviyo flow revenue logic (typically ~30 days of order dates; 90 days is used when that gives a stronger monthly run-rate, or when the rolling 30-day window has zero attributed orders but the 90-day window does not — so a single quiet month doesn’t drop the tier entirely). Prospect audits always attempt this tier, and the conditions above decide it: an account with no live Littledata-triggered flow earns nothing here, so a true prospect never gets it, while an account whose Littledata flows are plainly earning headlines its observed revenue rather than the more conservative prediction. It is deliberately not gated on a Customer record existing — that record is keyed on customers.klaviyo.accountId and misses accounts whose Klaviyo connection is recorded elsewhere. For the same reason the flow-exclusion check is resolved by Klaviyo entityId rather than by shop: a Klaviyo account that also belongs to a customer has that check written under the customer’s shopName, not under __prospect__<accountId>, and looking only at the prospect shopName withheld the tier from every account that had measured revenue to show.
Plain English: “How much incremental email/SMS-attributed order revenue are your Littledata-triggered abandonment flows driving, over the reporting window, when we trust the setup is not double-counting the same people as native flows?”
What it is: Total Klaviyo-attributed order revenue tied to Littledata abandonment triggers (the same three stages), summed across flows—not net of native. This is a gross “how much revenue is flowing through Littledata-named triggers” figure.
Window: Usually about the last 30 days of order dates in Klaviyo’s reporting; if 90 days of data shows a clearly stronger monthly run-rate, the system may use that longer window so sparse months do not understate the store.
Plain English: “How much revenue do your Littledata abandonment flows claim, before comparing to anything else?”
What it is: A modelled monthly dollar estimate when we have revenue from native (and/or Littledata) flows but want to translate audience reach into money. It does not replace the observed incremental tier; it is another way to express upside when the comparison is about how many people each side can reach.
When it appears: When the Klaviyo flow revenue snapshot on the customer has enough native and/or Littledata flow revenue to anchor the maths, and the Klaviyo account is connected so we can measure audiences. It is hidden from display once measured incremental Klaviyo revenue (tier 1) matches or beats the prediction on a monthly-equivalent basis — a smaller “predicted” figure beside a larger measured one reads as a downgrade. The tier is still computed and stored (prediction accuracy tracking, backtests, the audience-maturity modal); only the row is dropped.
Alternative-trigger prospects (Triple Whale, Elevar, …): A prospect already running a competitor’s server-side trigger has no native baseline, so instead of the native-uplift model we anchor on the competitor’s own flow revenue and credit only the net head-to-head uplift Littledata gains over the alternative (floored at parity — at break-even the value is consolidation + additional flows, not a stage uplift). The uplift comes from a rolling 12-month head-to-head benchmark of shops that ran both. Because the pitch is Littledata replacing the alternative as the single trigger source, we quote the upstream ratio (how Littledata performs when it’s the sole/first source), not a blend that includes shops where Littledata was only a downstream backstop. The benchmark is deliberately conservative in two ways. Until enough comparable upstream shops exist for a competitor + funnel stage, it shows no uplift (parity) rather than a noisy estimate. And where the only evidence is the cross-competitor pool for that stage — we have measured Littledata against some alternatives, but never against this one — we claim half of that uplift: tools at the same funnel stage differ a lot in what they capture, and an identity-resolution tool that already de-anonymises traffic is a much harder baseline to beat than a client-side tag, so quoting the pool at full strength would state a lead over a tool we have never raced. Once three stores run both, that competitor has its own ratio and goes to full weight. Where we can’t yet quote a revenue uplift, the bullet instead cites an audience-reach proof point — “Littledata’s tracking captured +X% more shoppers than {competitor} at the same stage” — drawn from a larger, cleaner cohort (any shop where both trigger metrics fire, not just both run live flows; reach isn’t distorted by flow attribution). Reach is a coverage figure only and never feeds the predicted dollar value. The ROI headline is unchanged; an “upgrade from {competitor}” bullet explains the replace-and-add story. See Klaviyo flows for how the benchmark is collected.
Two things follow from a prospect’s flows being someone else’s. The abandonment flow revenue we quote back to them (“your Klaviyo abandonment flows earned $X in the last 30 days”) counts every live browse / cart / checkout flow whichever tool fires it — matching only Klaviyo’s own three trigger names told a store running its whole programme on an alternative source that it had earned $116 when it had earned $4,619. And the replacement estimate is available on the first audit: the competitor check runs in the audit’s slow phase, so the value compute derives the revenue rows it needs from the live flows directly rather than waiting for a later run to publish them.
Think of it in four layers: window, pairs, audience uplift, money.
We compare Littledata and native on a fair calendar window:
So the “audience” side and the “revenue” side are trying to line up with how long Littledata has been in the picture (or 30 days, whichever is shorter in effect).
For each stage—viewed product, added to cart, checkout started—we compare:
We only use a stage if we have both metrics and enough Littledata-side reach to treat the number as stable (at least about 50 unique people on the Littledata trigger in that window). Stages that fail those checks are skipped for the prediction.
The revenue scaling is based on all unique people who triggered each metric during the comparison window. This gives a consistent result: if Littledata reaches X% more people overall than the native trigger, total flow revenue can be expected to scale by roughly X%.
As a separate, informational figure we also show net-new Klaviyo profiles—people whose Klaviyo profile was created after the start of the window (brand-new-to-Klaviyo). This tells you how much more Littledata is growing the Klaviyo list versus the native trigger, but it is not used as the revenue multiplier. Applying a “share of new profiles” ratio to total flow revenue (which includes long-standing profiles) would mix incompatible denominators and could overstate the result.
Behind the scenes, audience counts are built from daily snapshots of Klaviyo activity so we do not have to re-scan entire months on every run; if snapshots are not complete yet, we fall back to the “all unique people” count only.
If native abandonment flows have meaningful attributed revenue in the audit:
If there is no native abandonment revenue but there is Littledata flow revenue:
Audience-uplift damping and the floor (measured-uplift path): when we have the store’s own audience comparison, the Littledata-flow contribution is scaled by how much extra audience Littledata actually reaches versus the native trigger at each stage. A store with strong measured uplift keeps the full contribution; a store with little or no measurable uplift is held to a 30% floor rather than crediting the whole attributed amount. This stops a high-AOV store from booking a large “incremental” figure off last-touch-attributed flow revenue when there is no evidence Littledata expanded the audience — while still recognising that some revenue is genuinely rescued (via identity stitching) even when the Littledata-tagged audience doesn’t visibly exceed native.
Prospects and disconnected stores (peer-benchmark path): when Littledata’s Klaviyo connection isn’t enabled — so there’s no usable native-vs-Littledata audience comparison at all — we don’t fall back to the 30% floor on native revenue. Instead we multiply the store’s native flow revenue by the incremental-to-native flow-revenue ratio that similar, mature Littledata stores actually see (the cohort median, with a fixed fallback when the cohort is still thin). Only stores with ≥200 orders/month back this ratio — it divides two small, noisy numbers, so low-volume stores produce wild per-store ratios; the higher order floor (vs. the ≥30 used for absolute-value cohorts) trims that tail. Because both sides of that ratio are denominated in native flow revenue, the estimate is units-consistent and needs no proxy or extra conservatism trim. The 30% floor still applies only to the rarer fallback where a store has Littledata-tagged flow revenue but no native flows to anchor on.
The result is expressed as an estimated incremental dollars per month (not a guaranteed outcome—see Limitations below).
For integrations newer than about 30 days, raw audience-to-audience ratios are still ramping as rolling Klaviyo audiences fill. The prediction blends each store’s measured uplift with a peer prior (median fractional reach uplift from mature stores on the predicted tier, with a fixed fallback when the cohort is still thin), weighted by min(1, daysSinceConnect / 30). Per-stage maturity factors (from aggregated uplift research) scale early-window measurements toward longer-run behaviour. A small native-flow revenue lift assumption for rescued events (where research supports it) is added so the headline stays conservative but not blind to server-side rescue.
When building benchmark rows for peers, observed Klaviyo incremental and predicted incremental tier values omit shops whose Klaviyo connectedAt is within the last 30 days, so cohort percentiles are not skewed by immature audiences. Customer-facing copy can include prediction confidence and a short audience maturity caveat in the first month.
These cohort percentiles are computed at most once per week and cached (a single valueBenchmarksCache document); every customer/prospect estimate in that window reads the same snapshot. The cohort is a large, fat-tailed, continuously-re-stamped distribution, so recomputing on every audit otherwise made the median swing run-to-run. The first request after the snapshot ages past 7 days recomputes and re-stamps it; the admin value-benchmarks endpoint can force a refresh.
Two related numbers can appear for Google Ads, depending on what the account looks like.
Google Ads ROAS uplift (googleAdsROAS) compares the server-side Purchase - Littledata conversion action — orders Littledata uploads to Google with a matched GCLID — against the next best conversion goal already feeding Smart Bidding on the same Google Ads account, over the same spend. The dollar headline is the coverage gap (Littledata minus baseline) — revenue that next-best goal isn’t tracking — for the chosen window, scaled to a 30-day equivalent. Framed as “revenue your next best conversion goal doesn’t track” rather than “revenue LD adds”: those orders happen regardless, but without LD’s server-side upload they’re not attributed back to Google Ads, and Smart Bidding can’t bid against them.
Purchase revenue tracked (googleAdsTrackedRevenue, microsoftAdsTrackedRevenue) is the fallback on either channel when no baseline purchase goal can anchor a comparison — no goal at all, or one rejected by the baseline rules below.
These are deliberately not incrementality claims, and the naming reflects that. With no baseline there is no measured lift over one, so calling the figure “incremental” would credit Littledata for revenue no comparison established. Each tier states only what it can defend: the purchase revenue Littledata tracked on that channel’s clicks, and the spend it was tracked across — the number that stops the revenue reading as a return.
Consequently they carry no roi and are barred from every ROI path by isNonIncrementalValueTier — the same bar klaviyoLittledataDimensionRevenue sits behind. They cannot set the ROI multiple, headline a hero card, or join an incremental rollup. They surface as additional value: real, worth stating, not counted as a return. Being the only purchase signal an account can bid on is a meaningful finding on its own — it is just not the same as having proved the revenue incremental.
Why Google changed. This tier previously applied the 10% Smart Bidding lift to Littledata’s whole uploaded revenue and published the result as ROI — with no
SMART_BIDDING_SPEND_CAP, which only guarded the baseline branch. Across 79 accounts on the tier, 26 claimed a Smart Bidding contribution above half their ad spend and four above 100% of it (one at 305%): telling a merchant that bidding contributed three times their entire budget. Removing the lift removes the claim rather than capping it, which is the honest fix when there is no baseline to measure against.
The tooltips show the calculation only — revenue tracked, spend, resulting ROAS. Why no comparison exists belongs in the ad-platform audit checks, which name the goal and the rejection reason; repeating it in the tooltip made it an argument rather than a breakdown of the figure.
One exception on Google, because it was a factual error rather than commentary: the tooltip used to say “Purchase - Littledata is the only purchase conversion goal in this account” whenever no baseline was chosen. That is true for 77 of 79 accounts on the tier but false when a goal existed and was rejected — revendo.ch has sale_complete with 1,168 conversions, excluded because every one reports exactly 100.00 CHF. It now names the excluded action instead.
A purchase goal can exist and still be unusable as the other side of a ROAS comparison. src/lib/roasBaseline.ts holds the rules, shared by both ad channels:
| Reason | Meaning |
|---|---|
too_few_conversions |
Fires on a small fraction of the orders Littledata uploads — the client-side tag is blocked or half-installed, so the uplift multiple is meaningless |
no_value |
Records the conversion but no revenue, so roasWithoutLd is 0 and all of Littledata’s revenue reads as incremental |
implausible_aov |
Carries a placeholder or partial value (a fixed £1, shipping only), far off Littledata’s AOV |
hardcoded_value |
Reports the same exact value-per-conversion every day — a number typed into the ads UI, not order revenue |
Microsoft Ads enforces all four and falls back to microsoftAdsTrackedRevenue. Google Ads enforces hardcoded_value and the AOV rule; valueless actions stay eligible there, and a primaryForGoal action bypasses the AOV check, because Smart Bidding optimises against it regardless.
When multiple purchase-like conversion actions exist on the account, the system picks one baseline rather than summing them (summing would double-count the same orders across competing tags):
purchase, sale, plus localised equivalents: compra, köp, 구매, kauf, achat) or whose action type is GOOGLE_ANALYTICS_4_PURCHASE. Names suggesting non-purchase events (add_to_cart, begin_checkout, page_view, cart, store visit, add_payment, form, call, contact, lead, …) are excluded.primaryForGoal (Google’s flag for “Smart Bidding optimises against this”) → take the highest-volume primary action. (Primary is trusted regardless of AOV/category — Smart Bidding optimises against it whether we like it or not.)PURCHASE actionCategory (when present on the snapshot) and any whose AOV is outside [1/10, 10] × Littledata AOV (catches non-purchase goals whose name slipped through the include list, e.g. "HubSpot - Contact Sales" with a £1 placeholder value). From those, pick the first with conversions ≤ Littledata’s (so we don’t compare to something far larger and produce a misleadingly negative uplift).Why “primary first”: Google Smart Bidding only optimises toward conversion actions marked primary. Comparing Littledata’s ROAS against the action Smart Bidding is actually trying to maximise is the comparison that matters for spend efficiency.
If the chosen baseline is something with a known caveat — GA4 purchase, the Shopify Google Shopping App pixel (which reports product-feed values, not order values), or another UPLOAD_CLICKS server-side feed — a short baselineNote is surfaced explaining how that baseline tends to over- or under-count vs Littledata’s order-level upload. We do not silently re-order candidates around these caveats: Smart Bidding sees what it sees, and the comparison should reflect that.
We try five candidate windows — the last 7, 14, 30, 60 and 90 days — and pick the one that gives the fairest like-for-like ROAS comparison:
The chosen window’s length is the window label shown alongside the figure.
LD-active clamping: the window is also pinned to start no earlier than the first day Littledata had any activity in that account, or the Google Ads connectedAt date, whichever is later. This avoids attributing pre-install spend to Littledata. When connectedAt is unknown, we use a sustained-activity rule — an LD-active day must be followed by ≥7 more LD-active days in the next 14 — so a one-off historical upload isn’t mistaken for the start of live operation.
Inside the chosen window, on the Littledata-active span:
ldValue = sum of Purchase - Littledata conversion value (UPLOAD_CLICKS rows whose name starts with that prefix).baselineValue = sum of conversion value for the chosen baseline action.totalSpend = sum of account-level cost over the same days (the same denominator on both sides).roasWithLd = ldValue / totalSpend; roasWithoutLd = baselineValue / totalSpend.valueActual) = ldValue − baselineValue, converted to USD using the account’s reporting currency. This is what surfaces in the customer-facing “Littledata reveals $X/month of Google Ads revenue your next best conversion goal doesn’t track” headline.If the baseline conversions audit has flagged the chosen baseline as inflated — duplicate counting, currency mismatch, or an AOV skew that suggests pixel double-fires or modeled conversions — the Smart Bidding lift and ROI line are suppressed entirely. We don’t fabricate a dollar number from an untrusted gap; the baselineNote already surfaces the inflated-baseline caveat for support.
For the ROI line and the monthly headline:
coverageGap × 10%. The 10% (GOOGLE_ADS_LIFT_PERCENT) is the conservative auction-quality improvement we credit to LD’s data: Google bids ~10% more accurately on the incremental signal it didn’t see before. Applied to the gap, not to total LD revenue — the next-best goal already covers part of the auction signal, so the marginal lift is sized against what LD adds on top, not against total ad revenue.SMART_BIDDING_SPEND_CAP). No matter how large the coverage gap is, Smart Bidding can’t plausibly contribute more than half of spend in measured lift — this bounds low-coverage / low-ROAS edge cases where the raw gap × 10% would otherwise overstate the auction model’s ceiling. When the cap fires, googleAdsLiftBasisCapped: true is recorded and the modal explains.This whole section applies only when a baseline exists. When none does (the googleAdsTrackedRevenue fallback), no lift is applied and no ROI is produced: with nothing to subtract there is no coverage gap to size a lift against, so the tier reports tracked revenue as additional value instead. See the two related numbers above.
roasWithLd / roasWithoutLd > 1.5×): a baselineNote flags that the chosen baseline likely only captures a subset of purchases (e.g. one of several competing tags), and lists the other available candidates so support can investigate.window label reflects what was actually available (googleAdsSnapshotDays is exposed when the slice was shorter than 90 days).googleAdsTotalSpendAccountCurrency holds that sum); the final incremental figure is converted to USD, stored on the tier as valueUsd, and read back through tierValueUsd (see below). Spend carries its own converted copy as googleAdsTotalSpendUsd. Rates come from ExchangeRate-API (free Open Access endpoint, ~170 currencies, daily updates) — wide enough to cover every Google Ads / Klaviyo billing currency we’ve seen in production. If a currency’s rate is unavailable (provider outage, brand-new currency), the ROI line and USD monthly headline are suppressed for that tier rather than mislabeling the native amount as USD; the customer-facing message falls back to the native currency (e.g. CLP 6.7M/month rather than $6.7M/month).conversion_action resource when captured. The googleAdsGoalConfig audit compares Purchase - Littledata to the non–Littledata primary-for-goal purchase-like action; if either lookback differs by more than three days, the check moves to warning and furtherInfo explains that ROAS comparison may be skewed by different attribution surfaces. When a Google Shopping app purchase action exists, its CT/VT is always appended to furtherInfo for harmonisation context (the engaged-view window is not exposed on ConversionAction in the Google Ads API — only the UI). The googleAdsROAS tier can carry googleAdsLd* / googleAdsBaseline* window fields, and the admin metric tooltip adds a one-line caveat when the mismatch threshold applies.| Idea | What you are seeing |
|---|---|
| Meta additional conversions (ACR) | Percentage uplift: Conversions API vs pixel-only, from Meta’s reporting—not a dollar line by itself in the same way. |
| Google Ads ROAS / tracked revenue | Compares server-side Littledata purchase reporting to the best browser-side baseline purchase action over a dynamic 7–90 day window. See Google Ads section above for the full picking logic. |
| Meta Ads pixel ROAS uplift | Compares ROAS on the primary pixel identified as Littledata’s against other pixels on the same ad account. |
| Additional revenue tracked in GA4 | A rough estimate from recent GA4 purchase revenue (when connected), not a full incremental attribution model. |
| Klaviyo and paid media incremental value | When more than one of the dollar tiers above is positive, a combined monthly line may appear that adds Klaviyo’s best incremental story plus Google plus Meta Ads uplift pieces—so you can see total “incremental” across channels. |
When plan cost is known (from billing data we sync, including order limits and overage where applicable):
If plan cost is missing, ROI lines may be hidden or incomplete.
A store that grows past its order limit keeps paying per-order overage on the smaller plan long after another tier would work out cheaper — e.g. legacy Pro at $449 + 13,905 overage orders = $1,840/month, where the same volume on Plus is $1,257. The ROI reads low purely because the denominator is wrong.
Each audit prices every active tier at the store’s own order volume — base price plus overage on the orders above that tier’s included allowance, never the headline base price alone — and, when one is genuinely cheaper, adds a line under the ROI multiple:
On the Plus plan this would be 1.5× ROI
The plan’s price and the saving behind that multiple sit in the ROI tooltip, so the banner line stays one line wide:
The Plus plan would cost $1,257/month, $582/month less than the current plan
(Plus is $990 for the first 10,000 orders then $0.03/order, so 18,905 orders = 990 + 8,905 × 0.03 = $1,257.)
current cost ÷ alternative cost, so it always agrees with the headline.A brand often runs several Shopify stores off a single ad account — regional storefronts (a .co.uk and a .com), or a -stage copy of a production store. Across the estate this is common, not exceptional: 18 groups covering 47 shops share a Google Ads customer ID, and two 5-store groups share a Microsoft Ads account.
When such stores are linked as parent/subsidiary, the parent’s rollup must not add the subsidiary’s ad-channel value on top of its own — both describe the same account’s revenue, so the total would double-count.
sharedAdChannels() compares the two customers’ ad-account identifiers per channel (googleAds.customerId, meta.adAccountId, microsoftAds.accountId) and returns the channels where they match. Any tier whose destinationForTierName() resolves to a matching channel is dropped from the rollup.
destinationForTierName — the same registry the hero cards use — a newly added ad tier is covered automatically. Adding a channel means one entry in AD_ACCOUNT_ID_BY_DESTINATION.Note this dedupe applies to the parent/subsidiary rollup. Two unlinked stores sharing an account each still report that account’s value as their own, which is correct per store — but means any aggregate across customers must dedupe by ad-account id before summing.
value in the ad account’s own reporting currency and carry the conversion alongside it as valueUsd (set at compute time — display code has no rates to convert with). All customer-facing formatting reads it through tierValueUsd / tierMonthlyValueUsd in valueHelpers.ts, so a GBP Google Ads account and a USD Klaviyo account are directly comparable and both divide cleanly by the USD plan cost. When a currency has no rate, that tier falls back to the native amount with its own code (CLP 6,700,000, never $6,700,000) and states no ROI. Per-tier tooltips are the exception: they show the account-currency working (LD value, baseline value, spend) and name the account currency, because that is the arithmetic Google/Meta/Microsoft themselves report.Technical runbooks and cron debugging for staff live under engineering/ in the repo, not in this folder.