Webhooks & events
Receive signed HTTP callbacks for analysis results and other events: event catalog, payload, HMAC-SHA256 signatures, retries and delivery log.
Content reviewed September 26, 2026
Register an endpoint
Organization owners and security admins register endpoints in Settings → Workspace & API → Webhooks (field Endpoint URL). An API key with the scope webhooks:manage can manage endpoints through the API as well.
- The URL must use
httporhttps; production deployments accept onlyhttps. - The target must be publicly reachable. Addresses in private or loopback networks are rejected unless the operator allows private networks for self-hosted receivers.
- An endpoint registered without an event list receives
analysis.completed,analysis.failedandfinding.critical. Through the API you can passeventswith up to 50 event types, or*for all events.
Each endpoint has its own signing secret (whsec_…), stored encrypted. The API returns it exactly once, in the response of POST /api/v1/webhooks. The registration form in Settings does not display it: to obtain a secret in the web app, open Integrations → Webhooks and choose Rotate secret — the new secret is shown once.
Event catalog
GET /api/v1/webhooks/events returns the catalog; Integrations → Webhooks shows it as well.
analysis.completed— an analysis finished with statusCOMPLETEDorPARTIAL.analysis.failed— an analysis failed or could not be executed.analysis.cancelled— an analysis was cancelled; the findings of the run were discarded.finding.critical— an analysis producedBLOCKERorCRITICALfindings.finding.status_changed— the review status of a finding changed (for example verified, risk accepted, false positive, resolved).finding.assigned— a finding was assigned to a member.connector.unhealthy— a connector became unhealthy after repeated failures.traceability.task_dispatched— a work item was created from a finding in an external system.change_set.created,change_set.simulated,change_set.approved— a change set was created, its what-if simulation finished, or it was approved.agent.proposal_verdict,agent.proposal_approved,agent.proposal_executed— the Agent Gate produced a verdict on an agent proposal, a person approved it, or it was executed.test.failed— a regression test run in the Test Lab failed or ended in an error.release_watch.changed— a release watch detected changes in a new knowledge snapshot.organization.plan_changed— the plan of the organization changed.webhook.auto_disabled— a webhook endpoint was disabled after too many consecutive failed deliveries.usage.threshold_reached— a plan meter reached 80 % or 100 % of its limit.
The catalog lists every event type the platform emits, and GET /api/v1/webhooks/events names the data fields of each. An endpoint subscribed to * receives all of them. The earlier names finding.reviewed and organization.plan_upgraded are still accepted and deliver finding.status_changed and organization.plan_changed.
Payload and headers
Every delivery is an HTTP POST with a JSON body in the same envelope:
{
"id": "0b6f4c1e-…",
"event": "analysis.completed",
"organizationId": "7d1a…",
"timestamp": "2026-09-26T08:15:30.000Z",
"data": {
"analysisId": "…",
"projectId": "…",
"status": "COMPLETED",
"totalFindings": 12
}
}id is the event id; data carries the facts of the event. For analysis.completed and analysis.failed these are analysisId, projectId, triggeredBy, engineTypes, targetRelease, status, totalFindings and engineOutcomes, plus reason when the run failed. finding.critical carries analysisId, projectId, triggeredBy, blockerCount, criticalCount and engines. The delivery log shows the exact payload of every delivery.
Headers of each delivery:
Content-Type: application/jsonandUser-Agent: ERPPreflight-Webhooks/1.0X-ERPPreflight-Event-Id— the event id, identical for retries and replays; use it to de-duplicateX-ERPPreflight-Delivery-IdandX-Delivery-ID— the id of this deliveryX-Event-Type— the event typeX-ERPPreflight-Signature—t=<unix seconds>,v1=<hex>X-Hub-Signature-256—sha256=<hex>(GitHub-compatible)
Verify the signature
Both signatures are HMAC-SHA256, keyed with the endpoint's signing secret — the complete string including the whsec_ prefix:
X-Hub-Signature-256signs the raw request body.X-ERPPreflight-Signaturesigns<t>.<raw body>, wheretis the Unix timestamp from the same header. Prefer this signature: the timestamp lets you reject replayed requests.
Verify against the body exactly as received, before parsing the JSON, compare in constant time and reject timestamps more than 5 minutes (300 seconds) away from your clock. Example for Node.js:
const crypto = require('node:crypto');
// secret: whsec_…, rawBody: the request body as received (UTF-8 string),
// header: value of X-ERPPreflight-Signature
function verifyErpPreflightSignature(secret, rawBody, header, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));
const t = Number(parts.t);
if (!Number.isFinite(t) || !parts.v1) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceSec) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(parts.v1, 'hex');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Answer with a 2xx status as soon as the event is accepted, process it asynchronously and de-duplicate on X-ERPPreflight-Event-Id.
Retries, delivery log and replay
A delivery succeeds when your endpoint answers with a 2xx status within 10 seconds. Otherwise it is retried — up to 6 attempts in total: the first right away, then after about 30 seconds, 2 minutes, 10 minutes, 30 minutes and 2 hours. After the sixth failed attempt the delivery is marked DEAD. Each event is queued at most once per endpoint.
- Delivery log: Integrations → Webhooks → Show delivery log lists each delivery with event, status (
PENDING,SUCCEEDED,FAILED,DEAD), attempts, last HTTP status, error, duration and next retry. - Replay: Replay sends the payload of an earlier delivery again as a new, freshly signed delivery with the same event id.
- Test event: Send test event in Settings sends a signed
pingevent once; it is logged but never retried. - Automatic disabling: after 15 consecutive failed attempts (operator setting) the endpoint is disabled and the person who registered it and the organization owners are notified. A disabled endpoint receives no new events; its pending retries resume once you choose Enable under Integrations → Webhooks. Test events do not count towards this limit.
Management API
All routes need an organization owner or security admin, or an API key with webhooks:manage:
GET /api/v1/webhooks/events event catalog
POST /api/v1/webhooks {"url", "events"}; returns the secret once
GET /api/v1/webhooks endpoints with status and delivery counts
PATCH /api/v1/webhooks/{id} {"status": "ACTIVE"} or {"status": "DISABLED"}
DELETE /api/v1/webhooks/{id} delete an endpoint
POST /api/v1/webhooks/{id}/test send a signed ping
POST /api/v1/webhooks/{id}/rotate-secret new secret, returned once
GET /api/v1/webhooks/{id}/deliveries?limit=50 delivery log (at most 200, newest first)
GET /api/v1/webhooks/{id}/deliveries/{deliveryId}/payload payload of one delivery
POST /api/v1/webhooks/{id}/deliveries/{deliveryId}/replay replay a deliveryRegistering and deleting endpoints is recorded in the audit log.