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.
Install
Section titled “Install”npm install @mittr/sdk-nodepnpm add @mittr/sdk-nodeyarn add @mittr/sdk-nodeInitialise the client
Section titled “Initialise the client”import { Mittr } from "@mittr/sdk-node";
const mittr = new Mittr({ apiKey: process.env.MITTR_API_KEY! });Optional options:
baseUrl— defaults tohttps://app.mittr.io. Override for self-hosted instances.fetchImpl— inject a custom fetch for tests or sandboxed runtimes; defaults to the globalfetch.
Send events
Section titled “Send events”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:
await mittr.events.send( { eventType: "order.created", payload }, { idempotencyKey: `order-${order.id}-created` },);Paginate the list endpoints
Section titled “Paginate the list endpoints”list() returns one page; iterate() walks every page for you:
const page = await mittr.events.list({ status: "failed", limit: 100 });console.log(page.data.length, "of", page.pagination.total);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.
Verify inbound webhooks
Section titled “Verify inbound webhooks”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:
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 });Drop-in middleware
Section titled “Drop-in middleware”If you prefer a verify-then-continue middleware, webhookHandler is framework-agnostic — it duck-types Express / Connect / Fastify with raw-body support:
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"),});Error handling
Section titled “Error handling”Every non-2xx response throws a MittrError carrying the HTTP status and the parsed response body:
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.
Pricing
Section titled “Pricing”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.
API surface
Section titled “API surface”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? })Versioning
Section titled “Versioning”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.