Goals and server events
Count outcomes your server confirms, like purchases and signups, with @littlefriend/node or one HTTPS call, kept apart from clicks in the browser.
A goal is an outcome you care about: a signup, a purchase, a booked demo.
Two kinds of goal
- Browser goals match an event or a page view, like a click on Buy or a visit to /thanks. They are quick to set up and good for intent.
- Server-confirmed goals count only events your server sends with a secret key. Browser clicks with the same name never count toward them. Use these for money and anything else that must be true.
Each goal definition has a version number, so you can tell when a goal's meaning changed.
Match on event properties
An event goal can also require up to 3 of the event's properties to equal values you choose. A goal on signup.completed with plan equal to pro counts pro signups only. Every condition must hold.
import { track } from '@littlefriend/tracker';
// After the signup really went through
track('signup.completed', { plan: 'pro', seats: 5 });littlefriend goals create --name "Pro signup" --event signup.completed --prop plan=pro- In the dashboard, edit an event goal on the Goals page and choose Add a property. With the CLI, repeat
--prop key=valuefor each condition.goals updatewith--propreplaces the conditions, and--no-propsremoves them. - Conditions read the properties Little Friend stored. A property the collector dropped never matches.
- Keys are lowercase snake case, up to 32 characters, each used once. Values are up to 64 characters, with spaces at either end dropped. A value that looks like an email address, phone number or card number is refused, because no event can carry one.
- Values compare as text:
seatsequal to5matches the number 5, andtrialequal totruematches true. Capitals count, soPromatches only Pro. - Page view goals take no conditions. An event goal without any matches on the event alone.
- Changing the conditions makes a new version of the goal. Counts already made keep the definition they were made with. Session timelines, funnel goal steps and flows use the current one.
Send outcomes from your server
Create a server key in Settings, Keys. It starts with lfs_. Keep it in an environment variable on your server, never in a browser.
From Node.js
@littlefriend/node checks, batches and retries events for you. It needs Node 18.17 or later.
pnpm add @littlefriend/nodeimport { LittleFriend } from '@littlefriend/node';
const lf = new LittleFriend({ key: process.env.LF_SERVER_KEY! });
// After the payment really went through
lf.track({
id: 'order_8812', // the same id on a resend counts once
name: 'purchase',
props: { plan: 'pro' },
value: { amount: 4900, currency: 'USD' },
});Events wait in memory and go out in batches. Call await lf.flush() before a serverless function returns, and await lf.shutdown() before a long-running process exits.
From any other language
Post a batch to /v1/server with your server key as a bearer token. A batch holds up to 50 events.
{
"v": 1,
"events": [
{
"id": "order_8812",
"name": "purchase",
"correlationId": "chk_4f1a9c2e7b3d8a61",
"value": {
"amount": 4900,
"currency": "USD"
},
"props": {
"plan": "pro"
}
}
]
}// After the payment really went through
const res = await fetch('https://in.littlefriend.io/v1/server', {
method: 'POST',
headers: {
authorization: `Bearer ${process.env.LF_SERVER_KEY}`,
'content-type': 'application/json',
},
body: JSON.stringify(batch),
});
if (res.status === 429 || res.status === 503) {
// Busy: wait Retry-After seconds, then send the same batch again
}Event fields
| Field | Required | What it is |
|---|---|---|
id | Yes | Your unique id for this outcome, like an order id: 8 to 32 letters, digits, _ or -. Keep it the same when you retry. @littlefriend/node makes one if you leave it out. |
name | Yes | The event name. Same rules as custom events, like purchase or signup.confirmed. |
at | No | When it happened, as epoch milliseconds or an ISO string. Defaults to when we receive it, or to when you call track in @littlefriend/node. |
route | No | The page it relates to, if any. |
correlationId | No | A shared id that links this outcome to a browser event, like a hash of the checkout id: 16 to 64 letters, digits, dashes or underscores. |
sessionId | No | Journey mode only: the session id handed from the browser, if you pass it along. |
props | No | Up to 8 properties, with the same limits as browser events. |
value | No | Money, in minor units: { amount: 4900, currency: "USD" } is $49.00. |
Responses
| Status | Meaning |
|---|---|
| 202 | Stored. The body is {"accepted": n, "dropped": m}, sent only after the write commits. |
| 429 or 503 | Busy. Wait for the number of seconds in Retry-After, then send the same batch again. |
| 400 | Malformed. Don't retry: fix the request first. |
| 401 | The key is missing, wrong or revoked. Don't retry until the key is fixed. |
| 413 | The body is over 64 KiB. Split the batch. |