{ "id": "akfNprhHotiw4e2s", "name": "Zabbix Alert Ingestion", "nodes": [ { "parameters": { "httpMethod": "POST", "path": "zabbix-alert", "options": {} }, "id": "webhook-trigger", "name": "Zabbix Webhook", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [ 240, 400 ], "webhookId": "zabbix-alert-webhook", "notes": "Receives POST requests from Zabbix with alert data" }, { "parameters": { "jsCode": "// Parse and normalize incoming Zabbix alert data\nconst body = $input.item.json.body || $input.item.json;\n\nconsole.log('=== Zabbix Alert Received ===');\nconsole.log('Raw payload:', JSON.stringify(body, null, 2));\n\n// Extract alert fields from Zabbix webhook payload\nconst alert = {\n event_id: body.event_id || body.eventid || 'unknown',\n event_value: body.event_value || body.value || '1',\n problem_name: body.trigger_name || body.name || 'Unknown Problem',\n severity: parseInt(body.trigger_severity || body.severity || '0'),\n host_id: body.host_id || body.hostid || '',\n host_name: body.host_name || body.host || 'Unknown Host',\n trigger_id: body.trigger_id || body.triggerid || '',\n operational_data: body.event_opdata || body.opdata || '',\n tags: body.event_tags || body.tags || [],\n timestamp: body.event_date && body.event_time ? `${body.event_date} ${body.event_time}` : new Date().toISOString(),\n clock: body.clock || Math.floor(Date.now() / 1000)\n};\n\nif (typeof alert.tags === 'string') {\n try {\n alert.tags = JSON.parse(alert.tags);\n } catch (e) {\n alert.tags = alert.tags.includes(',') ? alert.tags.split(',').map(t => ({tag: t.trim()})) : [{tag: alert.tags}];\n }\n}\n\nif (!Array.isArray(alert.tags)) {\n alert.tags = [];\n}\n\nconst severityNames = {0: 'Not classified', 1: 'Information', 2: 'Warning', 3: 'Average', 4: 'High', 5: 'Disaster'};\nalert.severity_name = severityNames[alert.severity] || 'Unknown';\n\nconst locationMatch = alert.host_name.match(/\\.(dfw|lax|rdu|tpa|local)\\./i);\nalert.location = locationMatch ? locationMatch[1].toLowerCase() : 'unknown';\n\nlet systemType = 'unknown';\nconst hostname = alert.host_name.toLowerCase();\nconst tagStr = alert.tags.map(t => (t.tag || t.value || '').toLowerCase()).join(' ');\n\nif (hostname.includes('pve') || tagStr.includes('proxmox')) {\n systemType = 'proxmox';\n} else if (hostname.includes('pfsense') || tagStr.includes('pfsense')) {\n systemType = 'pfsense';\n} else if (hostname.includes('truenas') || tagStr.includes('truenas')) {\n systemType = 'truenas';\n} else if (hostname.includes('mysql') || tagStr.includes('mysql')) {\n systemType = 'database';\n} else if (hostname.includes('web') || hostname.includes('www')) {\n systemType = 'web';\n} else if (hostname.includes('usw') || hostname.includes('uap') || hostname.includes('ucg')) {\n systemType = 'network';\n} else if (hostname.includes('qnap') || hostname.includes('nas')) {\n systemType = 'storage';\n} else if (tagStr.includes('linux')) {\n systemType = 'linux';\n}\nalert.system_type = systemType;\n\nlet category = 'unknown';\nif (tagStr.includes('availability')) {\n category = 'availability';\n} else if (tagStr.includes('performance')) {\n category = 'performance';\n} else if (tagStr.includes('capacity')) {\n category = 'capacity';\n} else if (tagStr.includes('hardware') || tagStr.includes('temperature')) {\n category = 'hardware';\n}\nalert.category = category;\n\nlet priorityScore = alert.severity * 10;\nif (category === 'availability') priorityScore += 5;\nif (systemType === 'proxmox' || systemType === 'pfsense') priorityScore += 3;\nalert.priority_score = priorityScore;\nalert.raw_payload = body;\n\nconsole.log('=== Parsed Alert ===');\nconsole.log('Event ID:', alert.event_id);\nconsole.log('Host:', alert.host_name);\nconsole.log('Problem:', alert.problem_name);\nconsole.log('Severity:', alert.severity_name, `(${alert.severity})`);\nconsole.log('Location:', alert.location);\nconsole.log('System Type:', alert.system_type);\nconsole.log('Category:', alert.category);\n\nreturn { json: alert };" }, "id": "parse-alert-data", "name": "Parse Alert Data", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 464, 400 ], "notes": "Extracts and normalizes alert data from webhook payload" }, { "parameters": { "method": "POST", "url": "https://zabbix.ext.ben.io/api_jsonrpc.php", "authentication": "predefinedCredentialType", "nodeCredentialType": "zabbixApi", "sendBody": true, "specifyBody": "json", "jsonBody": "={\"jsonrpc\": \"2.0\", \"method\": \"host.get\", \"params\": {\"hostids\": \"{{ $json.host_id }}\", \"output\": [\"hostid\", \"host\", \"name\", \"status\"], \"selectGroups\": \"extend\", \"selectInterfaces\": [\"type\", \"ip\", \"port\"], \"selectInventory\": [\"location\", \"notes\", \"os\"]}, \"id\": 1}", "options": {} }, "id": "query-host-details", "name": "Query Host Details", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 688, 400 ], "credentials": { "zabbixApi": { "id": "j03eT0qxJpv7H5an", "name": "Zabbix account" } }, "notes": "Fetches full host information from Zabbix API" }, { "parameters": { "method": "POST", "url": "https://zabbix.ext.ben.io/api_jsonrpc.php", "authentication": "predefinedCredentialType", "nodeCredentialType": "zabbixApi", "sendBody": true, "specifyBody": "json", "jsonBody": "={\"jsonrpc\": \"2.0\", \"method\": \"problem.get\", \"params\": {\"eventids\": \"{{ $('Parse Alert Data').item.json.event_id }}\", \"output\": \"extend\", \"selectAcknowledges\": \"extend\", \"selectTags\": \"extend\", \"selectSuppressionData\": \"extend\"}, \"id\": 2}", "options": {} }, "id": "query-problem-details", "name": "Query Problem Details", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 912, 400 ], "credentials": { "zabbixApi": { "id": "j03eT0qxJpv7H5an", "name": "Zabbix account" } }, "notes": "Fetches problem details including acknowledgements" }, { "parameters": { "method": "POST", "url": "https://zabbix.ext.ben.io/api_jsonrpc.php", "authentication": "predefinedCredentialType", "nodeCredentialType": "zabbixApi", "sendBody": true, "specifyBody": "json", "jsonBody": "={\"jsonrpc\": \"2.0\", \"method\": \"trigger.get\", \"params\": {\"triggerids\": \"{{ $('Parse Alert Data').item.json.trigger_id }}\", \"output\": \"extend\", \"selectTags\": \"extend\"}, \"id\": 3}", "options": {} }, "id": "query-trigger-details", "name": "Query Trigger Details", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [ 1120, 400 ], "credentials": { "zabbixApi": { "id": "j03eT0qxJpv7H5an", "name": "Zabbix account" } }, "notes": "Fetches trigger configuration including resolution hints" }, { "parameters": { "jsCode": "const parsedAlert = $('Parse Alert Data').item.json;\nconst hostData = $('Query Host Details').item.json.result?.[0] || {};\nconst problemData = $('Query Problem Details').item.json.result?.[0] || {};\nconst triggerData = $('Query Trigger Details').item.json.result?.[0] || {};\n\nconst enrichedAlert = {\n ...parsedAlert,\n host_status: hostData.status === '0' ? 'enabled' : 'disabled',\n host_groups: (hostData.groups || []).map(g => g.name),\n host_interfaces: hostData.interfaces || [],\n host_inventory: hostData.inventory || {},\n acknowledged: problemData.acknowledged === '1',\n suppressed: problemData.suppressed === '1',\n acknowledgements: problemData.acknowledges || [],\n problem_tags: problemData.tags || [],\n trigger_description: triggerData.description || parsedAlert.problem_name,\n trigger_comments: triggerData.comments || '',\n trigger_expression: triggerData.expression || '',\n trigger_priority: triggerData.priority || parsedAlert.severity,\n enriched_at: new Date().toISOString(),\n enrichment_success: true\n};\n\nif (enrichedAlert.trigger_comments) {\n const comments = enrichedAlert.trigger_comments.toLowerCase();\n enrichedAlert.resolution_hints = [];\n if (comments.includes('restart')) enrichedAlert.resolution_hints.push('Service restart may resolve this issue');\n if (comments.includes('connectivity') || comments.includes('ping')) enrichedAlert.resolution_hints.push('Check network connectivity');\n if (comments.includes('acknowledge')) enrichedAlert.resolution_hints.push('Manual acknowledgement suggested');\n}\n\nenrichedAlert.should_notify = enrichedAlert.severity >= 3;\nenrichedAlert.notification_channels = [];\nif (enrichedAlert.severity >= 4) {\n enrichedAlert.notification_channels.push('email');\n} else if (enrichedAlert.severity === 3 && !enrichedAlert.suppressed) {\n enrichedAlert.notification_channels.push('email');\n}\n\nconsole.log('=== Enriched Alert Complete ===');\nconsole.log('Should Notify:', enrichedAlert.should_notify);\n\nreturn { json: enrichedAlert };" }, "id": "enrich-alert", "name": "Enrich Alert", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1344, 400 ], "notes": "Combines all enrichment data and determines notification strategy" }, { "parameters": { "conditions": { "boolean": [ { "value1": "={{ $json.should_notify }}", "value2": true } ] }, "options": {} }, "id": "should-notify-check", "name": "Should Notify?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [ 1568, 400 ], "notes": "Checks if notification should be sent based on severity and suppression" }, { "parameters": { "jsCode": "const alert = $input.item.json;\nconsole.log('=== Notification Skipped ===');\nconsole.log('Reason: Severity too low or alert suppressed');\nconsole.log('Severity:', alert.severity_name);\nconsole.log('Suppressed:', alert.suppressed);\nreturn {json: {skipped: true, reason: alert.suppressed ? 'Alert is suppressed' : 'Severity below notification threshold', alert_summary: `${alert.problem_name} on ${alert.host_name}`}};" }, "id": "skip-notification-log", "name": "Skip Notification Log", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [ 1792, 512 ], "notes": "Logs when notification is skipped" }, { "parameters": { "sendTo": "admin@ben.io", "subject": "=[{{ $json.severity_name }}] {{ $json.problem_name }} on {{ $json.host_name }}", "message": "Test Message", "options": {} }, "type": "n8n-nodes-base.gmail", "typeVersion": 2.1, "position": [ 1776, 304 ], "id": "e814bfea-ec17-46ac-8f98-7ab9b2e6244e", "name": "Send a message", "webhookId": "daf00ce2-f6a0-4c4e-94a8-0962bd3a1224", "credentials": { "gmailOAuth2": { "id": "Os1ux3h3zFlC2XkG", "name": "Gmail account" } } } ], "connections": { "Zabbix Webhook": { "main": [ [ { "node": "Parse Alert Data", "type": "main", "index": 0 } ] ] }, "Parse Alert Data": { "main": [ [ { "node": "Query Host Details", "type": "main", "index": 0 } ] ] }, "Query Host Details": { "main": [ [ { "node": "Query Problem Details", "type": "main", "index": 0 } ] ] }, "Query Problem Details": { "main": [ [ { "node": "Query Trigger Details", "type": "main", "index": 0 } ] ] }, "Query Trigger Details": { "main": [ [ { "node": "Enrich Alert", "type": "main", "index": 0 } ] ] }, "Enrich Alert": { "main": [ [ { "node": "Should Notify?", "type": "main", "index": 0 } ] ] }, "Should Notify?": { "main": [ [ { "node": "Send a message", "type": "main", "index": 0 } ], [ { "node": "Skip Notification Log", "type": "main", "index": 0 } ] ] } }, "settings": { "saveExecutionProgress": true, "saveManualExecutions": true, "saveDataErrorExecution": "all", "saveDataSuccessExecution": "all", "executionOrder": "v1" }, "triggerCount": 1, "versionId": "baf99192-61ed-4321-b831-5c212e89bf23", "owner": { "type": "personal", "projectId": "FeLO36wNUAcn61Wj", "projectName": "Ben W ", "personalEmail": "admin@ben.io" }, "parentFolderId": "LTWZD96boqxk9sIs", "isArchived": false }