"}]},{"@type":"HowToStep","position":2,"name":"Fire a Signup goal when the form succeeds","text":"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: 'free', source: 'organic' }). Either way, “Signup” appears as a goal in the dashboard within ~5 seconds.","itemListElement":[{"@type":"HowToDirection","text":"// Programmatic — preferred when you have rich metadata:\nfunction onSignupSuccess(user) {\n window.admaxxer('Signup', {\n plan: user.plan,\n source: getQueryParam('utm_source'),\n });\n}"}]},{"@type":"HowToStep","position":3,"name":"Identify the user post-signup","text":"After authenticating, call window.admaxxer.identify(userId, { email, plan }). This stitches the anonymous visitor to the named user — all downstream Stripe events tie back to the original session/source/medium.","itemListElement":[{"@type":"HowToDirection","text":"window.admaxxer.identify(user.id, {\n email: user.email,\n plan: user.plan,\n signed_up_at: user.createdAt,\n});"}]},{"@type":"HowToStep","position":4,"name":"Wire the Stripe webhook","text":"Connect Stripe in /integrations and add a webhook endpoint at https://admaxxer.com/api/pixel/webhooks/stripe/:website_id subscribed to: checkout.session.completed, payment_intent.succeeded, customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, customer.subscription.trial_will_end. Admaxxer fires “Trial Started”, “Trial Converted”, “Subscription Cancelled”, “Trial Ending Soon” goals automatically. See /documentation/install/stripe for restricted-key setup."},{"@type":"HowToStep","position":5,"name":"Pass admx_visitor_id to Stripe (optional but recommended)","text":"When creating a Stripe Checkout Session or Subscription server-side, pass the visitor ID as metadata so the conversion ties to the original anonymous session even if the user changes browsers or devices: { metadata: { admx_visitor_id: req.cookies.admx_visitor_id } } server-side, or window.admaxxer.getVisitorId() client-side. Without this, Admaxxer falls back to email-hash stitching (still works, slightly less accurate).","itemListElement":[{"@type":"HowToDirection","text":"// Client-side (Stripe.js):\nconst visitorId = window.admaxxer.getVisitorId();\nstripe.redirectToCheckout({\n sessionId: ...,\n // Pass at session-creation time on your server:\n});\n\n// Server-side (Node + Stripe SDK):\nconst session = await stripe.checkout.sessions.create({\n // ...your existing config\n metadata: { admx_visitor_id: req.cookies.admx_visitor_id || '' },\n});"}]},{"@type":"HowToStep","position":6,"name":"Verify the install","text":"Load any public page on your site in a fresh browser tab. Within a few seconds, the Admaxxer dashboard realtime view should show the event. If nothing lands after 2 minutes, re-check the snippet is actually in the rendered HTML <head> (View Source, not just DevTools)."}]}

Install guide · generic · ~8 min

Install Admaxxer on Custom SaaS website

Track signups → trials → MRR on your own SaaS site.

The end-to-end install for SaaS websites with signups, free trials, and recurring revenue. Five wiring points: (1) Pixel — paste script.plus.js in <head>. (2) Signup goal — fire when signup succeeds. (3) Identify — call window.admaxxer.identify() post-signup so anonymous visits stitch to the named account. (4) Stripe webhook — Admaxxer reads customer.subscription.* events and fires Trial Started / Trial Converted / Subscription Cancelled goals automatically. (5) Verify — place a $1 test trial and confirm all four events land in the dashboard.

Steps

  1. 1 Paste the pixel snippet

    Add script.plus.js (the Pro variant — auto-captures form submits, outbound clicks, file downloads, rage clicks) to your <head>. The Pro variant is recommended for SaaS because most signups are form-driven and the auto-form-submit goal saves you from wiring it manually.

    html
    <script defer
      data-website-id="YOUR_WEBSITE_ID"
      data-domain="yourdomain.com"
      src="https://admaxxer.com/js/script.plus.js"></script>
  2. 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: 'free', source: 'organic' }). Either way, “Signup” appears as a goal in the dashboard within ~5 seconds.

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

    After authenticating, call window.admaxxer.identify(userId, { email, plan }). This stitches the anonymous visitor to the named user — all downstream Stripe events tie back to the original session/source/medium.

    js
    window.admaxxer.identify(user.id, {
      email: user.email,
      plan: user.plan,
      signed_up_at: user.createdAt,
    });
  4. 4 Wire the Stripe webhook

    Connect Stripe in /integrations and add a webhook endpoint at https://admaxxer.com/api/pixel/webhooks/stripe/:website_id subscribed to: checkout.session.completed, payment_intent.succeeded, customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, customer.subscription.trial_will_end. Admaxxer fires “Trial Started”, “Trial Converted”, “Subscription Cancelled”, “Trial Ending Soon” goals automatically. See /documentation/install/stripe for restricted-key setup.

  5. 5 Pass admx_visitor_id to Stripe (optional but recommended)

    When creating a Stripe Checkout Session or Subscription server-side, pass the visitor ID as metadata so the conversion ties to the original anonymous session even if the user changes browsers or devices: { metadata: { admx_visitor_id: req.cookies.admx_visitor_id } } server-side, or window.admaxxer.getVisitorId() client-side. Without this, Admaxxer falls back to email-hash stitching (still works, slightly less accurate).

    js
    // Client-side (Stripe.js):
    const visitorId = window.admaxxer.getVisitorId();
    stripe.redirectToCheckout({
      sessionId: ...,
      // Pass at session-creation time on your server:
    });
    
    // Server-side (Node + Stripe SDK):
    const session = await stripe.checkout.sessions.create({
      // ...your existing config
      metadata: { admx_visitor_id: req.cookies.admx_visitor_id || '' },
    });
  6. 6 Verify the install

    Load any public page on your site in a fresh browser tab. Within a few seconds, the Admaxxer dashboard realtime view should show the event. If nothing lands after 2 minutes, re-check the snippet is actually in the rendered HTML <head> (View Source, not just DevTools).

Verify installation

Troubleshooting

Signup goal fires but no &ldquo;Trial Started&rdquo; ever shows up.
Check your Stripe webhook is subscribed to customer.subscription.created (not just charge.succeeded). Trial starts move no money, so payment-only events miss them. In Stripe → Developers → Webhooks, click your Admaxxer endpoint and verify the event list includes all six recommended events.
Trial Converted never fires when my trial ends.
Stripe emits customer.subscription.updated with previous_attributes.status: 'trialing' and the new status: 'active' when a trial converts. Admaxxer reads both fields to detect the transition. If your trial never converts because the customer's card fails, you'll see customer.subscription.updated with status: 'past_due' instead — that fires no goal (intentional; we don't treat past_due as a conversion). Add a separate goal for failed-trial-conversion if you need that signal.
My identify call runs but the visitor still shows as anonymous.
identify() POSTs asynchronously and the dashboard's visitor view caches for ~30 seconds. Wait one full minute and refresh. If the visitor is still anonymous after that, check DevTools → Network for a POST /api/identify with status 200. If you see 4xx, your data-website-id doesn't match the Admaxxer site — verify it starts with admx_.
I'm using server-rendered Next.js — should I use the npm package or the script tag?
Either works, but the npm package (/install/npm) is cleaner because initAdmaxxer() is SSR-safe (no-ops server-side). For server-side identify (e.g. when you create the user account in your API route), call POST /api/identify directly with the visitor cookie value forwarded — see API docs.
My SaaS has annual + monthly plans. Does MRR computation handle that?
Yes. Admaxxer reads subscription.items.data[0].price.unit_amount and recurring.interval + interval_count and normalizes to monthly. A $1200/year subscription contributes $100 MRR; a $99/month subscription contributes $99. See /documentation/revenue/stripe for the canonical formula.