Stripe knows who paid. Your marketing site knows who showed up. The gap between those two databases is where founders argue about “which blog post worked” while eating cold pizza over a spreadsheet.
This guide covers the Stripe revenue guides. Goal: checkout webhooks + first-session UTMs + landing path in one view, not perfect multi-touch, but enough to route traffic like adults.
What Stripe sends you that matters
For self-serve SaaS, these events usually carry the money story:
checkout.session.completed, cash or subscription start at checkout.invoice.paid, renewals (often excluded from “acquisition” RPV).customer.subscription.created, sometimes redundant with checkout session.
Pick one primary revenue event for marketing attribution (typically first successful checkout) and stick to it. Renewals belong in retention dashboards, not launch postmortems.
Amount, currency, customer id, and metadata land in the webhook payload. That is your revenue row.
What your site must capture on the way in
On first pageview in a session:
- Landing path (
/,/pricing,/blog/...). - UTM parameters if present.
- A stable
session_idin sessionStorage (no cookies required).
When checkout completes, you do not need the buyer to still have the same tab open if you linked Stripe customer or session metadata at payment time, but many indie flows never set metadata. Then attribution falls back to “same day, same site” heuristics or last-touch session. Know which you implemented.
Minimum wiring diagram
- Visitor hits tagged URL → tracker stores UTMs + landing path.
- Visitor converts → Stripe Checkout with
client_reference_idor metadatautm_sourcecopied from a hidden field or logged-in user record. - Webhook fires → analytics records revenue with metadata + timestamp.
- Dashboard joins revenue rows to session dimensions for the window.
If step 2 is empty, read first-touch UTM on Stripe customer metadata.
Landing page attribution vs “last page before pay”
Two honest models:
| Model | Pros | Cons |
|---|---|---|
| First landing in session | Stable for long research journeys | Ignores later high-intent pages |
| Last pageview before checkout click | Reflects final nudge | Breaks when checkout is email invoice |
Indie SaaS default: first landing for channel ROI; add last path only when you have volume. Compare with RPV by landing page.
Live mode vs test mode pollution
Test checkouts in live analytics destroy trust. Filter:
- Stripe livemode flag on webhook.
- Internal email domains.
- Known test card amounts ($0.50 fixtures).
One $49 test purchase before a launch is worth it; leaving tests in the funnel is not.
Currency and tax
RPV and channel tables should use one settlement currency per site. Stripe Tax and multi-currency displays can make weekly totals jump without marketing changes. Note tax-inclusive vs exclusive in your runbook.
Subscriptions vs one-time
subscription_started and one-time checkout.session.completed tell different stories. Mixing them in one “revenue” number confuses launch weeks when you add a monthly tier. Split metrics, see one-time vs subscription attribution.
Ordering bugs
Webhook before pageview sounds rare; it happens with mobile app handoff, email invoice links, and Apple Pay speed. Pageview after webhook ordering covers reconciliation habits.
What you still will not know
- Buyer read three blog posts on three devices without login.
- Partner sent a PDF with your domain typed from memory (
direct). - Sales-assisted deals that never touch self-serve checkout.
Stripe + site analytics covers self-serve tagged acquisition. That is most indie revenue for many products.
Weekly review (12 minutes)
- Revenue by
utm_source(28d). - Revenue by landing path (28d).
- Pin launch week if applicable (marketing pulse guides).
- One action: change lander, tag, or metadata wiring.
Tooling expectations
You need webhook ingestion beside the tracker, not a second product. KiboData maps checkout events next to UTMs on shared dates, it does not replace Stripe reporting or revenue recognition rules. Free tier includes webhooks with badge; lifetime removes badge requirement.
Sibling articles
Stripe already has the money. Your job is to stop letting it sit in a silo while marketing optimizes clicks.
Mapping Checkout success URL to funnel steps
Many teams treat /success as cosmetic. Instrument it as a funnel step anyway, even when webhooks carry dollars, so you can see drop-off between pricing click and completed session. If success pageviews are 20% lower than webhook counts, tab-close after payment is your answer, not “broken Stripe.”
Price IDs as hidden segmentation
Stripe price_ ids in webhook payloads let you split lifetime vs monthly revenue by landing page without asking marketing to rename UTMs. When /pricing drives annual plans and /start drives monthly, lander tables stop lying about intent.
Connect Customer Portal upgrades
Portal upgrades often lack fresh UTMs. Tag them medium=product or exclude from acquisition reports. Otherwise “newsletter” looks weak while existing users upgrade after reading help docs.
Refunds and chargebacks in launch pins
A launch pin with three refunds can tank RPV psychologically. Decide whether marketing reviews gross or net and keep consistent with RPV under 10k traffic. Note refunds in the pin comment, not by silently editing charts.
Stripe Radar and blocked payments
Blocked fraud attempts do not belong in marketing funnel counts. If your analytics counts payment_failed as negative revenue, split security events to ops metrics.
Multiple Stripe accounts
Acquired products sometimes keep separate Stripe accounts. Unify in analytics with account_id dimension or accept separate dashboards per product, merging without labels creates fake “direct” surges.
Founder's manual invoices
Manual invoices in Dashboard bypass site UTMs. Use metadata on invoice creation when you send a custom Payment Link after a sales call, or accept that enterprise-ish revenue will not map to blog posts.
Reading Stripe + traffic on the same chart
Overlay daily visitors and daily new-checkout revenue. Visual lag between traffic spike and revenue spike hints at trial length or long consideration, not broken tracking. Pair with marketing pulse guides pins for narrative.
Checklist before blaming marketing
- Livemode webhooks only
- Metadata on Checkout Session create
- Success page fires client event
- Renewals excluded from acquisition row
- Test purchases filtered
Customer Portal and off-session charges
Not every dollar arrives through Checkout Session you can tag at click time. Customer Portal upgrades, saved-card charges, and dunning retries often fire invoice.paid without a fresh marketing session. Decide in your metrics doc whether those rows are expansion (exclude from acquisition) or new product line (include with frozen metadata). If you never copied UTMs at first purchase, Portal revenue will look like (direct) forever, fix forward on new customers, not retroactively on ten thousand legacy rows.
Payment Links in email and Slack
Payment Links are convenient and attribution-hostile unless you generate them server-side per campaign. Pattern: marketing page CTA hits your API → checkout.sessions.create or Payment Link with metadata → redirect. A single static Payment Link in the footer poisons every channel with direct revenue. For one-off sales assists, set metadata.medium=sales manually when you paste the link in Zoom.
Trials that convert days later
checkout.session.completed on day zero might be a $0 trial authorization; cash lands on invoice.paid day fourteen. Marketing reviews that only count session completed undercount newsletter weeks that drive trial starts; reviews that count every invoice overcount renewals. Align event choice with one-time vs subscription attribution and stick to it for a year.
Join keys when metadata is partial
Sometimes you only have client_reference_id pointing at an internal user id. Join path: user row stores landing_path and UTMs at signup → webhook loads user → emits revenue with same dimensions. Without signup before pay, join fails, that is a product flow issue, not an analytics bug. Document which flows guarantee a user row before Stripe Customer creation.
Disputes and partial refunds
Chargebacks and partial refunds change net RPV without changing landing page quality. Store amount_refunded on the revenue row or filter net in weekly reviews. A pricing page with high gross RPV and high refunds is a packaging honesty problem; blaming utm_source=twitter wastes a week.
Multi-step B2B-ish flows on indie sites
Quote request → invoice sent → paid a week later is common even at small ARR. Self-serve attribution rules break unless you stamp metadata when the invoice is created, not when it is paid. If you only use Checkout for SMB and invoices for larger deals, split dashboards, blended “landing page RPV” will lie about blog performance.
Weekly Stripe + site reconciliation ritual
Beyond the twelve-minute review, once a month reconcile count of new customers in Stripe to count of attributed revenue rows in analytics for the same 28d window. Gaps usually mean: test mode leak, missing webhook signature verification dropping events, or duplicate dedupe keys eating real sales. Fix plumbing before rewriting hero copy.
Instrumentation ownership
Founders outsource “add Stripe” to a contractor who ships Checkout without metadata. Put metadata on Session create in the acceptance checklist for any payment PR. Link the checklist to first-touch UTM metadata in your internal wiki so the next hire does not repeat the mistake.