Pageviews tell you where people went. Custom events tell you what they did: clicked “Get started”, finished signup, bought something, downloaded the PDF. Without them you can see traffic, but you can’t answer the question that matters: which pages and sources produce results?
This guide covers what’s worth tracking, how to name events so your reports stay readable, and copy-paste examples for plain HTML, React and Next.js. The code uses the Cool Analytics tracker, but the ideas apply to any tool.
What to track (and what not to)
A good event answers a business question. Start with a handful:
- Primary conversion. Signup, purchase, demo booked, lead form sent. You need this one.
- Key intent signals. Clicked a pricing plan, started checkout, opened the signup form.
- Off-site goals. Clicked a phone number, an email link, an app store badge or an affiliate link.
- Content goals. Downloaded a file, played a video, copied a code snippet.
Avoid tracking every click on everything. Fifty events nobody looks at make the five important ones harder to find. If you can’t say what decision an event would change, skip it.
One rule above all: never put personal data in events. No emails, names, phone numbers or user IDs from your database in event names or properties. Use categories instead (plan: "pro", not email: "jane@…").
Naming events so they stay useful
Pick a convention once and write it down. A simple one that works well:
- lowercase with underscores or hyphens:
signup,checkout_started,pricing_plan_clicked - object then action, past tense:
video_played,file_downloaded - no variable data in the name:
plan_clickedwith aplanproperty, notplan_pro_clicked,plan_team_clicked…
The last rule matters most. Put variations in properties, and the event list stays short while the detail is still there.
The basic call
Once the tracker is on your site, it exposes a small API on window.hb:
// An event with just a name
hb.track("newsletter_joined");
// An event with properties
hb.track("plan_clicked", { plan: "pro", billing: "yearly" });
// A purchase: an "amount" property is treated as revenue
hb.track("purchase", { amount: 49, currency: "USD" });A few details worth knowing:
- Events are attached to the current page and to the visitor’s journey, so you can see which page someone was on and which source originally brought them.
- An event whose properties include
amount(orrevenue,valueorprice) with a positive number is shown as revenue. - Event names that look like a signup (
signup,sign_up,register,create_account) are recognised as signups. - Names are capped at 80 characters and properties at about 500 characters of JSON, which is plenty for a few labels.
The script loads with defer, so very early code may run before it exists. Use optional chaining to be safe: window.hb?.track("signup").
Plain HTML
For a button or link, an inline handler is the quickest option:
<a href="/signup" onclick="window.hb && hb.track('cta_clicked', { location: 'hero' })">
Start free trial
</a>For a form, track on submit:
<form id="contact">…</form>
<script>
document.getElementById("contact").addEventListener("submit", function () {
window.hb && hb.track("lead_sent", { form: "contact" });
});
</script>A tip for tracking many links without touching each one: add a data attribute and a single listener.
<a href="/whitepaper.pdf" data-event="file_downloaded" data-file="whitepaper">Download</a>
<script>
document.addEventListener("click", function (e) {
var el = e.target.closest("[data-event]");
if (!el || !window.hb) return;
var props = Object.assign({}, el.dataset);
delete props.event;
hb.track(el.dataset.event, props);
});
</script>React and Next.js
First, give TypeScript a type for the global so you get autocompletion and no errors:
// types/hb.d.ts
declare global {
interface Window {
hb?: { track: (name: string, props?: Record<string, unknown>) => void; ignore: () => void };
}
}
export {};Then a tiny helper you can import anywhere:
// lib/track.ts
export const track = (name: string, props?: Record<string, unknown>) => window.hb?.track(name, props);Use it in any client component:
"use client";
import { track } from "@/lib/track";
export function PricingButton({ plan }: { plan: string }) {
return (
<button onClick={() => track("plan_clicked", { plan })}>
Choose {plan}
</button>
);
}Route changes in Next.js, React Router, Vue Router and SvelteKit are tracked as pageviews automatically, so you only need track for actions. The Next.js installation guide shows where to put the script itself.
Tracking signups and purchases reliably
Client-side events can be lost if the page navigates away immediately, if the visitor has an aggressive blocker, or if the success happens on a different domain (like a hosted checkout). Some practical approaches:
- Track on the success page, not the button. Fire
purchaseon your “Thanks for your order” page, where you know the payment went through. Clicking “Pay” only shows intent. - Guard against double counting. If people can reload the thank-you page, store a flag in
sessionStorageafter the first event. - Send the real amount. Pass the order total in the currency you report in, so revenue per source is meaningful.
// On /thank-you
const key = "tracked_" + orderId;
if (!sessionStorage.getItem(key)) {
window.hb?.track("purchase", { amount: orderTotal, plan: planName });
sessionStorage.setItem(key, "1");
}Reading the results
Once events come in, the useful views are:
- Conversions by source. Which channels send people who sign up, not just people who visit? AI assistants and newsletters often punch above their weight.
- Conversions by entry page. Which landing pages start the journeys that convert?
- Individual journeys. Open a few converting visitors and read their path. You’ll spot the pages that do the persuading.
To turn counts into rates, divide conversions by visitors for the same segment. The free conversion rate calculator does this, and if you’re testing two versions of a page, the A/B test significance calculator tells you whether the difference is real. For paid campaigns, the ROAS calculator turns tracked revenue into return on ad spend.
A starter tracking plan
| Event | When | Properties |
|---|---|---|
signup | Account created (success screen) | plan |
purchase | Payment confirmed (thank-you page) | amount, plan |
plan_clicked | Pricing plan button clicked | plan, billing |
cta_clicked | Main call-to-action clicked | location |
lead_sent | Contact or demo form submitted | form |
Five events, all tied to decisions. Add more only when a question comes up that these can’t answer.