Overview
Webhooks let Shieldome push scan results to any URL the moment a scan completes. Use them to send alerts to Slack, create tickets in Jira, or trigger downstream workflows in your own systems.
Shieldome sends an HTTP POST request with a JSON payload to your configured URL. If delivery fails, it retries automatically with exponential backoff (attempts at 0 s, 2 s, 4 s, 8 s). If all four attempts fail, the delivery is abandoned and the failure is recorded in the email log - no further retries occur.
Configuring a webhook
Option A - Per-scan webhook (one URL per scan run)
On the scan form, expand Advanced options and paste your webhook URL into the Webhook URL field before starting the scan. The notification fires once when that scan completes.
Option B - Global Slack / Discord webhook
- In the Shieldome app, go to Settings → Integrations
- Scroll to the Slack or Discord section
- Enter your incoming webhook URL
- Click Save Webhook Settings
The global webhook fires for every scan that meets the configured severity threshold.
Option C - Per-scan webhook via API
Set a webhook URL directly in the scan request body:
{
"target_url": "https://your-app.com",
"scan_type": "vuln",
"scan_authorized": true,
"webhook_url": "https://hooks.example.com/shieldome"
}
Payload format
Shieldome sends a JSON object with the following structure:
{
"event": "scan_completed",
"scan_id": "a1b2c3d4-...",
"target_url": "https://example.com",
"risk_score": 42,
"total_findings": 12,
"vulnerability_counts": {
"critical": 1,
"high": 3,
"medium": 5,
"low": 2,
"info": 1
},
"timestamp": "2025-06-14T10:07:42Z"
}
Verifying the signature
Every generic-JSON delivery is signed so you can confirm it really came from Shieldome and was not tampered with. Each request carries:
X-Shieldome-Signature: sha256=<hex>
The signature is HMAC-SHA256 of the raw request body using your personal signing secret. Read the secret from GET /api/me (field webhook_signing_secret). Recompute it over the raw body and compare with a constant-time check:
import hmac, hashlib
from flask import request, abort
SECRET = "your_webhook_signing_secret" # from GET /api/me
@app.post("/shieldome")
def receive():
raw = request.get_data() # exact bytes, before JSON parsing
sent = request.headers.get("X-Shieldome-Signature", "")
expected = "sha256=" + hmac.new(SECRET.encode(), raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sent, expected):
abort(401)
data = request.get_json()
... # trusted from here
return "", 204
const crypto = require('crypto');
// capture the raw body: app.use(express.json({ verify: (r,_res,buf)=>{ r.rawBody = buf; } }))
app.post('/shieldome', (req, res) => {
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(req.rawBody).digest('hex');
const sent = req.get('X-Shieldome-Signature') || '';
if (sent.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(sent), Buffer.from(expected))) {
return res.sendStatus(401);
}
res.sendStatus(204);
});
X-Shieldome-Signature header is only on the generic JSON payload.Retry behaviour
If your endpoint returns an HTTP 5xx error or is unreachable, Shieldome retries the delivery:
| Attempt | Delay after previous |
|---|---|
| 1st (initial) | Immediately |
| 2nd | 2 seconds |
| 3rd | 4 seconds |
| 4th | 8 seconds |
4xx errors (e.g. 404, 401) are not retried - they indicate a misconfiguration that won't resolve by itself.
After all 4 attempts are exhausted with no successful delivery, the webhook attempt is permanently abandoned. Check your endpoint logs if you suspect missed deliveries - Shieldome will not queue or replay them.
Because there is no redelivery dashboard, make your receiver idempotent (key off scan_id) and, for critical alerts you cannot afford to miss, also poll GET /api/scan/{scan_id} or run scheduled scans as a backstop.
Receiving webhooks - example server (Python)
# webhook_receiver.py - minimal Flask receiver from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/shieldome', methods=['POST']) def receive(): data = request.get_json() scan_id = data.get('scan_id') counts = data.get('vulnerability_counts', {}) critical = counts.get('critical', 0) print(f'Scan {scan_id}: {critical} critical finding(s)') if critical > 0: # send Slack alert, create Jira ticket, etc. alert_team(data) return jsonify({'ok': True}), 200 def alert_team(data): print(f'ALERT: critical findings on {data["target_url"]}') if __name__ == '__main__': app.run(port=8080)
Receiving webhooks - example (Node.js)
// webhook-receiver.js const express = require('express'); const app = express(); app.use(express.json()); app.post('/shieldome', (req, res) => { const { scan_id, vulnerability_counts, target_url } = req.body; console.log(`Scan ${scan_id} on ${target_url}:`, vulnerability_counts); if ((vulnerability_counts?.critical || 0) > 0) { // notify Slack, PagerDuty, etc. console.warn(`CRITICAL: ${vulnerability_counts.critical} issue(s) on ${target_url}`); } res.json({ ok: true }); }); app.listen(8080, () => console.log('Webhook receiver on :8080'));
Sending to Slack
The easiest way to get Shieldome alerts in Slack is to paste a Slack Incoming Webhook URL directly as your Shieldome webhook URL. Shieldome detects Slack, Discord and Teams URLs automatically and sends each platform's native format, so no relay server is needed for these three.
A relay is only useful if you want to reshape the generic JSON for some other destination. In that case point your Shieldome webhook at a small relay that reformats the payload:
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
SLACK = 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK'
@app.route('/relay', methods=['POST'])
def relay():
d = request.get_json()
s = d.get('summary', {})
emoji = '🔴' if s.get('critical') else '🟠' if s.get('high') else '🟡'
requests.post(SLACK, json={'text':
f"{emoji} *Shieldome scan completed* - {d['target_url']}\n"
f"Critical: {s.get('critical',0)} | High: {s.get('high',0)} | "
f"Medium: {s.get('medium',0)}\n"
f"<{d.get('report_url')}|View full report>"
})
return jsonify({'ok': True})
if __name__ == '__main__':
app.run(port=8080)
Testing your webhook locally
Use ngrok to expose a local server to the internet for testing:
# 1. Start your receiver python webhook_receiver.py # 2. In another terminal, expose it via ngrok ngrok http 8080 # 3. Copy the https://xxxxx.ngrok.io URL # 4. Paste it as your webhook URL in Shieldome # 5. Run a scan - your local server receives the payload