Custom events
window.stepmetrics('signup');
window.stepmetrics('download');
window.stepmetrics('purchase', { plan: 'pro' }, { revenueCents: 2900, currency: 'USD' }); Money is passed in minor units so it stays integral all the way through.
Every custom event a site records appears on the dashboard's conversions row by itself,
busiest first, for as long as it is firing. To keep a name on the row at zero - so a
conversion that stops is seen to have stopped rather than quietly leaving - pin it in the
site's conversions configuration, in the order you want it shown.
Each metric can carry a description of what it measures, written from the tile itself on the
dashboard or as metricDescriptions in the site's configuration. It shows under the
number, truncated, and in full on click, and every API and MCP response that returns the metric
returns the description with it, so an agent reading "downloads" is told what a download is.
Across sites the dashboard keeps each site's conversions in its own row, because a
download on two sites can mean two different things.
Recorded without code
The tag records some things on its own. A click on a link to another site is recorded as
$outbound, and a click on a link to a file - a PDF, a zip, an installer, or any link
with a download attribute - as $download. Only the address is kept,
without its query string, since that is where signed links and tokens live. Put
data-sm-ignore on a link, or on anything around it, to leave it out. These show on
their own cards rather than among your conversions, unless you pin one.
Each page view also carries the page's title and, where the browser reports it, the HTTP status it was served with, which is what the Pages not found card is built from. Chrome, Edge and Firefox report it; Safari does not. A page can say so itself, and a single-page app's not-found route has to:
<meta name="stepmetrics:status" content="404"> A signed-in person
Give the tracker your own id for the person who is signed in, and the Visitors page shows it on
each of their visits and lists their other browsers and devices together. Use your internal
id, never an email address: one is refused. Call it on every page while they are signed in,
and with null when they sign out. It is kept in memory for the page, not stored in
the browser.
window.stepmetrics.identify('user_8213');
// Or before the script has loaded, through the usual queue:
window.stepmetrics = window.stepmetrics || function () {
(window.stepmetrics.q = window.stepmetrics.q || []).push(arguments);
};
window.stepmetrics('$identify', 'user_8213'); From a server
Some conversions are only known to your backend: a purchase confirmed by a payment webhook, an account created by a signup handler. Post them to the same endpoint and declare that a server is reporting, so the reporter's user agent is not read as a crawler.
POST https://app.stepmetrics.co/v1/collect
content-type: application/json
{
"siteId": "YOUR_SITE_ID",
"name": "purchase",
"path": "/checkout",
"client": { "platform": "server" },
"props": { "plan": "pro" },
"revenueCents": 2900,
"currency": "USD"
} The count and the revenue are exact, and the event appears in conversions and the event stream. It is not a visit and not a visitor: nobody loaded a page, so it never adds to your visits, visitors or bounce rate. What a server cannot supply is the buyer's browser, so the event does not join the visit that started on your pricing page. Attribute across that gap from your billing records, where the durable identifier is the order.
A server sends custom events only. A pageview declared from a server is refused with a 400, because nobody saw a page.