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 13 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 the revenue rollup analytics pipeline 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) — deduplication 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), deduplicated on 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 + 13 click-IDs (gclid, gbraid, wbraid, fbclid, ttclid, msclkid, scid, rdt_cid, epik, li_fat_id, _kx, ko_click_id, twclid). 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; the deduplication version key (last write wins).
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).
gbraidString DEFAULT ''- Google iOS app-to-web click identifier (privacy-safe gclid replacement).
wbraidString DEFAULT ''- Google web-to-app click identifier.
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.
scidString DEFAULT ''- Snapchat click identifier.
rdt_cidString DEFAULT ''- Reddit click identifier.
epikString DEFAULT ''- Pinterest click identifier.
li_fat_idString DEFAULT ''- LinkedIn first-party ad identifier.
kxString DEFAULT ''- Klaviyo profile identifier (_kx URL param) — joins email LTV onto the same row as paid acquisition.
ko_click_idString DEFAULT ''- Klaviyo click identifier.
twclidString DEFAULT ''- X / Twitter Ads click identifier.
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 Custom Pixel snippet to /api/event. Carries the full 46-column visitor_payments row, including 13 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": "",
"kx": "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, gbraid, wbraid, fbclid, ttclid, msclkid, scid, rdt_cid, epik, li_fat_id, _kx, ko_click_id, twclid) is captured on first arrival and persisted in localStorage for 90 days — the constant lives at client-pixel/src/pixel.ts. 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 revenue rollup 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. The revenue rollup sums both, so net revenue automatically deducts the refund amount and partial refunds are computed correctly.
Webhooks: ts-versioned deduplication
Both the pixel-payment and webhook-revenue streams deduplicate on 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 storage level, last write wins on ts. The Shopify-orders stream deduplicates the same way 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 the pixel-payment and Shopify-order streams. The revenue rollup 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 our analytics warehouse SOT)
Every formula on this page cites a specific line in Computed in the revenue rollup. — 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
our analytics warehouse SOT: Computed in the revenue rollup. Source-additive across the pixel-payment, webhook-revenue, and Shopify-order sources.
Net revenue (returns + tax-aware)
net_revenue = gross_revenue - returns - taxes
our analytics warehouse SOT: Computed in the revenue rollup from gross revenue minus returns and taxes. Used as the cash-turnover numerator.
AOV (Average Order Value)
aov = gross_sales / orders
our analytics warehouse SOT: Computed in the revenue rollup — same denominator as MER's NC-side counterparts.
True AOV (returns + tax-stripped)
true_aov = (gross_sales - returns - taxes) / orders
our analytics warehouse SOT: Computed in the revenue rollup — 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.
our analytics warehouse SOT: Computed in the revenue rollup with a 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.
our analytics warehouse SOT: Computed in the revenue rollup — 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
our analytics warehouse SOT: Computed in the revenue rollup. New-customer detection uses the pixel's new-customer flag for pixel rows + Shopify's native new-customer flag for webhook rows.
NCPA / CAC (New Customer Acquisition Cost)
ncpa = total_ad_spend / new_customer_orders
our analytics warehouse SOT: Computed in the revenue rollup.
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
our analytics warehouse SOT: server/routes/adsLtv.ts → an internal report — 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
our analytics warehouse SOT: Computed in the revenue rollup. 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
our analytics warehouse 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 | 13 (gclid, gbraid, wbraid, fbclid, ttclid, msclkid, scid, rdt_cid, epik, li_fat_id, _kx, ko_click_id, twclid) | 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 our analytics warehouse 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 + 13 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 revenue rollup 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, gbraid, wbraid, fbclid, ttclid, msclkid, …) are persisted in localStorage with a 90-day TTL — the constant lives at client-pixel/src/pixel.ts. 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. the revenue rollup 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. the revenue rollup 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 → our analytics warehouse 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 our analytics warehouse 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 13 click-IDs (gclid, gbraid, wbraid, fbclid, ttclid, msclkid, scid, rdt_cid, epik, li_fat_id, _kx, ko_click_id, twclid) 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 our analytics warehouse 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 analytics pipeline is open-source in the Admaxxer repo (our analytics pipelines). the revenue rollup 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
the revenue rollup(GL#313 follow-up). - Metric glossary — canonical formula + analytics pipeline 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.