Closes #2112
Adds a webhook-based SNMP provider that ingests SNMP trap events from network devices into Keep as alerts. Compatible with any SNMP trap forwarder (snmptrapd, Zabbix, OpenNMS, etc.) that can POST JSON to an HTTP endpoint.
generic_trap integer field1.3.6.1.6.3.1.1.5.3)linkDown, coldStart, authenticationFailure, etc.)linkDown → CRITICAL, authenticationFailure → WARNING, linkUp → INFOseverity field overrides OID default (supports: critical, major, minor, warning, info, clear)linkUp traps (v1 generic_trap=3 and v2c 1.3.6.1.6.3.1.1.5.4) set status to RESOLVEDstatus field: firing, resolved, acknowledged, clear{oid, value} objects, or raw string (snmptrapd pipe output)FINGERPRINT_FIELDS = ["oid", "agent_address", "enterprise", "specific_trap"]enterprise + specific_trap so enterprise-specific traps (generic_trap=6) from the same agent do not collapse into a single alertwebhook_markdown includes full snmptrapd config, handler script, and direct JSON payload examples for both v1 and v2c formatskeep/providers/snmp_provider/
├── __init__.py
└── snmp_provider.py
_format_alert() handles all format variants; _get_trap_info() and _parse_varbinds() are separately testable static methodstests/test_snmp_provider.py — 19 tests covering:
| Test | What it covers |
|---|---|
| v2c linkDown | CRITICAL severity, FIRING status, correct OID |
| v1 linkDown | generic_trap integer → OID resolution |
| v1 linkUp | auto-resolve to RESOLVED / INFO |
| coldStart | WARNING severity, not resolved |
| authFailure | WARNING, description preserved |
| Enterprise v3 + severity override | user severity=critical on unknown OID |
Custom name + major severity |
name preserved, HIGH severity |
| User status override | acknowledged overrides auto-status |
| Varbinds dict | passthrough unchanged |
| Varbinds list | normalised to flat dict |
| Varbinds string | wrapped as {"raw": ...} |
| Varbinds missing | defaults to {} |
| Empty event | no crash, valid AlertDto |
| simulate_alert() | mock wired correctly |
| Integer severity | no AttributeError on .lower() |
| Enterprise-specific trap dedup | specific_trap preserved for fingerprinting |
| id=None when missing | not empty string |
| OID numeric-key fallback | 1.3.6.1.6.3.1.1.4.1.0 key resolves correctly |
/claim #2112
Michael
@jonesy827
Keep (YC W23)
@keephq