Link browser and backend events
Browser SDKs assign each visitor a persistent visitor ID and a session ID. Backend events from @nexly/node do not get those IDs automatically. To show payments, subscriptions, or job completions on the same timeline as pageviews and clicks, forward the browser visitor ID to your server once and attach it when you emit backend events.
Enable Backend in Settings → Ingest before sending Node.js events — the toggle allowlists the backend SDK channel automatically. See Choose an SDK for the full ingest setup.
How IDs work in the browser
The Web SDK stores two identifiers in the browser:
- Visitor ID — a persistent pseudonymous UUID in
localStorage(trk_visitor_id). One per browser and site origin; survives tab close and return visits. - Session ID — a UUID in
localStorage(trk_session_id) with a 30-minute inactivity timeout. A new session starts after idle timeout or when the timeout window expires.
Every browser event includes these IDs in context automatically, so you can filter and group by visitor or session in the dashboard.
With privacy mode enabled, the SDK does not store these identifiers; getVisitorId() returns an empty string unless you set an identifier yourself with setVisitorId(). To link backend events in privacy mode, supply your own stable identifier on both sides.
Backend events have no visitor or session ID unless you pass them explicitly.
Step 1 — Forward the visitor ID from the browser
After sign-up, login, or any moment when your app knows the signed-in user, read the Nexly visitor ID and send it to your own backend.
import { getVisitorId, getSessionId } from '@nexly/web'
await fetch('/api/link-visitor', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
visitorId: getVisitorId(),
sessionId: getSessionId(), // optional — only if you need session-level joins
}),
})
getVisitorId() and getSessionId() are exported from @nexly/web (and re-exported through @nexly/react-web and @nexly/next). They read the same IDs the SDK already attaches to every browser event.
Linking a visitor ID to your user record makes the relationship identifiable to your application. Apply the privacy notices, access controls, and lawful basis required for your use case.
Step 2 — Store the mapping on your server
Persist the incoming visitor ID next to your own user identifier — for example in a nexly_visitor_id column on your users table or in your session store.
app.post('/api/link-visitor', async (req, res) => {
const user = await getSessionUser(req)
const { visitorId } = req.body
await db.users.update({
where: { id: user.id },
data: { nexly_visitor_id: visitorId },
})
res.status(204).end()
})
Your application owns this mapping. Nexly does not infer it from cookies, JWTs, or webhooks.
Step 3 — Emit linked backend events
Initialize @nexly/node and pass the stored visitor ID through context on the generic event(...) API:
import { Nexly } from '@nexly/node'
const nexly = Nexly.init({
appId: process.env.NEXLY_APP_ID!,
key: process.env.NEXLY_INGEST_KEY!,
})
nexly.event({
name: 'subscription_created',
type: 'custom',
cdata: {
plan: 'pro',
amount_cents: 4900,
},
context: {
visitor_id: user.nexly_visitor_id,
},
})
Use nexly.event(...) when you need Nexly-known context fields such as visitor_id, session_id, or path. The shorthand customEvent(name, cdata) does not accept a visitor ID — it is for standalone backend metrics.
Optional: pass session_id in the same context object when you forwarded it in step 1 and want session-level joins. Most teams only need visitor_id.
Anonymous backend events
If you do not need user-level stitching, omit context entirely:
nexly.customEvent('invoice_paid', {
amount_cents: 12000,
currency: 'USD',
})
The event is stored with an empty visitor_id and still works for aggregate metrics — revenue totals, job counts, error rates.
Backend paths
Node.js has no browser URL. Pass path inside context (or use nexly.pageview('/billing')) when a backend event belongs to a specific route or flow step:
nexly.event({
name: 'checkout_completed',
type: 'custom',
cdata: { amount_cents: 4200 },
context: {
visitor_id: user.nexly_visitor_id,
path: '/checkout',
},
})
Verify in the dashboard
- Browse your site so the Web SDK sends at least one pageview.
- Trigger a linked backend event for the same user (after the link-visitor call).
- Open Events for the property. Filter or scan for the visitor ID — you should see both
web(orreact/next) andnoderows sharing the same Visitor value.
Custom event names and properties must be approved under Goals and events before they appear in analytics widgets. Unapproved names are recorded as discovered and do not enter active reports until you accept them.
When linking is not needed
Skip the link flow when:
- The project has no browser frontend (backend-only analytics).
- You only need aggregate backend metrics, not per-user timelines.
- You use your own identity system and do not want browser visitor IDs tied to accounts.
Related guides
- Install on Next.js, React Native, and Node.js — SDK setup for each runtime.
- Custom events and properties — naming and approving custom events.
- Event model — how
context,cdata, and client source fit together.