Skip to content

Webhook Integration

Send detection events to your own endpoints for custom processing, SIEM integration, or automation workflows.

  • Forward events to a SIEM WebDecoy has no dedicated integration for (for example Elastic)
  • Trigger custom automation workflows
  • Relay events into incident management through your own handler (WebDecoy sends its own payload format, not a vendor-specific one)
  • Log to your own database
  • Integrate with internal security tools
  • Build custom dashboards
  1. Go to Integrations → Webhooks

  2. Click Add Webhook

  3. Configure:

    Field Description
    Name Friendly name for the webhook
    Webhook URL Your endpoint URL. Use HTTPS; private, loopback and localhost addresses are refused
  4. Configure triggers and filters:

    Setting Description
    All Detections Every new detection
    High Risk Detections (Score ≥ 80) Only detections whose bot_score is 80 or higher
    Rule Enforced Events When a response action enforced successfully against the detection
    Minimum Score (optional) Skip detections below this unified score
    Filter by Decoys (optional) Only send detections from the selected decoys
  5. Click Create Webhook. The signing secret is generated for you and shown once; copy it then. You can regenerate it later from the webhook card.

Your endpoint receives JSON payloads via POST request. Illustrative example (empty fields are omitted):

{
"event": "detection",
"timestamp": "2026-07-15T10:30:00Z",
"detection": {
"id": "5d0c8f1e-...",
"decoy_id": "c2e9f6a0-...",
"organization_id": "3f1c2a9e-...",
"ip_address": "192.168.1.100",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
"bot_score": 92,
"timestamp": "2026-07-15T10:30:00Z",
"rules_enforced": false
}
}

The payload is deliberately compact. Fetch the full enriched detection (geo, signals, MITRE mapping, threat category) from GET /organizations/{org}/detections/{id} using the detection.id.

metadata is present only on rule_enforced events (rules_enforced, plus enforcement_error when enforcement failed) and on test events ({"test": true}). detection.source is set only on test events ("test").

Event Description
detection New detection recorded
high_risk_detection Detection with a unified score or bot_score of 80 or more
rule_enforced Reserved for a rule enforced against the detection. The detection pipeline does not send it today, so subscribe to detection or high_risk_detection
test Sent by the Test button when configuring the webhook

Every webhook request includes these headers:

Header Description
Content-Type application/json
X-WebDecoy-Signature HMAC-SHA256 signature of the raw body (sha256=<hex>)
X-WebDecoy-Event Event type (e.g., detection)
X-WebDecoy-Delivery Unique delivery ID
User-Agent WebDecoy-Webhook/1.0

Verify webhooks are from WebDecoy using the signature header:

X-WebDecoy-Signature: sha256=abc123def456...
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return `sha256=${expected}` === signature;
}
// Express middleware
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-webdecoy-signature'];
const payload = req.body.toString();
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
// Process event...
res.status(200).send('OK');
});
import hmac
import hashlib
def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
return f'sha256={expected}' == signature
# Flask example
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-WebDecoy-Signature')
payload = request.get_data()
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
event = request.get_json()
# Process event...
return 'OK', 200
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
)
func verifySignature(payload []byte, signature, secret string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(payload)
expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature))
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
payload, _ := io.ReadAll(r.Body)
signature := r.Header.Get("X-WebDecoy-Signature")
if !verifySignature(payload, signature, webhookSecret) {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Process event...
w.WriteHeader(http.StatusOK)
}

Each event is delivered once. There are currently no automatic retries. Your endpoint must respond within 10 seconds; a non-2xx response or a timeout is recorded as a failed delivery (visible on the webhook’s stats in the dashboard). Design your handler to return 200 immediately and process asynchronously.

  • ✅ Verify signatures in production
  • ✅ Return 200 quickly - process events asynchronously
  • ✅ Handle duplicates - deduplicate on detection.id (each delivery gets a new X-WebDecoy-Delivery ID)
  • ✅ Log failures for debugging
  • ✅ Use HTTPS
  • ❌ Process events synchronously (causes timeouts)
  • ❌ Skip signature verification
  • ❌ Return errors for events you want to ignore
  • ❌ Expose webhook URL publicly
WebDecoy Webhook
│
▼
Your Webhook Endpoint
│
├── Verify signature
├── Return 200 immediately
└── Queue event for processing
│
▼
Background Worker
│
├── Process event
├── Forward to SIEM
└── Trigger automation
  1. Go to Integrations → Webhooks
  2. Find your webhook
  3. Click Test
  4. A test event (event: "test") is sent to your endpoint
  5. The result (success or the HTTP status) is shown, and the webhook’s stats update

Use a tunneling service to test locally:

Terminal window
# Using ngrok
ngrok http 3000
# Your webhook URL becomes:
# https://abc123.ngrok.io/webhook
  1. Verify webhook is enabled
  2. Check URL is correct and accessible
  3. Ensure the host resolves to a public address (private and local addresses are refused)
  4. Check trigger settings match expected events
  5. Check the webhook card’s failure count and last error
  1. Ensure you’re using the raw request body (not parsed JSON)
  2. Check secret matches exactly (no extra whitespace)
  3. Verify encoding is UTF-8
  4. Check signature header name is correct
  1. Return 200 before processing
  2. Move heavy processing to background jobs
  3. Increase endpoint timeout if possible