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
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:
{ "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": "160000",
"billable_seconds": 710, "billed_seconds": 720 } } } }reason is stopped, idle, time_limit, spend_cap, credit, crashed, no_capacity or start_failed.
Check every delivery
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.
- A delivery not answered 2xx within 5 seconds is sent again 1, 2, 4 and up to 60 minutes later, 12 times in all. Redirects are not followed.
- A delivery can arrive more than once: key on its
id, also in thenomac-deliveryheader. webhooks.deliveries(id)lists the last 30 days' deliveries, so you can read back what your endpoint missed while it was down.
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:
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