Receive signed events from Nomos when subscriptions move through their lifecycle, then react to them reliably in your backend.
Polling for state changes works, but it’s slow and wasteful. Webhooks invert the relationship: when something interesting happens (a subscription is confirmed, a contract activates, a customer cancels), Nomos sends a signed POST request to a URL you control, with the event payload.This guide goes from “I have a URL” to “I’m reliably reacting to subscription lifecycle events.”
In the Nomos dashboard, add a new webhook with your endpoint URL. The dashboard issues a webhook secret. Store it the same way you’d store any other production secret.Send a test event from the dashboard’s ”…” action to confirm the endpoint is reachable. The payload looks like:
{ "topic": "test.event"}
A 2xx response acks the event. Anything else is treated as a failure (see retries).
Every webhook arrives with a signature header so you can prove it came from Nomos and hasn’t been replayed. Reject anything that doesn’t verify, no exceptions.
The exact header name and signing scheme are documented under Verify requests. Use the raw request body (not a JSON-parsed-and-restringified one) when computing the HMAC, or signatures will silently mismatch.
Don’t JSON.parse and re-serialize the body before verifying. Frameworks
often reorder keys, and that breaks the signature. Capture the raw bytes
before anything else touches them.
app.post("/webhooks/nomos", async (req, res) => { const raw = await readRawBody(req); if (!verifyNomosSignature(raw, req.header("X-Nomos-Signature"), SECRET)) { return res.status(401).end(); } const event = JSON.parse(raw); switch (event.topic) { case "subscription.created": await onSubscriptionCreated(event.context.subscription_id); break; case "subscription.confirmed": await onSubscriptionConfirmed(event.context.subscription_id); break; case "subscription.activated": await onSubscriptionActivated(event.context.subscription_id); break; case "subscription.terminated": await onSubscriptionTerminated(event.context.subscription_id); break; case "subscription.ended": await onSubscriptionEnded(event.context.subscription_id); break; } res.status(200).end();});
The context only carries IDs. To act on the latest state, fetch the resource:
async function onSubscriptionConfirmed(subscriptionId: string) { const subscription = await fetch( `https://api.nomos.energy/subscriptions/${subscriptionId}`, { headers: { Authorization: `Bearer ${access_token}` } }, ).then((r) => r.json()); // do something with subscription}
This pattern keeps payloads small and avoids the “stale event” problem: by the time you read the resource, you have the truth, not a snapshot from when the event fired.
Return 2xx within 30 seconds. If you have heavy work to do, hand the event off to a background queue and ack immediately. See Retries and replays for the full retry schedule.