Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.nomos.energy/llms.txt

Use this file to discover all available pages before exploring further.

Anyone can POST to a public URL. Without verification, you can’t tell a real Nomos delivery from a forged one, and an intercepted payload could be replayed against you later. To prevent this, every webhook is signed with a secret tied to your endpoint and stamped with the time it was sent. Check both before acting on the event.
Each webhook endpoint has its own signing secret. Find it on the endpoint’s detail page in the Nomos dashboard under Developer → Webhooks.
1

Parse the header

Each request includes an X-Nomos-Signature header:
t=1768473000,v1=3523dcc0013f08dfa1855772441107330218793f399d7452bd3ff2159c6e0285
  • t: Unix timestamp (seconds) when Nomos sent the request.
  • v1: HMAC-SHA256 of ${t}.${rawBody}, hex-encoded, signed with your endpoint’s secret.
Pull t and v1 out of the header.
2

Reject stale timestamps

If t is more than 5 minutes off your clock, treat it as a replay and stop.
3

Recompute the signature

Run HMAC-SHA256(${t}.${rawBody}, secret) using your stored signing secret and the raw request body, byte-for-byte.
4

Compare the signatures

Compare your computed value to v1 and reject if they differ. Use a timing-safe helper (like Node’s timingSafeEqual) instead of ===, so an attacker can’t learn the signature from how long the comparison takes.

Example

import crypto from "node:crypto";

export function verifyWebhook(
  rawBody: string,
  header: string,
  secret: string,
): boolean {
  const { t, v1 } = Object.fromEntries(
    header.split(",").map((p) => p.split("=")),
  );
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
  const hmac = crypto.createHmac("sha256", secret);
  const expected = hmac.update(`${t}.${rawBody}`).digest("hex");
  if (v1.length !== expected.length) return false;
  return crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}
Pass the request body before any JSON parsing. Re-serializing a parsed body can change whitespace or key order, which breaks the signature.