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