# Wazuh

Wazuh has no webhook of its own: its integrator daemon runs a script for every alert at or above a level you choose. EvoHub provides that script. It posts the alert exactly as Wazuh wrote it, and uses only Python's standard library and the Python that ships with the Wazuh manager.

## Set it up

:::steps
### Create the integration
In EvoHub, go to **On-Call → Integrations → + Add Integration**, choose **Wazuh**, pick an **Escalation Policy** and click **Create Integration**. Copy the **Webhook URL**.
### Install the script
On the Wazuh manager, save the script below as `/var/ossec/integrations/custom-evohub`, then make it executable by the `wazuh` group:

```bash
chmod 750 /var/ossec/integrations/custom-evohub
chown root:wazuh /var/ossec/integrations/custom-evohub
```
### Test the connection
Run the script once by hand. EvoHub answers with success and opens no alert:

```bash
/var/ossec/integrations/custom-evohub --test 'https://evohub.io/ingest/wazuh?key=YOUR_INTEGRATION_KEY'
```
### Turn on the integration
Add the block below to `/var/ossec/etc/ossec.conf`, inside `<ossec_config>`, and restart the manager with `systemctl restart wazuh-manager`.
:::

Script (`/var/ossec/integrations/custom-evohub`):

```python
#!/var/ossec/framework/python/bin/python3
# EvoHub On-Call — Wazuh custom integration.
# Install as /var/ossec/integrations/custom-evohub (root:wazuh, mode 750).
# integratord runs it as: custom-evohub <alert file> <api key> <hook url>
# Test by hand:           custom-evohub --test <hook url>
import json
import sys
import urllib.error
import urllib.request


def post(url, payload):
    req = urllib.request.Request(
        url,
        data=json.dumps(payload).encode("utf-8"),
        method="POST",
        headers={"Content-Type": "application/json", "User-Agent": "evohub-wazuh/1"},
    )
    try:
        with urllib.request.urlopen(req, timeout=10) as resp:
            resp.read()
        return 0
    except urllib.error.HTTPError as e:
        sys.stderr.write("evohub: EvoHub answered HTTP %d\n" % e.code)
    except (urllib.error.URLError, OSError) as e:
        sys.stderr.write("evohub: could not reach EvoHub: %s\n" % getattr(e, "reason", e))
    return 1


def main(argv):
    if len(argv) == 3 and argv[1] == "--test":
        return post(argv[2], {"evohub_test": True})
    if len(argv) < 4:
        sys.stderr.write("usage: custom-evohub <alert file> <api key> <hook url>\n")
        return 2
    with open(argv[1], encoding="utf-8") as f:
        alert = json.load(f)
    return post(argv[3], alert)


if __name__ == "__main__":
    sys.exit(main(sys.argv))
```

`ossec.conf`:

```xml
<integration>
  <name>custom-evohub</name>
  <hook_url>https://evohub.io/ingest/wazuh?key=YOUR_INTEGRATION_KEY</hook_url>
  <level>10</level>
  <alert_format>json</alert_format>
</integration>
```

Every alert at `level` 10 or higher is sent. Add `<group>` or `<rule_id>` to the block to send only some rules, or lower the level to send more. If the script cannot reach EvoHub it prints the reason — never the URL — and exits with an error.

> [!NOTE]
> The first line of the script points at the Python bundled with Wazuh. If your manager has its own Python 3, `#!/usr/bin/env python3` works too.

## What EvoHub reads

| EvoHub alert | Taken from |
| --- | --- |
| Title | `rule.description` and the agent name, for example *sshd: brute force trying to get access to the system. Authentication failed. – web-01*. |
| Description | `full_log`, followed by the log location. |
| Labels | `rule_id`, `rule_level`, `groups`, `mitre_id`, `mitre_tactic`, `agent_id`, `agent_name`, `agent_ip`, `manager`, `decoder`, `location`, `alert_id`, `timestamp` and, when the decoder found them, `srcip`, `srcport`, `srcuser`, `dstuser`, `dstip`. |

### Severity

| Wazuh rule level | EvoHub severity |
| --- | --- |
| 12 and above | critical |
| 10–11 | high |
| 7–9 | medium |
| 4–6 | low |
| below 4 | info |

## Resolve and deduplication

An alert is identified by the rule and the agent. While it is open, the same rule firing again on the same agent is recorded as **Retriggered** instead of paging again; the same rule on another agent is a separate alert.

Wazuh alerts are events and Wazuh sends no recovery, so alerts stay open until someone resolves them in EvoHub.

## Related

- [Integrations overview](https://docs-dev.evohub.io/integrations-overview.md)
- [FortiGate](https://docs-dev.evohub.io/fortigate.md)
