Skip to content
ERP Preflight

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 http or https; production deployments accept only https.
  • 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.failed and finding.critical. Through the API you can pass events with 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 status COMPLETED or PARTIAL.
  • 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 produced BLOCKER or CRITICAL findings.
  • 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/json and User-Agent: ERPPreflight-Webhooks/1.0
  • X-ERPPreflight-Event-Id — the event id, identical for retries and replays; use it to de-duplicate
  • X-ERPPreflight-Delivery-Id and X-Delivery-ID — the id of this delivery
  • X-Event-Type — the event type
  • X-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-256 signs the raw request body.
  • X-ERPPreflight-Signature signs <t>.<raw body>, where t is 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 ping event 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 delivery

Registering and deleting endpoints is recorded in the audit log.

Webhooks & events — ERP Preflight Docs