Documentation · Revenue tracking
Revenue tracking on Admaxxer
Admaxxer ingests revenue from three independent sources and reconciles them into a single, dedup-aware ledger. A real-time first-party pixel writes visitor_payments with first-touch UTMs and 12 click-IDs at the per-order grain. Provider-signed server-side webhooks (Stripe, Paddle, Polar, Lemon Squeezy, Dodo) write revenue_events with sub-500ms freshness and gateway-fee awareness. Shopify admin webhooks plus OAuth-pulled GraphQL Admin API back-fills write orders as the authoritative ledger that survives ad-blockers, ITP, and CSP errors. The summary_kpis Tinybird pipe collapses all three using source-priority dedup — so MER, AOV, and cohort LTV are computed from the union, not whichever source happened to fire first. Beats Triple Whale and Datafast on attribution depth: every comparison cell on this page cites the underlying tool's own docs.
Three revenue datasources
Each source carries a different signal and survives a different failure mode. The pixel survives platform-attribution drift (iOS ATT, ad-blockers); the orders datasource survives the pixel itself; signed webhooks survive both for non-Shopify storefronts.
| Property | visitor_payments |
revenue_events |
orders |
|---|---|---|---|
| Who fires the row | Shopify Custom Pixel checkout_completed (browser) → POST /api/event. Server-side mirror also writes from admin-webhook orders/create + Stripe-side ingest (provider differentiates). | Provider-signed webhooks: Stripe-Signature, Paddle-Signature, Polar-Webhook-Signature, X-Signature (Lemon), X-Dodo-Signature. | Shopify admin webhook (orders/create + orders/paid + refunds/create) AND OAuth-pulled Shopify GraphQL Admin API for backfill. |
| Dedup key | (provider, external_payment_id) — ReplacingMergeTree collapses dupes on ts. Refund rows carry external_payment_id='refund:<id>' so they DON'T replace the parent. | (provider, event_id) — every Stripe / Paddle / Polar / Lemon / Dodo event ID is idempotent at the provider, so retries never double-count. | (shop_domain, order_id) on ReplacingMergeTree(ts) — webhook updates and OAuth backfill rows can both land; last write wins on ts. |
| Freshness | Sub-second for the pixel path. Server-side mirror lands within 30 seconds of the admin webhook. | Sub-500ms once the provider fires. Auto-registered webhook on Stripe means no manual setup. | Real-time on admin webhooks (~1-2s). OAuth backfill runs every 6 hours and on Connect — fills install-day gaps. |
| Captured fields | 46 columns: revenue + cart breakdown (subtotal/tax/discount/shipping), customer history, statuses, gateway, landing/referrer, AND first-touch UTMs + 12 click-IDs (gclid, fbclid, ttclid, msclkid, twclid, li_fat_id, epik, rdt_cid, sccid, yclid, klaviyo_id, amzn_cid). The only datasource that carries first-touch attribution at the per-order grain. | 15 columns: amount, currency, fee, recurrence flag, visitor stitch, event type, raw payload. No cart breakdown (Stripe doesn't carry shipping / discount on a charge object directly). | 19 columns: per-line totals, customer-history flags, financial + fulfillment status, refund delta, source_name, tags. Authoritative for the Shopify ledger. |
What gets captured
visitor_payments (46 columns)
The pixel-side ledger. Schema-strict ingest with DEFAULT '' on every String column per GL#357 — a partially-mapped row never silently drops to quarantine. The mapper still must populate every field; the defaults are belt-and-braces.
workspace_idString- Owning workspace UUID — every read filters on this.
website_idLowCardinality(String)- Pixel-issued site identifier (admx_…).
visitor_idNullable(String)- Stable per-browser ID written by the pixel; nullable for server-side rows.
tsDateTime64(3, 'UTC')- Event timestamp; ReplacingMergeTree version key.
providerLowCardinality(String)- shopify_pixel · stripe · paddle · polar · lemonsqueezy · dodo · shopify_webhook.
external_payment_idString- Provider-side payment ID. Refund rows use 'refund:<id>' so they never replace the original revenue row.
amount_centsInt64- Order total in minor currency units. Negative on refund rows.
currencyLowCardinality(String)- ISO 4217 (USD, EUR, GBP, …).
email_hashString DEFAULT ''- SHA-256(email) for CAPI match-rate joins. Never raw email.
attributed_utm_sourceLowCardinality(String) DEFAULT ''- Last-touch utm_source at payment time.
attributed_utm_campaignString DEFAULT ''- Last-touch utm_campaign at payment time.
subtotal_centsInt64 DEFAULT 0- Items only — excludes tax, shipping, discounts.
total_tax_centsInt64 DEFAULT 0- Tax collected at checkout.
total_discount_centsInt64 DEFAULT 0- Discount codes + automatic discounts applied.
total_shipping_centsInt64 DEFAULT 0- Shipping line.
refund_centsInt64 DEFAULT 0- Negative on refund rows; carries the partial-refund delta linked back to the original via external_payment_id='refund:<id>'.
units_soldInt32 DEFAULT 0- Sum of line-item quantities.
line_item_countInt32 DEFAULT 0- Distinct line items in the cart.
is_new_customerUInt8 DEFAULT 0- 1 if this customer's first paid order; 0 if returning. Drives NC-ROAS / NCPA.
customer_order_countInt32 DEFAULT 0- Lifetime order count up to and including this row.
customer_total_spent_centsInt64 DEFAULT 0- Lifetime revenue from this customer up to this row — feeds cohort LTV.
financial_statusLowCardinality(String) DEFAULT ''- paid · pending · refunded · partially_refunded · voided.
fulfillment_statusLowCardinality(String) DEFAULT ''- fulfilled · partial · unfulfilled · null.
gatewayLowCardinality(String) DEFAULT ''- shopify_payments · stripe · paypal · klarna · etc. — payment method.
landing_siteString DEFAULT ''- First page of the session (full URL, query string included).
referring_siteString DEFAULT ''- document.referrer at session start; smart-classified (l.facebook.com → facebook).
source_nameLowCardinality(String) DEFAULT ''- web · pos · mobile_app · api · draft_order — Shopify's order origin tag.
gross_sales_centsInt64 DEFAULT 0- Subtotal + tax + shipping (Shopify-equivalent definition); excludes discounts.
gclidString DEFAULT ''- Google click identifier captured at payment time. Survives 90-day localStorage TTL (client-pixel/src/pixel.ts:1036-1037).
fbclidString DEFAULT ''- Facebook / Instagram click identifier; captured at payment time within the 90-day window.
ttclidString DEFAULT ''- TikTok click identifier.
msclkidString DEFAULT ''- Microsoft / Bing Ads click identifier.
twclidString DEFAULT ''- X / Twitter Ads click identifier.
li_fat_idString DEFAULT ''- LinkedIn first-party ad identifier.
epikString DEFAULT ''- Pinterest click identifier.
rdt_cidString DEFAULT ''- Reddit click identifier.
sccidString DEFAULT ''- Snapchat click identifier.
yclidString DEFAULT ''- Yandex Metrica click identifier.
klaviyo_idString DEFAULT ''- Klaviyo profile identifier (_kx) — joins email LTV onto the same row as paid acquisition.
amzn_cidString DEFAULT ''- Amazon Ads click identifier (DSP / Sponsored Products).
first_utm_sourceLowCardinality(String) DEFAULT ''- First-touch utm_source persisted in localStorage for 365 days.
first_utm_mediumLowCardinality(String) DEFAULT ''- First-touch utm_medium.
first_utm_campaignString DEFAULT ''- First-touch utm_campaign — survives every cart abandonment within the 365-day window.
first_utm_termString DEFAULT ''- First-touch utm_term (paid-search keyword).
first_referrerString DEFAULT ''- Smart-classified first-touch referrer (google · facebook · direct · tiktok · …).
first_landing_pathString DEFAULT ''- First-touch landing path (no host, no query) — primary attribution surface for content marketing.
revenue_events (15 columns)
Provider-signed server-side webhook ledger. Stripe / Paddle / Polar / Lemon / Dodo all write here. Idempotent on (provider, event_id).
workspace_idString- Owning workspace UUID.
website_idLowCardinality(String)- Pixel-issued site identifier.
tsDateTime64(3, 'UTC')- Provider-emitted event timestamp.
providerLowCardinality(String)- stripe · paddle · polar · lemonsqueezy · dodo.
event_idString- Provider event UUID — idempotency key, dedup on (provider, event_id).
event_typeLowCardinality(String)- charge.succeeded · payment_intent.succeeded · invoice.paid · charge.refunded · checkout.session.completed · subscription.created · etc.
external_payment_idString- Provider's payment / charge / invoice ID. Refund deltas keyed back to the original here.
amount_centsInt64- Net amount on this event (negative on refund / chargeback events).
currencyLowCardinality(String)- ISO 4217.
visitor_idNullable(String)- Pixel visitor stitched via metadata.admaxxer_visitor_id (Stripe / Polar / Dodo) · custom_data.admx_visitor_id (Paddle) · meta.custom_data.admx_visitor_id (Lemon).
email_hashString DEFAULT ''- SHA-256(customer email) for CAPI joins.
is_recurringUInt8 DEFAULT 0- 1 for subscription renewals so MRR / one-time can be split downstream.
fee_centsInt64 DEFAULT 0- Stripe / Paddle gateway fee — net-of-fees MER subtracts this from the numerator.
raw_payloadString DEFAULT ''- JSON-encoded provider payload (truncated; verbatim for audit).
orders (19 columns)
The authoritative Shopify ledger. Streamed via admin webhooks; backfilled via OAuth-pulled GraphQL Admin API. Survives every ad-blocker / ITP / CSP failure that would silently drop a pixel row.
workspace_idString- Owning workspace UUID.
website_idLowCardinality(String)- Pixel-issued site identifier.
tsDateTime64(3, 'UTC')- Order creation timestamp from Shopify.
shop_domainLowCardinality(String)- <store>.myshopify.com — multi-store joins use this.
order_idString- Shopify GID — dedup key on (shop_domain, order_id).
order_numberInt32 DEFAULT 0- Human-readable Shopify order number (e.g., 1042).
currencyLowCardinality(String)- ISO 4217.
subtotal_price_centsInt64 DEFAULT 0- Items only.
total_tax_centsInt64 DEFAULT 0- Tax collected.
total_discounts_centsInt64 DEFAULT 0- Discount codes + automatic discounts.
total_shipping_centsInt64 DEFAULT 0- Shipping line.
total_price_centsInt64 DEFAULT 0- Gross-of-discounts gross-of-tax order total.
total_refunded_centsInt64 DEFAULT 0- Cumulative refunded to date — partial-refund-aware.
financial_statusLowCardinality(String) DEFAULT ''- paid · partially_paid · refunded · partially_refunded · voided · pending.
fulfillment_statusLowCardinality(String) DEFAULT ''- fulfilled · partial · unfulfilled · null.
is_new_customerUInt8 DEFAULT 0- Shopify-native flag (customer.orders_count = 1).
customer_idString DEFAULT ''- Shopify customer GID.
email_hashString DEFAULT ''- SHA-256(customer.email).
source_nameLowCardinality(String) DEFAULT ''- web · pos · mobile_app · api · draft_order.
tagsString DEFAULT ''- Comma-separated Shopify tags — useful for cohort filtering.
Example payloads
Shopify Custom Pixel checkout_completed
Posted by the canonical v3 Custom Pixel snippet to /api/event. Carries the full 46-column visitor_payments row, including 12 click-IDs and the first-touch UTM five-tuple.
POST /api/event HTTP/1.1
Host: admaxxer.com
Content-Type: application/json
{
"event": "checkout_completed",
"website_id": "admx_a1b2c3d4e5",
"visitor_id": "v_8f3a2c91d4e0b7f6",
"ts": "2026-05-05T14:32:11.482Z",
"provider": "shopify_pixel",
"external_payment_id": "5731234567890",
"amount_cents": 12950,
"currency": "USD",
"email_hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"subtotal_cents": 11000,
"total_tax_cents": 950,
"total_discount_cents": 500,
"total_shipping_cents": 1000,
"units_sold": 2,
"line_item_count": 2,
"is_new_customer": 1,
"customer_order_count": 1,
"customer_total_spent_cents": 12950,
"financial_status": "paid",
"fulfillment_status": "unfulfilled",
"gateway": "shopify_payments",
"source_name": "web",
"gross_sales_cents": 12950,
"attributed_utm_source": "facebook",
"attributed_utm_campaign": "spring-launch-2026",
"first_utm_source": "google",
"first_utm_medium": "cpc",
"first_utm_campaign": "brand-search-2026-q1",
"first_referrer": "google",
"first_landing_path": "/best-running-shoes",
"gclid": "EAIaIQobChMI8eqU7vqB_QIVC_7jBx2Lrw1iEAAYAyAAEgI3HfD_BwE",
"fbclid": "IwAR2k4xZ9...",
"ttclid": "",
"msclkid": "",
"klaviyo_id": "01HXY3K6Q9R0M2"
}
Stripe payment_intent.succeeded
Server-to-server, signed with HMAC-SHA256 via the Stripe-Signature header. Visitor stitch via metadata.admaxxer_visitor_id.
POST /api/pixel/webhooks/stripe/admx_a1b2c3d4e5 HTTP/1.1
Host: admaxxer.com
Stripe-Signature: t=1714915931,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a1a8b8cd0e0fa1631...
{
"id": "evt_1Pa9X2L1abCdEfGhIjKlMnOp",
"object": "event",
"type": "payment_intent.succeeded",
"created": 1714915931,
"data": {
"object": {
"id": "pi_3Pa9X2L1abCdEfGh0001Aaaa",
"amount": 4900,
"amount_received": 4900,
"currency": "usd",
"status": "succeeded",
"customer": "cus_QabCdEfGhIjKlMn",
"metadata": {
"admaxxer_visitor_id": "v_8f3a2c91d4e0b7f6"
},
"charges": {
"data": [{
"id": "ch_3Pa9X2L1abCdEfGh0001Aaaa",
"balance_transaction": "txn_3Pa9X2L1abCdEfGh0001Bbbb",
"fee": 172
}]
}
}
}
}
# → ingested as one revenue_events row:
# provider = 'stripe'
# event_id = 'evt_1Pa9X2L1abCdEfGhIjKlMnOp'
# event_type = 'payment_intent.succeeded'
# external_payment_id = 'pi_3Pa9X2L1abCdEfGh0001Aaaa'
# amount_cents = 4900
# currency = 'usd'
# visitor_id = 'v_8f3a2c91d4e0b7f6' ← stitched
# fee_cents = 172
Attribution model
Admaxxer's attribution model is built for modern DTC: ad-blockers + iOS ATT have made third-party cookies unreliable, so every signal lives in first-party storage on the customer's own browser — localStorage on the merchant's domain, never a tracking cookie.
Click-IDs (90-day localStorage TTL)
Every supported click-ID parameter (gclid, fbclid, ttclid, msclkid, twclid, li_fat_id, epik, rdt_cid, sccid, yclid, klaviyo_id, amzn_cid) is captured on first arrival and persisted in localStorage for 90 days — the constant lives at client-pixel/src/pixel.ts:1036-1037. When checkout_completed fires, the pixel reads localStorage and stamps every click-ID onto the visitor_payments row.
First-touch UTMs (365-day persistence)
The first_utm_source / first_utm_medium / first_utm_campaign / first_utm_term / first_referrer / first_landing_path tuple is written exactly once per browser — on the very first session where any of those fields is set — and persisted for 365 days. A customer who clicks a Meta ad in March and buys in May still attributes to the original campaign without any cookie.
Smart referrer classification
Raw document.referrer values are noisy: l.facebook.com, m.facebook.com, lm.facebook.com, and www.facebook.com are all the same source. The pixel runs a domain-classification table at capture time so l.facebook.com → facebook, t.co → twitter, out.reddit.com → reddit, android-app://com.google.android.gm/ → gmail. The classified value lands in first_referrer; the raw value is preserved in referring_site for forensics.
Last-touch vs first-touch
Both are emitted on every visitor_payments row. attributed_utm_* carries the LAST-touch UTM (whatever was on the URL when checkout fired); first_utm_* carries the FIRST-touch UTM (persisted from the first session). The summary_kpis pipe lets attribution model selection (Last Click / First Click / Linear / Time-Decay / Position-Based) pick which side feeds the dashboard.
Refunds and dedup
Pixel: negative-row pattern
The pixel emits a NEGATIVE row with external_payment_id='refund:<original_id>' and a negative refund_cents. Because the dedup key includes external_payment_id, the refund row does NOT replace the parent revenue row — both rows coexist. summary_kpis sums both, so net revenue automatically deducts the refund amount and partial refunds are computed correctly.
Webhooks: ReplacingMergeTree + ts versioning
Both visitor_payments and revenue_events use ClickHouse's ReplacingMergeTree engine with ts as the version key. Stripe's signed retries (up to 3 days, exponential backoff) are safe — the same (provider, event_id) deduplicates at the engine level, last write wins on ts. orders uses the same engine keyed on (shop_domain, order_id), so admin-webhook updates and OAuth-backfill rows can both land without double-counting.
Pixel-vs-orders dedup
For Shopify stores running BOTH the pixel and the OAuth orders connector, every order is present in both visitor_payments AND orders. The summary_kpis pipe applies an untracked_pixel_revenue subtractor (see GL#257) — the orders side wins for revenue, the pixel side still contributes attribution metadata (UTMs, click-IDs). Net result: dollars counted exactly once, attribution preserved at full fidelity.
Metric formulas (canonical Tinybird SOT)
Every formula on this page cites a specific line in tinybird/pipes/summary_kpis.pipe — the source of truth for the Summary dashboard. The pipe is in the public Admaxxer repo; pinning to line numbers lets an AI search engine or auditor verify the formula directly from the source.
Gross revenue
gross_revenue = SUM(amount_cents) / 100 -- across visitor_payments + revenue_events + orders, deduped
Tinybird SOT: tinybird/pipes/summary_kpis.pipe → assemble_current node, output column total_sales (line 972). Source-additive across visitor_payments + sp_gross_revenue.
Net revenue (returns + tax-aware)
net_revenue = gross_revenue - returns - taxes
Tinybird SOT: tinybird/pipes/summary_kpis.pipe → assemble_current node, returns (line 978) + taxes (line 980). Used as the cash_turnover numerator.
AOV (Average Order Value)
aov = gross_sales / orders
Tinybird SOT: tinybird/pipes/summary_kpis.pipe line 1001 — same denominator as MER's NC-side counterparts.
True AOV (returns + tax-stripped)
true_aov = (gross_sales - returns - taxes) / orders
Tinybird SOT: tinybird/pipes/summary_kpis.pipe line 996 — matches Triple Whale's True AOV ("minus shipping and taxes" definition for pixel-aware workspaces).
MER (Marketing Efficiency Ratio)
MER = total_revenue / total_ad_spend -- HIGHER is better; industry-standard direction.
Tinybird SOT: tinybird/pipes/summary_kpis.pipe line 942 — assemble_current.mer. Source-additive numerator (greatest of per-event vs polled). NOTE: Triple Whale prints MER with the same formula, but their dashboard tile semantics treat "MER going UP" as "efficiency improving" — same direction as Admaxxer. Never invert.
Blended ROAS
blended_roas = total_revenue / total_ad_spend -- numerically identical to MER in Admaxxer.
Tinybird SOT: tinybird/pipes/summary_kpis.pipe line 943 — emitted as a separate column so operators who learned the term "blended ROAS" first see it under their preferred name.
NC-ROAS (New Customer ROAS)
nc_roas = new_customer_revenue / total_ad_spend
Tinybird SOT: tinybird/pipes/summary_kpis.pipe line 951 — assemble_current.nc_roas. New-customer detection uses visitor_payments.is_new_customer for pixel rows + Shopify's native is_new_customer for webhook rows.
NCPA / CAC (New Customer Acquisition Cost)
ncpa = total_ad_spend / new_customer_orders
Tinybird SOT: tinybird/pipes/summary_kpis.pipe line 959 — assemble_current.ncpa.
Cohort LTV (7 / 30 / 90-day)
ltv_window_days = SUM(amount_cents WHERE customer's first_order BETWEEN window_start AND window_end AND ts <= first_order + INTERVAL window_days) / cohort_customers
Tinybird SOT: server/routes/adsLtv.ts → tinybird/pipes/cohort_ltv.pipe — partial-refund-aware (subtracts refund_cents from the cohort numerator).
Unique customers
unique_customers = COUNT(DISTINCT email_hash) -- across visitor_payments + orders, falling back to customer_id when email_hash is empty
Tinybird SOT: tinybird/pipes/summary_kpis.pipe → assemble_current.customers. email_hash is SHA-256 of the lowercased email so cross-source joins work without leaking PII.
CAPI Match Rate
capi_match_rate = matched_server_events / pixel_conversions -- Hyros-style; closer to 1.0 = better attribution coverage
Tinybird SOT: server/routes/adsCapiMatchRate.ts. Joins meta_capi_events / google_capi_events against visitor_payments on email_hash AND visitor_id.
Comparison vs Triple Whale + Datafast
Attribution depth, not feature surface. We're comparing how each tool ingests, types, and dedupes revenue data — the foundation every dashboard tile reads from.
| Capability | Admaxxer | Triple Whale | Datafast |
|---|---|---|---|
| Revenue sources covered | First-party pixel + 5 server-side webhook providers (Stripe, Paddle, Polar, Lemon, Dodo) + Shopify OAuth orders | Pixel + Shopify-only direct integration | Pixel + Stripe / Polar / Lemon / Shopify |
| Subtotal / tax / shipping / discount captured per order | Yes — 4 dedicated columns on every visitor_payments row | Yes — Shopify-side; not exposed at the row level | Echoed only — no per-component breakdown |
| Click-IDs captured at payment time | 12 (gclid, fbclid, ttclid, msclkid, twclid, li_fat_id, epik, rdt_cid, sccid, yclid, klaviyo_id, amzn_cid) | 0 — click-IDs live in cohort joins, not on the row | 0 — not tracked |
| First-touch UTM on the payment row | Yes — first_utm_source / medium / campaign / term / referrer / landing_path persisted 365 days | Via Customer Cohorts (joined, not row-level) | No — last-touch UTM only |
| Partial refunds | Yes — refund row with negative refund_cents, parent row preserved | Yes — refund_amount field on the order record | Boolean refunded flag only (no partial delta) |
| Multi-currency | Yes — ISO 4217 column on every row, ECB reference rates with per-row historical accuracy | Yes — display-currency conversion | Echoed only — no rate conversion at the dashboard |
| MER direction | Industry-standard (higher is better) | Industry-standard (higher is better) | N/A — Datafast doesn't ingest ad spend |
| Schema-strict ingest | Yes — every column has a typed ClickHouse schema with DEFAULT '' fallback (GL#357) | No — JSON-blob ingest with downstream cleanup | No — JSON-blob ingest |
| Real-time freshness | Sub-second (pixel) / sub-500ms (signed webhooks) | Minutes (cohort recompute lag) | Minutes (session-aggregation lag) |
External cite hrefs above link directly to each tool's own documentation. Triple Whale's metrics library: kb.triplewhale.com; Datafast's revenue attribution guide: datafa.st/docs/revenue-attribution-guide. We never paraphrase — if a Triple Whale or Datafast claim is on this page, the link goes to their original.
Frequently asked
- Why three datasources instead of one?
- Each source carries different signal. visitor_payments has first-touch UTM + 12 click-IDs at the per-order grain (the only path that does). orders is the authoritative Shopify ledger and survives ad-blockers / ITP / CSP errors that would silently drop pixel rows. revenue_events covers non-Shopify storefronts (Stripe, Paddle, Polar, Lemon, Dodo) where the pixel can't see the checkout. The summary_kpis pipe collapses all three with a source-priority dedup so MER, AOV, and LTV are computed from the union — not whichever source happened to fire first.
- How does the pixel attribute revenue to an ad click that happened 60 days ago?
- Click-IDs (gclid, fbclid, ttclid, msclkid, …) are persisted in localStorage with a 90-day TTL — the constant lives at client-pixel/src/pixel.ts lines 1036-1037. First-touch UTMs persist for 365 days on the same key. When checkout_completed fires, the pixel reads localStorage and stamps both the click-ID and the first-touch UTM onto the visitor_payments row. This is why a customer who clicked a Meta ad in March and bought in May still attributes to the original campaign — without any cookie, just first-party storage on the customer's browser.
- What happens on a partial refund?
- The pixel emits a NEGATIVE row with external_payment_id='refund:<original_id>' and a negative refund_cents. Because the dedup key includes external_payment_id, the refund row does NOT replace the parent revenue row — both rows coexist. summary_kpis sums both, so net revenue automatically deducts the refund amount. Server-side webhook ingest (revenue_events) does the same thing via charge.refunded events; the orders datasource carries a cumulative total_refunded_cents that updates via Shopify's refunds/create webhook.
- Is there double-counting between visitor_payments and orders for Shopify stores?
- No. summary_kpis applies an untracked_pixel_revenue subtractor (GL#257) — for any Shopify order present in BOTH visitor_payments (via the pixel) AND orders (via admin webhook), the orders side wins and the pixel-side amount is subtracted. visitor_payments still contributes attribution metadata (UTMs, click-IDs) on those orders, but the dollars are counted exactly once.
- How fresh is each source?
- Pixel: sub-second (browser → /api/event → Tinybird async ingest, typically under 2 seconds end-to-end). Direct webhooks: sub-500ms (Stripe, Paddle, Polar, Lemon, Dodo all sign and POST in under half a second). Shopify admin webhooks: 1-2 seconds. OAuth-pulled orders backfill: every 6 hours, plus an immediate +24h backfill on Connect that fills the install-day gap.
- What's the difference between MER and Blended ROAS?
- Numerically identical — same formula, same Tinybird column. We emit both because operators learn the metric under either name. Triple Whale draws the same equivalence in their docs ("MER… is very similar to blended ROAS"). Never invert — both go UP when efficiency improves. (Some ad-tech vendors flip the ratio to spend ÷ revenue and call it "COGS-style" — Admaxxer does NOT.)
- How does Admaxxer beat Triple Whale on attribution depth?
- Three things. (1) Click-IDs at PAYMENT time: Admaxxer captures 12 click-IDs (gclid, fbclid, ttclid, msclkid, twclid, li_fat_id, epik, rdt_cid, sccid, yclid, klaviyo_id, amzn_cid) on the visitor_payments row at checkout. Triple Whale's pixel doesn't expose click-IDs at the per-order grain — they live in cohort joins, not on the row. (2) First-touch UTMs on the payment row: same row, 365-day persistence. Triple Whale carries first-touch in the Customer Cohorts surface but not on the payment record itself, so per-order MMM is harder. (3) Schema-strict ingest: every visitor_payments column has an explicit ClickHouse type and a DEFAULT '' fallback (GL#357), so a partially-mapped row never silently drops to quarantine.
- How does Admaxxer beat Datafast on attribution depth?
- Datafast is positioned as a simplified web-analytics + revenue-attribution tool, not a DTC ad-ops platform. Datafast tracks visitor revenue per UTM source (good) but does NOT ingest ad spend (so no MER, no Blended ROAS, no NC-ROAS, no NCPA) and does NOT capture click-IDs at payment time. Datafast's pixel is sessions-and-goals-first; revenue is a top-line attribution view rather than a 46-column ledger. Operators graduating from Datafast to Admaxxer typically encounter MER, click-ID-aware partial-refund-aware cohort LTV, and CAPI Match Rate for the first time.
- Can I connect a non-Shopify storefront?
- Yes. Stripe, Paddle, Polar, Lemon Squeezy, and Dodo all stream into revenue_events via signed webhooks. Each connector uses its own visitor-stitch key: metadata.admaxxer_visitor_id (Stripe / Polar / Dodo), custom_data.admx_visitor_id (Paddle), or meta.custom_data.admx_visitor_id (Lemon). The Stripe walkthrough at /documentation/revenue/stripe explains the auto-registered webhook and the 13-scope restricted key.
- Where do I see the canonical formula for a metric?
- Every Tinybird pipe is open-source in the Admaxxer repo (tinybird/pipes/*). summary_kpis.pipe is the source of truth for the Summary dashboard's 75 KPI tiles — every formula above cites a specific line number. The /documentation/metric-glossary page lists all 30+ metrics with their pipe paths and 3-way comparisons against Triple Whale + Datafast.
Next steps
- Connect Stripe — the auto-registered webhook + 13-scope restricted key walkthrough.
- Shopify Custom Pixel architecture — the Web Worker sandbox saga (GL#305 → GL#306 → GL#307).
- Revenue data flow — how the four ingestion paths collapse inside
summary_kpis(GL#313 follow-up). - Metric glossary — canonical formula + Tinybird pipe path for every KPI Admaxxer surfaces, with 3-way comparison vs Triple Whale + Datafast.
- Open the analytics dashboard — see the metrics on this page rendered live.