Skip to content
mittr

Node.js SDK

@mittr/sdk-node is the official Node.js SDK. Zero runtime dependencies (built on fetch + node:crypto), ESM, Node 18+, full TypeScript types.

npm
npm install @mittr/sdk-node
pnpm
pnpm add @mittr/sdk-node
yarn
yarn add @mittr/sdk-node
Node
import { Mittr } from "@mittr/sdk-node";
const mittr = new Mittr({ apiKey: process.env.MITTR_API_KEY! });

Optional options:

  • baseUrl — defaults to https://app.mittr.io. Override for self-hosted instances.
  • fetchImpl — inject a custom fetch for tests or sandboxed runtimes; defaults to the global fetch.
Node
const event = await mittr.events.send({
eventType: "order.created",
payload: { orderId: "ord_123", amount: 9900 },
});
console.log(event.id, event.status);

send() auto-generates an idempotency key per call. Pass a stable key when you have an upstream id — that way your own retries don’t double-bill or double-deliver:

Stable idempotency key
await mittr.events.send(
{ eventType: "order.created", payload },
{ idempotencyKey: `order-${order.id}-created` },
);

list() returns one page; iterate() walks every page for you:

Single page
const page = await mittr.events.list({ status: "failed", limit: 100 });
console.log(page.data.length, "of", page.pagination.total);
Iterate every page
for await (const event of mittr.events.iterate({ status: "failed" })) {
await mittr.events.replay(event.id);
}

The same list / iterate shape is available on mittr.endpoints.

verifyWebhook is a standalone function — no Mittr instance required. Always pass the raw request body. With Express, capture the raw body before any JSON parser touches it:

Express
import express from "express";
import { verifyWebhook } from "@mittr/sdk-node";
const app = express();
app.post(
"/webhooks/mittr",
express.raw({ type: "*/*" }),
(req, res) => {
const ok = verifyWebhook(
process.env.MITTR_WEBHOOK_SECRET!,
req.headers,
req.body,
);
if (!ok) return res.status(401).send("invalid signature");
const event = JSON.parse(req.body.toString("utf8"));
// ... handle event ...
res.status(200).send("ok");
},
);

The verifier rejects signatures whose timestamp is more than 5 minutes from now by default. Tighten or loosen with toleranceSeconds:

verifyWebhook(secret, req.headers, req.body, { toleranceSeconds: 60 });

If you prefer a verify-then-continue middleware, webhookHandler is framework-agnostic — it duck-types Express / Connect / Fastify with raw-body support:

Express middleware
import { webhookHandler } from "@mittr/sdk-node";
app.post(
"/webhooks/mittr",
express.raw({ type: "*/*" }),
webhookHandler(process.env.MITTR_WEBHOOK_SECRET!),
(req, res) => {
// signature already verified — req.body is the raw Buffer
const event = JSON.parse(req.body.toString("utf8"));
// ... handle event ...
res.sendStatus(200);
},
);

On a bad signature the handler responds 401 invalid signature. Override the response by passing onInvalid:

webhookHandler(secret, {
toleranceSeconds: 60,
onInvalid: (_req, res) => res.status(418).send("teapot"),
});

Every non-2xx response throws a MittrError carrying the HTTP status and the parsed response body:

Catch a 400
import { MittrError } from "@mittr/sdk-node";
try {
await mittr.events.send({ payload: null });
} catch (err) {
if (err instanceof MittrError && err.status === 400) {
console.error("bad input:", err.body);
return;
}
throw err;
}

The body field is whatever JSON the API returned ({ error, code, details } for validation failures, free-form for upstream errors). For non-JSON responses (rare) it’s the raw text.

The SDK is open source under the MIT License and calls through the SDK are billed identically to raw HTTP — same plan quota, same overage rates, no markup, no discount. mittr.events.send(...) lands on the same POST /api/v1/events and counts against your plan the same way.

The one indirect win: events.send() defaults to a fresh idempotency key per call, so your own internal retries don’t double-bill or double-deliver.

Public exports
new Mittr({ apiKey, baseUrl?, fetchImpl? })
mittr.events
.send(input, { idempotencyKey? }) // POST /api/v1/events
.list(params?) // GET /api/v1/events
.iterate(params?) // async iterator across pages
.get(id) // GET /api/v1/events/:id
.replay(id) // POST /api/v1/events/:id/replay
mittr.endpoints
.create(input) // POST /api/v1/endpoints
.list(params?) // GET /api/v1/endpoints
.iterate(params?) // async iterator across pages
.get(id) // GET /api/v1/endpoints/:id
.update(id, input) // PATCH /api/v1/endpoints/:id
.delete(id) // DELETE /api/v1/endpoints/:id
verifyWebhook(secret, headers, rawBody, { toleranceSeconds?, now? })
webhookHandler(secret, { toleranceSeconds?, onInvalid? })

Pre-1.0; the public surface may change between minor versions. The webhook signature wire format is pinned by signature.test.ts against the canonical Go signer in the Mittr platform and won’t drift silently — the algorithm itself is documented in Webhook Security.

For the precise endpoint shapes — request bodies, response schemas, error codes — see the live API reference.