Install guide · SaaS funnel · ~8 minute install · Updated May 16, 2026 · By Admaxxer Team

Install Admaxxer on a custom SaaS website — track signups, trials, and MRR

If your product is a custom-coded SaaS website with signups, free trials, and recurring revenue, you want Admaxxer to track the whole funnel — anonymous visitor → signup → free trial → paid conversion → MRR → cancellation — with the same UTM chain following the customer from ad-click to churn. Five wiring points get you there: the pixel snippet, a Signup goal, an identify() call post-signup, the Stripe webhook, and (recommended) passing admx_visitor_id to Stripe Checkout. Admaxxer fires Trial Started, Trial Converted, Subscription Cancelled, and Trial Ending Soon goals automatically once the Stripe webhook is connected for card-on-file trials — you don't write any webhook handler code. For a no-card / app-side trial (Stripe never sees it) you fire the same goal names yourself from the pixel.

Why this install path matters for SaaS

Most analytics platforms can show you pageviews and a generic "Conversion" event. SaaS funnels need more: every step of signup → trial → paid is a discrete goal, every Stripe subscription transition needs to be attributed to the original UTM/source, and every churn needs to be tied back to the channel that acquired the customer in the first place. Without all five touch points wired, your dashboard shows pageviews but not trial conversion rate by channel, MRR by source, or churn-by-cohort.

This guide ties the standard Admaxxer pixel (the same one used by e-commerce sites) to the Stripe subscription lifecycle events via a single webhook. The pixel sees the anonymous visitor and the signup; the Stripe webhook drives the trial-and-revenue chain; the identify() call stitches the two together so the customer's whole journey is attached to the original visitor_id. Net effect: one pane of glass for the whole funnel, no merchant-side webhook code, no third-party reverse-ETL.

The five wiring points

Each point is independent — you can ship them in any order and verify each step on its own. Estimated total install time: 8 minutes.

1. Paste the pixel snippet

Add script.plus.js (the Pro variant) to your <head>. The Pro variant auto-captures form submits, outbound clicks, file downloads, and rage clicks, which saves you from wiring those goals manually. Recommended for SaaS because most signups are form-driven and the auto-form-submit goal lets your Signup goal fire even before you wire the programmatic call below.

<script defer
  data-website-id="YOUR_WEBSITE_ID"
  data-domain="yourdomain.com"
  src="https://admaxxer.com/js/script.plus.js"></script>

Replace YOUR_WEBSITE_ID and yourdomain.com with your real values (find them at /dashboard/pixel). If you prefer a framework-aware install, see the npm install guide — the npm package is SSR-safe and works in Next.js, Nuxt, Remix, SolidStart, etc.

2. Fire a Signup goal when the form succeeds

Two ways. Zero-JS: add data-admx-goal="Signup" to your submit button — the pixel fires automatically on click and stitches to the next pageview. Programmatic: in your form-success handler, call window.admaxxer('Signup', { plan, source }). The programmatic path is preferred when you have rich metadata (plan tier, referral source, signup flow variant) because it captures all of that for downstream reporting. This goal is always user-fired from the pixel — there's no Stripe equivalent because signup happens before any payment.

// Programmatic — preferred when you have rich metadata:
function onSignupSuccess(user) {
  window.admaxxer('Signup', {
    plan: user.plan,
    source: getQueryParam('utm_source'),
  });
}

Either way, "Signup" appears as a goal in your Admaxxer dashboard within ~5 seconds. The canonical pixel API is window.admaxxer(eventName, metadata) — the legacy window.admx global does NOT exist; any doc that references it is stale (see GL#433).

3. Identify the user post-signup

After authenticating the user (signup or login), call window.admaxxer.identify(userId, { email, plan }). This stitches the anonymous visitor_id (set the first time the pixel loaded) to the named user. All downstream Stripe events tie back to the user's original session, source, and medium — even months later when they finally convert or churn.

window.admaxxer.identify(user.id, {
  email: user.email,
  plan: user.plan,
  signed_up_at: user.createdAt,
});

The identify call is idempotent — safe to call on every page load. Polymorphic shape: identify(emailOnly) triggers Sonar email-stitch on the server side, while identify(userId, traits) is the canonical full-fidelity path.

4. Wire the Stripe webhook (auto-fires four goals)

Connect Stripe in /integrations by pasting a restricted key (rk_live_* or rk_test_* — never a secret key). Add a webhook endpoint at https://admaxxer.com/api/pixel/webhooks/stripe/:website_id subscribed to:

For card-on-file trials (where Stripe sees the subscription) Admaxxer auto-fires these named goals from the webhook handler — you don't write any code:

No-card / app-side trials never touch Stripe, so the webhook can't fire these. In that case fire the same canonical goal name yourself from the pixel — e.g. window.admaxxer('Trial Started', { plan }) when the trial starts and window.admaxxer('Trial Converted', { plan }) on upgrade. Using the identical name keeps both trial types in one funnel tile.

See the Stripe revenue connector doc for the restricted-key setup, the exact scopes needed, and a recovery playbook.

5. Pass admx_visitor_id to Stripe Checkout (recommended)

When creating a Stripe Checkout Session server-side, pass the visitor_id as Checkout Session metadata so the conversion ties back to the original anonymous session even if the user changes browsers or devices between trial signup and paid conversion:

// Client-side — read the visitor_id from the cookie/local storage:
const visitorId = window.admaxxer.getVisitorId();

// Server-side (Stripe Node SDK):
const session = await stripe.checkout.sessions.create({
  // ...your existing config
  metadata: { admx_visitor_id: req.cookies.admx_visitor_id || '' },
});

Without this, Admaxxer falls back to email-hash stitching — ties the Stripe customer to any prior pixel event that recorded the same email (via identify or a Newsletter goal). Still works, slightly less accurate if your users sign up with a different email than they pay with.

What fires when — the funnel timeline

Once all five points are wired, the funnel goals fire across the lifecycle — from the Stripe webhook for card-on-file trials, or fired from the pixel yourself for no-card / app-side trials:

  1. Signup — from your form-submit handler or the data-admx-goal attribute. Fires the moment the user creates an account. Always user-fired from the pixel.
  2. Trial Started — from customer.subscription.created for a card-on-file trial, or fired from the pixel for a no-card / app-side trial. Fires when the trial begins.
  3. Trial Converted — from customer.subscription.updated trialing → active (or fired from the pixel on upgrade for a no-card trial). The headline SaaS conversion.
  4. MRR Attribution — Stripe subscription revenue stitched to the original visitor_id via Checkout Session metadata or email-hash fallback. Visible in the /marketing-acquisition revenue column.
  5. Subscription Cancelled — from customer.subscription.deleted. The original UTM/campaign is preserved on the customer's first event so you can analyse churn-by-channel.

Verify the install

  1. Open your live site in a fresh browser tab. Within 5 seconds, a Pageview should appear in Pixel › Realtime.
  2. Submit a test signup. The Signup goal should appear within 5 seconds.
  3. After signup, open DevTools › Application › Cookies and confirm admx_visitor_id is set on your domain.
  4. Place a $1 test trial via Stripe Checkout. Within ~30 seconds, Trial Started should fire in the dashboard. (On a no-card / app-side trial, fire it yourself from the pixel instead — Stripe never sees the trial.)
  5. In the Stripe test dashboard, transition the subscription to status=active. Trial Converted should fire within ~10 seconds.
  6. Cancel the test subscription. Subscription Cancelled should fire within ~10 seconds, with the original UTM still attached on the customer's first event.

If any step doesn't fire, check Pixel › Realtime for the raw event stream — you'll see exactly what reached the collector and what didn't. The most common failure modes are: pixel snippet missing on the post-signup page, identify() called before the pixel script loaded, or Stripe webhook subscribed to the wrong endpoint URL.

FAQ

Do I need to use script.plus.js, or will plain script.js work?

Both work. script.plus.js (the Pro variant) is recommended for SaaS because it auto-captures form submits, outbound clicks, file downloads, and rage clicks — saving you from manually wiring goals on signup buttons and CTAs. If you only need pageviews + the explicit window.admaxxer() calls, script.js is fine.

Why pass admx_visitor_id to Stripe? Isn't email-hash stitching enough?

Email-hash stitching ties the Stripe customer to any prior pixel event that recorded the same email (via identify or a Newsletter goal). It works, but it's fragile: if the user uses a different email on signup vs purchase, or if the original session never identified, the chain breaks. Passing admx_visitor_id explicitly to the Checkout Session as metadata is the most reliable stitch — it ties revenue directly to the original anonymous session, regardless of browser, device, or email reuse.

What if I use Paddle / Lemon Squeezy / Polar instead of Stripe?

All four are supported via the same pattern — connect a restricted-key paste token in /integrations and Admaxxer subscribes to the matching subscription-lifecycle events on each platform. See /documentation/revenue/paddle, /documentation/revenue/lemonsqueezy, and /documentation/revenue/polar for the exact event mappings.

When should I call window.admaxxer.identify()?

Right after authenticating the user. On signup, call identify(user.id, { email, plan, signed_up_at }) once your auth flow returns the user. On login, call identify with the same userId — it's idempotent. The pixel will stitch the anonymous visitor_id from cookies to the named userId on the server side, so all prior events in that browser tie to the named account.

What about server-side events for higher-value goals?

If you want to fire a Signup goal server-side (e.g., to capture form submissions from API endpoints that don't load the pixel), use the pixel's server-side API: POST /api/event with the canonical visitor cookie. The same logic applies for any custom goal you want fired from the backend — typically for high-fidelity goals like "License Activated" or "Demo Booked".

Can I A/B test which channels produce real Trial Converted customers?

Yes. Once the Stripe webhook is wired, the Trial Converted goal fires with the original UTM/source attached. Filter your /marketing-acquisition dashboard by goal=Trial Converted and you'll see channel-level conversion rates, cost per converted trial, and the LTV curve segmented by acquisition source.

How does Admaxxer fire Trial Converted automatically?

For a card-on-file trial, the Stripe webhook handler watches for customer.subscription.updated events where previous_attributes.status === 'trialing' and the new status === 'active'. That transition is uniquely "trial just converted to paid" in Stripe's data model. Admaxxer fires the Trial Converted goal with the original visitor_id (via admx_visitor_id metadata or email-hash fallback) and the subscription amount. No merchant-side webhook code needed. For a no-card / app-side trial (Stripe never sees it), fire window.admaxxer('Trial Converted', { plan }) from the pixel on upgrade — the identical name lands in the same funnel tile.

What happens if my user signs up but never starts a trial?

The Signup goal fires from the form-success handler regardless of what happens next. If the user doesn't go on to start a trial, you'll see them in the Signup goal funnel but not the Trial Started funnel — useful for measuring the drop-off between signup and trial start. Pair with /marketing-acquisition to see which channels produce signups that DON'T trial.

Ready to ship?

Start with the pixel snippet (step 1 above), then wire the Signup goal (step 2), then add the Stripe webhook (step 4). Each step is independently verifiable in Pixel › Realtime. Once all five wiring points are live, your /marketing-acquisition dashboard will show per-channel signup → trial → paid conversion rates, MRR by source, and churn-by-cohort — without writing any webhook handler code on your side.

Connect Stripe · Universal script install · npm package install · Browse all install paths