Push findings to Jira, GitHub or Slack

Create a Jira, GitHub or Slack integration before you launch a pentest, then test credentials and sync findings.

What you’ll build

An outbound Jira connection (GitHub and Slack are the same shape) that files tickets from finding events. You create the integration before the pentest so live events open tickets. An integration connected afterwards does not backfill — use sync for that.

Optionally register a short tenant webhook first. Webhooks deliver a signed JSON envelope to your URL; integrations talk to the tracker. Verify webhook (and Jira inbound) signatures with rank.verify_signature. Providers today are jira, github and slack only.

This is not the Tickets API (Rank’s internal support Jira).

Prerequisites

  • A Rank account and an API token (see Authentication).
  • Jira Cloud credentials (email + api_token) and a project key. Swap the provider block for GitHub (token, owner, repo) or Slack (bot_token, channel).
pip install rank-sdk
export RANK_API_KEY=rk_...

Steps

GET /integrations/providers (client.integrations.providers()) describes what each provider needs. Failed (validation_status: failed) findings never open tickets.

Run it

Save the following as integrations_jira.py, set RANK_API_KEY and Jira credentials, then run python integrations_jira.py. The webhook step is skipped unless RANK_WEBHOOK_URL is set.

"""Create a Jira integration (before the pentest), test it, and optionally sync.

What this script does:
  1. Optionally registers a tenant webhook (RANK_WEBHOOK_URL).
  2. Creates a Jira integration and tests credentials.
  3. Syncs RANK_PENTEST_ID if set (backfill; live events need the
     integration to exist before launch).
  4. Lists finding↔ticket links.

Run:
    pip install rank-sdk
    export RANK_API_KEY=rk_...
    export RANK_JIRA_EMAIL=bot@example.com
    export RANK_JIRA_TOKEN=ATATT3x...
    export RANK_JIRA_PROJECT=SEC
    python integrations_jira.py

Optional:
    RANK_WEBHOOK_URL   HTTPS endpoint for a tenant webhook.
    RANK_PENTEST_ID    Existing pentest to sync after the fact.
    RANK_JIRA_BASE     Jira Cloud site (default: https://acme.atlassian.net).
"""

from __future__ import annotations

import os

import rank

WEBHOOK_URL = os.environ.get("RANK_WEBHOOK_URL", "")
JIRA_EMAIL = os.environ.get("RANK_JIRA_EMAIL", "bot@example.com")
JIRA_TOKEN = os.environ.get("RANK_JIRA_TOKEN", "replace-me")
JIRA_PROJECT = os.environ.get("RANK_JIRA_PROJECT", "SEC")
JIRA_BASE = os.environ.get("RANK_JIRA_BASE", "https://acme.atlassian.net")
PENTEST_ID = int(os.environ.get("RANK_PENTEST_ID", "0"))


def main() -> None:
    with rank.Rank() as client:
        if WEBHOOK_URL:
            created = client.webhooks.create(
                url=WEBHOOK_URL,
                events=["vulnerability.validated", "pentest.killed"],
                description="SOC inbox",
            )
            hook = created.webhook
            print(f"Webhook id={hook.id if hook else '?'} secret={created.secret}")
            print("Verify deliveries with rank.verify_signature(raw_body, headers, secret)")

        providers = client.integrations.providers()
        for p in providers.providers:
            print(f"Provider {p.provider} inbound={p.supports_inbound}")

        integ = client.integrations.create(
            provider="jira",
            name="SecOps Jira",
            credentials={"email": JIRA_EMAIL, "api_token": JIRA_TOKEN},
            config={"project_key": JIRA_PROJECT, "inbound_secret": "a-long-random-secret"},
            events=["vulnerability.validated", "vulnerability.resolved"],
            sync_inbound=True,
            base_url=JIRA_BASE,
        )
        integration = integ.integration
        if integration is None:
            raise SystemExit("Integration create returned no object.")
        print(f"Integration id={integration.id}")

        test = client.integrations.test(integration.id)
        print(f"Test ok={test.ok} {test.error or test.note or ''}")

        if PENTEST_ID > 0:
            synced = client.integrations.sync(integration.id, pentest_id=PENTEST_ID)
            print(f"Sync: {synced.message} {synced.summary}")

        links = client.integrations.links(integration.id)
        print(f"Links: {len(links.items)}")
        for item in links.items[:10]:
            print(f"  {item}")


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

Where to go next