Guides
Webhooks
Register an https endpoint in the dashboard. Each endpoint carries its own signing secret and its own disclosure class.
what arriveshttp
POST /your/endpoint HTTP/1.1
content-type: application/json
webhook-id: msg_2fk9xq7t4b1a
webhook-timestamp: 1756512000
webhook-signature: v1,K8mN2pQ...
{"type":"attestation.verdict","policy_id":"...","verdict":{...}}Verify the signature
The signed string is {webhook-id}.{webhook-timestamp}.{raw body}, HMAC-SHA256 under the endpoint secret, base64, prefixed v1,. Compare in constant time and reject anything older than five minutes.
import crypto from "node:crypto";
export function verify(req: { headers: Record<string, string>; rawBody: string }, secret: string) {
const id = req.headers["webhook-id"];
const ts = req.headers["webhook-timestamp"];
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const mac = crypto.createHmac("sha256", key).update(`${id}.${ts}.${req.rawBody}`).digest("base64");
const expected = Buffer.from(`v1,${mac}`);
return req.headers["webhook-signature"].split(" ").some((candidate) => {
const got = Buffer.from(candidate);
return got.length === expected.length && crypto.timingSafeEqual(got, expected);
});
}Validate before you act
An endpoint that skips these headers accepts forged events from anyone who learns its URL.
Events
attestation.verdict— a device was appraised. Can be narrowed to specific policies.device.enrolled— a device completed enrolment.usage.alert— active devices crossed a percentage you set of a budget you set (blank = the plan's included devices). Fires once per billing period.
Disclosure class
Per endpoint, independent of what verify requested: verdict (booleans and enums, no identifier), pseudonymous (adds the per-tenant device id; default), derived (lifecycle facts), full.
Delivery
- At-least-once, unordered. Deduplicate on
webhook-id, constant across retries. - 2xx is success. 3xx is a failure; redirects are not followed.
410 Gonedisables the endpoint. - Retries run for about three days with increasing backoff. Keep the deduplication window at least that long.
- Twenty consecutive failures disable the endpoint; re-enable it in the dashboard.
- Rotating a secret keeps signing with the previous one for 24 hours; both ride in the same header.
- The URL must be https, carry no credentials, and resolve to a publicly routable address on every A and AAAA record. Pro and above.