Skip to content
KismetKismetDevelopers
llms.txt

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.

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-collection
KISMET_TRACKING_KEY=ctk_...

Confirm your production and staging hostnames are on the collection’s authorized domains.

Terminal window
npm install @kismet-tech/telemetry-node

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.

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.

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.

The middleware only needs the raw request and response.

// Fastify
fastify.addHook('onRequest', (request, reply, done) => middleware(request.raw, reply.raw, done));
// then request.raw.kismet.seed in the handler
// Koa
app.use((ctx, next) => new Promise((r) => middleware(ctx.req, ctx.res, r)).then(next));
// then ctx.req.kismet.seed
// node:http
createServer((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.

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.

  1. Load a page in a fresh browser. Expect a _kid_sid cookie on the dotted domain and window.Kismet._kidSid equal to it, with the response header x-kismet-anchor-tier: minted.
  2. Reload. Same cookie, tier cookie, no new Set-Cookie.
  3. curl -A GPTBot https://your-host/some-page: no Set-Cookie, and the page seeds _sidSuppressed.
  4. curl https://your-host/llms.txt: recorded as a fetch with no session.
  5. From a consent jurisdiction without consent: no cookie, page still recorded.
  6. 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.