Tenant webhooks

Subscribe to tenant webhooks, verify HMAC signatures with rank.verify_signature, and list deliveries.

What you’ll build

A tenant webhook: one HTTPS subscription that fires for every pentest of its owner (you, or a team). You register the URL and events, store the signing secret (returned once), verify each delivery with rank.verify_signature, and list delivery history.

Prefer tenant webhooks over legacy per-pentest hooks (/pentests/{id}/webhooks). Integrations (Jira / GitHub / Slack) are a different channel — see Push findings to Jira, GitHub or Slack.

The full event catalog (finding, pentest, control) is on Webhooks.

Prerequisites

  • A Rank account and an API token (see Authentication).
  • An HTTPS endpoint Rank can reach (SSRF-checked: no private IPs, localhost, or cloud metadata).
pip install rank-sdk
export RANK_API_KEY=rk_...
export RANK_WEBHOOK_URL=https://hooks.example.com/rank

Steps

Event names are append-only. Finding events carry vulnerability_id; pentest and control events carry pentest_id. See the tables on Webhooks.

Run it

Save the following as tenant_webhooks.py, set RANK_API_KEY and RANK_WEBHOOK_URL, then run python tenant_webhooks.py.

"""Subscribe to a tenant webhook, demonstrate signature verification, list deliveries.

What this script does:
  1. Registers a tenant webhook (secret returned once).
  2. Builds a genuine HMAC and verifies it with rank.verify_signature.
  3. Sends a signed ping and lists recent deliveries.

Run:
    pip install rank-sdk
    export RANK_API_KEY=rk_...
    export RANK_WEBHOOK_URL=https://hooks.example.com/rank
    python tenant_webhooks.py
"""

from __future__ import annotations

import hashlib
import hmac
import os
import time

import rank

WEBHOOK_URL = os.environ.get("RANK_WEBHOOK_URL", "")


def main() -> None:
    if not WEBHOOK_URL:
        raise SystemExit("ERROR: set RANK_WEBHOOK_URL to an HTTPS endpoint.")

    with rank.Rank() as client:
        created = client.webhooks.create(
            url=WEBHOOK_URL,
            events=[
                "vulnerability.validated",
                "vulnerability.retested",
                "pentest.completed",
                "control.kill_requested",
                "control.approval_requested",
            ],
            description="SOC inbox",
        )
        hook = created.webhook
        secret = created.secret
        print(f"Webhook id={hook.id if hook else '?'} secret={secret}")

        if secret:
            raw = b'{"id":"demo","type":"ping","version":2}'
            timestamp = str(int(time.time()))
            digest = hmac.new(
                secret.encode(),
                timestamp.encode() + b"." + raw,
                hashlib.sha256,
            ).hexdigest()
            headers = {
                "X-Rank-Timestamp": timestamp,
                "X-Rank-Signature": "v1=" + digest,
            }
            rank.verify_signature(raw, headers, secret)
            print("verify_signature: ok")

        if hook is not None:
            ping = client.webhooks.test(hook.id)
            delivery = ping.delivery
            print(
                f"Ping: {ping.message} "
                f"status={delivery.status if delivery else None}"
            )
            deliveries = client.webhooks.list_deliveries(hook.id)
            print(f"Deliveries on this page: {len(deliveries.items)}")
            for d in deliveries.items[:5]:
                print(f"  [{d.id}] {d.event} {d.status} http={d.http_status}")


if __name__ == "__main__":
    try:
        main()
    except rank.AuthenticationError:
        print("ERROR: invalid or missing API key. Set RANK_API_KEY.")
    except rank.SignatureVerificationError as exc:
        print(f"Signature error: {exc}")
    except rank.APIError as exc:
        print(f"API error ({exc.status_code}): {exc.message}")

Where to go next