Integrations
Connect VersionOps with your existing tools and workflows. Send alerts to Slack, trigger CI/CD pipelines, or build custom dashboards with our API.
Available Integrations
VersionOps supports multiple integration types to fit your workflow:
Notifications
Slack, Email, Webhooks
API Access
REST API, Service Tokens
CI/CD
GitHub Actions, GitLab CI, Jenkins
Quick Overview
Slack
Send CVE alerts and version updates to Slack channels
Webhooks
Real-time event notifications to any HTTP endpoint
GitHub Actions
Pre-deployment security checks in your CI/CD
GitLab CI
Integrate version checks into GitLab pipelines
Jenkins
Add security gates to Jenkins pipelines
Email alerts for CVEs and version changes
Trivy Scanner
Container image, IaC, and Kubernetes vulnerability scanning
Container Registries
Private registry support for Docker Hub, ECR, GCR, ACR, and more
Slack Integration
Send real-time alerts to Slack channels when CVEs are detected, versions change, or new hosts are registered.
Setup Steps
Create Slack Incoming Webhook
Go to Slack API and create an app with Incoming Webhooks enabled. Copy the webhook URL.
Configure in VersionOps
Go to Dashboard Settings and add your Slack webhook URL in the Notifications section.
Create Notification Rules
Set up rules to determine when to send alerts. Filter by severity, application, or host tags.
Message Format
VersionOps sends rich Slack messages with actionable information:
Example: Send Alert via Webhook
curl -X POST https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX \
-H 'Content-Type: application/json' \
-d '{
"text": "CVE Alert: Critical vulnerability detected",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*CVE-2024-1234* detected on *web-server-01*\nSeverity: HIGH | CVSS: 8.1"
}
}
]
}'Webhook Integration
Receive real-time event notifications at any HTTP endpoint. Perfect for custom automation, SIEM integration, or building your own dashboards.
Configuration
| Endpoint URL | Your HTTPS endpoint that will receive POST requests |
| Secret Key | Used to sign payloads for verification (HMAC-SHA256) |
| Event Types | Select which events trigger webhooks |
| Retry Policy | 3 retries with exponential backoff (1s, 5s, 30s) |
Event Types
new_hostWhen a new host registers with VersionOps
host_offlineWhen a host stops reporting (after 15 minutes)
version_changeWhen an application version changes on a host
cve_detectedWhen a new CVE is found affecting your hosts
cve_resolvedWhen a CVE is fixed by version upgrade
scan_completedWhen a scheduled CVE scan completes
Payload Format
All webhooks are sent as POST requests with JSON payloads:
{
"event": "new_host",
"timestamp": "2024-01-20T15:30:00Z",
"data": {
"host_id": "host_abc123",
"hostname": "web-server-01",
"ip_address": "192.168.1.10",
"os": "Ubuntu 22.04 LTS",
"agent_version": "1.0.5"
},
"organization": {
"id": "org_xyz789",
"name": "Acme Corp"
}
}Signature Verification
All webhook requests include a X-VersionOps-Signature header for payload verification:
import hmac
import hashlib
def verify_webhook_signature(payload, signature, secret):
"""Verify VersionOps webhook signature"""
expected = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
# In your webhook handler
@app.post("/webhook")
def handle_webhook(request):
payload = request.body
signature = request.headers.get("X-VersionOps-Signature")
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
return {"error": "Invalid signature"}, 401
# Process webhook...
event = json.loads(payload)
print(f"Received event: {event['event']}")Headers Included
X-VersionOps-Signature | HMAC-SHA256 signature of the payload |
X-VersionOps-Event | Event type (e.g., cve_detected) |
X-VersionOps-Delivery | Unique delivery ID for deduplication |
Content-Type | application/json |
API Integration
Access VersionOps data programmatically using service tokens. Build custom integrations, dashboards, or automation workflows.
Service Tokens
Service tokens provide secure, long-lived API access for machine-to-machine communication.
Create a Service Token
Go to Dashboard → Settings → Service Tokens and click Create Token. Give it a descriptive name.
Copy the Token
The token is only shown once. Copy it and store it securely in your secrets manager.
Use in API Requests
Include the token in the Authorization header:Bearer vops_agent_...
# Create a service token in Dashboard -> Settings -> Service Tokens
# Use the token in API requests
curl -X GET https://api.versionops.com/api/hosts \
-H "Authorization: Bearer vops_agent_abc123def456..." \
-H "Content-Type: application/json"Token Permissions
| Permission | Access |
|---|---|
read:hosts | List and view hosts in your organization |
read:applications | View application inventory and versions |
read:vulnerabilities | Access CVE data and security alerts |
write:hosts | Create/update host data (agent access) |
Rate Limits
- Standard: 100 requests/minute per token
- Agent endpoints: 1000 requests/minute
- Burst: Up to 10 concurrent requests
Rate limit headers are included in all responses:X-RateLimit-Remaining, X-RateLimit-Reset
Python Client Example
import requests
class VersionOpsClient:
def __init__(self, api_token):
self.base_url = "https://api.versionops.com"
self.headers = {
"Authorization": f"Bearer {api_token}",
"Content-Type": "application/json"
}
def get_hosts(self, page=1, per_page=20):
"""Get all hosts in your organization"""
response = requests.get(
f"{self.base_url}/api/hosts",
headers=self.headers,
params={"page": page, "per_page": per_page}
)
return response.json()
def get_vulnerabilities(self):
"""Get detected CVEs across all hosts"""
response = requests.get(
f"{self.base_url}/api/vulnerabilities",
headers=self.headers
)
return response.json()
def get_application_stats(self, app_name):
"""Get version distribution for an application"""
response = requests.get(
f"{self.base_url}/api/applications/{app_name}/stats",
headers=self.headers
)
return response.json()
# Usage
client = VersionOpsClient("vops_agent_abc123...")
hosts = client.get_hosts()
print(f"Total hosts: {hosts['total']}")See the complete API reference for all available endpoints.
View API DocumentationCI/CD Integration
Add security gates to your deployment pipelines. Check for vulnerabilities before deploying and verify that target versions match expectations.
Use Cases
Pre-deployment Security Checks
Block deployments if critical CVEs exist in production
Version Verification
Ensure deployed versions match your requirements
Compliance Reports
Generate security reports as part of release process
GitHub Actions
name: Pre-deployment Version Check
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
version-check:
runs-on: ubuntu-latest
steps:
- name: Check for vulnerable packages
env:
VERSIONOPS_TOKEN: ${{ secrets.VERSIONOPS_TOKEN }}
run: |
# Get vulnerabilities for our stack
VULNS=$(curl -s -H "Authorization: Bearer $VERSIONOPS_TOKEN" \
"https://api.versionops.com/api/vulnerabilities?severity=HIGH,CRITICAL")
# Parse and check
CRITICAL_COUNT=$(echo $VULNS | jq '.items | map(select(.severity == "CRITICAL")) | length')
if [ "$CRITICAL_COUNT" -gt "0" ]; then
echo "::error::Found $CRITICAL_COUNT critical vulnerabilities!"
echo $VULNS | jq '.items[] | select(.severity == "CRITICAL")'
exit 1
fi
echo "No critical vulnerabilities found"
- name: Verify target versions
env:
VERSIONOPS_TOKEN: ${{ secrets.VERSIONOPS_TOKEN }}
run: |
# Ensure target versions are tracked
NGINX_VERSION=$(curl -s -H "Authorization: Bearer $VERSIONOPS_TOKEN" \
"https://api.versionops.com/api/applications/nginx" | jq -r '.latest_version')
echo "Latest nginx version: $NGINX_VERSION"GitLab CI
stages:
- security-check
- deploy
version-audit:
stage: security-check
image: curlimages/curl:latest
variables:
VERSIONOPS_API: "https://api.versionops.com"
script:
- |
# Check for outdated packages
OUTDATED=$(curl -s -H "Authorization: Bearer $VERSIONOPS_TOKEN" \
"$VERSIONOPS_API/api/applications" | \
jq '[.items[] | select(.has_updates == true)] | length')
echo "Found $OUTDATED applications with available updates"
# Get CVE report
curl -s -H "Authorization: Bearer $VERSIONOPS_TOKEN" \
"$VERSIONOPS_API/api/vulnerabilities" | \
jq '.items[] | "\(.cve_id): \(.application) (\(.severity))"'
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
deploy-production:
stage: deploy
script:
- echo "Deploying to production..."
needs:
- version-audit
rules:
- if: $CI_COMMIT_BRANCH == "main"Jenkins Pipeline
pipeline {
agent any
environment {
VERSIONOPS_TOKEN = credentials('versionops-api-token')
VERSIONOPS_API = 'https://api.versionops.com'
}
stages {
stage('Security Scan') {
steps {
script {
// Get vulnerability count
def vulns = sh(
script: """
curl -s -H "Authorization: Bearer ${VERSIONOPS_TOKEN}" \
"${VERSIONOPS_API}/api/vulnerabilities"
""",
returnStdout: true
).trim()
def vulnData = readJSON text: vulns
def criticalCount = vulnData.items.findAll { it.severity == 'CRITICAL' }.size()
if (criticalCount > 0) {
error "Found ${criticalCount} critical vulnerabilities!"
}
echo "Security scan passed - no critical vulnerabilities"
}
}
}
stage('Version Verification') {
steps {
script {
// Verify expected versions are deployed
def hosts = sh(
script: """
curl -s -H "Authorization: Bearer ${VERSIONOPS_TOKEN}" \
"${VERSIONOPS_API}/api/hosts?search=production"
""",
returnStdout: true
).trim()
echo "Production hosts verified"
}
}
}
stage('Deploy') {
steps {
echo 'Deploying application...'
}
}
}
}npm Dependency Scanning in CI/CD
Integrate dependency scanning into your build pipelines for package-lock.json, yarn.lock (v1), and yarn berry.
Each pipeline ships in two parts. The base job uploadspackage.json and the lockfile to VersionOps and exits — results land in the dashboard. Add the optional gate step to poll the scan job and fail the build when CRITICAL vulnerabilities are found. Pick the parts you want; you can ship upload-only and decide to enforce the gate later.
Use Cases
Pre-merge Security Gates
Block PRs with vulnerable npm dependencies
Automated Dependency Audit
Scan on every commit against OSV and GitHub Advisory
Update Recommendations
Get semver-aware suggestions in PR comments
GitHub Actions
name: VersionOps Dependency Scan
on:
push:
branches: [main]
paths: ['package.json', 'package-lock.json', 'yarn.lock']
pull_request:
branches: [main]
paths: ['package.json', 'package-lock.json', 'yarn.lock']
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Upload to VersionOps
id: scan
env:
VERSIONOPS_TOKEN: ${{ secrets.VERSIONOPS_TOKEN }}
run: |
set -euo pipefail
LOCK=$(ls package-lock.json yarn.lock 2>/dev/null | head -n1 || true)
PAYLOAD=$(jq -n \
--arg name "$GITHUB_REPOSITORY" \
--arg sha "$GITHUB_SHA" \
--arg ref "$GITHUB_REF_NAME" \
--arg lockname "${LOCK:-}" \
--slurpfile pkg package.json \
--rawfile lock "${LOCK:-/dev/null}" \
'{
project_name: $name,
package_json: $pkg[0],
lock_file_text: ($lock | if . == "" then null else . end),
lock_file_name: ($lockname | select(. != "")),
commit_sha: $sha,
branch: $ref
}')
RESPONSE=$(curl -sSf --fail-with-body \
-X POST https://api.versionops.com/api/public/cicd/scan \
-H "Authorization: Bearer $VERSIONOPS_TOKEN" \
-H "Content-Type: application/json" \
--data-binary "$PAYLOAD")
echo "$RESPONSE" | jq .
echo "job_id=$(jq -r .job_id <<<"$RESPONSE")" >> "$GITHUB_OUTPUT"Optional: block build on CRITICAL
# Append this step after "Upload to VersionOps" to fail the build when
# the scan reports CRITICAL vulnerabilities. Omit it to upload results
# without blocking the pipeline.
- name: Wait for scan and enforce CRITICAL gate
env:
VERSIONOPS_TOKEN: ${{ secrets.VERSIONOPS_TOKEN }}
JOB_ID: ${{ steps.scan.outputs.job_id }}
run: |
set -euo pipefail
for _ in $(seq 1 60); do
JOB=$(curl -sSf -H "Authorization: Bearer $VERSIONOPS_TOKEN" \
"https://api.versionops.com/api/public/cicd/jobs/$JOB_ID")
STATUS=$(jq -r .status <<<"$JOB")
if [[ "$STATUS" =~ ^(completed|failed|cancelled)$ ]]; then
jq '.result.vulnerabilities // {}' <<<"$JOB" | tee -a "$GITHUB_STEP_SUMMARY"
exit "$(jq -r .exit_code <<<"$JOB")"
fi
sleep 5
done
echo "::error::Scan timed out after 5 minutes"; exit 4GitLab CI
stages:
- scan
versionops:upload:
stage: scan
image: alpine:3.19
variables:
VERSIONOPS_API: "https://api.versionops.com"
before_script:
- apk add --no-cache curl jq
script:
- |
set -euo pipefail
LOCK=$(ls package-lock.json yarn.lock 2>/dev/null | head -n1 || true)
PAYLOAD=$(jq -n \
--arg name "$CI_PROJECT_PATH" \
--arg sha "$CI_COMMIT_SHA" \
--arg ref "$CI_COMMIT_REF_NAME" \
--arg lockname "${LOCK:-}" \
--slurpfile pkg package.json \
--rawfile lock "${LOCK:-/dev/null}" \
'{
project_name: $name,
package_json: $pkg[0],
lock_file_text: ($lock | if . == "" then null else . end),
lock_file_name: ($lockname | select(. != "")),
commit_sha: $sha,
branch: $ref
}')
RESPONSE=$(curl -sSf --fail-with-body \
-X POST "$VERSIONOPS_API/api/public/cicd/scan" \
-H "Authorization: Bearer $VERSIONOPS_TOKEN" \
-H "Content-Type: application/json" \
--data-binary "$PAYLOAD")
echo "$RESPONSE" | jq .
jq -r .job_id <<<"$RESPONSE" > scan-job.txt
artifacts:
paths: [scan-job.txt]
expire_in: 1 hour
rules:
- changes: [package.json, package-lock.json, yarn.lock]Optional: block pipeline on CRITICAL
# Append this job to fail the pipeline when the scan reports CRITICAL
# vulnerabilities. Omit it to upload results without blocking.
stages:
- scan
- gate
versionops:gate:
stage: gate
needs: ['versionops:upload']
image: alpine:3.19
variables:
VERSIONOPS_API: "https://api.versionops.com"
before_script:
- apk add --no-cache curl jq
script:
- |
set -euo pipefail
JOB_ID=$(cat scan-job.txt)
for _ in $(seq 1 60); do
JOB=$(curl -sSf -H "Authorization: Bearer $VERSIONOPS_TOKEN" \
"$VERSIONOPS_API/api/public/cicd/jobs/$JOB_ID")
STATUS=$(jq -r .status <<<"$JOB")
case "$STATUS" in
completed|failed|cancelled)
jq '.result.vulnerabilities // {}' <<<"$JOB"
exit "$(jq -r .exit_code <<<"$JOB")"
;;
esac
sleep 5
done
echo "Scan timed out"; exit 4Jenkins Pipeline
pipeline {
agent any
environment {
VERSIONOPS_TOKEN = credentials('versionops-api-token')
VERSIONOPS_API = 'https://api.versionops.com'
}
stages {
stage('VersionOps: upload') {
steps {
script {
def lockName = ['package-lock.json', 'yarn.lock'].find { fileExists(it) }
writeJSON file: 'scan-request.json', json: [
project_name : env.JOB_NAME,
package_json : readJSON(file: 'package.json'),
lock_file_text: lockName ? readFile(lockName) : null,
lock_file_name: lockName,
commit_sha : env.GIT_COMMIT,
branch : env.GIT_BRANCH,
]
def response = sh(
returnStdout: true,
script: '''
curl -sSf --fail-with-body \
-X POST "$VERSIONOPS_API/api/public/cicd/scan" \
-H "Authorization: Bearer $VERSIONOPS_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @scan-request.json
'''
).trim()
def queued = readJSON text: response
env.VERSIONOPS_JOB_ID = queued.job_id
echo "Scan queued: ${queued.job_id}"
}
}
}
}
}Optional: block build on CRITICAL
// Append this stage after 'VersionOps: upload' to fail the build when
// the scan reports CRITICAL vulnerabilities. Omit it to upload results
// without blocking. VERSIONOPS_JOB_ID is set by the upload stage.
stage('VersionOps: gate') {
steps {
script {
def job = null
for (int i = 0; i < 60; i++) {
def body = sh(
returnStdout: true,
script: '''
curl -sSf -H "Authorization: Bearer $VERSIONOPS_TOKEN" \
"$VERSIONOPS_API/api/public/cicd/jobs/$VERSIONOPS_JOB_ID"
'''
).trim()
job = readJSON text: body
if (job.status in ['completed', 'failed', 'cancelled']) break
sleep time: 5, unit: 'SECONDS'
}
echo "Vulnerabilities: ${job?.result?.vulnerabilities ?: [:]}"
if ((job?.exit_code ?: 4) != 0) {
error "VersionOps gate failed (exit_code=${job?.exit_code}, status=${job?.status})"
}
}
}
}API reference
POST /api/public/cicd/scan queues an async scan and returns { job_id, project_id, poll_url, status: "queued" }. Upload-only pipelines stop here.
GET /api/public/cicd/jobs/{job_id} returns the job state. Gate steps poll until status iscompleted, failed, or cancelled, then exit with exit_code:0 (no CRITICAL), 1 (CRITICAL found),2 (scan error), 3 (cancelled).
Request fields
project_name | Required. Project identifier (created on first scan). |
package_json | Required. Parsed package.json as a JSON object. |
lock_file_text | Raw lockfile contents as a string. Works forpackage-lock.json, yarn.lock v1, and yarn berry. Required for transitive dependencies and exact-version CVE matching. |
lock_file_name | Original filename — selects the parser. Required wheneverlock_file_text is set. |
commit_sha, branch, build_number | Optional CI metadata stored on the scan record. |
Email Notifications
Receive security alerts and status updates via email. Configure recipients, frequency, and alert types.
Email Types
Real-time Alerts
Immediate notification for critical CVEs and security events
Daily Digest
Summary of all changes, new hosts, and resolved issues
Weekly Report
Comprehensive security posture report with trends
Configuration
| Setting | Description |
|---|---|
| Recipients | Email addresses that receive notifications (comma-separated) |
| Severity Filter | Minimum severity level for real-time alerts (LOW, MEDIUM, HIGH, CRITICAL) |
| Digest Frequency | Daily, Weekly, or Disabled |
| Quiet Hours | Suppress non-critical emails during specified hours |
Custom SMTP (Enterprise)
Enterprise customers can configure custom SMTP servers for email delivery. Go to Settings → SMTP Configuration to set up your server.
Custom Integrations
Build custom solutions using the VersionOps API and webhooks. Here are some examples to get you started.
Example: Custom Security Dashboard
Build a React dashboard that displays real-time vulnerability data:
import React, { useState, useEffect } from 'react';
function SecurityDashboard() {
const [vulns, setVulns] = useState([]);
const [stats, setStats] = useState(null);
const API_TOKEN = process.env.REACT_APP_VERSIONOPS_TOKEN;
const API_URL = 'https://api.versionops.com';
useEffect(() => {
const fetchData = async () => {
const headers = { 'Authorization': `Bearer ${API_TOKEN}` };
// Fetch vulnerabilities
const vulnRes = await fetch(`${API_URL}/api/vulnerabilities`, { headers });
const vulnData = await vulnRes.json();
setVulns(vulnData.items);
// Fetch host stats
const hostRes = await fetch(`${API_URL}/api/hosts`, { headers });
const hostData = await hostRes.json();
setStats({
totalHosts: hostData.total,
criticalVulns: vulnData.items.filter(v => v.severity === 'CRITICAL').length
});
};
fetchData();
const interval = setInterval(fetchData, 60000); // Refresh every minute
return () => clearInterval(interval);
}, []);
return (
<div className="dashboard">
<div className="stats">
<div className="stat-card">
<h3>Total Hosts</h3>
<span>{stats?.totalHosts || 0}</span>
</div>
<div className="stat-card critical">
<h3>Critical CVEs</h3>
<span>{stats?.criticalVulns || 0}</span>
</div>
</div>
<div className="vuln-list">
<h2>Recent Vulnerabilities</h2>
{vulns.slice(0, 10).map(vuln => (
<div key={vuln.cve_id} className={`vuln-item ${vuln.severity.toLowerCase()}`}>
<strong>{vuln.cve_id}</strong>
<span>{vuln.application}</span>
<span className="severity">{vuln.severity}</span>
</div>
))}
</div>
</div>
);
}Example: Webhook Consumer
A Flask application that processes VersionOps webhooks and routes events to different systems:
from flask import Flask, request, jsonify
import hmac
import hashlib
import json
app = Flask(__name__)
WEBHOOK_SECRET = "your-webhook-secret"
def verify_signature(payload, signature):
expected = hmac.new(
WEBHOOK_SECRET.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
@app.route("/webhook/versionops", methods=["POST"])
def handle_versionops_webhook():
signature = request.headers.get("X-VersionOps-Signature", "")
if not verify_signature(request.data, signature):
return jsonify({"error": "Invalid signature"}), 401
event = request.json
event_type = event.get("event")
if event_type == "cve_detected":
handle_cve_alert(event["data"])
elif event_type == "version_change":
handle_version_change(event["data"])
elif event_type == "new_host":
handle_new_host(event["data"])
return jsonify({"status": "processed"}), 200
def handle_cve_alert(data):
"""Process CVE detection event"""
cve_id = data["cve_id"]
severity = data["severity"]
affected_hosts = data["affected_hosts"]
# Send to PagerDuty for critical CVEs
if severity == "CRITICAL":
notify_pagerduty(cve_id, affected_hosts)
# Log to security SIEM
log_to_siem("cve_detected", data)
def handle_version_change(data):
"""Process version change event"""
# Update CMDB or asset inventory
update_cmdb(data["host_id"], data["application"], data["new_version"])
def handle_new_host(data):
"""Process new host registration"""
# Add to monitoring systems
add_to_monitoring(data["hostname"], data["ip_address"])
if __name__ == "__main__":
app.run(port=5000)Integration Ideas
SIEM Integration
Forward CVE events to Splunk, ELK, or your security platform
CMDB Sync
Keep your configuration management database up to date
PagerDuty Alerts
Create incidents for critical security vulnerabilities
Compliance Automation
Generate audit reports and track remediation progress
Ready to Integrate?
Create your account and start connecting VersionOps to your workflow.
