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
-
(Optional) Register a tenant webhook so control and finding events also hit your endpoint. The signing secret is returned once. Full catalog: Webhooks.
import rank client = rank.Rank() created = client.webhooks.create( url="https://hooks.example.com/rank", events=["vulnerability.validated", "pentest.killed"], description="SOC inbox", ) print(created.secret) # In your HTTPS handler: rank.verify_signature(raw_body, headers, created.secret) -
Create the Jira integration before you create and launch pentests. Credentials are never returned on later GETs.
sync_inbound=Truerequiresconfig.inbound_secret.integ = client.integrations.create( provider="jira", name="SecOps Jira", credentials={"email": "bot@example.com", "api_token": "ATATT3x..."}, config={ "project_key": "SEC", "issue_type": "Bug", "inbound_secret": "a-long-random-secret", }, events=["vulnerability.validated", "vulnerability.resolved"], sync_inbound=True, base_url="https://acme.atlassian.net", ) integration = integ.integration -
Test credentials (does not create a ticket), then sync a pentest if findings already exist. Closing a linked Jira ticket marks the finding resolved and queues a retest when
auto_retest_on_ticket_closeis on.test = client.integrations.test(integration.id) print(test.ok) synced = client.integrations.sync(integration.id, pentest_id=123) print(synced.message, synced.summary) links = client.integrations.links(integration.id) for item in links.items: print(item)
-
(Optional) Tenant webhook.
curl https://api.aleex-rank.ai/api/v2/webhooks \ -H "X-API-Key: $RANK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "url": "https://hooks.example.com/rank", "events": ["vulnerability.validated", "pentest.killed"], "description": "SOC inbox" }' -
Create the Jira integration before the pentest.
curl https://api.aleex-rank.ai/api/v2/integrations \ -H "X-API-Key: $RANK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "provider": "jira", "name": "SecOps Jira", "credentials": {"email": "bot@example.com", "api_token": "ATATT3x..."}, "config": { "project_key": "SEC", "issue_type": "Bug", "inbound_secret": "a-long-random-secret" }, "events": ["vulnerability.validated", "vulnerability.resolved"], "sync_inbound": true, "base_url": "https://acme.atlassian.net" }'GitHub:
credentials.token,config.owner+config.repo. Slack:credentials.bot_token,config.channel— Slack has no inbound (sync_inbound: trueis400). -
Test, sync, list links.
POST /api/v2/integrations/21/test POST /api/v2/integrations/21/sync Content-Type: application/json {"pentest_id": 123} GET /api/v2/integrations/21/links X-API-Key: rk_...
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}")