Install on Node
View .md@kismet-tech/telemetry-node wraps the core for Node servers: a middleware on the raw node:http request and response that does the whole contract on every page request, the seed for your template, and the checkout call. Node 20 or later, ESM and CommonJS.
Contract 1.0 limit: externalListingId can be emitted, but ingest-side resolution and generic conversions are Contract 1.1 additions. For property-level attribution today, arrange a registered serving-URL mapping or use a known Kismet property slug. Confirm backend support before relying on the 1.1 fields.
Before you start
Section titled “Before you start”Request from Kismet during onboarding: the collection slug and the collection’s tracking key. Put them in the environment, never in a page:
KISMET_COLLECTION_SLUG=your-collectionKISMET_TRACKING_KEY=ctk_...Confirm your production and staging hostnames are on the collection’s authorized domains.
npm install @kismet-tech/telemetry-node1. The middleware
Section titled “1. The middleware”Mount it before your routes.
import express from 'express';import { kismetTelemetry, consentFromCookie } from '@kismet-tech/telemetry-node';
const app = express();
app.use( kismetTelemetry({ collectionSlug: process.env.KISMET_COLLECTION_SLUG, trackingKey: process.env.KISMET_TRACKING_KEY, // Your consent manager's cookie. Consent gates the session, not the recording. consent: consentFromCookie('CookieConsent', /statistics:true/), // Which URLs are what, once. Your own listing id is fine: send it as // externalListingId; ingest resolution requires Contract 1.1. profile: { property: { pattern: /^\/stays\/[^/]+\/([^/]+)\/?$/, as: 'externalListingId' }, searchPaths: ['/stays', /^\/stays\/in\//], intent: { path: '/stays/checkout', checkinParam: 'checkin', checkoutParam: 'checkout', guestsParam: 'guests' }, }, }));Behind a reverse proxy (nginx, Apache, a load balancer), make sure it forwards X-Forwarded-Host, X-Forwarded-Proto and X-Forwarded-For. The adapter reads them to rebuild the public URL, decide Secure on the cookie, and take the visitor’s IP. A Node server has no Cloudflare or Vercel country header, so pass the consent hook, or country: (headers) => ... if your proxy sets a country header of its own.
2. The seed
Section titled “2. The seed”The middleware leaves { kidSid, suppressed, tier, classification, seed } on req.kismet and res.locals.kismet. Print seed first thing in <head>, unescaped. It is one inline script that sets the id and then appends the browser tracker tag itself, or empty when there is nothing to seed.
app.get('/stays/:town/:house', (req, res) => { res.render('property', { kismetSeed: res.locals.kismet?.seed ?? '' });});<head> <%- kismetSeed %> <title>...</title></head>A page that prints the seed is per visitor, and the middleware marks it private, no-store for that reason. If you serve HTML from a shared cache, do not print the seed into it; use the cache-safe bootstrap in the contract instead, the pattern the WordPress plugin uses.
3. The checkout call
Section titled “3. The checkout call”From the server-side handler that knows the reservation succeeded, once:
import { bookingBridge } from '@kismet-tech/telemetry-node';
await bookingBridge(config, { kidSid, confirmationCode, bookingEngine: 'custom', domain: 'example.co.uk' });kidSid is the _kid_sid cookie on the checkout request (req.kismet.kidSid on any page request); carry it through your booking pipeline to wherever the confirmation code is known. Bounded, never throws, must not affect the booking.
Fastify, Koa, plain node:http
Section titled “Fastify, Koa, plain node:http”The middleware only needs the raw request and response.
// Fastifyfastify.addHook('onRequest', (request, reply, done) => middleware(request.raw, reply.raw, done));// then request.raw.kismet.seed in the handler
// Koaapp.use((ctx, next) => new Promise((r) => middleware(ctx.req, ctx.res, r)).then(next));// then ctx.req.kismet.seed
// node:httpcreateServer((req, res) => middleware(req, res, () => render(req, res)));Frameworks that speak Request and Response (Hono, Cloudflare Workers, Deno): createKismetResolver(config).resolve(request) returns the decision (state, headers, Set-Cookie lines) without applying it.
Browser signals the server cannot see
Section titled “Browser signals the server cannot see”import { createTracker } from '@kismet-tech/telemetry-node/client';const track = createTracker({ collectionSlug: 'your-collection' });
track.propertyView({ externalListingId: listing.id, checkIn, checkOut, guests, stayTotalCents });track.save({ externalListingId: listing.id });track.bookIntent({ externalListingId: listing.id, checkIn, checkOut, guests, stayTotalCents });What the middleware does on every page request
Section titled “What the middleware does on every page request”Resolves the visitor (threaded id, cookie, suppressed for bots and visitors without consent, else a locally minted id reconciled with Kismet after the response), emits one server-plane event (agent surfaces as fetch with no identity), sets _kid_sid and _kid_vid on the dotted serving domain, marks the response private, no-store, and leaves the seed on the request for the template. The page never waits on Kismet, and a middleware error serves the page without telemetry rather than failing it. The overview walks through the six steps; the contract specifies them.
Verify
Section titled “Verify”- Load a page in a fresh browser. Expect a
_kid_sidcookie on the dotted domain andwindow.Kismet._kidSidequal to it, with the response headerx-kismet-anchor-tier: minted. - Reload. Same cookie, tier
cookie, no newSet-Cookie. curl -A GPTBot https://your-host/some-page: noSet-Cookie, and the page seeds_sidSuppressed.curl https://your-host/llms.txt: recorded as a fetch with no session.- From a consent jurisdiction without consent: no cookie, page still recorded.
- Complete a test booking: the bridge call returns 201.
The package’s own tests run it through the contract’s conformance suite as a real Express app on a real HTTP server behind forwarded headers. middleware.flush() awaits the background work, for your own tests and for graceful shutdown.