Count every paid order once
One server client sends your paid orders from any JavaScript runtime, and an idempotency key makes a repeated webhook count once.
Package @mirafive/sdk-server
Set up in 5 steps
Install the package
It runs on Node.js 20 or later, Bun, Deno and Cloudflare Workers, is ESM only and has no runtime dependencies. In Deno, run
deno add npm:@mirafive/sdk-server.Code for step 1 of the Node.js setup: Install the package. npm install @mirafive/sdk-server
Add the secret key
Use the secret key of a server source from Data → Sources. The package reads no environment variables itself, so you pass the key. It stays on the server;
new Mira()throws aTypeErrorin a browser.Code for step 2 of the Node.js setup: Add the secret key. MIRAFIVE_SECRET_KEY=mf_…
Create one client per process
track()buffers, and a batch leaves after 1 second or at 100 events. A process that ends sooner loses the buffer, so a server shuts the client down onSIGTERM, and a script awaitsmira.flush()before it exits.Code for step 3 of the Node.js setup: Create one client per process. import { Mira } from '@mirafive/sdk-server' export const mira = new Mira({ key: process.env.MIRAFIVE_SECRET_KEY, host: process.env.MIRAFIVE_HOST, }) process.on('SIGTERM', async () => { await mira.shutdown() process.exit(0) })
Send the paid order from the webhook
Payment webhooks are sometimes delivered twice.
send()delivers at once and resolves to the server's receipt, and with an idempotency key a repeat is stored and billed once. Therevenueproperty feeds your purchase goal.Code for step 4 of the Node.js setup: Send the paid order from the webhook. import { mira } from './mira' // In the webhook that confirms the payment await mira.send( [{ name: 'order_paid', userId: order.customerId, properties: { revenue: 328, currency: 'EUR' } }], { idempotencyKey: `order-${order.id}` }, )
Check the key
The install check proves the key and host work and is never stored or billed. It prints a receipt whose reason is
install_check; a wrong key rejects with aMiraError. Then track a real event and find it under Data → Live.Code for step 5 of the Node.js setup: Check the key. import { Mira } from '@mirafive/sdk-server' const mira = new Mira({ key: process.env.MIRAFIVE_SECRET_KEY }) console.log(await mira.send([{ name: '$install_check' }]))
MIRA FIVE is analytics built around the person. @mirafive/sdk-server is its server side for Node.js, Bun, Deno,
Cloudflare Workers and Vercel functions: it buffers events, retries failed deliveries with backoff, and evaluates
feature flags in your process. The Next.js,
TanStack Start, Nuxt and Astro packages build on it and wire it
for you.
How does a paid order reach its channel?
Through the person who paid. Server events are sent in full mode by default and carry the ids you pass; your site
holds the consent or other lawful basis for them. With consent, the browser keeps an anonymous id. Send it to your
server after sign-up or sign-in, and identify links the visitor’s earlier events to your user id:
mira.identify(user.id, { plan: user.plan }, { anonymousId })The order carries the same userId, your own pseudonymous id and never an email address. MIRA FIVE puts the
purchase on that person, next to the channel that first brought them. Create a purchase goal for order_paid to
see buyers and revenue per channel and campaign.
When do I use track instead of send?
For everything that may wait a second. track() buffers and never throws for transport reasons:
mira.track('order_paid', {
userId: order.customerId,
properties: { revenue: 328, currency: 'EUR' },
})send() is for webhooks and jobs. Its batch id is derived from the idempotency key, so every MIRA FIVE SDK maps
the same key to the same batch, and the server keeps a batch id for a day. Retries resend the byte-identical body.
The source’s Install tab has Let your coding agent install it, a prompt for Claude Code, Cursor or Codex with every step and the check; for a server source it never contains the secret key. The Node.js SDK docs cover each runtime, errors and flags, and the setup page shows who does what, in order.
Other setups
The same numbers, whichever way your site is built.
Questions and answers
How do events survive on Cloudflare Workers?
ctx.waitUntil(mira.flush()). Put the key in a Worker secret with npx wrangler secret put MIRAFIVE_SECRET_KEY.And on Vercel functions?
waitUntil from @vercel/functions to new Mira(). Every delivery, including the one the flush timer starts, is handed to it, so the function stays alive until the batch is sent.Do failed deliveries throw?
track(), identify() and flush() report failures to onError, which defaults to console.warn. Only send() rejects, with a MiraError that says whether a retry can succeed.Can I count without user ids?
mode: 'consentless' counts without identifiers, for revenue, invoices and totals. Passing a userId then throws a TypeError, because the server would refuse the whole batch.