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/RESOLVED events 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 service value 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_id starts a new source namespace: a RESOLVED can't match a PROBLEM received 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#

  1. Alerts → Actions → Trigger actions → Create action, event source Triggers.
  2. Operation: "Send message" via this media type, to a service user (not a person).
  3. Recovery operation: the same media type, so RESOLVED events 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_labels to 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:

  1. 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.
  2. 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".
  3. Users → Create user. Assign the role from step 1 and the group from step 2.
  4. 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 Authorization header is stripped before PHP sees it. The health check needs no auth, so it still passes.
  • Fix: add CGIPassAuth On to 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_reason and 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.