How revenue lands in Admaxxer — 4 ingestion paths, one source-additive dashboard
Admaxxer pulls revenue from four independent ingestion paths into Tinybird, then folds them into one source-additive dashboard. Some paths are real-time and per-order detailed; one is a daily total backfill. Knowing which path is feeding which tile is the difference between “the dashboard is broken” and “my Custom Pixel hasn’t seen its first checkout yet” — both very different problems.
Why we ingest revenue four different ways
Most attribution tools standardize on one revenue source — usually the platform’s own webhook stream — and inherit that stream’s blind spots. Admaxxer takes a different position: every connection enhances the dashboard independently, and any one path going dark must not zero out the tiles that other paths can fill. That’s the rule we documented as GL#257 (“source-additive dashboards”), and it’s why a Shopify-connected workspace has FOUR places revenue can land:
- Custom Pixel — first-party events fired from the storefront and checkout sandbox.
- Admin webhooks — Shopify’s server-side push for
orders/create,orders/paid, andrefunds/create. - Daily Admin-API poll — a worker that fetches Shopify’s authoritative daily totals once per day at 02:00 UTC.
- Direct revenue webhooks — Stripe / Paddle / Polar / Lemon Squeezy / Dodo Payments push, gated behind a future opt-in.
Each path fills a gap the other three can’t. The Custom Pixel is the only path that carries visitor-session attribution (the click that drove the purchase). Admin webhooks are the only path with refund deltas at the moment a refund is issued. The daily poll is the only path that still has data when the other two are empty — on install day, before the first checkout, before webhook registration completes — because Shopify’s authoritative report API is queryable for any historical date. Direct revenue webhooks (Stripe, Paddle, etc.) are the only path that works for non-Shopify storefronts. The dashboard is the union of all four, not the intersection.
The four paths in detail
1. Custom Pixel — first-party event stream
- Latency
- Real-time. The pixel fires
checkout_completedfrom Shopify’s Customer Events sandbox the instant the buyer hits the Thank-You page. Events reach Tinybird in single-digit seconds. - Tinybird datasource
visitor_payments- What it sees
- Per-order detail: amount, currency, line items, the visitor’s session ID (the click that drove the purchase), the UTM parameters, the device, the geographic region. This is the only path that carries first-touch and last-touch attribution at the per-order grain.
- What’s missing
- Refunds aren’t in this stream — the pixel only fires on checkout completion, not on refund issuance. Orders placed before the pixel was installed don’t backfill. Sessions where the buyer disabled JavaScript, blocked the pixel domain, or shopped in a brand-new browser without consent never produce an event.
- When it’s empty
- Hours-to-days after install before the first real checkout. The pixel is wired correctly but no buyer has hit Thank-You yet. This is the most common “why is my dashboard empty?” pattern on install day.
Pixel architecture deep-dive (Web Worker sandbox, GL#305 to GL#307 saga): /documentation/architecture/shopify-pixel.
2. Admin webhooks — server-side order mirror
- Latency
- Real-time. Shopify pushes the webhook within seconds of the order being created or refunded.
- Tinybird datasource
orders- What it sees
- Order-level detail from Shopify’s server: gross amount, currency, line-item totals, financial status (paid, partially_paid, refunded), customer ID, and — critically — refund deltas. The
refunds/createtopic fires when a refund is issued, so net revenue can be computed in real time. - What’s missing
- No visitor session, no UTM attribution — webhooks don’t see the click that drove the purchase. (The visitor session lives only in the Custom Pixel.) Orders placed BEFORE webhook registration aren’t in this stream either — webhooks are forward-looking by definition.
- When it’s empty
- Until
webhookSubscriptionCreatecompletes during the OAuth callback. Most install days this lands inside ~2 minutes; rare cases (transient network failure, HMAC verification mismatch, URL drift between dev and prod) leave webhooks unregistered for hours-to-days.
3. Daily Admin-API poll — the canonical backfill
- Latency
- +24 hours. The
worker/shopify-reports-syncBullMQ job runs at 02:00 UTC and fetches Shopify’s reported metrics for the prior calendar day (yesterday). - Tinybird datasource
shopify_reported_metrics- What it sees
- Workspace-level totals per (shop, date): gross sales, net sales, returns amount, orders count, sessions, conversion rate. This is Shopify’s authoritative daily ledger — the same numbers a merchant sees in Shopify Analytics.
- What’s missing
- No per-order detail. No
is_new_customerflag, no per-line-item units, no individual UTMs — only daily aggregates. Tiles that need per-event detail (new vs returning customer split, units sold, average items per order) cannot be filled from this path. - When it’s populated
- Always, once the worker has run at least once. The poll backfills any prior day on demand — so the moment a Shopify connection completes, the prior day’s totals can be fetched. This is the only path that has any data on install day before the first new checkout.
4. Direct revenue webhooks — Stripe / Paddle / Polar / Lemon / Dodo
- Latency
- Real-time. Same shape as Shopify admin webhooks: provider pushes on each successful charge.
- Tinybird datasource
revenue_events- What it sees
- Per-charge detail signed with the provider’s standard signature header (
Stripe-Signature,Paddle-Signature,Polar-Webhook-Signature,X-Signaturefor Lemon Squeezy,Dodo-Signature). Visitor stitched via the metadata field each provider exposes (metadata.admaxxer_visitor_idorcustom_data.admx_visitor_id, depending on provider). - Status
- Gated. The connector code paths exist (see /documentation/revenue/stripe, /documentation/revenue/paddle, etc.) and the Tinybird datasource is provisioned, but the in-app connect flow is not yet exposed for self-serve activation. Direct revenue webhooks are the canonical path for non-Shopify storefronts; today they enable case-by-case onboarding.
Visual map — what feeds what
The four ingestion paths fan into Tinybird, then converge inside the summary_kpis pipe (which the dashboard’s tiles read from). Each path enhances the tile set independently:
Storefront browser Shopify admin server Shopify Admin API Stripe / Paddle / Polar / Lemon / Dodo
(Customer Events) (orders/* webhooks) (poll worker, 02:00 UTC) (direct webhook)
| | | |
| checkout_completed | orders/create | GET /admin/api/.../reports | charge.succeeded
| (real-time) | orders/paid | (+24h, daily totals) | (real-time)
| | refunds/create | |
v v v v
/api/event /api/pixel/webhooks/shopify worker/shopify-reports-sync /api/webhooks/<provider>
| | | |
v v v v
visitor_payments orders shopify_reported_metrics revenue_events
\__________________________/_______________________________/
|
v
summary_kpis pipe (source-additive)
|
v
Dashboard tiles + chat answers
The collapse from four datasources to one pipe is where GL#313 lived. Before the fix, summary_kpis read three of the four (it omitted shopify_reported_metrics) so install-day workspaces saw zero revenue tiles even when the daily-poll worker had backfilled real data. After the fix, every datasource is folded in via greatest(per_event_value, polled_value) — per-event sources win on ties (they’re more attributable), but the polled source fills any per-event gap.
Timing matrix — what data is available when
Each ingestion path warms up on its own schedule. For a brand-new Shopify-connected workspace, here’s what to expect:
| Time since install | Custom Pixel | Admin webhooks | Daily poll | Direct webhooks |
|---|---|---|---|---|
| 0 – 1 minute | Wired but no data (no checkouts yet) | Registration in flight | Pending next 02:00 UTC tick | Gated |
| 1 – 60 minutes | Empty unless a real checkout fires | Active, but only catches orders placed AFTER registration | Pending next 02:00 UTC tick | Gated |
| 1 – 24 hours | Empty until first checkout | Active and accumulating | First poll runs at the next 02:00 UTC tick — backfills yesterday | Gated |
| 24 – 72 hours | Populated as checkouts happen | Populated; refund deltas now visible | Updated daily; one row per (shop, date) | Gated |
| 7+ days | Source of truth for visitor-session attribution | Source of truth for refund-aware net revenue | Source of truth for total revenue (Shopify ledger) | Source of truth for non-Shopify revenue (when activated) |
The key insight: for the first hours after install, Path 3 (the daily poll) is often the only path with data. Without GL#313’s fix, the dashboard would render “no data” even though Tinybird had real numbers. With the fix, the dashboard renders correct totals from the polled source while the per-event paths warm up.
What you’ll see — three workspace scenarios
Scenario A — Pixel-only workspace (no Shopify connection)
You’ve installed the Admaxxer pixel on a non-Shopify site (Webflow, Next.js, custom React, etc.) but haven’t connected a payment provider. Path 1 (Custom Pixel) is the only active source.
- Pageviews, sessions, sources, and UTMs all populate normally.
- Revenue tiles show zero unless your pixel fires a custom
checkout_completedevent with an amount — many no-code sites don’t. - The dashboard banners point you toward connecting a revenue provider (Stripe / Paddle / Polar / Lemon Squeezy / Dodo) so Path 4 can fill the revenue tiles.
This is the expected state for marketing-only or top-of-funnel use cases — not a bug.
Scenario B — Shopify-connected install-day workspace
You connected Shopify today, pasted the Custom Pixel snippet, and Admaxxer registered admin webhooks during OAuth callback. Path 1 is wired but has zero events (no checkout has happened yet). Path 2 is registered but empty (no order has come in since registration). Path 3 fires at the next 02:00 UTC tick and backfills yesterday’s totals.
- For the first 2–24 hours: revenue tiles show prior-day totals from Path 3, real-time tiles are empty.
- For tiles that need per-event detail (new vs returning split, units sold per order, refund-aware net revenue): expect zero until Path 1 or Path 2 has its first row. This is correct, not a bug — the daily poll doesn’t carry that detail.
- The Dashboard’s Get-Connected checklist surfaces “Waiting for first checkout” status until either the pixel or webhook records its first order.
Scenario C — Mature workspace (all paths firing)
You’ve been live for a week or more. Every path has data. The summary_kpis pipe takes the maximum across paths per metric:
- Per-event paths win when they’re populated — visitor-attributable revenue is more useful than untyped daily totals.
- The polled value is used as a floor — if your webhook stream missed a few orders due to transient network failure, the daily poll picks them up and the tile reflects the higher number.
- Refund deltas come from Path 2 in real time; the daily poll cross-validates them against Shopify’s reported returns the next morning.
This is the steady state. The cross-validation tile on the Dashboard shows the agreement between paths — 95%+ is healthy, lower than that flags a per-event ingestion issue worth looking at.
Why some tiles legitimately stay zero
Not every tile can be filled by every path. The data-fidelity hierarchy from GL#313 ranks paths by how much detail each carries:
visitor_payments(Path 1) — per-event, attributable to visitor session.orders(Path 2) — per-event, refund-aware, but no visitor session.revenue_events(Path 4) — per-charge detail, when activated.shopify_reported_metrics(Path 3) — daily aggregates only.
Tiles requiring per-event detail can only be filled by paths 1, 2, or 4 — never by Path 3 alone. Examples:
- New customer (NC) vs returning customer (RC) revenue split. Requires the
is_new_customerflag on each order. Carried by Paths 1 and 2; absent from Path 3. - Units sold per order, average items per cart. Requires the line-item array on each order. Carried by Paths 1 and 2; absent from Path 3.
- Refund-aware net revenue at the order grain. Requires per-refund detail. Carried by Path 2; absent from Path 1 (pixel doesn’t fire on refund) and absent from Path 3 at the per-order grain.
- Per-order tax, shipping, and discount breakdown. Requires the order subtotal/tax/shipping decomposition. Carried by Paths 1 and 2; rolled up in Path 3.
If you’re in Scenario B (install day) and these tiles render zero, that’s correct — not a bug. The data exists at the workspace-total grain (the dashboard’s revenue tile is populated from Path 3) but cannot be split until a per-event row arrives. The first checkout that fires the Custom Pixel is the moment the NC/RC split, units sold, and per-order tax breakdown all become live.
This is also why the dashboard says “Waiting for first per-event checkout” in those tiles instead of just zeroing them — the empty-state intent is “not yet,” not “your store made no money.”
Debugging your workspace
If you’re looking at a quiet dashboard and want to figure out which path is feeding it, run through this checklist:
5-minute checklist
- Open /integrations. Confirm the Shopify connection card shows “Connected.” If it’s in a re-auth or error state, fix that first — nothing downstream can work until the connection is healthy.
- Open the Dashboard. Look for the Get-Connected checklist (top of page). It surfaces per-source health: pixel status, webhook delivery count last 24h, last successful daily-poll timestamp.
- Time-since-install check. If you connected within the last 24 hours and your dashboard is dominated by zero tiles, you’re probably in Scenario B above. The 02:00 UTC daily poll backfills the prior-day totals on its first run; until then, only the per-event paths have data and they’re empty until your first checkout.
- Check the cross-validation card. The “Shopify cross-validation” tile compares per-event revenue (Paths 1+2) to polled revenue (Path 3). Healthy: 95%+ agreement. Below that: open the drill-down to see which days are off and by how much.
- If all paths show zero after 48 hours. Either no orders have been placed since you connected (legitimate — a quiet store is a quiet store), or one of the upstream connections is silently failing. Email support@admaxxer.com with your workspace ID; we’ll look at webhook delivery logs and the daily-poll worker queue.
Common “quiet dashboard” root causes
- Pixel snippet not saved — Shopify error “the pixel cannot be connected until a custom pixel script is saved for it.”
- Shopify’s Custom Pixel field is a JS editor (Web Worker, no DOM). Pasting the universal HTML
<script>tag triggers this error. Use the canonical JS snippet, click Save first, then Connect. See the Shopify Custom Pixel architecture page for the full GL#305 saga. - Custom Pixel installed but events stop at the server —
allowed_domainsmismatch. - If your storefront proxies through
<shop>.myshopify.cominstead of your custom domain, the pixel’s HTTPOriginmay not match your pixel’s allowed-domains list. GL#291 ships an auto-fix in the OAuth callback. If you connected Shopify before that shipped, manually add both your custom domain and your.myshopify.comslug to Settings › Pixel › Allowed Domains. - Webhook registration silently failed during OAuth.
- Re-run the Shopify connect flow. Admaxxer’s callback re-runs
webhookSubscriptionCreateidempotently — it won’t duplicate the registration; it’ll repair a missing one. Path 3 (daily poll) is the safety net while you fix this; Path 2 catches up the moment registration succeeds. - Daily poll hasn’t run yet (you connected after 02:00 UTC today).
- Path 3 backfills prior-day totals on its next run. If you connected at 04:00 UTC today, the next poll fires at 02:00 UTC tomorrow. Until then, your dashboard depends on Paths 1 and 2 having their first events. This is normal install-day behavior; revenue tiles populate when either a checkout fires or the next 02:00 UTC tick lands.
- Tiles requiring per-event detail (NC/RC split, units sold) stay zero even after the daily poll runs.
- Correct behavior. The daily poll is workspace-level totals only — it doesn’t carry the per-event fields these tiles need. The first checkout that fires the Custom Pixel (or the first order that fires the admin webhook) is the moment those tiles become live.
Related documentation
- /integrations — manage Shopify, Meta, Google, and other connections.
- /documentation/architecture/shopify-pixel — deep dive on the Custom Pixel sandbox semantics (GL#305 to GL#307 saga).
- /documentation/revenue/shopify — Shopify webhook setup, signature verification, and visitor stitching.
- /documentation/revenue/stripe, /documentation/revenue/paddle, /documentation/revenue/polar, /documentation/revenue/lemonsqueezy, /documentation/revenue/dodo — direct revenue webhook providers (Path 4, when activated).
- /documentation/analytics — pixel architecture, attribution model, and how MER / cohort LTV / MMM read from these datasources.