Wazuh 5.0 moved notifications off the manager entirely. There is no <email_alerts>, no <integration> block, no integratord, and no custom integration scripts under the manager’s integrations directory. Delivery is now a pair of indexer modules: Alerting creates monitors that query findings on a schedule and evaluate trigger conditions, and Notifications holds the channels those triggers send through. This lesson builds one severity-based monitor end to end and covers what breaks when you scale it.
What you need
- A Wazuh 5.0 deployment producing findings, and dashboard access as an admin.
- Whatever credential the destination needs: an SMTP account, a Slack incoming webhook URL, a bot token.
- Ideally the custom rules lesson first, so you know what a finding is and how severity is expressed.
- Groups configured, from the previous lesson, if you plan to route by group.
- The companion files: a monitor definition and a webhook payload template.
The notification model
Four pieces, in order:
| Piece | Where | What it does |
|---|---|---|
| Finding | wazuh-findings-v5-<category> data stream |
A Sigma rule matched an event |
| Monitor | Alerting module, indexer | Queries findings on a schedule |
| Trigger | Inside the monitor | Decides whether the result warrants an alert |
| Action | Inside the trigger | Renders a message and hands it to a channel |
| Channel | Notifications module, indexer | Delivers to Slack, email, a webhook and so on |
The monitor is the scheduled part. The trigger is the condition. The action is the message. The channel is the transport. Confusing the trigger with the channel is the usual cause of “I configured Slack and nothing happened”.
One 4.x pattern has no replacement at all. The bi-directional integrations, VirusTotal and Maltiverse, worked by sending an alert out, receiving an enriched response, and re-injecting it as a new alert. The migration guide states plainly that there is no equivalent mechanism in 5.0 and these cannot be migrated. Enrichment now happens inline in the normalisation pipeline, with two built-in plugins, Geo/ASN and IoC matching, which cannot be extended with third-party services.
Create the channel
Channels live under Explore > Notifications > Channels. Wazuh creates four on first start, disabled and holding placeholder values:
| Channel name | Type | Fixed ID |
|---|---|---|
| Slack Channel | slack |
default_slack_channel |
| Jira Channel | webhook |
default_jira_channel |
| PagerDuty Channel | webhook |
default_pagerduty_channel |
| Shuffle Channel | webhook |
default_shuffle_channel |
Those four are what a fresh install actually has. Listing the notification configurations on a 5.0.0-beta5 stack that had just finished starting returned:
notification configs: 4 ['Slack Channel/slack', 'Jira Channel/webhook', 'PagerDuty Channel/webhook', 'Shuffle Channel/webhook']
Only the Slack channel has a first-class type. Jira, PagerDuty and Shuffle are plain webhooks with a payload written for them, which tells you what “supported integration” means here: a worked example, not a maintained connector. Anything you add is the same kind of object.
Unmute one and fill in the credential, or create your own. The supported channel types are slack, chime, microsoft_teams, webhook, email, sns, plus the three supporting types smtp_account, ses_account and email_group that other channels reference.
Send a test message from the channel before you go anywhere near a monitor. It isolates the transport from the query.
An email channel needs an smtp_account or a ses_account. Credentials go in the indexer keystore, not a configuration file:
/usr/share/wazuh-indexer/bin/opensearch-keystore add \
opensearch.notifications.core.email.my_smtp_account.username
/usr/share/wazuh-indexer/bin/opensearch-keystore add \
opensearch.notifications.core.email.my_smtp_account.password
The key pattern is opensearch.notifications.core.email.<account_name>.username and .password, where <account_name> matches the SMTP account you configured. Use an email_group for distribution lists rather than pasting addresses into every channel.
Slack
A slack channel takes an incoming webhook URL and renders the message as plain text. If you want a structured Slack payload with blocks or attachments, use a custom webhook channel pointed at the same URL instead and write the JSON yourself.
Telegram, n8n, and anything else
There is no Telegram channel type. Use a custom webhook, which is also what the shipped PagerDuty, Jira and Shuffle channels are underneath. Custom webhooks support PUT, POST and PATCH, an endpoint defined either as a plain URL or as a URL with query parameters, and arbitrary headers such as Authorization and Content-Type.
For Telegram that means the Bot API sendMessage endpoint with the bot token in the URL and the chat id in the body. For an automation platform, point it at the platform’s generic webhook trigger and treat the message body as an interface contract, which is the approach the SOAR lesson builds on.
There is a network guard worth knowing about. opensearch.notifications.core.http.host_deny_list blocks the Notifications module from reaching listed hosts or CIDR ranges. Populate it. An indexer that can POST to anything on your internal network is a useful pivot for anyone who gains monitor-creation rights.
Build a severity monitor
Go to Explore > Alerting > Monitors > Create Monitor.
The monitor types available are query-level, bucket-level, cluster metrics, document-level, and the Wazuh-specific Active Response type. For notification routing, a query-level monitor over a findings data stream is the workhorse: it runs a search, evaluates the result as a whole, and produces one alert.
| Field | Value |
|---|---|
| Monitor name | wazuh-high-severity-security-findings |
| Monitor type | Per query monitor |
| Frequency | By interval, every 5 minutes |
| Index | wazuh-findings-v5-security |
Severity in 5.0 is a string. wazuh.rule.level takes informational, low, medium, high or critical, not a number from 0 to 15. The migration guide is explicit that there is no mechanical conversion from the old numeric levels, and that you have to decide how your existing thresholds map onto the five categories. Do that once, write it down, and use the same mapping everywhere.
The trigger is a Painless condition over the query result:
ctx.results[0].hits.total.value > 0
The action renders a message with Mustache and names a channel. The companion files for this lesson include the full monitor body; the shape is:
{
"name": "notify-channel",
"destination_id": "REPLACE_WITH_NOTIFICATION_CHANNEL_ID",
"message_template": {
"source": "Monitor {{ctx.monitor.name}} matched {{ctx.results.0.hits.total.value}} findings between {{ctx.periodStart}} and {{ctx.periodEnd}} UTC."
},
"throttle_enabled": true,
"throttle": {
"value": 30,
"unit": "MINUTES"
}
}
Post it to the indexer rather than clicking through the UI once you have it right:
curl -sk -u "<USER>:<PASSWORD>" -X POST \
"https://<INDEXER>:9200/_plugins/_alerting/monitors" \
-H "Content-Type: application/json" \
--data-binary @high-severity-monitor.json
Your monitor joins a crowd. The Alerting module is also where Wazuh’s own detection content runs: a 5.0.0-beta5 install came up with 22 monitors already scheduled, on 1, 2 and 5 minute periods, one per shipped integration. Filter by name before you conclude that a monitor you created has vanished.
Which context variables are available
ctx.monitor.*, ctx.trigger.*, ctx.periodStart, ctx.periodEnd, ctx.results and ctx.error are documented OpenSearch monitor variables. For a per-document monitor, matched documents are reachable as ctx.alerts.0.sample_documents.0._source.<field>, which is the form the Wazuh migration guide uses for its PagerDuty and Jira payloads. Field paths under _source are Wazuh Common Schema: wazuh.rule.id, wazuh.rule.title, wazuh.rule.level, wazuh.agent.name, source.ip, file.path, event.original.
Throttle, always
throttle_enabled with a throttle value is the difference between a notification channel people read and one they mute. The maximum throttle interval is governed by plugins.alerting.action_throttle_max_value, which defaults to 24 hours, and plugins.alerting.max_actionable_alert_count caps how many alerts one monitor execution will act on, defaulting to 50.
Design for the monitor limit
This is the constraint that shapes everything.
The Wazuh Alerting module documentation states that it supports up to 10 custom monitors per user, across all monitor types. The same page documents plugins.alerting.monitor.max_monitors with a default of 1000, and shows how to change cluster settings dynamically. The two figures contradict each other, and the beta documentation does not reconcile them.
The lab settles one small part of it. A default install already runs 22 monitors, so whatever the 10 counts, it is not every monitor in the cluster. That is consistent with a per-user limit on user-created monitors and with nothing else. It does not tell you the limit applies to the account your automation uses, and no monitor was created in the lab to find out.
What that means in practice:
- Route by severity, not by team. Two or three monitors covering critical, high and everything-else, each sending to a channel the right people watch. Fan-out to teams belongs in whatever receives the webhook.
- Do not create a monitor per tenant. Include the tenant field in the payload and let the receiving system split. A monitor per tenant does not scale past the tenth tenant.
- Do not create a monitor per rule. That is what the detection content is for. A rule that needs its own notification path is a rule whose severity is wrong, which is a tuning problem.
- One trigger per routing decision. A monitor supports up to 10 triggers by default, and each trigger can carry several actions, so a single monitor can feed several channels with different payloads.
What gets missed
Three gaps to plan around.
Detector latency. Findings are produced by detectors running as scheduled jobs on the indexer, and a query-level monitor adds its own interval on top. Total latency is detector schedule plus monitor interval plus delivery. A five-minute monitor does not mean five-minute notification. Measure it before you promise it in an SLA.
Alert deduplication. A query-level alert stays Active while its condition holds and is marked Completed when it stops. Findings can re-fire. Whatever receives the notification needs to be idempotent, keyed on something stable in the payload.
Nothing at all. If the indexer stops receiving events, no monitor fires and no notification arrives, which looks exactly like a quiet night. Add a cluster metrics monitor for indexer health and a low-volume heartbeat, so silence is provably silence.
Common errors
The test message works but the monitor never notifies
Cause: the query matches nothing. Usually the index pattern names a category that has no findings, or the severity string is wrong.
Fix: run the same query in Discover over the same time window. Confirm the data stream name against the category the rule belongs to. Check wazuh.rule.level values as they actually appear in your documents rather than assuming them.
The monitor alerts but nothing is delivered
Cause: destination_id does not name a live channel, the channel is still muted, or the throttle is suppressing the action.
Fix: check the alert’s state in Explore > Alerting. An action failure puts the alert into the Error state. Then re-send a channel test and confirm the channel is unmuted.
The webhook returns success but nothing appears in the destination
Cause: the payload does not match the format the service expects, which is the most common webhook failure by far.
Fix: read the destination’s API reference and match the body exactly. Check Content-Type. Remember that a Slack channel type renders plain text while a custom webhook sends raw JSON, so the same message body cannot serve both.
Monitor cannot be created or a limit error on creation
Cause: the per-user monitor limit.
Fix: consolidate. Move routing logic downstream of the webhook. If you have verified you genuinely need more, plugins.alerting.monitor.max_monitors is a dynamic cluster setting, but treat raising it as a decision with a reason recorded.
The indexer cannot reach the webhook endpoint
Cause: opensearch.notifications.core.http.host_deny_list, egress filtering, or a connection timeout. opensearch.notifications.core.http.connection_timeout defaults to 5000 ms and socket_timeout to 50000 ms.
Fix: check the deny list first, since it fails in a way that looks like a network problem. Then test the endpoint from the indexer host.
Official documentation for this topic: the Alerting module and the Notifications module. Monitor and channel structure is documented upstream by OpenSearch in Alerting monitors and Notifications. The 4.x conversion path is in integratord-notifications.md.
Next: Wazuh Active Response: How It Works and When to Leave It Off.
Files for this lesson
Every file this lesson refers to, exactly as used. Open a file to read it in the browser, or download the set as a zip. Download wazuh-alert-notifications.zip. The full set for all lessons is at wazuh-tutorial-files.zip.
README.md2.4 KBhigh-severity-monitor.json2.3 KBwebhook-payload.mustache.json1.4 KB
Frequently asked questions
How do I send Wazuh email alerts in 5.0?
Create an smtp_account and an email channel in the Notifications module on the indexer, store the SMTP credentials in the indexer keystore, then create an Alerting monitor whose trigger action targets that channel. The manager email_alerts block from 4.x no longer exists, and the maild daemon has been removed.
How do I send Wazuh alerts to Telegram?
Telegram is not a supported channel type. Use a custom webhook channel pointing at the Telegram Bot API sendMessage endpoint, with the bot token in the URL and the chat id in the message payload. The webhook channel type supports PUT, POST and PATCH with custom headers.
How many alerting monitors can I create in Wazuh?
The Wazuh documentation for the Alerting module states a limit of 10 custom monitors per user, while the same page documents plugins.alerting.monitor.max_monitors with a default of 1000. Design for the lower figure: route by severity rather than creating a monitor per team, per rule or per tenant.
What replaced integratord in Wazuh 5.0?
Nothing on the manager. The integratord daemon, the integration blocks in ossec.conf and the scripts under /var/ossec/integrations are all gone. Notification delivery moved to the indexer as Alerting monitors plus Notifications channels, and there is no automatic conversion tool.
Why is my Wazuh notification not arriving?
Work through the layers in order: send a test message from the channel, confirm the monitor query returns documents in Discover for the same time window, check that the trigger condition evaluates true, check the action throttle is not suppressing it, and confirm the payload matches the format the receiving service expects.