Docs

Install, throw an error, and let Signal0 decide when it matters.

There are only two moving parts in the MVP: a project key in the request header and a minimal JSON payload. That keeps infra cheap and onboarding fast.

Next.js route handlersignal0
import { createSignal0 } from "./signal0";

const signal0 = createSignal0({
  endpoint: "https://your-domain.com/api/v1/events",
  projectKey: process.env.SIGNAL0_PROJECT_KEY!,
  environment: process.env.NODE_ENV,
  release: process.env.VERCEL_GIT_COMMIT_SHA,
});

export async function GET() {
  try {
    throw new Error("Database is out of sync");
  } catch (error) {
    await signal0.captureError(error, {
      route: "/api/orders/sync",
      requestId: crypto.randomUUID(),
      context: {
        job: "orders-sync",
        tenant: "launch-demo",
      },
    });
  }

  return Response.json({ ok: true });
}

`POST /api/v1/events`

Required header: x-project-key

Required body fields: errorType, message

Optional fields: stack, timestamp, environment, release, route, requestId, context

Duplicate events are grouped with the fingerprint formula errorType + message + normalizedStack + primaryContext.

Create a project key
Node scriptsignal0
import { createSignal0 } from "./signal0.js";

const signal0 = createSignal0({
  endpoint: "https://your-domain.com/api/v1/events",
  projectKey: process.env.SIGNAL0_PROJECT_KEY,
  environment: "production",
  release: "cron@2026-03-27T16:00:00Z",
});

async function run() {
  try {
    throw new Error("Nightly import failed");
  } catch (error) {
    await signal0.captureError(error, {
      route: "cron:nightly-import",
      requestId: crypto.randomUUID(),
      context: {
        source: "billing",
        batchSize: 1200,
      },
    });
  }
}

run();
Tiny SDK sourcesignal0
export function createSignal0(config) {
  async function send(payload) {
    await fetch(config.endpoint, {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "x-project-key": config.projectKey,
      },
      body: JSON.stringify({
        ...payload,
        environment: payload.environment ?? config.environment,
        release: payload.release ?? config.release,
      }),
    });
  }

  return {
    captureMessage(message, context = {}) {
      return send({
        errorType: "Message",
        message,
        context: context.context,
        route: context.route,
        requestId: context.requestId,
      });
    },
    captureError(error, context = {}) {
      return send({
        errorType: error?.name ?? "Error",
        message: error?.message ?? String(error),
        stack: error?.stack,
        context: context.context,
        route: context.route,
        requestId: context.requestId,
      });
    },
    wrap(handler) {
      return async (...args) => {
        try {
          return await handler(...args);
        } catch (error) {
          await this.captureError(error);
          throw error;
        }
      };
    },
  };
}