Install tracking
The tracking snippet reports pageviews, custom events, scroll depth, and time-on-page. Everything downstream — CONSIDERATION counts, splits by UTM/device/country, email identity — depends on it.
:::tip The two-step install
- Paste the snippet (this page, sections 1–4) — gives you anonymous traffic, country/device/UTM splits, and engagement metrics.
- Add one
identify()call (section 5) — links anonymous visitors to their email when they sign up, so first-touch attribution survives all the way to the paid purchase. Without this, FunnelFizz can't tell that the customer who paid today first visited 30 days ago via Twitter.
Both steps are necessary to get the value FunnelFizz is built for. :::
1. Grab the snippet
The onboarding wizard (or Settings → Tracking sites) gives you a snippet:
<!-- FunnelFizz tracking -->
<meta name="funnelfizz-verify" content="YOUR_TOKEN" />
<script>
(function(w,d,t){
w._fn = w._fn || [];
w.funnelfizz = function(){ w._fn.push(arguments) };
var s = d.createElement('script');
s.async = 1;
s.src = 'https://funnelfizz.com/track.js?t=' + t;
d.head.appendChild(s);
})(window, document, 'YOUR_TOKEN');
</script>
YOUR_TOKEN is unique to your site.
2. Paste it into <head>
As high in <head> as possible, before other scripts.
Next.js (App Router)
import Script from 'next/script';
export default function RootLayout({ children }) {
return (
<html>
<head>
<meta name="funnelfizz-verify" content="YOUR_TOKEN" />
</head>
<body>
<Script id="funnelfizz" strategy="afterInteractive">
{`(function(w,d,t){w._fn=w._fn||[];w.funnelfizz=function(){w._fn.push(arguments)};var s=d.createElement('script');s.async=1;s.src='https://funnelfizz.com/track.js?t='+t;d.head.appendChild(s)})(window,document,'YOUR_TOKEN');`}
</Script>
{children}
</body>
</html>
);
}
Astro / Remix / SvelteKit
In your root layout, add the meta + script tags inside <head>. All three frameworks support raw HTML in layouts.
WordPress
Use any "Insert Headers and Footers" plugin → paste into the Scripts in Header box. Or edit header.php inside <head>.
Webflow / Framer / Squarespace / Shopify
Site-wide Custom Code → Head. Paste both tags. Publish.
Plain HTML
Paste into <head> of every page (or a shared include).
3. Multiple sites?
Paste the same snippet into each one (your main site, app, docs, blog).
To track the same visitor as one person across example.com and app.example.com, the cookie has to be set at the apex.
For flat 2-label apex domains (example.com, myapp.io, acme.dev), the script auto-detects the apex and sets the cookie domain to .example.com automatically — no flag needed.
For multi-label TLDs (example.co.uk, mysite.com.au), the auto-detect plays it safe and stays host-only. Set data-cookie-domain explicitly:
<script
src="https://funnelfizz.com/track.js?t=YOUR_TOKEN"
data-cookie-domain=".example.co.uk"
async
></script>
If you want subdomains tracked separately, set data-cookie-domain="host" to opt out of auto-detect — each origin gets its own visitor ID.
4. Verify
Visit your live site. The wizard auto-advances within ~5s once it sees the first event. Verification is host-agnostic, any URL with the snippet flips it to verified.
If nothing happens:
- View source, confirm
funnelfizz-verifyandtrack.jsare actually on the page. - Confirm
YOUR_TOKENwas replaced with the real token in both the<meta>and<script>. - DevTools → Network → filter
tracking, you should see a POST tofunnelfizz.com/api/tracking/…. - Disable ad-blockers / privacy extensions for the test.
5. Identify your users — the line that makes attribution actually work
The snippet alone gives you anonymous traffic stats. To get the thing FunnelFizz is built for — "this paying customer first visited via Twitter 30 days ago" — you need ONE more line of code at the moment you first capture an email.
Why one line is needed
The cookie identifies the browser, not the person. When Stripe (or your billing system, or your signup webhook) tells FunnelFizz "Jane just paid", the system has Jane's email but no link from [email protected] to the anonymous visitor record from 30 days ago. Without that link, you get accurate anonymous traffic and accurate revenue numbers — but no chain between them.
identify() is what builds the link. You only need to call it once per session, the first time you have the user's email or userId.
The line
// Anywhere you collect an email — signup form, waitlist, free-tier registration, lead capture:
// If you also have an internal user ID (after auth), include it:
That's it. From this point, FunnelFizz knows visitorId → email, and any future Stripe payment, email click, or app event with that email merges back into the original visitor record — preserving firstSource, firstUtm, firstReferrer, and the full session history.
Where to put it
| Scenario | Where to call identify() |
|---|---|
| Signup form on your marketing site | Right after a successful submit, with the email the user typed |
| Free-tier signup with no card | After the auth handler returns, with userId + email |
| Email-gated content (waitlist, free download) | At submit, before the redirect / thank-you |
| OAuth login (Google, GitHub) | In your post-login callback, with the resolved user info |
| App page that requires auth | At app boot, on every authenticated page (idempotent — safe to call repeatedly) |
The earlier in the funnel you call it, the more of the journey gets attributed. Calling it at signup is enough; calling it on every authenticated page is even safer.
Don't have an email until checkout?
If your only email-capture point is Stripe Checkout (no signup form, no lead capture before payment), pass the visitor ID through Stripe instead — the webhook handler will match it back:
// Read the visitor ID directly from the cookie set by the tracker.
function getCookie(name) {
const m = document.cookie.match(new RegExp('(?:^|; )' + name + '=([^;]*)'));
return m ? decodeURIComponent(m[1]) : null;
}
const visitorId = getCookie('_fn_vid');
const session = await stripe.checkout.sessions.create({
// ... your normal session config ...
client_reference_id: visitorId,
});
FunnelFizz's Stripe webhook handler picks up client_reference_id and stitches the customer back to the visitor automatically.
6. Custom events
Once loaded, window.funnelfizz() is global. Fire events from anywhere:
funnelfizz('event', 'signup');
funnelfizz('event', 'form_submit', { formId: 'pricing-contact' });
Or mark any element as a goal:
<a href="/signup" data-event="signup_click">Start free trial</a>
Events are filterable in splits and conversions. See Tutorials → Tracking & custom events.
7. What the script sends
Events sent automatically:
| Event | When |
|---|---|
pageview | Every page load + SPA route changes |
leave | Page unload, with timeOnPage + scrollDepth |
scroll | 25 / 50 / 75 / 90% milestones |
goal | Click on any [data-event] element |
outbound_click | Click on a link that leaves the tracked origin |
form_start | First focus on a <form> field |
form_submit | <form> submit |
stripe_checkout | Click on a link to a checkout.stripe.com URL |
identify | Your funnelfizz('identify', …) calls |
First-class typed events
These names are recognized by the tracking ingest and route through stage-progression logic — fire them by name (don't translate to underscores or other variants):
| Fire | What it does |
|---|---|
funnelfizz('event', 'signup') | Records a signup; advances the funnel link to CONSIDERATION if the profile isn't already further along |
funnelfizz('event', 'trial_signup') | Advances the funnel link to TRIAL (non-Stripe trials) |
funnelfizz('event', 'trial_start') | Same as trial_signup — alias |
funnelfizz('identify', { email }) | Bridges the visitor to a profile and unlocks PII visibility on the funnel |
Anything else you fire via funnelfizz('event', name, props) lands as a custom event with props.goal = name. Custom events show on the profile timeline, can drive split conditions, and are queryable from MCP.
Events batch every 3s; sendBeacon on unload prevents drops.
Privacy
- Honors
navigator.doNotTrack, DNT visitors send nothing. - No cross-site tracking, no fingerprinting, no data sale.
- For GDPR/CCPA, defer the snippet until your CMP grants "analytics" consent.
Next: Connect Stripe →