🏠 Home ← Docs Index
Open Beta 📖 Interactive Reader Raw .md llms.txt

Wazuh Alerts That Run a SOP

Wazuh writes every alert it raises to its indexer (OpenSearch), in wazuh-alerts-4.x-*. An alerting monitor in the indexer can search those alerts on a schedule and, through a webhook notification channel, call the gateway with what it found: each run that finds Wazuh alerts runs a SOP, with their rule, agent and log as its input.

flowchart LR
    MGR["<b>Wazuh Manager</b>"] -->|alerts| IDX["<b>Indexer</b><br/>wazuh-alerts-4.x-*"]
    MON["<b>Monitor 'GoClaw: &lt;title&gt;'</b>"] -->|"every N min"| IDX
    MON -->|matches| CH["<b>Channel goclaw-&lt;key&gt;</b>"]
    CH -->|"POST (X-Token)"| GW["<b>/goclaw/hook/wazuh/&lt;key&gt;</b>"]
    GW --> SOP["<b>SOP Run on the Agent</b>"]

Nothing changes on the Wazuh manager: no ossec.conf integration, no restart.

There are two ways to set it up:


Before you start

What Where
Wazuh 4.4 to 4.14 (indexer on OpenSearch 2.4 to 2.19) 4.3's indexer has no notifications plugin; 5.0 keeps alerts in other indices, which the agent does not search yet
The indexer's URL, an indexer user and its password, and allow_write: true (only when the agent makes hooks) tools.observability.wazuh.<name>: the Integrations page or config.yaml
An address the indexer can reach the gateway at gateway.ingress.url, and a listener there
A SOP for the runs to run the SOPs page, or the agent's sop_save tool (Standard Operating Procedures)
gateway:
  bind: lan
  ingress:
    url: "http://192.0.2.10:8080"         # as the Wazuh indexer sees this gateway

tools:
  observability:
    wazuh:
      siem:
        url: "https://wazuh.example.com:55000"          # the manager API (wazuh_query)
        user: "wazuh-api"
        password: "${secret:WAZUH_API_PASSWORD}"
        indexer_url: "https://wazuh.example.com:9200"   # the indexer, for wazuh_alert
        indexer_user: "admin"
        indexer_password: "${secret:WAZUH_INDEXER_PASSWORD}"
        insecure: true                                  # Wazuh's own certificates; applies to both
        allow_write: true                               # off by default: the agent cannot make or remove hooks

The indexer user needs the alerting_full_access and notifications_full_access roles and read access to wazuh-alerts-*; Wazuh's admin indexer user has all of them. A monitor searches with the roles of the user that made it.


Asking for an alert hook

Run intrusion-triage on Wazuh alerts of level 10 and above from web-01 and db-01 in the authentication_failed group.
When rule 5712 fires anywhere, run ssh-brute-force-response. Check every 2 minutes.

The agent answers with a plan; nothing is created until you confirm it in your next message:

Plan 41d0c8e2 — nothing is created yet.

Wazuh "siem" indexer (OpenSearch 2.19.3) gets an alerting monitor "GoClaw: SSH brute force":
- every 5 minute(s) it searches wazuh-alerts-4.x-* for Wazuh alerts at rule level 10 or above, in group authentication_failed, from agent web-01 or db-01
- a run that finds some sends the first 20 to its own notification channel goclaw-ssh-brute-force, which calls http://192.0.2.10:8080/goclaw/hook/wazuh/ssh-brute-force with a new secret only this route knows
- the manager's configuration, other monitors and channels are not changed
GoClaw: each run with matches runs SOP "intrusion-triage" (Intrusion triage) as agent main.

What the request can set: the lowest rule level (min_level, default 10), rule IDs, rule groups, agent names, and how often the monitor runs (every_minutes, default 5).

Each run searches the interval before it, ending a minute early so that an alert the indexer takes a moment to write is found by the next run; runs do not overlap. A run that finds nothing calls nothing. One run with matches is one SOP run, however many Wazuh alerts it found: a burst of fifty failed logins is one run, not fifty.

The plan is refused, with the reason, when the indexer lacks the alerting or notifications plugin (Wazuh 4.3), is a Wazuh 5 indexer, has no wazuh-alerts-4.x-* index, already has a channel or monitor of that name, the route key is taken, the instance has no indexer settings, or it does not allow writes.

After creating it, the agent has the indexer send the channel's test message and says whether it reached the route.


What the SOP is given

Each run is the trigger data, in an untrusted section of every step. The agent is told:

Monitor: GoClaw: SSH brute force — 34 Wazuh alert(s) between 2026-10-09T02:00:00Z and 2026-10-09T02:05:00Z
- level 10, rule 5712: sshd: brute force trying to get access to the system.
  Agent: web-01 (192.0.2.10)
  Time: 2026-10-09T02:01:00.000+0000
  Groups: syslog, sshd, authentication_failures
  Source IP: 203.0.113.7
  Location: /var/log/auth.log
  Log: Oct  9 02:01:00 web-01 sshd[811]: Failed password for root from 203.0.113.7 port 52211 ssh2
  Alert ID: 1760000000.123
…

Up to ten of the run's alerts are told in full, and the count of the rest. One monitor's runs keep one session.

The indexer does not deliver a run again when the gateway does not answer: the next run sends the alerts it finds then.

The route's secret is a header of the indexer's notification channel, which anyone who may read the channel's settings in the indexer can see.


Listing, checking, removing

Only a human admin can do this, in a conversation of their own: never a SOP, cron, webhook or subagent run.


Connecting Wazuh without the agent

  1. On the Webhooks page, create a route of type Wazuh with a key, the agent and the SOP. Saving shows the URL, the secret, and the monitor action's message to paste, once.
  2. In the Wazuh dashboard, Explore › Notifications › Channels › Create channel: type Webhook, the route's URL, method POST, and the headers Content-Type: application/json and X-Token with the route's secret. The indexer sends only the headers it is given. Send test message: the route answers it and its Hits go up.
  3. Explore › Alerting › Monitors › Create monitor: a per-query monitor on wazuh-alerts-4.x-*, a trigger such as ctx.results[0].hits.total.value > 0, and an action that notifies the channel with the message from step 1.

Related