Webhooks
Get notified the instant a scan completes — no polling, no cron jobs. QorBOM™ POSTs a signed JSON envelope to your registered endpoint and retries on any non-2xx response.
Event types
| Event | When |
|---|---|
cbom.scan.completed | Scan finished successfully — BOM is ready to download. |
cbom.scan.failed | Scan errored (network failure, malformed repo, timeout). data.error describes the cause. |
Payload shape
Every event has the same envelope. The data object varies by event type.
{
"id": "evt_8f2a1c0b9d3e",
"type": "cbom.scan.completed",
"created": "2026-05-31T11:24:18Z",
"tenant_id": "tnt_abc123",
"data": {
"scan_id": "cbom_a1b2c3d4e5f6g7h8",
"repo_url": "https://github.com/octocat/Hello-World",
"status": "completed",
"score": 78,
"components_total": 142,
"findings_total": 6,
"bom_url": "https://api.qorbom.com/api/v1/cbom/scans/cbom_a1b2.../bom.json"
},
"methodology_version": "qortrace-cbom-method-v0.1"
}Signature verification
Every request is signed with HMAC-SHA256 using your webhook signing secret (different from your API key — see your partner portal). Verify it before processing.
X-QorBOM-Signature: t=<ts>,v1=<hex>Comma-separated. t is the unix timestamp; v1 is HMAC-SHA256(secret, "t.body").
Reject events older than 5 minutes (now - t > 300). That guards against replay attacks if your signing secret ever leaks.
import hmac, hashlib, time
from fastapi import HTTPException, Request
SECRET = os.environ["QORBOM_WEBHOOK_SECRET"]
TOLERANCE = 300 # seconds
@app.post("/qorbom-webhook")
async def webhook(request: Request):
body = await request.body()
sig_header = request.headers.get("x-qorbom-signature", "")
parts = dict(p.split("=", 1) for p in sig_header.split(","))
t = int(parts.get("t", 0))
v1 = parts.get("v1", "")
if abs(time.time() - t) > TOLERANCE:
raise HTTPException(400, "stale event")
expected = hmac.new(
SECRET.encode(),
f"{t}.{body.decode()}".encode(),
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected, v1):
raise HTTPException(400, "bad signature")
# ... process event ...
return {"ok": True}Retries & delivery guarantees
We retry any non-2xx response with exponential backoff for up to 24 hours: 30s, 2m, 10m, 1h, 6h, 24h. Return 200 as soon as you've persisted the event; process asynchronously.
id. We guarantee at-least-once delivery, not exactly-once — a flaky network in the response path can produce a duplicate even on a successful processor.Register an endpoint
Webhook endpoints are configured per-partner in your portal at /qorbom/portal. Up to 5 endpoints per tenant; each gets its own signing secret.