Webhooks
Receive a signed POST request to any HTTPS endpoint whenever Nixer detects new critical or high findings in a connected repo.
Event: scan.new_findings
Fired once per scan when at least one new critical or high finding is detected that was not present on the previous scan of the same repo.
Payload schema (version 1)
{
"event": "scan.new_findings",
"version": "1",
"timestamp": "2026-06-08T18:30:00.000Z",
"scan_id": 12345,
"repo_url": "https://github.com/your-org/your-repo",
"scan_url": "https://nixer.polsia.app/app/scans/12345",
"new_findings": {
"critical": 2,
"high": 5,
"total": 7
},
"top_findings": [
{
"rule_id": "prompt-injection",
"severity": "critical",
"title": "Direct prompt injection in user-facing chain",
"file": "src/chains/user_query.py",
"line": 42,
"description": "LLM receives raw user input without sanitization or trust boundary."
},
{
"rule_id": "hardcoded-secret",
"severity": "high",
"title": "OpenAI API key hardcoded in source",
"file": "config/settings.py",
"line": 17,
"description": "API key detected in plaintext — rotate immediately."
}
]
}
Test event payload
When you click Test on an integration, we send this instead of a real finding alert:
{
"event": "test",
"version": "1",
"timestamp": "2026-06-08T18:30:00.000Z",
"message": "Nixer webhook connection test — your integration is working correctly."
}
Signature verification
If you configure a secret on your webhook integration, every request includes:
X-Nixer-Signature: sha256=<hex digest>
The digest is an HMAC-SHA256 of the raw request body using your secret as the key. Verify it before processing the event:
Node.js
const crypto = require('crypto');
function verifySignature(secret, rawBody, signatureHeader) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signatureHeader)
);
}
// Express example
app.post('/webhooks/nixer', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-nixer-signature'];
if (!verifySignature(process.env.NIXER_WEBHOOK_SECRET, req.body, sig)) {
return res.status(401).send('Bad signature');
}
const event = JSON.parse(req.body);
console.log('Nixer event:', event.event, 'scan:', event.scan_id);
res.sendStatus(200);
});
Python
import hmac, hashlib, os
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ['NIXER_WEBHOOK_SECRET'].encode()
@app.route('/webhooks/nixer', methods=['POST'])
def handle():
sig = request.headers.get('X-Nixer-Signature', '')
expected = 'sha256=' + hmac.new(SECRET, request.data, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
event = request.get_json()
print('Nixer event:', event['event'], 'scan:', event.get('scan_id'))
return '', 200
Delivery & retry
Nixer attempts delivery up to 3 times with exponential backoff:
| Attempt | Delay from previous failure |
|---|---|
| 1 | Immediate (fires after scan completes) |
| 2 | 30 seconds |
| 3 | 5 minutes |
We consider a delivery successful when your endpoint returns any 2xx HTTP status. After 3 failures the attempt is marked failed and no further retries occur for that event. The last 20 delivery attempts are visible on the integration card at /app/integrations.
Integration settings
| Setting | Values | Description |
|---|---|---|
severity_filter | criticals_and_highs (default)criticals_only | Which severity levels trigger an alert. |
muted_repo_urls | One URL per line | Repos for which alerts are silenced. |
Schema stability
The payload schema is versioned. The current version is 1 (the version field). We will increment this version and provide migration guidance before making breaking changes. Additive fields (new top-level keys) may be added without a version bump.