Webhooks
Subscribe to platform events, verify signatures, handle retries, and configure endpoints from the tenant app.
Webhooks
Webhooks let you react to events in your AUDIGYD workspace by receiving an HTTPS POST every time something interesting happens — an assessment is finalized, a finding is resolved, a report is ready.
Configuring an endpoint
settings:manage_settings permission, which is held by Owner by default).whsec_<64 hex chars> and shows it once in a banner that reads "Copy your signing secret now — it won't be shown again". Copy it before navigating away.From the same page you can pause / re-activate (toggle isActive), edit the endpoint's name and event subscriptions, send a test event, inspect deliveries (with a per-delivery Resend button), and delete an endpoint.
Event catalogue
The complete set of event types you can subscribe to today (returned by GET /api/t/:tenantId/webhooks/events):
| Event | When it fires | Notes |
|---|---|---|
assessment.created | A new assessment is created | Includes id, name, status |
assessment.submitted | A respondent submits an assessment | |
assessment.finalized | Reviewer finalizes an assessment | Payload includes the scoreEnvelope |
assessment.score_updated | Score envelope recomputed | |
finding.created | New finding is created (during review or manually) | GRC-relevant |
finding.status_changed | Finding status moves (e.g. open → acknowledged → resolved → accepted_risk) | GRC-relevant |
finding.resolved | Finding transitions to resolved | GRC-relevant |
report.generated | A report is generated | |
report.ready | A report finishes processing and is downloadable |
> Reacting to GRC changes. The table above is the source of truth for what is dispatched as a webhook today. The finding.* events are the primary GRC integration surface — when teams workflow evidence, policy, or risk decisions through findings, those decisions become observable as finding lifecycle transitions (open → acknowledged → resolved → accepted_risk). For evidence, policy, risk, and control-readiness state that is not mirrored through a finding, query the corresponding REST endpoints (e.g. GET /api/t/:tenantId/evidence, /policies, /risks, /controls) on a schedule, or use the MCP server (next article) to ask a model for changes since a given timestamp.
Payload shape
Every delivery is a JSON POST with this envelope:
{
"id": "1f8e92c0-2b1a-4d4e-9b66-2f9b5a76b7f0",
"event": "assessment.finalized",
"tenant_id": "ten_01HXYZ...",
"created_at": "2026-04-16T18:22:31.412Z",
"data": { ... event-specific fields ... }
}
Example finding.status_changed data block:
{
"id": "fnd_01J...",
"title": "Encryption-at-rest not documented",
"previousStatus": "open",
"status": "resolved",
"severity": "high"
}
Request headers
Every delivery includes:
- •
Content-Type: application/json - •
User-Agent: AUDIGYD-Webhooks/1.0 - •
X-AUDIGYD-Event— the event name (e.g.assessment.finalized) - •
X-AUDIGYD-Signature— HMAC-SHA256 of the raw request body, hex-encoded; see the next section for the exact key
Verifying the signature
> Important: AUDIGYD computes the HMAC using the SHA-256 hash of your signing secret as the key (not the raw whsec_... value). To verify, derive the same key in your code: hashKey = sha256_hex(rawSecret), then HMAC_SHA256(hashKey, rawRequestBody). Use a constant-time comparison against X-AUDIGYD-Signature.
// Node.js (Express)
import crypto from "crypto";
const RAW_SECRET = process.env.AUDIGYD_WEBHOOK_SECRET!; // whsec_...
const HASH_KEY = crypto.createHash("sha256").update(RAW_SECRET).digest("hex");
app.post("/webhooks/audigyd", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.header("X-AUDIGYD-Signature") ?? "";
const expected = crypto
.createHmac("sha256", HASH_KEY)
.update(req.body)
.digest("hex");
const ok =
sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!ok) return res.status(401).end();
const event = JSON.parse(req.body.toString());
// handle event...
res.status(200).end();
});
The raw whsec_... secret is what you store; AUDIGYD only ever persists its SHA-256 hash, and that hash is the value used to sign every delivery. To replace a secret today, delete the endpoint and create a new one — there is no in-place rotation flow yet.
Retries & backoff
- •A delivery is successful when your endpoint returns an HTTP
2xxwithin 10 seconds. - •Failed deliveries (non-2xx, timeout, or network error) are retried up to 5 attempts total (1 initial + 4 retries). The wait between attempts is 5m → 30m → 2h → 6h.
- •After 5 consecutive endpoint-level failures, the endpoint is automatically deactivated and an email is sent to the tenant's contact email. Re-activate it after fixing the issue.
- •You can re-trigger any delivery manually from Webhooks → Deliveries → Resend.
Idempotency
The envelope id is unique per delivery (a fresh UUID is generated on every attempt). To deduplicate retries, store the event-specific data.id alongside the event name and skip if you have already processed it.
Tips
- •Always return
2xxquickly and queue the work asynchronously — long handlers cause spurious timeouts and retries. - •Use a tunnelling tool (e.g. ngrok) to receive deliveries during local development, then promote to a real HTTPS URL.
