# Record your own events

An event is one thing that happened on your site: a page was viewed, a plan was chosen, a form was sent. The script records page views on its own; everything else you record yourself, with one call.

## Record an event

The snippet defines a global `lt` function. Call it with the `track` command, anywhere on your site after the snippet:

```js
lt('track', 'signup_completed');
lt('track', 'signup_completed', { plan: 'pro', seats: 5 });
```

The second argument is the event name and is required — a call without one is ignored. The third is an optional object of properties. Nothing else is needed: you do not register the name anywhere first, and there is no configuration to change when you start sending a new one.

The event shows up in `Live` within seconds and in the catalog on the `Events` page the first time it arrives.

Two things the call guarantees. It never throws into your page: a bad argument, or a failure inside the script, leaves your site running and the event simply missing. And calls made before the script has finished loading are queued by the snippet and recorded once it is ready — as long as you use the loader snippet rather than the [minimal variant](/acquire/tracking/install).

One more command exists: `lt('flush')` sends whatever is buffered immediately instead of waiting for the next batch. You rarely need it — the script already flushes when the page is hidden or left.

## Name events so they stay readable

Names are free-form text, compared exactly. `Signup` and `signup` are two different events. The name is the only thing that groups events together in the catalog and in the reports, so pick a convention and keep to it:

- One style for everything, for example lower case with underscores — `signup_completed`, `checkout_started`, `plan_upgraded`.
- The thing that happened, not where the button was.
- A stable name. Renaming an event splits its history into two entries.

## Attach properties

Properties are the details of the event: which plan, which currency, how many seats. Pass any JSON object; keys and values are stored as sent.

```js
lt('track', 'checkout_started', { plan: 'pro', seats: 5, coupon: 'SPRING' });
```

Keep them small. The script flushes a batch early once it approaches 60 KB, and a batch over 64 KB is refused outright — so a few short values per event is the shape that works. Do not put passwords, card numbers or anything else you would not want stored in a property.

Some details you never pass yourself. Every event already carries the page URL, the referrer and the page title, and Tracking derives from the URL:

- `utm_source`, `utm_medium`, `utm_campaign`, `utm_term` and `utm_content`;
- the advertising click id, when one is present — `gclid`, `fbclid`, `msclkid`, `ttclid`, `twclid`, `li_fat_id`, `dclid`, `gbraid` or `wbraid`. Only the first one found is kept, in that order.

The visitor's country is added on arrival.

On a site that asks visitors for consent, a visitor who declines still produces events — they simply carry less, and the properties above are dropped. See [What tracking does with a consent answer](/privacy/tracking-consent).

## Check what you are actually sending

**The catalog.** The `Events` page lists every event name your sites have sent. It fills itself: a name appears the first time an event with it arrives. Each row shows the name, how many times it has been seen, when it was last seen, a sample of the property keys used with it (up to 50 keys per name) and its status. You can search by name and sort by count, last seen, first seen or name, in either direction; tabs split the list into `All`, `Active` and `Archived`.

A typo in a name shows up here as a near-duplicate row with a small count.

**MCP.** [`tracking_event-names_list`](/reference/mcp-tools/tracking_event-names_list) returns the same catalog to an agent.

**The live feed.** `Live` shows events as they land, newest first, refreshed every 5 seconds. Each row carries the event name, when it arrived, the page it happened on, where the visitor came from and which visitor it was; you can narrow the stream to a single domain. The stream pauses while the browser tab is in the background and resumes when you come back to it. To an agent, the same stream is [`tracking_live-events_list`](/reference/mcp-tools/tracking_live-events_list).

`Live` is the right tool while you are wiring events up. For counts over time use [the reports](/acquire/tracking/reports); for the list of names use the catalog.

## Hide a name you no longer want to see

`Archive` on the `Events` page hides a name from the dashboards. Nothing else changes: the events keep arriving, the counts keep rising, nothing is deleted, and `Unarchive` brings the name back. It is how you get rid of noise — a name from an old experiment, or one you sent by mistake — without losing data.

**MCP.** [`tracking_event-names_archive`](/reference/mcp-tools/tracking_event-names_archive) and [`tracking_event-names_unarchive`](/reference/mcp-tools/tracking_event-names_unarchive).

## Send events from your backend instead

Some things never happen in a browser: a payment confirmed by your billing provider, a subscription that ended, a trial started by an operator. Those you send from your own backend, over HTTP, authenticated with a tracking API key.

That route is deliberately narrow. It accepts a batch of up to 500 events under an `events` key, and the event name has to be one of six lifecycle names Tracking understands: `signup`, `trial_started`, `subscription_started`, `payment`, `refund` and `churn`. `payment` and `refund` must carry an amount. Free-form names belong in the browser call above.

Every event in the batch carries an `event_id` that you choose, and the response tells you how many events were accepted, how many were duplicates, and which ones were rejected and why — so a retry after a timeout is safe. See [Send revenue from your backend](/acquire/tracking/revenue) for the fields, [Send events from your backend](/acquire/tracking/api-keys) for the key, and the [Tracking API reference](/reference/openapi/tracking) for the route itself.

## What does not arrive

Not everything sent is stored, on purpose:

| Dropped | Why |
|---|---|
| Bot traffic | Events from browsers whose user agent identifies a bot, crawler, preview fetcher or headless automation tool are dropped on arrival — they never reach the catalog, the reports or `Live`. Events sent with an API key are not filtered this way; the key already proves who is calling. |
| Duplicates | A backend event whose `event_id` has been seen before for your product is counted as a duplicate and stored once. In the browser, the script prevents the obvious repeats itself: the same route is not counted as two page views, and the same identity submitted twice in a row is recorded once. |
| Implausible timestamps | An event dated more than two days in the future, or more than 90 days in the past, is dropped — it can only come from a broken clock. |
| Oversized batches | A batch larger than 64 KB is refused. |

## Next steps

- [Identify a visitor](/acquire/tracking/identity): turn an anonymous browser into a named person.
- [Send revenue from your backend](/acquire/tracking/revenue): the six lifecycle events and the fields that carry money.
- [Read the attribution reports](/acquire/tracking/reports): what the events you send add up to.
- [Fix events that never arrive](/acquire/tracking/troubleshooting): symptom by symptom.
