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

  1. 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

  2. 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 a TypeError in a browser.

    Code for step 2 of the Node.js setup: Add the secret key. MIRAFIVE_SECRET_KEY=mf_…

  3. 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 on SIGTERM, and a script awaits mira.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) })

  4. 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. The revenue property 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}` }, )

  5. 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 a MiraError. 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?
Keep one client per isolate and hand each request's delivery to ctx.waitUntil(mira.flush()). Put the key in a Worker secret with npx wrangler secret put MIRAFIVE_SECRET_KEY.
And on Vercel functions?
Pass 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?
No. 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?
Yes. 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.

See which channel brings buyers

Free for 25,000 events a month. No card needed.