Skip to content

Webhooks

A webhook sends a rule’s events to your own HTTPS endpoint, so your other systems can act on them. You can add a webhook to a rule on any trigger: new vulnerabilities, status changes, SLA alerts, digests, security detections, and more. Fencer signs every request so you can verify it came from Fencer, and retries failed deliveries automatically.

  • Permission to edit organization settings (organization admins have it). To add a webhook to an automation, you also need permission to edit automations.
  • An HTTPS endpoint on a public address that accepts POST requests.

Webhooks go on organization rules only. Personal notification rules cannot send to a webhook.

Each webhook on a rule gets its own endpoint in Fencer’s delivery service. When the rule runs, Fencer queues the message, and the delivery service sends it to your URL with retries and signature headers. Deleting the rule, or removing the webhook from it, removes the endpoint.

  1. Go to Settings > Notifications (for SIEM detections, you can also open Operations > Security Monitoring and select the Alerting tab).

  2. Click New rule and pick a starting point, or open an existing rule. The Critical and high vulnerabilities to a webhook and Security detections to a webhook starters turn on the webhook for you.

  3. Under Send it to, turn on Webhook.

  4. Enter the Endpoint URL. It must use HTTPS and a public address.

  5. To send extra headers, fill in Headers (optional) with a JSON object whose values are text. This is useful for authentication:

    {
    "Authorization": "Bearer your-token-here"
    }
  6. To verify signatures, enter a Signing secret (optional) of 32 to 100 base64 characters; a whsec_ prefix is optional. If you leave it blank, Fencer generates a secret that you cannot read. When you edit the rule, leave the field blank to keep the current secret.

  7. On a Security detection rule, choose the Payload format: Standard event (v1) (the default) or Detection (legacy format) (see below). Other triggers always use the standard format.

  8. Save the rule, then click Test send, choose Send to Webhook only, and confirm. The test sends recent events from the last 14 days that match the rule, or a few recent events if none match.

Open the automation under Settings > Automations, click Add action, choose Notify, and turn on Webhook. Then fill in the webhook as above. An automation’s write actions cannot be test-sent, so a rule that has one shows Preview instead of Test send. A rule whose only action is a webhook is a notification, so Fencer saves it under Settings > Notifications.

In the standard format, event_type is the key of the rule’s trigger:

Triggerevent_type
New vulnerability detectedvulnerability.detected
Vulnerability status changedvulnerability.status_changed
Vulnerability SLA alertvulnerability.sla_alert
Vulnerability deferral overduevulnerability.deferral_overdue
Vulnerability comment addedvulnerability.comment_added
Daily vulnerability digestdigest.daily_vulnerability
Weekly security digestdigest.weekly_security
Weekly dependency digestdigest.weekly_dependency
Weekly identity digestdigest.weekly_identity
Penetration test results uploadedpentest.results_uploaded
Task assignedtask.assigned
Organization member addedorganization.member_added
Integration brokenintegration.broken
Plan limit reachedplan.limit_reached
Personal API key approval requestedpersonal_api_key.approval_requested
Personal API key grant decidedpersonal_api_key.grant_decided
Security detectiondetection.created
Detection comment addeddetection.comment_added

The two detection triggers need an active SIEM subscription.

The standard format sends one request per rule run, for any trigger. subjects lists up to 100 items; subject_count gives the full count. event_id stays the same when a delivery is retried, so you can use it to drop duplicates. A test send’s event_id starts with test-.

{
"schema_version": 1,
"event_type": "detection.created",
"event_id": "run-812-action-97",
"occurred_at": "2026-09-28T10:15:02.114000+00:00",
"organization": { "id": 1, "slug": "acme-corp", "name": "Acme Corp" },
"rule": { "id": 31, "name": "Critical detections to our SIEM", "trigger": "detection.created" },
"headline": "1 detection",
"url": "https://app.fencer.dev/a/acme-corp/secops/",
"subject_count": 1,
"subjects": [
{
"title": "Suspicious login from new country",
"subtitle": "github-org · New country login · New · 85% confidence",
"url": "https://app.fencer.dev/a/acme-corp/secops/DET-42/",
"badge": "High"
}
]
}

The detection format sends one request per detection. It is the format SIEM alert webhooks used before rules, so existing receivers keep working. severity and status are display labels, and confidence is a string (or null). A test send in this format sends one recent detection, and sends nothing if there is none.

{
"detection_id": 42,
"detection_title": "Suspicious login from new country",
"asset_name": "github-org",
"detection_rule": "new_country_login",
"detection_rule_display_name": "New country login",
"severity": "High",
"status": "New",
"confidence": "85",
"description": "Fencer detected a login from a country not previously seen for this user.",
"recommendations": ["Verify the login with the user", "Check for other suspicious activity"],
"detection_url": "https://app.fencer.dev/a/acme-corp/secops/DET-42/",
"organization_id": 1,
"organization_slug": "acme-corp"
}

Fencer signs every webhook delivery with the signing secret you set on the rule. Each request includes the following headers:

  • svix-id — unique message identifier, the same on every retry of a message
  • svix-timestamp — Unix timestamp of the attempt
  • svix-signature — HMAC-SHA256 signature

To verify, concatenate the svix-id, svix-timestamp, and request body separated by periods, then compute the HMAC-SHA256 using your webhook secret. Compare the result against the svix-signature header. To block replayed requests, also reject a request whose svix-timestamp is more than a few minutes old.

import hashlib
import hmac
import base64
def verify_webhook(payload: bytes, headers: dict, secret: str) -> bool:
msg_id = headers["svix-id"]
timestamp = headers["svix-timestamp"]
signature = headers["svix-signature"]
# Decode the secret (base64-encoded, strip "whsec_" prefix if present)
secret_bytes = base64.b64decode(secret.removeprefix("whsec_"))
to_sign = f"{msg_id}.{timestamp}.{payload.decode()}".encode()
expected = base64.b64encode(hmac.new(secret_bytes, to_sign, hashlib.sha256).digest()).decode()
return any(hmac.compare_digest(expected, sig.removeprefix("v1,")) for sig in signature.split(" "))
  • Webhook not firing — Confirm the rule is enabled and its conditions match the event. Use Test send to check the endpoint.
  • “Save the rule to apply your changes, then send a test.” — A test goes to the saved endpoint. Save the rule first, then send the test again.
  • “Enter a valid HTTPS URL” or “The webhook URL must be a public address” — Webhook URLs must use HTTPS and a public address. Other URLs are rejected when you save the rule.
  • Delivery failures — Fencer retries failed deliveries automatically. If your endpoint consistently returns errors, check your server logs and ensure it responds with a 2xx status code.
  • Secret mismatch — The secret is write-only. If you lost it, enter a new one on the rule and save.
  • URL or header values are hidden — Members without permission to edit organization settings do not see the endpoint URL, and see header values masked.