Webhooks

Register HTTPS webhooks on a pentest to receive vulnerability lifecycle events, verify their HMAC signature, and consume the payload.

All paths are relative to https://api.aleex-rank.ai/api/v2 and authenticate with X-API-Key: rk_... (see REST API). Webhooks let you react to vulnerability changes in real time — notify Slack, kick off a pipeline, or sync findings into your own tracker — instead of polling.

Webhooks are registered per pentest and are gated to Enterprise teams (see Teams & tiers).

Register a webhook

POST /pentests/{id}/webhooks
Content-Type: application/json
FieldTypeRequiredNotes
urlstringYesMust be a valid HTTPS URL
eventsstring[]YesAt least one event from the list below
secretstringNoMin 16 characters; enables signature verification
{
  "url": "https://ci.example.com/hooks/rank",
  "secret": "a-long-random-secret-value",
  "events": ["vulnerability.resolved", "vulnerability.status_changed"]
}

Response:

{
  "success": true,
  "data": {
    "message": "Webhook registered successfully",
    "webhook_id": 7,
    "url": "https://ci.example.com/hooks/rank",
    "events": ["vulnerability.resolved", "vulnerability.status_changed"]
  }
}

List and delete

GET    /pentests/{id}/webhooks
DELETE /pentests/{id}/webhooks/{hookId}
{
  "success": true,
  "data": {
    "webhooks": [
      {"id": 7, "url": "https://ci.example.com/hooks/rank", "events": ["vulnerability.resolved"]}
    ],
    "total": 1
  }
}

Secrets are never returned in listings.

Events

EventFires when
vulnerability.createdA new finding is created (e.g. agents surface it during a run)
vulnerability.status_changedA finding changes state
vulnerability.assignedA finding is assigned or unassigned
vulnerability.resolvedA finding is resolved
vulnerability.reopenedA finding is reopened
vulnerability.commentedA comment is added to a finding

Payload

Each delivery is an HTTP POST with a JSON body wrapping the event name and a payload object:

{
  "event": "vulnerability.resolved",
  "payload": {
    "vulnerability_id": 42,
    "pentest_id": 10,
    "triggered_by": 1,
    "timestamp": "2026-02-10T15:00:00+00:00",
    "old_status": "in_progress",
    "new_status": "resolved",
    "resolution_type": "evidenced"
  }
}

The exact payload fields vary by event (an assigned event carries the assignee, a commented event carries the comment), so treat payload as event-specific.

Delivery headers

HeaderValue
Content-Typeapplication/json
User-AgentRank-Webhook/1.0
X-Webhook-Eventthe event name, e.g. vulnerability.resolved
X-Webhook-Signaturesha256=<hmac> — only when a secret is configured

Delivery is fire-and-forget with a short timeout: respond 2xx quickly and do heavy work asynchronously. Endpoints must be reachable over HTTPS with a valid certificate.

Verify the signature

When you set a secret, every delivery includes an X-Webhook-Signature header containing the HMAC-SHA256 of the raw request body keyed by your secret. Compute the same digest over the exact bytes you received and compare in constant time. Reject the request if it doesn’t match.

import hmac
import hashlib

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature_header or "")
import crypto from "node:crypto";

function verify(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(`sha256=${expected}`);
  const b = Buffer.from(signatureHeader || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Example consumer

A minimal receiver that verifies the signature, then acts on the event:

from flask import Flask, request, abort
import hmac, hashlib, os

app = Flask(__name__)
SECRET = os.environ["RANK_WEBHOOK_SECRET"]

@app.post("/hooks/rank")
def rank_webhook():
    raw = request.get_data()  # raw bytes, before JSON parsing
    sig = request.headers.get("X-Webhook-Signature", "")
    expected = "sha256=" + hmac.new(SECRET.encode(), raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, sig):
        abort(401)

    body = request.get_json()
    event = body["event"]
    payload = body["payload"]

    if event == "vulnerability.resolved":
        notify_team(payload["vulnerability_id"], payload.get("resolution_type"))

    return "", 200  # acknowledge fast

Register from CI

A common pattern is to register a webhook as part of a pipeline so a build is notified when criticals are resolved:

curl -X POST https://api.aleex-rank.ai/api/v2/pentests/10/webhooks \
  -H "X-API-Key: rk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://ci.example.com/hooks/rank",
    "secret": "a-long-random-secret-value",
    "events": ["vulnerability.resolved", "vulnerability.reopened"]
  }'

For pulling findings on demand rather than receiving pushes, pair webhooks with the Vulnerabilities API — for example fetch the summary or run a quality gate when an event arrives.