SaaS analytics setup — identify users, fire funnel events, and attribute MRR
Installed the pixel and connected Stripe, but your funnel and channel attribution are empty? Two steps close the loop: identify users on signup and fire custom funnel events. Once those are wired, every trial, subscription, and dollar of recurring revenue traces back to the channel that earned it — so you get trial-to-paid by channel, MRR by source, and CAC payback by cohort.
Why pageviews + Stripe alone leave your funnel empty
Pageviews tell you traffic. Stripe tells you revenue. Neither one knows which paying customer came from which channel — that link is created when you call identify(user.email) on signup and fire a named event at each funnel step. On a framework (Next.js, Remix, SvelteKit), use the SSR-safe @admaxxer/pixel npm SDK so these calls never crash during server render. A SaaS funnel is signup → trial → subscription → recurring revenue; this guide instruments exactly those steps. (If you run an online store instead, you want the products-and-orders path, not this one — the two are intentionally separate.)
Four steps: install, identify, events, Stripe
Each step is independent — ship them in any order and verify each one on its own. Estimated total: 10 minutes.
1. Install the pixel
One snippet in your site’s <head> starts capturing pageviews and the anonymous visitor journey. The Pro variant (script.plus.js) also auto-captures form submits and outbound clicks — handy because most SaaS signups are form-driven.
<script defer
data-website-id="YOUR_WEBSITE_ID"
data-domain="yourdomain.com"
src="https://admaxxer.com/js/script.plus.js"></script>
Find YOUR_WEBSITE_ID and your domain at /dashboard/pixel. Using Next.js, Remix, SvelteKit, or SolidStart? The npm package is SSR-safe.
2. Identify users on signup
Call identify(user.email) the moment a user signs up or logs in. This is the step that links the anonymous visitor who clicked your ad to the paying customer in your billing system — so every future subscription and dollar of recurring revenue is attributed back to the channel, campaign, and source that originally acquired them. Most SaaS apps are SSR frameworks (Next.js, Remix, SvelteKit), so use the SSR-safe npm SDK — calling window.admaxxer.identify(…) directly from a server-rendered component would crash with window is not defined.
// RECOMMENDED for SaaS frameworks — the SSR-safe npm SDK. identify() and
// track() no-op during server render and buffer until the pixel loads, so
// you can call them anywhere with NO "typeof window" guards.
import { initPixel, identify, track } from '@admaxxer/pixel';
initPixel({ websiteId: 'YOUR_WEBSITE_ID' });
// Simplest form — pass the email you already have at signup:
identify(user.email);
// Richer form — pass your stable user id plus traits:
identify(user.id, {
email: user.email,
plan: user.plan,
signed_up_at: user.createdAt,
});
// Plain / non-framework sites use the hosted <script>'s window.admaxxer
// global instead — SSR-guard it so a server render can't crash:
if (typeof window !== 'undefined') {
window.admaxxer.identify(user.email);
}
Why the npm SDK: its identify()/track() are SSR-safe (no-op on the server, buffered until the pixel loads), so they're callable anywhere with no guards. The call is idempotent, so it’s safe to run on every authenticated page load — the original ad-click chain then stays attached through trial, conversion, and churn, across devices and months later. The hosted-script global is window.admaxxer; the legacy window.admx global does NOT exist.
3. Fire custom funnel events
Send a named event at each step of your funnel with track('event_name', { ...props }) from the SSR-safe npm SDK. These become goals you can filter and attribute by channel — so you see which sources produce real trial starts and paid subscriptions, not just pageviews.
import { track } from '@admaxxer/pixel';
// When a free trial begins:
track('trial_started', { plan: 'pro' });
// When the trial converts to a paid subscription:
track('subscription_started', { plan: 'pro', mrr: 49 });
// Any milestone that matters to your funnel:
track('demo_booked', { source: 'pricing_page' });
track('activated', { milestone: 'first_project_created' });
// Plain / non-framework sites fire events by calling the window.admaxxer
// global directly — SSR-guard it:
if (typeof window !== 'undefined') {
window.admaxxer('trial_started', { plan: 'pro' });
}
Pass any plain-object properties you want to segment by later (plan, MRR, source, milestone). Prefer zero-JS? Add data-admx-goal="trial_started" to a button and the event fires on click — no handler code. Use names that match your funnel stages; there’s no fixed list.
4. Connect Stripe for MRR
Paste a Stripe restricted key (rk_live_…) in /integrations and recurring revenue flows in automatically — trial starts, paid conversions, and cancellations all stitch back to the original visitor. No webhook handler to write on your side.
// Recommended: tie revenue to the original visitor across devices by
// passing the visitor id into your Stripe Checkout Session.
// Client-side — read the id the pixel set:
const visitorId = window.admaxxer.getVisitorId();
// Server-side — attach it as Checkout Session metadata:
const session = await stripe.checkout.sessions.create({
// ...your existing config
metadata: { admx_visitor_id: req.cookies.admx_visitor_id || '' },
});
Use a restricted key (read-only scopes), never a secret key. Without the visitor-id metadata, revenue still stitches via the email you passed to identify() — slightly less precise if a customer signs up and pays with different emails. Full walkthrough: Stripe revenue connector.
What shows up where
Once the four steps are wired, each funnel stage is attributed to the source that produced it:
- Visit — pixel pageview with UTM/source captured: anonymous visitor recorded with the channel that referred them.
- Signup —
identify(user.email)on account creation: the anonymous visitor is linked to the named user, and the original source now follows them forever. - Trial started —
track('trial_started', { plan })or the Stripe auto-event: trial-start rate by channel becomes visible, not just raw signups. - Subscription started —
track('subscription_started', { plan, mrr })or Stripe: paid-conversion rate and new recurring revenue are attributed to the acquiring source. - MRR — Stripe restricted key: recurring revenue rolls up by channel, campaign, and cohort — true CAC payback.
- Cancellation — Stripe subscription cancelled: churn ties back to the channel that acquired the customer (churn-by-source).
All of this lands in Marketing Acquisition, where you can filter by any funnel goal and split it by channel and source.
Set it up with your AI agent
Already coding with an AI assistant? Connect Claude, Cursor, or any MCP client to Admaxxer, then ask it to call the admaxxer_get_event_setup tool. It returns the exact install, identify, and funnel-event snippets above — pre-filled with your real website id — so your agent can drop them straight into your app. No hunting for IDs, no manual copy-paste.
- Connect your AI — generate a token and paste it into your AI client’s MCP config (about two minutes). See Connect any AI.
- Ask for the setup — prompt your agent: “Fetch my Admaxxer event setup and wire it into this app.”
- Agent wires it in — it receives the snippets pre-filled with your website id and adds them for you.
The admaxxer_get_event_setup tool is read-only — it returns code text only and changes nothing in your account. Browse every supported AI client and the full tool list at Connect any AI.
Verify it works
- Open your live site in a fresh tab. Within ~5 seconds a Pageview should appear in Pixel › Realtime.
- Sign up with a test account. Your signup goal and the
identifycall should both register within ~5 seconds. - Fire a test funnel event — e.g.
track('trial_started', { plan: 'pro' })— and confirm it appears as a goal. - Run a $1 test subscription through Stripe Checkout. New recurring revenue should attribute to your test visitor’s original source.
- Open Marketing Acquisition and filter by your funnel goal — you should see it split by channel and source.
Related Admaxxer documentation
- Install on a custom SaaS website — the full wiring reference for the pixel, signup goal, identify, and the Stripe webhook.
- NPM package install — for Next.js, Remix, SvelteKit, SolidStart, and other bundled frontends. SSR-safe.
- Custom goals — naming, properties, and how goals show up in reporting.
- Connect any AI — connect Claude, Cursor, or any MCP client and have it fetch this event setup pre-filled with your website id via the
admaxxer_get_event_setuptool. - Stripe revenue connector — the restricted-key setup and recurring-revenue stitch.
- Marketing Acquisition — where source-level trial-to-paid, MRR, and churn results appear.
- Browse all integrations — ad platforms, revenue providers, and analytics.
FAQ
I already have pixel pageviews and Stripe MRR — why is my funnel empty?
Pageviews and revenue alone can’t tell which paying customer came from which channel. The link is the identify() call on signup plus the custom funnel events you fire. With the SSR-safe npm SDK, call identify(user.email) when a user signs up, then track('trial_started', …) and track('subscription_started', …) at each step. (On a plain HTML site you can use window.admaxxer.identify(…) and window.admaxxer('trial_started', …) instead, wrapped in a typeof window check.) Once those are in place, trial-to-paid conversion by source, MRR by channel, and CAC payback populate automatically.
What exactly does identify(userEmail) do?
It links the anonymous visitor in the current browser to the named user. Every event that browser fired before signup — including the original ad click and its UTM/source — gets attached to that user. From then on, every trial, subscription, and dollar of recurring revenue is attributed back to the channel that acquired them, even across devices and months later.
I’m on Next.js / Remix / SvelteKit — how do I call identify and track safely?
Install the SSR-safe npm SDK with npm install @admaxxer/pixel, then import { initPixel, identify, track } from '@admaxxer/pixel'. Call initPixel({ websiteId }) once, then identify(user.email) and track('trial_started', { plan }) anywhere — including server components or module top-level. The SDK no-ops during server render and buffers calls until the pixel loads, so you never need a typeof window guard and never hit window is not defined. Calling window.admaxxer.identify(…) directly in a server-rendered component would crash instead.
Should I pass the email or a user id to identify()?
Either works. The single-argument form identify(userEmail) is the quickest to ship. The richer form identify(user.id, { email, plan }) ties the visitor to your stable internal user id and lets you attach traits (plan, signup date) for segmenting later. If you have both, prefer the id form and include email in traits.
What event names should I use for a SaaS funnel?
Use names that match your funnel stages — e.g. trial_started, subscription_started, demo_booked, activated, upgraded. Pass properties you want to segment by, such as { plan: 'pro', mrr: 49 }. There’s no fixed list; whatever you name becomes a goal you can filter and attribute by channel.
Do I have to fire events myself if I connect Stripe?
Connecting Stripe with a restricted key brings in trial starts, paid conversions, cancellations, and MRR automatically — no handler code. Firing the client-side events yourself is still recommended for funnel steps Stripe never sees (demo booked, account activated, feature adopted) and for instant in-app confirmation. The two sources reinforce each other.
Is this the same setup as an online store?
No. A store tracks products, carts, and orders. A SaaS funnel tracks signups, trials, subscriptions, and monthly recurring revenue. This guide is the SaaS path — identify on signup, fire funnel events, connect Stripe for MRR — and intentionally leaves out e-commerce concepts that don’t apply.
Ready to close the loop?
Install the SSR-safe @admaxxer/pixel SDK, call identify(user.email) on your signup handler, then add a trial_started and subscription_started event with track(), then connect Stripe with a restricted key. Each step is independently verifiable in Pixel › Realtime. Within minutes your Marketing Acquisition dashboard shows trial-to-paid by channel, MRR by source, and churn-by-cohort.
Full SaaS install reference · Connect Stripe · Custom goals · All documentation