# Count every paid order once

> Send MIRA FIVE events and paid orders from Node.js, Bun, Deno or Cloudflare Workers with @mirafive/sdk-server, and count every repeated webhook once.

URL: https://mirafive.io/for/nodejs  
Updated: 2026-09-28

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`.  
```  
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.  
```  
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.  
```  
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.  
```  
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**.  
```  
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](https://mirafive.io/glossary/feature-flag) in your process. The [Next.js](https://mirafive.io/for/nextjs), [TanStack Start](https://mirafive.io/for/tanstack-start), [Nuxt](https://mirafive.io/for/nuxt) and [Astro](https://mirafive.io/for/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:

```ts
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:

```ts
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](https://docs.mirafive.io/sdks/node) cover each runtime, errors and flags, and the [setup page](https://mirafive.io/setup) shows who does what, in order.

## Other setups

The same numbers, whichever way your site is built.

- [Astro](https://mirafive.io/for/astro)
- [Next.js](https://mirafive.io/for/nextjs)
- [React](https://mirafive.io/for/react)
- [Laravel](https://mirafive.io/for/laravel)
- [Nuxt](https://mirafive.io/for/nuxt)
- [Symfony](https://mirafive.io/for/symfony)
- [PHP](https://mirafive.io/for/php)
- [Vue](https://mirafive.io/for/vue)
- [TanStack Start](https://mirafive.io/for/tanstack-start)
- [Convex](https://mirafive.io/for/convex)
- [JavaScript](https://mirafive.io/for/javascript)
- [any website](https://mirafive.io/for/website)

[Read the docs](https://docs.mirafive.io)

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

[Start free](https://app.mirafive.io/register)[See pricing](https://mirafive.io/pricing)
