Identity from the server
Server events land on the same people as browser events. Which person comes down to the IDs you send; this page is the exact rules.
Updated
The one rule
Use the same user ID everywhere. The ID you pass to identify in the browser is the distinct_id you send from your server. Pick a stable internal ID, such as a database key, not an email address.
With distinct_id
An item with a distinct_id (and no anonymous_id) goes to the person who owns that ID:
- If a browser has already identified with it, the event joins that person, with all of their browser history.
- If no browser has yet, ClickClacks creates the person from the ID. When a browser later calls
identifywith the same ID, it’s linked to them, and its anonymous visits come along. The order doesn’t matter.
// Your webhook handler, after a payment succeeds
clickclacks.track({
event: 'Invoice paid',
distinctId: invoice.userId, // the same ID the browser passes to identify
insertId: `inv_${invoice.id}_paid`,
properties: { amount_cents: invoice.amountCents, $revenue: invoice.amountCents / 100, $currency: 'USD' },
})Identify from the server
An identify item sets a person’s traits from your backend, which is often the better place: your database knows their plan and seat count for certain. Traits come from the newest $identify, whichever side sent it. Identify calls are free.
{
"items": [
{
"type": "identify",
"distinct_id": "user_8412",
"properties": { "plan": "pro", "seats": 12, "company": "Acme" }
}
]
}With anonymous_id
Sometimes a server event belongs to a specific visit before anyone has signed in: a cart checkout, a form handled by your backend. Send the browser’s anonymous ID (per_…) as anonymous_id and the event lands on that browser’s person.
The tracker keeps it in local storage under ccp (and the session under ccs, in session storage). Pass them to your server with the request:
// In the browser: send the tracker's IDs along with a request.
// ccp (local storage) holds the anonymous ID; ccs (session storage) the session.
await fetch('/api/checkout', {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-clickclacks-anonymous-id': localStorage.getItem('ccp') ?? '',
'x-clickclacks-session-id': sessionStorage.getItem('ccs') ?? '',
},
body: JSON.stringify(cart),
})// On your server: attach the event to that browser, and its session.
const anonymousId = req.headers['x-clickclacks-anonymous-id']
const sessionId = req.headers['x-clickclacks-session-id']
clickclacks.track({
event: 'Checkout started',
distinctId: user?.id, // when signed in
anonymousId: /^per_/.test(anonymousId) ? anonymousId : undefined,
sessionId: /^ses_/.test(sessionId) ? sessionId : undefined,
properties: { items: cart.items.length },
})- An
identifyitem with bothdistinct_idandanonymous_idlinks that browser to the ID, exactly as callingidentifyin the browser would. - A browser that already belongs to an ID isn’t moved to a different one.
- An anonymous ID that doesn’t start with
per_is refused withinvalid_anonymous_id. Before the tracker has run, or after the visitor opted out, there isn’t one: leave the field out.
Sessions
A server event without a session_id belongs to no browser session, so it never creates or lengthens one and never changes session counts. Pass the browser’s ses_… ID when the event is part of a visit and you want it in that session’s path, as in the example above.
Timing
Links between IDs are recorded just after the request is accepted, so a person’s timeline can take a moment to show a brand-new link. Reports read identity when they run, so a later identify also joins events that were sent before it.
Next steps
- Send events: the item fields in full.
- Identify people: the browser side, and reset on sign-out.
- Node SDK: the same fields in camelCase.