Integrations
Zabbix
Send Zabbix problems to AlertINT, and give triage read-only Zabbix context and checks.
AlertINT works with Zabbix in two roles. Enable either or both.
| Role | Direction | Gives you |
|---|---|---|
Push (zabbix.ingress) |
Zabbix → AlertINT webhook | Problems and resolutions become Situations, like Alertmanager alerts |
Pull (zabbix.api) |
AlertINT → Zabbix API, read-only | Runbooks, dependencies, host inventory, open problems, and verification checks |
Tested against: Zabbix 7.0.x LTS.
Push: the alert source (zabbix.ingress)#
- Zabbix
PROBLEM/RESOLVEDevents go through the same pipeline as Alertmanager alerts: correlated, grouped into Situations, triaged. - Severity ranks correctly out of the box, including installs that renamed their severity display names.
- Per-host grouping with zero configuration. Tag a trigger with a
servicevalue to correlate across hosts for that condition.
AlertINT config#
zabbix:
instance_id: prod-zbx-eu # stable non-secret installation identity
ingress:
enabled: false
webhook_token_env: ALERTINT_ZABBIX_WEBHOOK_TOKEN # bearer token Zabbix presents to /webhook/zabbix
The bearer token comes from the named variable, never inline:
export ALERTINT_ZABBIX_WEBHOOK_TOKEN="$(openssl rand -hex 24)"
- All fields: configuration reference.
- Adding or changing
instance_idstarts a new source namespace: aRESOLVEDcan't match aPROBLEMreceived before the change. Do it in a quiet window, after open problems have resolved.
Zabbix media type (copy-paste)#
Fastest: import examples/zabbix-media-type.yaml
(Alerts → Media types → Import), then set url and token.
By hand: Alerts → Media types → Create media type, type Webhook, one parameter per field. The script builds the JSON; never hand-write it in a parameter.
| Parameter | Value |
|---|---|
url |
http://<alertint-host>:9911/webhook/zabbix |
token |
<the value of ALERTINT_ZABBIX_WEBHOOK_TOKEN> |
event_id |
{EVENT.ID} |
status |
{EVENT.STATUS} |
severity |
{EVENT.SEVERITY} |
nseverity |
{EVENT.NSEVERITY} |
host |
{HOST.HOST} |
host_visible |
{HOST.NAME} |
trigger_id |
{TRIGGER.ID} |
trigger_name |
{TRIGGER.NAME} |
item_key |
{ITEM.KEY} |
item_value |
{ITEM.VALUE} |
tags |
{EVENT.TAGSJSON} |
clock |
{EVENT.DATE} {EVENT.TIME} |
recovery_clock |
{EVENT.RECOVERY.DATE} {EVENT.RECOVERY.TIME} |
generator_url |
{$ZABBIX_URL}/tr_events.php?triggerid={TRIGGER.ID}&eventid={EVENT.ID} |
Script:
var params = JSON.parse(value);
var tags = [];
try {
tags = JSON.parse(params.tags);
} catch (e) {
tags = [];
}
var payload = {
event_id: params.event_id,
status: params.status,
severity: params.severity,
nseverity: params.nseverity,
host: params.host,
host_visible: params.host_visible,
trigger_id: params.trigger_id,
trigger_name: params.trigger_name,
item_key: params.item_key,
item_value: params.item_value,
tags: tags,
clock: params.clock,
recovery_clock: params.recovery_clock,
generator_url: params.generator_url
};
var req = new HttpRequest();
req.addHeader('Content-Type: application/json');
req.addHeader('Authorization: Bearer ' + params.token);
var resp = req.post(params.url, JSON.stringify(payload));
if (req.getStatus() >= 300) {
throw 'alertint replied ' + req.getStatus() + ': ' + resp;
}
return 'OK';
| Gotcha | Why |
|---|---|
| One macro per parameter | Macro values can contain quotes (db.odbc.select[locks,"mydb"]). A hand-written JSON string breaks on them (400: zabbix: invalid JSON); parameters plus JSON.stringify escape correctly. |
| Add message templates (Problem, Problem recovery; defaults are fine) | The script ignores the message, but without a template Zabbix won't send at all ("No message defined for media type"). The import file includes them. |
Define {$ZABBIX_URL} |
A user macro holding your frontend URL. Only used for the generator_url link back to the event. |
Trigger action#
- Alerts → Actions → Trigger actions → Create action, event source Triggers.
- Operation: "Send message" via this media type, to a service user (not a person).
- Recovery operation: the same media type, so
RESOLVEDevents reach AlertINT and drive recovery.
What an alert looks like#
| Alert field | Source | Notes |
|---|---|---|
| Fingerprint | event_id |
zabbix:<instance_id>:<event_id> (or zabbix:<event_id> without instance_id). The same for PROBLEM and RESOLVED, so the resolution lands on the same alert. |
| Status | status |
PROBLEM → firing, RESOLVED → resolved. |
| Timestamps | receipt time | Stamped on arrival. clock/recovery_clock carry no timezone, so they're kept as annotations only. |
alertname label |
trigger_name |
Aligns with Alertmanager's grouping vocabulary. |
host label |
host |
The technical host name ({HOST.HOST}) — the correlator's per-host identity. |
severity label |
severity |
Verbatim, with a fallback — see below. |
zabbix_trigger_id label |
trigger_id |
Stable across firing episodes of the same condition — safe for grouping. |
| tag labels | tags[] |
Each {tag: value} becomes a label, key sanitised to [a-zA-Z0-9_]. A tag colliding with a core label name is dropped; the core label wins. |
zabbix_event_id, host_visible, trigger_name, item_key, item_value, generator_url, clock, recovery_clock annotations |
as named | Display/debug context, never used for grouping. |
Severity#
The severity label keeps Zabbix's own word. AlertINT ranks it on its shared ladder:
| Zabbix severity | Rank | Sits with |
|---|---|---|
information |
1 | debug / info / low |
warning |
2 | notice / medium |
average |
2 | same tier as warning |
high |
3 | error |
disaster |
5 | emergency / fatal / page — Zabbix's top severity |
not classified |
0 (unranked) | unknown ranks below everything; never manufactures an escalation |
Renamed severities: an unrecognized name is ranked by its number
({EVENT.NSEVERITY}, 0–5), and your word is kept as a severity_display
annotation. A rename to a known word (e.g. "critical") ranks normally.
Grouping#
- Default: per host (
host=<technical-host>), no labels needed. - Across hosts (e.g. a shared dependency failing on several): add a common trigger tag and set
correlator.group_labelsto it. This replaces per-host grouping.
Pull: the Zabbix context (zabbix.api)#
AlertINT reads context from the Zabbix API at triage time and adds it to the
prompt, the Finding, and two MCP tools. It calls only *.get methods (plus the
unauthenticated apiinfo.version health check); it never changes Zabbix.
Configuration#
zabbix:
instance_id: prod-zbx-eu # same identity used by ingress
api:
# enabled: false # uncomment to force OFF even when base_url is set
base_url: https://zbx.example.com # Zabbix frontend root; /api_jsonrpc.php is appended
api_token_env: ZABBIX_API_TOKEN # env var holding a read-only API token
timeout_seconds: 10 # default
default_range_minutes: 60 # default; zabbix_metric_history look-back when start is omitted
history_retention_days: 7 # default; windows older than this fall back to hourly trends
flap_window_hours: 24 # default; look-back for the trigger flap count
host_label: host # default; alert label carrying the Zabbix technical host name
Setting base_url turns it on; enabled: false forces it off.
Read-only API identity#
Use a dedicated service user, not a person's account:
- User roles → Create role. Set API access to a dedicated role whose
API methods are an Allow list containing only:
host.get, trigger.get, problem.get, event.get, item.get, history.get, trend.get. Zabbix enforces it: even a leaked token can't do more. - Users → User groups → Create user group. Grant Read permission on the host groups AlertINT should see. An unlisted group returns empty results, not an error, so it looks like "no context".
- Users → Create user. Assign the role from step 1 and the group from step 2.
- Users → API tokens → Create API token, scoped to that user, with no
expiry (or a long one you rotate deliberately). Export it as the env var
named by
api_token_env:
export ZABBIX_API_TOKEN="<the generated token>"
AlertINT sends the token as an Authorization: Bearer header.
The Apache gotcha (ZBX-22952)#
- Symptom: health is green, but every context fetch is empty or unauthorized.
- Cause: under Apache, the
Authorizationheader is stripped before PHP sees it. The health check needs no auth, so it still passes. - Fix: add
CGIPassAuth Onto the frontend's Apache config. nginx is unaffected.
What the Zabbix context adds#
| Class | Needs | Adds |
|---|---|---|
| Operator knowledge | zabbix_trigger_id label |
The trigger's runbook (comments), a jump-off url, the raw trigger expression, upstream dependency triggers (possible root cause), and how many times it has fired in the flap window. |
| CMDB/topology | the configured host_label only — works even for non-Zabbix-origin incidents |
Host groups, templates (role/stack), a curated inventory subset (contact, location, OS, hardware), live maintenance state, per-interface reachability, and other currently-open problems on the same host. |
| Problem detail | zabbix_event_id annotation |
Ongoing/duration, suppression (maintenance vs. manual, with an until-time), operational data, and acknowledgement history (who already knows, with their message). |
- Free text is data, not instructions. Runbooks, descriptions, and acknowledgements are shown to the model as untrusted text.
- Empty isn't healthy. A missing or failed section never counts as evidence the host is fine.
MCP tools#
Available when zabbix.api is enabled:
| Tool | Description |
|---|---|
zabbix_metric_history |
An item's metric history for a host (raw history.get, or hourly trend.get for windows older than history_retention_days — the response's source field says which). Parameters: host, item_key (required), start/end (optional RFC3339), limit (optional). |
zabbix_host_problems |
Currently-open Zabbix problems on a host, with severity, tags, duration, and ack/suppression state. Parameters: host (required), severity_min (optional, "0"–"5"). |
Both take a host and item key, never internal IDs; AlertINT resolves the item and its value type for you.
Show CPU history for web01 over the last 2 hours.
List open problems on db-primary with severity at least high.
Situation evidence#
The Situation controller can also read Zabbix while gathering evidence:
| Check | Reads |
|---|---|
zabbix_metric_range |
Bounded metric history |
zabbix_problem_history |
Bounded open problems |
They share the same per-update limits as every other source
(situations.preparation.max_source_calls_per_cycle,
situations.preparation.max_wall_seconds; see
situations).
Rule version#
With both instance_id and zabbix.api set, the problem-history check also
records which version of the trigger's configuration is live.
- What's hashed: the expanded firing and recovery expressions, severity and enabled state, tags, host scope, direct dependencies, referenced items, and preprocessing. Only digests are stored, never raw expressions.
- Where to see it:
alertint_get_situation→source_provenance: rule IDs, version, observation and expiry times, freshness. - When it can't tell: the entry says
unavailable_reasonand carries no version. An older value is kept as history, never used as current.
| Supported | Reported unavailable |
|---|---|
| Direct trigger dependencies | Nested dependencies |
| Zabbix agent, agent active, trapper, internal, and calculated items | Dependent and other item types |
| Unresolved macros, oversized sets, ambiguous or inconsistent reads |
Limits: Zabbix 7.0 keeps no configuration history, so the version is current truth only, never proof of what produced an older event. It refreshes with the Situation evidence cadence (normally 5 minutes), so a change that is reverted in between isn't seen.
Verification#
With zabbix.api configured, the verification round
adds two floor checks, no extra config:
- Host reachability: per interface, aware of maintenance.
- Neighbours: whether the host's smallest host groups have other open problems.
Without Prometheus, these are what let a Finding pass verification instead of
carrying ⚠ unverified — checks unavailable.