What a goal lets you do
A goal counts one action, so you can see how many visitors took it, which AI sources brought those visitors, and how many of them went on to pay. A goal is a custom event.traceten.track("demo_booked"), the HTML attribute data-traceten-goal="demo_booked", and the server SDK call goal("demo_booked") all send the same event, and it shows up on the Goals page either way. There is nothing to create in advance: the first time an event arrives with a new name, Traceten registers it as a goal automatically.
The four ways to fire a goal
Click: data-traceten-goal
Put the goal name on the element:
signup goal. Details:
- Clicks on children count. The snippet walks up to 6 ancestor levels from whatever was clicked, so a click on the
<span>inside the button still fires the goal. - Repeat clicks within 1 second are deduplicated, per element, so a double-click is one goal.
data-tracetenanddata-traceten-goalare independent. An element can carry both, and adata-traceten-goalon an inner element does not suppress adata-tracetenlabel on an outer one, or the reverse.- The nearest
data-traceten-goalwins. One click fires at most one goal. If that nearest name is invalid or reserved nothing fires, because an outer element’s valid goal is not used as a fallback. This matches how the nearestdata-tracetenlabel wins. - A goal and a click label are found in the same pass, in either order. One click on a
data-traceten-goalbutton inside adata-tracetenwrapper records both: the goal, and the click event carrying the wrapper’s label. Two events, two different endpoints, both billable. If you only want one, do not put both attributes on the same path.
Properties: data-traceten-goal-*
Any attribute starting with data-traceten-goal- becomes a property. The suffix is converted from kebab-case to snake_case:
signup with { "plan_type": "pro", "seats": "5" }.
Properties work the same way on a scroll goal, on the same element as the data-traceten-scroll attribute.
Server SDK: goal()
goal() exists in the Node, Python, and Go SDKs. It takes the same arguments as track() and posts to the same endpoint. The one difference is that it rejects the reserved names below.
visitorId is required. A server has no Traceten cookie to read, so without it the goal cannot be connected to the visit that produced it.
Goal name rules
A goal name must match^[a-z][a-z0-9_]*$:
Valid:
signup, demo_booked, scroll_to_pricing, trial_started_v2.
Invalid: Signup (uppercase), demo-booked (hyphen), 2fa_enabled (leading digit), _internal (leading underscore).
An invalid name is dropped silently in the browser. The snippet checks the name before sending, so a typo in an attribute costs you the goal but never sends a bad request, and never affects any other event batched alongside it. In the server SDKs an invalid name throws immediately instead, because a server-side typo is a bug you want to see in development.
Names are case-sensitive and are never normalised. Signup is not a mis-spelled signup; it is rejected.
Reserved names
These eleven names belong to the Stripe and Shopify integrations, which emit them for real payment and subscription events:payment, free_trial, trial_started, trial_converted, subscription_started, subscription_upgraded, subscription_downgraded, subscription_renewed, subscription_cancel_scheduled, subscription_reactivated, subscription_ended.
data-traceten-goal, data-traceten-scroll, and the SDK goal() all refuse them. If your own subscription_started mixed into the one Stripe sends, your revenue funnel would be counting two different things under one name, and nothing would look broken.
traceten.track() and the SDK track() still accept them, because that is how those events are legitimately sent.
Property rules
Properties are a flat object of strings, numbers, and booleans.
The browser limits are the snippet’s own; the server limits are what ingestion enforces on every path, browser included. In other words a browser goal is clamped twice, at 10 keys and 200 characters.
Fewer than 10 properties can survive. The snippet takes the first 10 attributes in document order and then drops any whose key is longer than 40 characters, so ten long-keyed attributes can crowd out short-keyed ones that would otherwise have been kept. If a property matters, put it early and keep its name short.
For the HTML triggers, note that the
data-traceten-goal- prefix is not part of the key: only the part after it counts toward the 40 characters.
The reserved property keys value_cents and currency count against the browser’s 10-key budget. They are lifted out of the properties and recorded as the goal’s revenue. See the browser API reference.
An HTML attribute cannot record revenue. Attribute values are always strings, and value_cents is only read as revenue when it is a number. data-traceten-goal-value-cents="4900" is stored as an ordinary property called value_cents holding the string "4900", and the goal carries no revenue. To attach revenue, call traceten.track() with a number, or send it from a server SDK.
currency behaves differently from value_cents here: a 3-character string is promoted to the top-level currency field and removed from the properties, so data-traceten-goal-currency="usd" does take effect. The code is uppercased at ingestion and stored as USD, so the case you write does not matter. Anything that is not exactly 3 characters stays an ordinary property, because the ingestion endpoint rejects a currency of any other length and the whole goal would be lost.
What happens to property keys and values, exactly
Goal property keys and values are both stored, and both are read back.GET /v1/goals/{name}/properties returns every key you have sent with a goal and that key’s most common values, and each goal completion on a visitor’s timeline lists the properties sent with it. Anything you put in a property, on either side, is something you and your team will read later.
Before storage, Traceten does two things to a property:
- Drops the property outright when its key is exactly
email,phone,name,password,token,ssn,credit_cardorcard_number. - Redacts patterns inside string values: email addresses, phone numbers, card-shaped digit runs, and national ID numbers are replaced with a redaction marker.
- Keys are never scanned, only matched against those eight names. A property key is stored and displayed exactly as sent. This is worth pausing on for the HTML triggers, where the key comes from the attribute name:
data-traceten-goal-alice-chen="1"stores a property key ofalice_chen. - The key list is an exact match, and only that.
full_name,customer_nameandbilling_addressare not on it and are kept. - There is no pattern for a personal name or a postal address, because no reliable one exists. A value of
"Alice Chen"or"14 Rue de Rivoli"is stored and returned as written.
POST /v1/ingest/conversions, the authenticated account API used by the data tooling rather than by the snippet or the SDKs, applies the value redaction but does not drop the eight key names.
So: put plans, tiers, page names, categories and A/B variants in properties. Do not put anything that identifies a person, in a key or in a value. Traceten cannot detect that for you.
When a goal is not recorded
Nothing is sent when any of the following is true:- The visitor denied consent, or your site uses
consentDefault: "pending"and consent is still unresolved. See the consent guide. - The visitor has Do Not Track enabled, or the
_traceten_optoutcookie set. - The goal name is invalid or reserved.
- For scroll goals only: the browser has no
IntersectionObserver. See scroll tracking. - For click goals only: the page has been restored from the browser’s back/forward cache. The click listener is removed when the page is hidden and is not reinstalled on restore, which is pre-existing behaviour of
data-tracetenclick tracking that goal clicks inherit. Scroll goals are unaffected and keep working on a restored page. - For both HTML triggers: a consent tool on the page has not answered yet. The click listener and the scroll observer are installed only once consent resolves, so a click or a scroll in the window before your CMP decides records nothing. A
traceten.track()call in that same window is kept, because an explicit API call is treated as intentional while the decision is in flight. But “kept” means buffered in memory, not sent immediately: no cookie is written and nothing reaches the server until consent resolves. See Consent behavior.
traceten.track() from your own click handler instead of using the attribute.
There is also a ceiling on automatic goal registration: 200 distinct names per site. It exists because the goal name is a free-text field on a public endpoint, so signup_<random> would otherwise mint registry rows forever. Past 200, a newly seen name stops being registered automatically, though the events themselves are still recorded and still counted. Archive goals you no longer need to make room.
The ceiling applies to that automatic discovery only. Creating a goal explicitly through POST /v1/goals is not capped, so a site can hold more than 200 goals if you declare them deliberately.
What is stored, and for how long
A goal completion is stored as a conversion record for 365 days: the goal name, the properties you attached, and the pseudonymous visitor and session identifiers. Each one also writes a narrow visitor step row, retained for 730 days, holding the goal name and the page path but no property values. Both are removed by a deletion request. The full retention table, and exactly what is scanned or dropped on the way in, is in data collected.Verify it worked
- Open your site with the browser devtools Network tab filtered to
conversions. - Click the element, or scroll to it.
- You should see one
POSTtohttps://ingest.traceten.com/v1/conversionswith your goal name in the body.
In the dashboard
Open Goals in the sidebar. The chart starts with the six goals that have the most completions. Add or remove any goal with the Goals menu above it; there is no limit. The legend shows each goal’s emoji beside its line colour. The date picker reaches back up to 365 days. Under the chart, a table lists your goals with their completions and the last day each one was completed in the date range, most completions first, fifty to a page. Search it by name, and switch between three filters. Goals, the default, lists your own goals. System lists every reserved goal, such aspayment. System goals appear only under this filter, and they are read-only. Archived lists the goals you archived.
Select a row to put that goal on the chart and list the visitors who completed it below the table, 50 people a page, in the same rows as the Visitors page. Each row’s menu has View visitors, and for your own goals Edit (rename it or give it an emoji, which is optional and must be a single emoji) and Archive, or Restore under Archived. A goal’s position in the table has no effect on how it is recorded.
The visitor list has one row per person, while a goal’s count counts every completion, so the list is usually shorter. Click a visitor to open their timeline, where each goal completion lists its properties.
Next
- Scroll tracking: fire a goal when a section comes into view.
- Funnels: put goals in sequence and see where visitors drop off.
- Goals API: rename, reorder, and archive goals over HTTP.

