Docs

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

  1. In the Shieldome app, go to Settings → Integrations
  2. Scroll to the Slack or Discord section
  3. Enter your incoming webhook URL
  4. 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:

json
{
  "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:

json
{
  "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"
}
ℹ️
Slack, Discord, and Teams URLs are auto-detected. If your webhook URL belongs to one of these platforms, Shieldome sends a rich formatted message instead of the generic JSON payload above (Slack blocks, Discord embeds, or Teams Adaptive Card).

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:

python
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
javascript (Node/Express)
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);
});
ℹ️
Slack/Discord/Teams deliveries use each platform's native format and are not signed - those platforms do not verify a Shieldome signature. The 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:

AttemptDelay after previous
1st (initial)Immediately
2nd2 seconds
3rd4 seconds
4th8 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)

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)

javascript
// 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:

python
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:

bash
# 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