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, so 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.