Event tracking recipes
Copy-paste event tracking for signups, checkout funnels, file downloads, outbound links, 404 pages, scroll milestones, video, A/B tests and user plans.
Signup#
On the page after a successful signup (or in your signup success handler):
privatus.track('Signup', { plan: 'free', method: 'email' })
Then create an Event goal named Signup.
Checkout funnel#
privatus.track('Checkout started', { items: cart.items.length })
// …
privatus.track('Payment details entered')
// on the confirmation page:
privatus.track('Purchase', { revenue: order.total, currency: order.currency, items: order.items.length })
Build a funnel with the three events as steps. Never send order ids, emails or addresses.
File downloads#
Automatically, with the auto module (File Download events with file
and host properties):
<script defer src="https://privatusstats.com/js/pa.js" data-site="pa_YOURSITEID" data-modules="engage,auto,vitals,clicks,ask"></script>
Or for a single link:
<a href="/files/price-list.pdf" data-privatus-event="Price list download">Price list (PDF)</a>
A File download goal counts the File Download events of the auto
module, matched on the file name.
Outbound links#
The auto module sends Outbound Link with domain and url. Create an
Outbound click goal for a specific domain, e.g. your app's signup page
on another domain. It also sends Email Link for mailto: links, Phone
Link for tel: links and Form Submit for forms.
404 pages#
The auto module sends a 404 event when the page title contains "404"
or "not found". If your title says neither, add this to your 404 template:
<meta name="privatus-404" content="true">
Then look at the 404 event's path property in the
Events explorer to find broken links. Without
auto:
<script>
window.addEventListener('load', function () {
privatus.track('404', { path: location.pathname });
});
</script>
Scroll milestones#
The engage module already reports scroll depth for every page. For an
event at a specific point (e.g. the end of an article):
const end = document.querySelector('#article-end')
new IntersectionObserver((entries, observer) => {
if (entries.some((e) => e.isIntersecting)) {
privatus.track('Article finished', { section: 'blog' })
observer.disconnect()
}
}).observe(end)
Or create an Engagement goal (scroll depth of at least 75% on
/blog/*) with no code.
Video#
HTML5 video:
document.querySelectorAll('video').forEach((video) => {
const name = video.dataset.name || video.currentSrc.split('/').pop()
video.addEventListener('play', () => privatus.track('Video play', { video: name }), { once: true })
video.addEventListener('ended', () => privatus.track('Video complete', { video: name }))
})
YouTube embeds need the YouTube IFrame API: listen to onStateChange and
track YT.PlayerState.PLAYING and ENDED the same way.
Experiments (A/B tests)#
Privatus Analytics can run the test for you, with no code. If you run tests with your own tool instead, send the variant as a property on every hit:
privatus.props({ experiment: 'pricing_2026_09', variant: variant })
with manual mode if the variant isn't known until after load. The report for other tools (Business) takes the property and a goal, assigns each visit to the first variant it saw, and calculates the conversion rate per variant and its statistical significance against the control.
prop:variant breakdowns and filters look at custom events only, so they
cover the events sent after privatus.props(), not the pageviews.
Logged-in state or plan#
privatus.props({ logged_in: true, plan: 'business' })
Every later pageview and event of the page load carries the properties.
Break down or filter custom events by prop:plan.
Never send a user id: it would make visitors identifiable and defeat the cookieless design.