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: <title>'</b>"] -->|"every N min"| IDX
MON -->|matches| CH["<b>Channel goclaw-<key></b>"]
CH -->|"POST (X-Token)"| GW["<b>/goclaw/hook/wazuh/<key></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:
wazuh_alert tool
makes the monitor and its channel in the indexer and the route in GoClaw, and removes them
together. See Asking for an alert hook.wazuh route on the Webhooks page, and in the Wazuh
dashboard a webhook channel and a monitor that notifies it. See
Connecting Wazuh without the agent.| 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.
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.
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.
Only a human admin can do this, in a conversation of their own: never a SOP, cron, webhook or subagent run.
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.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.wazuh_query tool and the instance's configuration