SNMP Trap to HTTP Request
SNMP Trap to HTTP Request
Overview
This Node-RED flow listens for SNMP traps from any external system, parses the trap content, and converts it into a structured HTTP request. It also logs each trap and the server response for troubleshooting or reporting.
It is designed to work generically with any HTTP endpoint that accepts JSON payloads and headers, but has been specifically been implemented for creating Maximo Service Requests using the /maxrest/oslc/os/mxsr endpoint.
SNMP Trap Format
Traps must contain a pipe-delimited key=value string inside an OctetString. The flow will only parse the contents of the first OctetString in the trap so only send one.
Example:
timestamp=2025-08-20 16:46:55 +9.5H|
alarmstate=Alarm|
payload.reportedpriority=1|
payload.description=Test Alert|
payload.siteid=A1|
payload.location=HWCP-ICG-L06-07|
url=https://example.com/maxrest/oslc/os/mxsr?lean=1|
headers.apikey=abcd1234|
method=POST
The following keys in the trap string are used in the HTTP request:
payload.* keys→msg.payloadheaders.*keys →msg.headersurl→ HTTP request URL (required)method→ HTTP request method (default POST if omitted)
Any other key=value items in the trap string are ignored in the HTTP request.
Logging
The msg.logRecord output at the end of the flow has the following structure:
{
"_msgid": "795fcc430b2bf91d",
"timestamp": "2025-08-22T03:41:14.105Z",
"src_address": "10.176.60.164",
"octetstring": "timestamp=2025-08-22 13:11:13 +9.5H|alarmstate=Alarm|payload.reportedpriority=1|payload.description=Test Alert|payload.siteid=A1|payload.location=HWCP-ICG-L06-07|url=https://example.com/maxrest/oslc/os/mxsr?lean=1|headers.apikey=abcd1234|method=POST",
"url": "https://example.com/maxrest/oslc/os/mxsr?lean=1",
"method": "POST",
"payload": {
"reportedpriority": "1",
"description": "Test Alert",
"siteid": "A1",
"location": "HWCP-ICG-L06-07"
},
"statusCode": 400,
"responseTimestamp": "2025-08-22T03:41:14.996Z",
"error": "BMXAA3906E - Site A1 is not valid."
}
Requirements
- Node-RED v2.x+
node-red-contrib-snmp-trap-listener- Optional logging backend (JSON file, MongoDB, SQLite, PostgreSQL, etc.)
- Target HTTP endpoint accessible
Usage
- Deploy the flow in Node-RED.
- Configure SNMP trap sources (V1, V2 or V3).
- Adjust default values in the parser if required.
- Optionally configure
msg.logRecordis written to the chosen logging backend. - Monitor logs and HTTP responses for validation.
[{"id":"f6f2187d.f17ca8","type":"tab","label":"SNMP Trap to HTTP Request","disabled":false,"info":"# SNMP Trap to HTTP Request\r\n\r\n## Overview\r\n\r\nThis Node-RED flow listens for SNMP traps from any external system, parses the trap content, and converts it into a structured HTTP request. It also logs each trap and the server response for troubleshooting or reporting.\r\n\r\nIt is designed to work generically with any HTTP endpoint that accepts JSON payloads and headers, but has been specifically been implemented for creating Maximo Service Requests using the `/maxrest/oslc/os/mxsr` endpoint.\r\n\r\n## SNMP Trap Format\r\n\r\nTraps must contain a pipe-delimited key=value string inside an OctetString. The flow will only parse the contents of the first OctetString in the trap so only send one. \r\n\r\nExample:\r\n\r\n```\r\ntimestamp=2025-08-20 16:46:55 +9.5H|\r\nalarmstate=Alarm|\r\npayload.reportedpriority=1|\r\npayload.description=Test Alert|\r\npayload.siteid=A1|\r\npayload.location=HWCP-ICG-L06-07|\r\nurl=https://example.com/maxrest/oslc/os/mxsr?lean=1|\r\nheaders.apikey=abcd1234|\r\nmethod=POST\r\n```\r\n\r\nThe following keys in the trap string are used in the HTTP request:\r\n\r\n - `payload.* keys` → `msg.payload`\r\n - `headers.*` keys → `msg.headers`\r\n - `url` → HTTP request URL (required)\r\n - `method` → HTTP request method (default POST if omitted)\r\n\r\n Any other key=value items in the trap string are ignored in the HTTP request.\r\n\r\n ## Logging\r\n\r\nThe `msg.logRecord` output at the end of the flow has the following structure:\r\n\r\n```\r\n{\r\n \"_msgid\": \"795fcc430b2bf91d\",\r\n \"timestamp\": \"2025-08-22T03:41:14.105Z\",\r\n \"src_address\": \"10.176.60.164\",\r\n \"octetstring\": \"timestamp=2025-08-22 13:11:13 +9.5H|alarmstate=Alarm|payload.reportedpriority=1|payload.description=Test Alert|payload.siteid=A1|payload.location=HWCP-ICG-L06-07|url=https://example.com/maxrest/oslc/os/mxsr?lean=1|headers.apikey=abcd1234|method=POST\",\r\n \"url\": \"https://example.com/maxrest/oslc/os/mxsr?lean=1\",\r\n \"method\": \"POST\",\r\n \"payload\": {\r\n \"reportedpriority\": \"1\",\r\n \"description\": \"Test Alert\",\r\n \"siteid\": \"A1\",\r\n \"location\": \"HWCP-ICG-L06-07\"\r\n },\r\n \"statusCode\": 400,\r\n \"responseTimestamp\": \"2025-08-22T03:41:14.996Z\",\r\n \"error\": \"BMXAA3906E - Site A1 is not valid.\"\r\n}\r\n```\r\n\r\n## Requirements\r\n\r\n - Node-RED v2.x+\r\n - `node-red-contrib-snmp-trap-listener`\r\n - Optional logging backend (JSON file, MongoDB, SQLite, PostgreSQL, etc.)\r\n - Target HTTP endpoint accessible\r\n\r\n## Usage\r\n\r\n- Deploy the flow in Node-RED.\r\n- Configure SNMP trap sources (V1, V2 or V3).\r\n- Adjust default values in the parser if required.\r\n- Optionally configure `msg.logRecord` is written to the chosen logging backend.\r\n- Monitor logs and HTTP responses for validation."},{"id":"da660c58e1283b58","type":"http request","z":"f6f2187d.f17ca8","name":"","method":"use","ret":"obj","paytoqs":"ignore","url":"","tls":"5699997290b13e4b","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":670,"y":80,"wires":[["fe8271a0592f3667","78e274db57be7626"]]},{"id":"fe8271a0592f3667","type":"debug","z":"f6f2187d.f17ca8","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1160,"y":140,"wires":[]},{"id":"4a5a53bad5bb6148","type":"snmp-trap-listener","z":"f6f2187d.f17ca8","port":162,"snmpV1":true,"snmpV2":true,"snmpV3":true,"communities":[{"community":"public"}],"snmp_users":[{"name":"ibams","authProtocol":"sha","authKey":"ibams1234","privProtocol":"aes","privKey":"ibams1234"},{"name":"hello","authProtocol":"none","authKey":"","privProtocol":"none","privKey":""}],"ipfilter":"","ipmask":"","x":110,"y":80,"wires":[["fe8271a0592f3667","6d8df86c953070f7"]]},{"id":"6d8df86c953070f7","type":"function","z":"f6f2187d.f17ca8","name":"snmp-trap-to-http-request","func":"// SNMP trap comes in as an array of varbinds (msg.payload)\n// Find the OctetString that contains the key=value pipe-delimited string\nconst vb = (msg.payload || []).find(v => v.typename === \"OctetString\" && typeof v.string_value === \"string\");\nif (!vb) {\n node.error(\"No OctetString with string_value found in SNMP trap\");\n return null;\n}\n\nconst KV = vb.string_value;\nconst DEFAULT_LOCATION = \"\"; // change to \"UNKNOWN\" if you want a visible fallback\n\nconst payload = {};\nconst headers = {};\nlet url = \"\";\nlet method = \"POST\"; // default method is POST if not provided\n\nKV.split(\"|\").forEach(piece => {\n if (!piece) return;\n const idx = piece.indexOf(\"=\");\n if (idx < 0) return;\n\n const key = piece.slice(0, idx).trim();\n const val = piece.slice(idx + 1).trim();\n\n if (key.startsWith(\"payload.\")) {\n const field = key.slice(\"payload.\".length);\n payload[field] = val;\n } else if (key.startsWith(\"headers.\")) {\n const h = key.slice(\"headers.\".length);\n headers[h] = val;\n } else if (key === \"url\") {\n url = val;\n } else if (key === \"method\") {\n method = val;\n }\n});\n\n// Optional fallback for empty/absent location\nif (!payload.location || payload.location === \"\") {\n payload.location = DEFAULT_LOCATION;\n}\n\n// Error if url missing\nif (!url) {\n node.error(\"No 'url' provided in SNMP trap\");\n return null;\n}\n\n// Add a logRecord object for persistence\nconst logRecord = {\n _msgid: msg._msgid || null, // Node-RED msg id\n timestamp: new Date().toISOString(),\n src_address: msg.src.address || null,\n octetstring: vb.string_value,\n url: url,\n method: method,\n payload: payload,\n};\n\n// Build a clean message (drop everything else from the original msg)\nmsg = {\n payload: payload, // only payload.* keys\n headers: headers, // headers.*\n url: url, // required\n method: method, // optional (defaults to POST)\n logRecord: logRecord\n};\n\n\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":410,"y":80,"wires":[["fe8271a0592f3667","da660c58e1283b58"]]},{"id":"78e274db57be7626","type":"function","z":"f6f2187d.f17ca8","name":"http-response-logrecord","func":"// Update logRecord after HTTP request\n\n// Make sure logRecord exists\nmsg.logRecord = msg.logRecord || {};\n\n// Attach response details\nmsg.logRecord.statusCode = msg.statusCode || null;\nmsg.logRecord.responseTimestamp = new Date().toISOString();\nmsg.logRecord.error = msg.payload.Error ? msg.payload.Error.message || String(msg.payload.Error) : null;\n\n// Optionally capture response body (careful if it's huge)\n//if (typeof msg.payload === \"string\" || typeof msg.payload === \"object\") {\n// msg.logRecord.responseSnippet = \n// (typeof msg.payload === \"string\" ? msg.payload : JSON.stringify(msg.payload)).slice(0, 500);\n//}\n\n// Return message unchanged, with updated logRecord\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":950,"y":80,"wires":[["fe8271a0592f3667"]]},{"id":"5699997290b13e4b","type":"tls-config","name":"","cert":"","key":"","ca":"","certname":"","keyname":"","caname":"","servername":"","verifyservercert":false,"alpnprotocol":""},{"id":"6d928f6df205d4e4","type":"global-config","env":[],"modules":{"node-red-contrib-snmp-trap-listener":"2.4.0"}}]