Node.js SDK (sdk-node)

Server-side instrumentation for Express, Fastify, and background workers. Uses the same ingest format as the browser SDK, so all events land in the same dashboard.

Install

npm install @emit-vision/sdk-node
# or: pnpm add @emit-vision/sdk-node

Initialization

Call init() once at process startup, before any routes or workers run. Immediately after, call installProcessHandlers() to capture uncaught exceptions and unhandled promise rejections at the process level.

import { init, installProcessHandlers } from "@emit-vision/sdk-node";
 
init({
  apiKey: process.env.EMIT_VISION_API_KEY,
  environment: process.env.NODE_ENV,
  release: process.env.APP_RELEASE,
});
 
installProcessHandlers();

installProcessHandlers() covers unexpected process-level failures that middleware alone cannot see — always call it right after init().

HTTP frameworks

Choose the integration that matches your server framework. Both capture route errors and flush telemetry automatically on each response.

// server.ts
import express from "express";
import {
  emitVisionExpress,
  emitVisionRequestContext,
  flush,
  init,
  installProcessHandlers,
} from "@emit-vision/sdk-node";
 
init({
  apiKey: process.env.EMIT_VISION_API_KEY,
  environment: process.env.NODE_ENV,
  release: process.env.APP_RELEASE,
});
installProcessHandlers();
 
const app = express();
app.use(emitVisionRequestContext()); // scope URL, method, user-agent to each request
 
// ... your routes ...
 
app.use(emitVisionExpress()); // error handler — must come after all routes
 
const server = app.listen(3000);
process.on("SIGTERM", () => {
  void flush()
    .catch((err) => console.error("emit-vision flush failed", err))
    .finally(() => server.close(() => process.exit(0)));
});

Background workers and cron jobs

For processes with no HTTP request context, call the SDK directly and flush in the finally block so telemetry is delivered before the worker exits.

import {
  captureError,
  captureEvent,
  flush,
  init,
  installProcessHandlers,
} from "@emit-vision/sdk-node";
 
init({
  apiKey: process.env.EMIT_VISION_API_KEY,
  environment: process.env.NODE_ENV,
  release: process.env.APP_RELEASE,
});
installProcessHandlers();
 
async function runJob() {
  captureEvent("worker_started", { queue: "email-delivery" });
  try {
    await deliverEmailBatch();
    captureEvent("email_batch_delivered", { queue: "email-delivery" });
  } catch (error) {
    captureError(
      error instanceof Error ? error : new Error("job failed"),
      { context: { queue: "email-delivery" } },
    );
  } finally {
    await flush(); // always flush before the job ends
  }
}
 
process.on("SIGTERM", () => void flush().finally(() => process.exit(0)));
void runJob();

System events

Use captureSystemEvent() for heartbeat- or probe-style telemetry you want a record of, but don't want cluttering your events feed. It shares captureEvent's batching, flush, and retry pipeline — the only difference is the envelope's type.

import { captureSystemEvent, flush, init } from "@emit-vision/sdk-node";
 
init({
  apiKey: process.env.EMIT_VISION_API_KEY,
  environment: process.env.NODE_ENV,
});
 
captureSystemEvent("health_check_probe", { source: "load-balancer" });
 
await flush();

System events are excluded from the events feed by default, excluded from billable usage, and cannot trigger event alerts. View them in the dashboard with the Show system events toggle on the Events page, or from the CLI with emit-vision events recent <projectId> --system.

Requires an emit-vision server at sprint 600 or later — self-hosted servers older than that reject type: "system" payloads at ingest validation. Hosted instances (app.emitvision.com) always run the latest server.

Not sure whether a signal belongs here or on a Pulse ping URL? See the decision tree in Pulse Monitoring.

Logging vs. telemetry

Keep these two concerns separate:

  • SDK — product events, handled exceptions, user identification, and structured context you want to query later.
  • Logger — operational details like debug lines, retries, and infrastructure diagnostics.
  • Avoid sending secrets, auth headers, passwords, or raw request bodies through either channel unless you have explicitly scrubbed them first.

Batch compression

Event batches larger than 8 KB are automatically gzipped and sent with the Content-Encoding: gzip header. The API accepts both compressed and uncompressed bodies. If your environment does not support compression, the SDK automatically falls back to sending uncompressed JSON — no configuration needed.

If you run the SDK behind a reverse proxy, WAF, or corporate middlebox and see ingest errors, confirm the proxy is configured to accept Content-Encoding: gzip.

Common mistakes

  • Calling init() more than once — a second call resets the internal queue. Call it once at the top of your entry point.
  • Forgetting installProcessHandlers() — middleware only sees errors that reach the HTTP layer. Process crashes bypass it entirely.
  • Not flushing before process.exit() — batched events are lost when the process exits. Always await flush() in your SIGTERM handler.
  • Sending raw request bodies — request bodies often contain PII or credentials. Never attach them to event properties or error context without explicit scrubbing.