Webhooks API

Real-time notifications for security events. Receive webhooks when scans complete, vulnerabilities are found, or alerts are triggered.

Overview

Webhooks allow your applications to receive real-time HTTP notifications when events occur in Bloodhound. Instead of polling the API, you can subscribe to events and respond immediately.

Real-time
Instant delivery
Signed
HMAC verification
Retries
Automatic retry

Webhook Setup

Create and manage webhook endpoints via the API.

POST/webhooks
Bash
1# Create a webhook endpoint
2curl -X POST https://api.bloodhoundsecurity.ca/v1/webhooks \
3 -H "Authorization: Bearer bh_your_api_key" \
4 -H "Content-Type: application/json" \
5 -d '{
6 "url": "https://your-app.com/webhooks/bloodhound",
7 "events": [
8 "scan.completed",
9 "scan.failed",
10 "finding.new"
11 ],
12 "secret": "your_webhook_secret_here",
13 "description": "Production webhook endpoint"
14 }'
15
16# Response
17{
18 "id": "webhook_abc123xyz",
19 "url": "https://your-app.com/webhooks/bloodhound",
20 "events": ["scan.completed", "scan.failed", "finding.new"],
21 "status": "active",
22 "created_at": "2024-01-15T14:30:00Z",
23 "secret_last_4": "****here"
24}
25
26# List all webhooks
27curl https://api.bloodhoundsecurity.ca/v1/webhooks \
28 -H "Authorization: Bearer bh_your_api_key"
29
30# Update webhook
31curl -X PATCH https://api.bloodhoundsecurity.ca/v1/webhooks/webhook_abc123xyz \
32 -H "Authorization: Bearer bh_your_api_key" \
33 -H "Content-Type: application/json" \
34 -d '{
35 "events": ["scan.completed", "finding.new", "alert.triggered"]
36 }'
37
38# Delete webhook
39curl -X DELETE https://api.bloodhoundsecurity.ca/v1/webhooks/webhook_abc123xyz \
40 -H "Authorization: Bearer bh_your_api_key"
41
42# Test webhook (sends a test event)
43curl -X POST https://api.bloodhoundsecurity.ca/v1/webhooks/webhook_abc123xyz/test \
44 -H "Authorization: Bearer bh_your_api_key"

Event Types

Subscribe to specific event types to receive only relevant notifications.

scan.started

A new scan has begun processing

scan.completed

Scan finished successfully with findings

scan.failed

Scan encountered an error

finding.new

New vulnerability discovered (not in previous scans)

finding.resolved

Previously detected vulnerability is no longer present

report.ready

Scheduled or requested report is available

alert.triggered

Custom alert condition was met

JSON
1# Subscribe to all events
2{
3 "events": ["*"]
4}
5
6# Subscribe to scan events only
7{
8 "events": ["scan.started", "scan.completed", "scan.failed"]
9}
10
11# Subscribe to critical findings only
12{
13 "events": ["finding.new"],
14 "filters": {
15 "severity": ["critical"]
16 }
17}

Payload Format

All webhook payloads follow a consistent JSON structure.

JSON
1# Common webhook payload structure
2{
3 "id": "evt_xyz123abc",
4 "type": "scan.completed",
5 "created_at": "2024-01-15T14:35:42Z",
6 "data": {
7 // Event-specific data
8 }
9}
10
11# scan.completed payload
12{
13 "id": "evt_xyz123abc",
14 "type": "scan.completed",
15 "created_at": "2024-01-15T14:35:42Z",
16 "data": {
17 "scan_id": "scan_abc123xyz",
18 "repository": "owner/repo",
19 "branch": "main",
20 "commit": "abc123def456",
21 "status": "completed",
22 "duration_seconds": 337,
23 "summary": {
24 "total_findings": 47,
25 "new_findings": 3,
26 "resolved_findings": 5,
27 "by_severity": {
28 "critical": 2,
29 "high": 8,
30 "medium": 21,
31 "low": 12,
32 "info": 4
33 }
34 },
35 "links": {
36 "dashboard": "https://app.bloodhoundsecurity.ca/scans/scan_abc123xyz",
37 "findings": "https://api.bloodhoundsecurity.ca/v1/scans/scan_abc123xyz/findings"
38 }
39 }
40}
41
42# finding.new payload
43{
44 "id": "evt_def456ghi",
45 "type": "finding.new",
46 "created_at": "2024-01-15T14:35:45Z",
47 "data": {
48 "scan_id": "scan_abc123xyz",
49 "finding": {
50 "id": "finding_xyz789",
51 "rule_id": "SQL-001",
52 "title": "SQL Injection",
53 "severity": "critical",
54 "confidence": "high",
55 "cwe": "CWE-89",
56 "location": {
57 "file": "src/api/users.ts",
58 "line": 142
59 },
60 "first_seen": "2024-01-15T14:35:42Z"
61 },
62 "repository": "owner/repo",
63 "branch": "main"
64 }
65}
66
67# alert.triggered payload
68{
69 "id": "evt_ghi789jkl",
70 "type": "alert.triggered",
71 "created_at": "2024-01-15T14:36:00Z",
72 "data": {
73 "alert_id": "alert_abc123",
74 "alert_name": "Critical Vulnerability Detected",
75 "condition": "severity == critical AND repository == payment-service",
76 "triggered_by": {
77 "scan_id": "scan_abc123xyz",
78 "finding_id": "finding_xyz789"
79 }
80 }
81}

Security & Verification

Always verify webhook signatures to ensure requests come from Bloodhound.

JavaScript
1# Webhook request headers
2POST /webhooks/bloodhound HTTP/1.1
3Host: your-app.com
4Content-Type: application/json
5X-Bloodhound-Event: scan.completed
6X-Bloodhound-Delivery: evt_xyz123abc
7X-Bloodhound-Signature: sha256=a1b2c3d4e5f6...
8X-Bloodhound-Timestamp: 1705330542
9
10# Verify signature (Node.js example)
11const crypto = require('crypto');
12
13function verifyWebhookSignature(payload, signature, secret, timestamp) {
14 // Check timestamp is recent (within 5 minutes)
15 const currentTime = Math.floor(Date.now() / 1000);
16 if (Math.abs(currentTime - timestamp) > 300) {
17 throw new Error('Webhook timestamp is too old');
18 }
19
20 // Compute expected signature
21 const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;
22 const expectedSignature = crypto
23 .createHmac('sha256', secret)
24 .update(signedPayload)
25 .digest('hex');
26
27 // Compare signatures
28 const providedSignature = signature.replace('sha256=', '');
29 if (!crypto.timingSafeEqual(
30 Buffer.from(expectedSignature),
31 Buffer.from(providedSignature)
32 )) {
33 throw new Error('Invalid webhook signature');
34 }
35
36 return true;
37}
38
39// Express.js middleware
40app.post('/webhooks/bloodhound', (req, res) => {
41 try {
42 verifyWebhookSignature(
43 req.body,
44 req.headers['x-bloodhound-signature'],
45 process.env.WEBHOOK_SECRET,
46 parseInt(req.headers['x-bloodhound-timestamp'])
47 );
48
49 // Process webhook
50 const event = req.body;
51 console.log(`Received ${event.type}: ${event.id}`);
52
53 // Handle specific events
54 switch (event.type) {
55 case 'scan.completed':
56 handleScanCompleted(event.data);
57 break;
58 case 'finding.new':
59 handleNewFinding(event.data);
60 break;
61 }
62
63 res.status(200).json({ received: true });
64 } catch (error) {
65 console.error('Webhook error:', error.message);
66 res.status(400).json({ error: error.message });
67 }
68});

Security Best Practices

  • • Always verify the webhook signature
  • • Check the timestamp to prevent replay attacks
  • • Use HTTPS endpoints only
  • • Rotate secrets periodically

Retries & Debugging

Bloodhound automatically retries failed webhook deliveries with exponential backoff.

Retry Schedule

Attempt 1Immediate
Attempt 2After 1 minute
Attempt 3After 5 minutes
Attempt 4After 30 minutes
Attempt 5After 2 hours
Final attemptAfter 24 hours

After all retries fail, the webhook is marked as failed and an email notification is sent.

Bash
1# View webhook delivery history
2curl https://api.bloodhoundsecurity.ca/v1/webhooks/webhook_abc123xyz/deliveries \
3 -H "Authorization: Bearer bh_your_api_key"
4
5# Response
6{
7 "data": [
8 {
9 "id": "delivery_xyz789",
10 "event_id": "evt_abc123",
11 "event_type": "scan.completed",
12 "status": "success",
13 "response_code": 200,
14 "response_time_ms": 145,
15 "attempts": 1,
16 "created_at": "2024-01-15T14:35:42Z"
17 },
18 {
19 "id": "delivery_abc456",
20 "event_id": "evt_def789",
21 "event_type": "finding.new",
22 "status": "failed",
23 "response_code": 500,
24 "response_body": "Internal Server Error",
25 "attempts": 3,
26 "next_retry_at": "2024-01-15T15:05:42Z",
27 "created_at": "2024-01-15T14:30:00Z"
28 }
29 ]
30}
31
32# Manually retry a failed delivery
33curl -X POST https://api.bloodhoundsecurity.ca/v1/webhooks/deliveries/delivery_abc456/retry \
34 -H "Authorization: Bearer bh_your_api_key"
35
36# Response requirements:
37# - Your endpoint must return 2xx status within 30 seconds
38# - Non-2xx responses trigger a retry
39# - Timeouts trigger a retry