Skip to content

Webhooks

Webhooks push events to your endpoint as they happen — a scan finishing, a regression, a VPAT going live — so you don’t have to poll.

Subscribe

Create an endpoint in the dashboard (Settings → Webhooks) or via the API. Each endpoint gets a signing secret, shown once. Delivery is an at-least-once, signed POST with a JSON body.

Event types

EventFires when
scan.completedA scan finishes successfully
scan.failedA scan errors or times out
scan.scheduled_dueA scheduled scan is due — your runner/Action can pick it up
scan.regressedA scan regressed vs. the previous run of the same schedule
scan.hosted_enqueuedA hosted scan was queued for the runner fleet
vpat.finding_reviewedA reviewer confirmed or overrode a criterion
vpat.publishedA VPAT version was published
findings.submittedFindings were ingested for a product
cache.invalidatedAn intent cache was invalidated (e.g. a deploy)
capability.invoked / capability.failedA live-assist capability ran or failed
quota.exceededAn org hit a rate/usage limit
member.role_changed / member.removedTeam membership changed
invite.created / invite.acceptedAn invite was sent or accepted
sso.auto_joinA user auto-joined an org via SSO domain match

Verify the signature

Every delivery carries three headers:

x-wholisphere-signature: t=<unix-seconds>,v1=<hmac-sha256-hex>
x-wholisphere-event-id: <uuid>
x-wholisphere-event-type: scan.completed

The signature is HMAC-SHA256(secret, "<t>.<raw-body>"). Verify over the raw request bytes, before JSON parsing. The SDK does this with a constant-time comparison. verifyWebhook is async (it uses SubtleCrypto) — always await it, or the try/catch won’t catch verification failures:

import { verifyWebhook, WebhookVerificationError } from '@wholisphere.ai/sdk';
try {
const event = await verifyWebhook({
secret: process.env.WHOLISPHERE_WEBHOOK_SECRET!,
signature: req.headers['x-wholisphere-signature'],
payload: rawBody,
});
// event.type, event.data …
} catch (e) {
if (e instanceof WebhookVerificationError) return res.status(400).end();
throw e;
}

Reject anything that fails verification, and treat a stale t (older than a few minutes) as replay.

Delivery + retries

Deliveries retry with exponential backoff (roughly 60s → 4h across 5 attempts) and land in a dead-letter after the final failure. Return a 2xx quickly (within a few seconds); do heavy work asynchronously. Deduplicate on x-wholisphere-event-id — the same event may be delivered more than once.