Data architecture · ~7 minute read · Last updated 2026-04-29

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:

  1. Custom Pixel — first-party events fired from the storefront and checkout sandbox.
  2. Admin webhooks — Shopify’s server-side push for orders/create, orders/paid, and refunds/create.
  3. Daily Admin-API poll — a worker that fetches Shopify’s authoritative daily totals once per day at 02:00 UTC.
  4. 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_completed from 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/create topic 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 webhookSubscriptionCreate completes 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-sync BullMQ 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_customer flag, 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-Signature for Lemon Squeezy, Dodo-Signature). Visitor stitched via the metadata field each provider exposes (metadata.admaxxer_visitor_id or custom_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.

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.

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:

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:

  1. visitor_payments (Path 1) — per-event, attributable to visitor session.
  2. orders (Path 2) — per-event, refund-aware, but no visitor session.
  3. revenue_events (Path 4) — per-charge detail, when activated.
  4. 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:

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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_domains mismatch.
If your storefront proxies through <shop>.myshopify.com instead of your custom domain, the pixel’s HTTP Origin may 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.com slug to Settings › Pixel › Allowed Domains.
Webhook registration silently failed during OAuth.
Re-run the Shopify connect flow. Admaxxer’s callback re-runs webhookSubscriptionCreate idempotently — 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.