Webhooks and billing · NoMac

Webhooks and billing

NoMac tells your backend when each phone is ready and when it ends, with your user's label and what the phone cost, so you can charge your users your own way.

Make a webhook

TypeScript

const hook = await nomac.webhooks.create({ url: "https://platform.example/nomac", events: ["phone.ready", "phone.ended"] });
// Keep hook.secret: it is shown once.

An https URL at a public address, up to 5 per account. Each event is a JSON POST:

phone.ended

{ "id": "whd_...", "type": "phone.ended", "created_at": "2026-10-24T10:00:00.000Z",
  "data": { "reason": "idle",
            "session": { "id": "ses_...", "state": "stopped", "device": "iphone", "label": "user-42",
                         "ready_at": "...", "shutdown_at": "...", "failure_code": null,
                         "cost": { "currency": "USD", "accrued_microunits": "180000",
                                   "billable_seconds": 710, "billed_seconds": 720 } } } }

reason is stopped, idle, time_limit, spend_cap, credit, crashed, no_capacity or start_failed.

Check every delivery

TypeScript Python

import { verifyWebhook } from "@nomac/simulator";

app.post("/nomac", express.raw({ type: "application/json" }), async (req, res) => {
  const event = await verifyWebhook(req.body, req.get("nomac-signature"), process.env.NOMAC_WEBHOOK_SECRET);
  if (event.type === "phone.ended") await charge(event.data.session.label, event.data.session.cost);
  res.sendStatus(204);
});

Pass the raw body as received, not parsed JSON. The nomac-signature header is t=<unix seconds>,v1=<hex HMAC-SHA256 of "t.body" under the secret>, and a signature older than 5 minutes is refused.

Bill your users

Each phone.ended carries what you need: the label (your user), billed_seconds (whole minutes, at least one) and accrued_microunits, what NoMac charged your account in millionths of a dollar. Charge your user from those, with your own price on top.

To reconcile, list a user's phones with their cost:

TypeScript

const phones = await nomac.phones.list({ label: "user-42" });
const spent = phones.reduce((sum, p) => sum + Number(p.session.cost?.accrued_microunits ?? 0), 0) / 1e6; // dollars

At $0.90 an hour, a 12-minute phone costs 18¢: 720 billed seconds and 180,000 microdollars.