Tracking events
Sending custom events from your app, and the property limits the collector enforces.
Pageviews tell you people showed up. Custom events tell you what they did. This page covers sending them.
The basic call
lp.track('ticket_purchased', { ticket_type: 'vip', seats: 2 });
First argument is the event name. Second is an optional object of properties. That’s the whole API for events — see the SDK reference for the rest of the tracker.
The event goes out immediately. You don’t await it, and a failed request never breaks your page.
Event names
| Rule | Value |
|---|---|
| Allowed characters | letters, digits, ., _, - |
| Length | 1–64 characters |
Leading $ | Rejected with a console warning |
The $ prefix is reserved for built-in events. If you call lp.track('$something'), the tracker refuses to send it and warns in the console.
There are exactly two built-in events:
$pageview— fired on init, and again on SPA navigation.$identify— fired byidentify().
Everything else in your data is something you sent on purpose.
Properties
Properties are the details that make an event answerable later: which plan, how many seats, which variant. They show up in the events log row detail.
The collector enforces hard limits. Violating any one of these rejects the entire event — not just the offending property. The event is dropped, so it’s worth staying well inside the lines.
| Limit | Value |
|---|---|
| Max properties per event | 64 |
| Max key length | 64 characters |
| Key charset | letters, digits, _, ., - |
| Max string value length | 1024 characters |
| Max nesting depth | 3 |
| Max array/object entries | 32 |
| Max serialized JSON size | 8192 bytes |
In practice you’ll never come near these with a sensible event. If you’re bumping into the 8 KB ceiling, you’re probably shipping a whole API response as a property — send the three fields you’ll actually filter on instead.
Reserved $ property keys
$revenue and $currency are the only $-prefixed property keys allowed. Any other $-prefixed key rejects the whole event.
lp.track('order_completed', { $revenue: 49.00, $currency: 'USD' });
Revenue has its own page — see revenue tracking.
Best practices
These are the habits that separate data you trust from data you argue with.
Fire conversion events only after the action truly succeeds
Not on button click. Not on form submit. After the account is actually created.
// Wrong — counts people whose signup failed
form.addEventListener('submit', () => {
lp.track('account_created');
});
// Right — counts people who have an account
const res = await createAccount(payload);
if (res.ok) {
lp.track('account_created');
}
An inflated conversion number is worse than no number. It hides the exact failure — a broken signup — that you most need to see, because the graph keeps looking healthy while nobody gets in.
Prefer your backend for first-value events
First value means the user got something real. Your backend is where you know that for certain: the project was created, the export finished, the invite was accepted. Fire it from there, after the operation completes, using the server-side API.
Client-side first-value events fire when the UI thinks something worked. Backend events fire when it did.
Never send personal information
No emails, names, phone numbers, or addresses — not in event properties, and not in identify(). Use an internal user id.
// Don't
lp.track('plan_upgraded', { email: 'ana@example.com' });
// Do
lp.track('plan_upgraded', { user_id: 'u_8813', plan: 'pro' });
An internal id answers every question an email would, and it keeps personal data out of a system that doesn’t need it. See security for what LaunchPulse does and doesn’t store.
Be consistent, and treat names as permanent
Pick snake_case and stick to it. account_created, not accountCreated in one place and Account Created in another — they’re three different events as far as any analytics tool is concerned, and your funnel splits three ways.
A name is forever, or it needs an alias. Renaming an event in your code splits your history at the moment of deploy: old data under the old name, new data under the new one. That’s fixable — event aliases map the incoming name onto your configured name at query time, across all past and future data — but the cleanest path is to name it well the first time.
A quick check before you ship a name: would you understand it in six months, in a dropdown, with no context? If not, rename it now while it’s free.
Track the few events that change decisions
You’re capped at 12 extra tracked events in your project config, and that’s a feature. Most products have a handful of moments that matter: signup, first real use, upgrade, cancel. Instrument those properly instead of instrumenting everything badly.
Making events count
Sending an event isn’t the same as measuring it. Once your app is sending a name, go to Settings → Tracking and give it a job — conversion, first value, or one of the extra tracked events.
The Detected events panel there lists names your site is sending that aren’t mapped to anything yet, with one-click buttons to assign them. It’s the fastest way to close the loop after a deploy. See event aliases for how that panel works.
Because all of that is read-time configuration, you can send events now and decide what they mean later. Nothing is lost while you make up your mind.
Where to go next
- Conversions and activation — turn an event into your success metric
- Event aliases — reconcile names that don’t match
- Revenue tracking —
$revenueand$currency - SDK reference — every method on the tracker
- Events log — see what arrived