Should I vibe code
Web analytics that ties revenue back to the marketing channel that produced it
Attribution is not a measurement, it is an opinion. Yours will be wrong in a way that looks fine.
?
Their verdict, the Starter price and the build-time estimate come from their entry, MIT-licensed. Checked 2026-08-04.
?
Our verdict, the regret score and everything below it. Editorial and unsponsored — nobody can pay to be moved.
The honest answer
why the verdict is what it is
A chart that says Twitter made you $4,200 is a weekend. A chart you would move a budget on is not, because attribution is a modelling choice rather than a measurement — and the errors that matter here are the quiet ones, where the number is plausible, stable and wrong.
What actually breaks
not "if". the specific failures.
- Duplicate webhook deliveries, which are normal, documented Stripe behaviour and which quietly double the revenue on your best channel
- Refunds, chargebacks and failed renewals, which land weeks later and never get subtracted from the month they were credited to
- Last-click, which hands most of your revenue to the search result for your own brand name
- A 30-day attribution window against a 90-day sales cycle, so the channel that actually worked is invisible
- The word revenue, which in Stripe means gross, net, proration, tax and currency depending on which field you read
- Test-mode events reaching the production table, because the only thing separating them is a key you set once
- The join itself, which turns anonymous analytics into a per-person record of everything a paying customer read before they bought
Is that you?
the verdict is a default, not a law
- The numbers inform your curiosity rather than your spending
- You reconcile the dashboard against Stripe's own reporting before you believe it
- You can state your attribution window and model in one sentence, on the dashboard, next to the number
- A budget moves because of a figure nobody has reconciled
- Refunds and chargebacks are not subtracted from the attributed totals
- Webhook handling is not idempotent
- You are storing a browsing history joined to a named customer without saying so anywhere
If you build it anyway
the checklist, then the prompt that enforces it
- Make webhook handling idempotent on the event id before you write a single chart. Stripe will deliver the same event twice, and double-counted revenue is the defining bug of this category.
- Store raw events immutably and compute attribution as a view. When the model turns out to be wrong — and it will — you want to recompute rather than rediscover.
- Print the model and window on the dashboard: 'last non-direct click, 30 days'. An unlabelled attribution number is a number nobody can argue with, which is worse than a wrong one.
- Subtract refunds, chargebacks and failed renewals from the period the original charge was attributed to, not the period they arrived in.
- Reconcile the total against Stripe's own reports on a schedule and alert when they diverge by more than a threshold you choose in advance.
- Separate test and live at the storage layer, not the config layer. A test-mode subscription in the production table is a $10,000 channel that never existed.
- Decide deliberately whether you are joining sessions to identified customers, then say so in your privacy page — this is the step where privacy-friendly analytics stops being either.
Before you build revenue-attribution analytics, apply these and push back if I ask you to break them.
1. Start with the payment webhook, not the dashboard. Handle it idempotently
on the Stripe event id, enforced by a database constraint rather than an
application check. Tell me duplicate delivery is documented, expected
behaviour and the most common cause of inflated revenue.
2. Store raw events immutably — pageview, session, payment — and derive every
attribution figure as a query over them, so I can change the model later
and recompute history.
3. Ask for my attribution model and window before writing the join, and render
both as text beside every revenue figure. Never show an attributed number
without its model attached.
4. Handle refunds, chargebacks, failed renewals and cancellations explicitly,
subtracting them from the period the original charge was attributed to.
5. Pick one revenue definition — gross, net of fees, net of tax — put it in the
README, and use only that field. Normalise currency at charge time and
store the rate used.
6. Separate test-mode from live-mode data at the storage layer. A test
subscription in the production table invents a channel.
7. Build a reconciliation job comparing your monthly total to Stripe's own
reporting, alerting on divergence beyond a threshold I set.
8. Before writing the session-to-customer join, tell me plainly that it turns
anonymous analytics into an identified browsing history, and ask whether my
privacy page says so.
9. Never fill an attribution gap with a guess. Unattributed is a real category
and belongs on the dashboard.
10. Out of scope unless I ask: multi-touch models, LTV prediction, cohort
forecasting, session recording.That one keeps you out of trouble. For the prompt that actually builds it, canivibecodeit.com has one.
their build prompt ↗Or don’t build it
the boring option, and the way back out
The moment the number decides where money goes. $9 a month is less than one hour of chasing a discrepancy between your dashboard and your Stripe balance, and that chase is the recurring cost of the self-built version.
$9/mo is cheaper than your weekend.
Keep raw pageview and payment events in your own tables with stable ids, and treat every dashboard number as a query rather than a stored total. The events are portable and re-runnable; a pre-aggregated attribution table computed with a model you have since decided was wrong is worth nothing to your successor.
Open source and self-hostable, with revenue analytics and channel attribution already built
Open source analytics with goals and revenue goals; the Stripe join is still yours to write
Lightweight self-hosted analytics with UTM tracking and no revenue side at all
Questions
Why is this DEMO ONLY when your Plausible entry is SHIP IT?
Because counting pageviews is measurement and attributing revenue is modelling. Plausible's number can be verified against the thing it counts; an attributed revenue figure cannot be verified against anything, since there is no ground truth for which touchpoint caused a purchase. That makes it very easy to build something confident and impossible to build something checkable.
What actually goes wrong first?
Duplicate Stripe webhooks. Retries are normal and documented, and a handler that inserts on every delivery produces a revenue figure that is too high by a plausible-looking margin on precisely your busiest days. It never errors, and it is invisible unless you reconcile against Stripe's own totals.
Does joining payments to sessions change my privacy position?
Yes, materially. Aggregate analytics can be genuinely anonymous; a table that links a paying customer to every page they read before converting is personal data with a name attached. It may well be fine — but it is a different thing from what your privacy page probably says, and the change happens silently the day you add the join.
Every week, someone ships something they shouldn’t have.
New verdicts, the worst thing that landed in the trap, and the occasional incident report. No other email, ever.
Counting page views without cookies is a POST and a table. This one you should own.
Fathom counts pageviews. This one knows who Sarah is, where she works and everything she clicked since March.
June gave customers 30 days to export their history. That is the dependency you were calling "buying".
last reviewed 2026-08-04 · verdict is editorial and unsponsored · shared entry data from canivibecodeit under MIT · not legal advice