Webhooks
Overview
Section titled “Overview”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.
Prerequisites
Section titled “Prerequisites”- 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.
How it works
Section titled “How it works”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.
Usage / Configuration
Section titled “Usage / Configuration”Adding a webhook to a rule
Section titled “Adding a webhook to a rule”-
Go to Settings > Notifications (for SIEM detections, you can also open Operations > Security Monitoring and select the Alerting tab).
-
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.
-
Under Send it to, turn on Webhook.
-
Enter the Endpoint URL. It must use HTTPS and a public address.
-
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"} -
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. -
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.
-
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.
Adding a webhook to an automation
Section titled “Adding a webhook to an automation”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.
Examples
Section titled “Examples”Event types
Section titled “Event types”In the standard format, event_type is the key of the rule’s trigger:
| Trigger | event_type |
|---|---|
| New vulnerability detected | vulnerability.detected |
| Vulnerability status changed | vulnerability.status_changed |
| Vulnerability SLA alert | vulnerability.sla_alert |
| Vulnerability deferral overdue | vulnerability.deferral_overdue |
| Vulnerability comment added | vulnerability.comment_added |
| Daily vulnerability digest | digest.daily_vulnerability |
| Weekly security digest | digest.weekly_security |
| Weekly dependency digest | digest.weekly_dependency |
| Weekly identity digest | digest.weekly_identity |
| Penetration test results uploaded | pentest.results_uploaded |
| Task assigned | task.assigned |
| Organization member added | organization.member_added |
| Integration broken | integration.broken |
| Plan limit reached | plan.limit_reached |
| Personal API key approval requested | personal_api_key.approval_requested |
| Personal API key grant decided | personal_api_key.grant_decided |
| Security detection | detection.created |
| Detection comment added | detection.comment_added |
The two detection triggers need an active SIEM subscription.
Standard event payload (v1)
Section titled “Standard event payload (v1)”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" } ]}Detection payload (legacy format)
Section titled “Detection payload (legacy format)”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"}Verifying webhook signatures
Section titled “Verifying webhook signatures”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 messagesvix-timestamp— Unix timestamp of the attemptsvix-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 hashlibimport hmacimport 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(" "))Troubleshooting
Section titled “Troubleshooting”- 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
2xxstatus 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.