@nexly/node
v0.15.3
Published
Nexly server-side ingest SDK for Node.js: fire-and-forget events with optional linking to browser visitors
Downloads
760
Maintainers
Readme
@nexly/node
Server-side ingest SDK for Nexly. Fire-and-forget fetch transport, no batching, no state — for emitting backend events (webhooks, jobs, CLI tools, Lambda handlers) straight to the collector.
Install
npm install @nexly/nodeRequires Node.js ≥ 20 (for the built-in fetch with keepalive).
Usage
import { Nexly } from '@nexly/node'
const nexly = Nexly.init({
appId: process.env.NEXLY_APP_ID!,
key: process.env.NEXLY_INGEST_KEY!,
})
// Anonymous backend event — no visitor binding.
nexly.event({
name: 'subscription_created',
type: 'custom',
data: { plan: 'pro', amount_cents: 4900 },
})Events are fire-and-forget: the call returns immediately, the fetch fires in the background, failures are swallowed so analytics never crash the host process.
Graceful shutdown
Long-running servers don't need anything special. For short-lived processes (CLI scripts, AWS Lambda, cron jobs) call flush() before exit so in-flight events aren't dropped:
process.on('SIGTERM', async () => {
await nexly.flush() // defaults to 2s timeout
process.exit(0)
})
// or inside a Lambda handler:
export async function handler(event) {
nexly.event({ name: 'order_processed', type: 'custom', data: { order_id: event.id } })
await nexly.flush()
}Origin allowlist
The SDK sends Origin: nexly-node://server on every request. Add this exact string once to the app's allowed origins in the dashboard, same way as you'd add a web origin.
Linking backend events to browser visitors
Important. This is the primary way to stitch server-side events (payments, subscription changes, background jobs) onto the same user timeline as their browser activity.
Backend events have no visitor_id by default — they stand alone unless you opt in. To connect an event to a specific browser session, your browser code forwards its Nexly visitor ID to your backend once (e.g. on sign-up, login, or when first authenticating), and you store the mapping alongside your own user id.
1. Browser: grab the visitor id
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 linking
}),
})getVisitorId() is exported from @nexly/web and returns the persistent anonymous ID generated on first ingest for that browser. It's a plain localStorage string, safe to read from any component.
2. Backend: persist the mapping
// your users table (or equivalent):
// ALTER TABLE users ADD COLUMN nexly_visitor_id TEXT;
app.post('/api/link-visitor', async (req, res) => {
const user = await getSessionUser(req)
await db.users.update({ where: { id: user.id }, data: { nexly_visitor_id: req.body.visitorId } })
res.status(204).end()
})3. Backend: emit linked events
nexly.event({
name: 'subscription_created',
type: 'custom',
data: { plan: 'pro' },
context: { visitor_id: user.nexly_visitor_id }, // ← the link
})In the dashboard these events now appear on the same timeline as that user's browser pageviews / clicks, filterable by visitor_id.
Without linking
If your project has no frontend or you just don't care about stitching, omit context entirely:
nexly.event({ name: 'invoice_paid', type: 'custom', data: { amount_cents: 12000 } })The event lands with visitor_id='' and is perfectly usable for aggregate metrics — it just isn't associated with any browser user.
Verify locally
After wiring the SDK and whitelisting nexly-node://server, fire a test event and check it landed:
nexly.event({ name: 'smoke_test', type: 'custom', data: {}, context: { visitor_id: 'v_demo' } })
await nexly.flush()Server-side (Postgres):
SELECT client, event_name, visitor_id, received_at
FROM events
WHERE app_id = 'app_…' AND client = 'node'
ORDER BY received_at DESC LIMIT 5;
-- To confirm linking worked, join with a browser event sharing the same visitor_id:
SELECT client, event_name, visitor_id FROM events
WHERE visitor_id = 'v_demo' ORDER BY received_at;Common failures and what they mean:
| Response | Cause |
| --- | --- |
| 401 unauthorized | app_id + api_token pair not in the dashboard |
| 403 forbidden | Token belongs to a different account |
| 403 origin not allowed | Forgot to whitelist nexly-node://server |
| 400 invalid client | Using a pre-0.13 backend that doesn't accept 'node' |
Custom props
Anything specific to your domain goes in data. Use context only for Nexly-known fields (visitor_id, session_id, path):
nexly.event({
name: 'checkout_completed',
type: 'custom',
data: {
order_id: 'ord_123',
currency: 'USD',
amount_cents: 4200,
items: 3,
internal_user_id: user.id, // your own ids are fine here
},
context: { visitor_id: user.nexly_visitor_id },
})