SignetSignet

Webhooks

Platform events posted to an endpoint of yours, so you learn about a membership change or a disabled key without polling for it.

The envelope

POST your-endpoint
{
  "id": "0b8b2b3e-…",
  "event": "key.disabled",
  "app_id": "6f2393b5-…",
  "created_at": "2026-08-31T18:12:04Z",
  "data": { "key_id": "oauth:…", "curve": "ecdsa_secp256k1" }
}

Verifying a delivery

Two headers accompany every request. Signet-Timestamp is the send time, and Signet-Signature is an HMAC-SHA256 over the timestamp and the raw body together, keyed by the secret shown when you created the endpoint.

verify.ts
import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(secret: string, headers: Headers, rawBody: string) {
  const timestamp = headers.get("signet-timestamp") ?? "";
  const signature = headers.get("signet-signature") ?? "";

  // The timestamp is signed, but a genuine old delivery can still be
  // resent — so reject anything stale outright.
  if (!timestamp || Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const mac = createHmac("sha256", secret);
  mac.update(timestamp);
  mac.update(".");
  mac.update(rawBody);            // the raw bytes, before any JSON parsing
  const expected = `v1=${mac.digest("hex")}`;

  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && timingSafeEqual(a, b);
}

Deliveries are not retried

A failed delivery is recorded and left alone. Treat webhooks as a fast path and reconcile against the API for anything you cannot afford to miss — an endpoint that was down for ten minutes will simply have missed those events.

Events

EventFires when
app.deployedA signing group was attached to an app.
app.updatedAn app's details changed.
group.node_invitedAn operator was invited to a group.
group.node_joinedAn operator became active.
group.removal_queuedA removal entered its timelock.
group.removal_executedAn operator was removed.
group.reshare_requestedA key refresh was requested.
issuer.addedA login method was added.
issuer.removedA login method was removed.
auth_key.addedAn authorization key was recorded.
auth_key.revokedAn authorization key was revoked.
key.createdA user key was generated.
key.disabledA key was disabled.
key.enabledA key was re-enabled.
delegation.issuedA session signer was minted.
delegation.revokedA session signer was revoked.
user.first_seenA user authenticated for the first time.
usage.threshold_reachedUsage crossed a configured threshold.
billing.low_balanceThe billing balance fell below its floor.

An endpoint with no events selected receives everything, including events added later — the least surprising reading of “I did not narrow it”.