# Alerts The alert events, the JSON object the webhook gets, and the messages Slack and ntfy get. "Alerting" in [`SPEC.md`](../SPEC.md) says when an alert is sent, held back or dropped. ## Events - `ban`: a ban `smallwebwaf` made. - `permanent_ban`: a permanent ban it made, or an existing ban made permanent by a request. - `waf_block`: the Core Rule Set scored a request at or over the threshold. The request was refused unless the alert's `detail` gives `mode`: `detect` or `observe`. - `anomaly`: a count over an anomaly threshold. - `reputation_hit`: a request from a client on a blocklist, the CrowdSec decision list or a DNS blocklist, or with an AbuseIPDB score that is a hit. One alert per source. - `source_failure`: one of: - GeoJS failing; - a list that cannot be fetched; - a DNS blocklist or AbuseIPDB failing or refusing a query; - the day's AbuseIPDB checks used up. - `file_error`: one of: - a rule file or state file edit with an error; - a state file that cannot be written; - a replacement lookup database that cannot be read. - `summary`: the hourly summary of alerts held back. ## The JSON object This is the alert for a ban for a broken rate limit, shown indented; it is sent on one line: ```json { "instance": "fsn1app1/gitea", "time": "2026-10-06T12:00:00.123461Z", "event": "ban", "client": "203.0.113.9", "netblock": "203.0.113.9/32", "asn": "", "as_name": "", "country": "", "reason": "requests per minute over the limit of 1000", "detail": { "ban_expires": "2026-10-06T13:00:00.123Z", "cause": "limit", "notes": { "asn": "", "as_name": "", "country": "", "kind": "requests", "limit": 1000, "window": "minute", "count": 1001, "request": { "time": "2026-10-06T12:00:00.123456789Z", "method": "GET", "host": "app.example", "path": "/owner/repo/commits/branch/main?page=812", "status": 403, "user_agent": "scraper/1.0" }, "requests": 5210, "refused": 0, "earlier_bans": { "limit": 0, "attack": 0, "admin": 0, "crowdsec": 0 } } }, "suppressed_repeats": 0 } ``` - `instance`: `SWWAF_INSTANCE_NAME`. `time`: when the alert was raised. `event`: one of the events above. - `client`: the address whose request raised the alert. Empty for `source_failure`, `file_error` and `summary`. - `netblock`: empty for `source_failure`, `file_error` and `summary`; otherwise one of: - the ban's netblock; - the netblock an anomaly counted, empty in the `asn` and `total` scopes; - for the other events, the client as rate limiting counts it: an IPv4 address as a /32, or an IPv6 group. - `asn`, `as_name` and `country`: the client's, as known when the alert was raised. They are empty when GeoJS had not answered yet. - `reason`: a short sentence, such as `requests per minute over the limit of 1000` or `listed by a blocklist`. - `detail`: what is particular to the event: - for a ban, its `cause`, `ban_expires` and `notes` (the notes are listed under `bans.json` in [`state-files.md`](state-files.md)); - for an anomaly, the `scope`, `window`, `kind`, `count` and `threshold`; - for a reputation hit, the `source` and, for AbuseIPDB, the `score`; - for a Core Rule Set match, the `rule_ids`, `score`, `method` and `path`; - for a source failure, the `source` and `error`; - for a file error, the `file` and `error`; - for a summary, the `hour` it covers, the `count` of alerts held back, and that count by event in `events`; - `mode`, when the request was not refused: `detect` or `observe`. - `suppressed_repeats`: how many repeats the cooldown held back before this alert. ## Slack and ntfy Slack and ntfy get a message with a title and a text. The title is the instance and the event, such as `fsn1app1/gitea: ban`. The text is the `reason`, then one line for each of these that the alert has: - the client, the netblock and the country; - the file, the source, the error and the mode from `detail`; - the suppressed repeats, if any. Slack gets the title in bold and the text below it, with `&`, `<` and `>` escaped. ntfy gets the text as the message, with these headers: - `Title`: the title; - `Authorization`: the token, as `Bearer `, while `SWWAF_ALERT_NTFY_TOKEN` is set; - `Priority` and `Tags`: by event, as listed below. The priority and tag by event: - `ban`: priority `default`, tag `no_entry`. - `permanent_ban`: priority `high`, tag `no_entry`. - `waf_block`: priority `default`, tag `shield`. - `anomaly`: priority `high`, tag `chart_with_upwards_trend`. - `reputation_hit`: priority `low`, tag `label`. - `source_failure` and `file_error`: priority `high`, tag `warning`. - `summary`: priority `default`, tag `bar_chart`.