This Django app provides alert ingestion and incident orchestration.
It exposes a simple webhook endpoint that accepts JSON payloads from multiple alert sources (Alertmanager, Grafana, and a generic/fallback format), normalizes them into a common schema, and stores them as Alert + Incident records.
See Architecture for how this app fits in the pipeline.
The app exposes two endpoints:
POST /alerts/webhook/— auto-detect driverPOST /alerts/webhook/<driver>/— force a specific driver
A GET request to the same URLs acts as a small health check.
Drivers live in apps/alerts/drivers/ and are responsible for:
- validating a payload (
validate()) - parsing it into a normalized
ParsedPayload(parse())
Built-in drivers:
alertmanager— Prometheus Alertmanager webhook formatgrafana— Grafana Unified Alerting webhook formatpagerduty— PagerDuty V2/V3 webhook formatdatadog— Datadog webhook formatnewrelic— New Relic Alerts webhook formatopsgenie— OpsGenie webhook formatzabbix— Zabbix webhook formatgeneric— flexible fallback format for custom integrations
Driver selection:
- auto-detect tries each driver’s
validate() - you can force a driver via
/alerts/webhook/<driver>/
Alert— the normalized alert record (fingerprint + status + severity + labels/annotations + raw payload)Incident— groups related firing alerts and tracks state (open/ack/resolved/closed)AlertHistory— an audit trail of state transitions (created/resolved/refired)
Business logic lives in apps/alerts/services.py (AlertOrchestrator, IncidentManager).
Access the admin interface at /admin/alerts/ to manage alerts and incidents:
-
Alert — View and manage individual alerts
- Colored severity badges (critical/warning/info)
- Colored status badges (firing/resolved)
- Link to parent incident
- Inline alert history (audit trail)
- Filter by status, severity, source
- Search by name, fingerprint, description
-
Incident — View and manage incidents
- Colored severity/status badges
- Alert count and firing alert count displays
- Pipeline runs count (orchestration integration)
- Inline alerts list
- Inline pipeline runs from orchestration layer
- Filter by status, severity
-
AlertHistory — Read-only audit trail
- View state transitions for alerts
- Cannot add/edit (audit records only)
Note: Incidents display related pipeline runs from the orchestration layer, providing full traceability from alert ingestion through the entire processing pipeline.
POST /alerts/webhook/ (auto-detect) or POST /alerts/webhook/alertmanager/:
{
"version": "4",
"receiver": "webhook",
"status": "firing",
"alerts": [
{
"status": "firing",
"labels": {
"alertname": "HighCPU",
"severity": "critical",
"instance": "server1:9090"
},
"annotations": {
"summary": "High CPU usage detected",
"description": "CPU usage is above 90%"
},
"startsAt": "2024-01-08T10:00:00Z",
"fingerprint": "abc123"
}
],
"externalURL": "http://alertmanager:9093"
}POST /alerts/webhook/ (auto-detect) or POST /alerts/webhook/grafana/:
{
"receiver": "webhook",
"status": "firing",
"state": "alerting",
"title": "Disk alerts",
"alerts": [
{
"status": "firing",
"labels": {
"alertname": "DiskFull",
"severity": "warning"
},
"annotations": {
"summary": "Disk is almost full"
},
"startsAt": "2024-01-08T10:00:00Z"
}
],
"externalURL": "http://grafana:3000"
}POST /alerts/webhook/ (auto-detect) or POST /alerts/webhook/pagerduty/:
{
"event": {
"id": "evt-123",
"event_type": "incident.triggered",
"resource_type": "incident",
"occurred_at": "2024-01-08T10:00:00Z",
"data": {
"id": "INC-456",
"type": "incident",
"html_url": "https://myorg.pagerduty.com/incidents/INC-456",
"number": 42,
"status": "triggered",
"title": "High latency on API endpoints",
"urgency": "high",
"service": {
"id": "SVC-789",
"summary": "Production API"
}
}
}
}POST /alerts/webhook/ (auto-detect) or POST /alerts/webhook/datadog/:
{
"id": "123456",
"title": "CPU High on web-server-1",
"alert_id": "789",
"alert_status": "Triggered",
"alert_transition": "Triggered",
"alert_type": "error",
"alert_title": "CPU High",
"event_msg": "CPU usage is above 90% on web-server-1",
"hostname": "web-server-1",
"priority": "P1",
"tags": "env:production,service:web",
"url": "https://app.datadoghq.com/monitors/789",
"org": {
"id": "org-123",
"name": "MyOrg"
}
}POST /alerts/webhook/ (auto-detect) or POST /alerts/webhook/newrelic/:
{
"account_id": 12345,
"account_name": "Production",
"condition_id": 67890,
"condition_name": "High Memory Usage",
"current_state": "open",
"details": "Memory usage exceeded 85% threshold",
"event_type": "INCIDENT",
"incident_id": 111222,
"incident_url": "https://alerts.newrelic.com/accounts/12345/incidents/111222",
"policy_name": "Infrastructure Alerts",
"severity": "CRITICAL",
"timestamp": 1704711600,
"targets": [
{"name": "web-server-1", "type": "host"}
]
}POST /alerts/webhook/ (auto-detect) or POST /alerts/webhook/opsgenie/:
{
"action": "Create",
"alert": {
"alertId": "abc-123-def",
"message": "Database connection pool exhausted",
"tags": ["env:production", "team:backend"],
"tinyId": "1234",
"entity": "db-primary",
"alias": "db-pool-alert",
"createdAt": 1704711600000,
"description": "Connection pool has no available connections",
"team": "Backend Team",
"source": "monitoring-service",
"priority": "P1"
},
"integrationId": "integration-456",
"integrationName": "Webhook Integration"
}POST /alerts/webhook/ (auto-detect) or POST /alerts/webhook/zabbix/:
{
"event_id": "12345",
"event_name": "High CPU load",
"event_severity": "High",
"event_status": "PROBLEM",
"event_value": "1",
"event_date": "2024.01.08",
"event_time": "10:00:00",
"host_name": "web-server-1",
"host_ip": "192.168.1.100",
"trigger_id": "67890",
"trigger_name": "CPU load is too high",
"trigger_severity": "High",
"trigger_status": "PROBLEM",
"item_name": "CPU Load",
"item_value": "95.5",
"alert_message": "CPU load on web-server-1 is 95.5%",
"zabbix_url": "http://zabbix.example.com/tr_events.php?triggerid=67890"
}Single alert:
{
"name": "My Alert",
"status": "firing",
"severity": "warning",
"description": "Something happened",
"labels": {"service": "api"}
}Multiple alerts:
{
"source": "my-system",
"alerts": [
{
"title": "My Alert",
"state": "ok",
"priority": "high",
"message": "Alert description"
}
]
}Notes on flexibility:
- name fields:
name,alert_name,title,alertname - status fields:
status(firing/resolved) orstate(ok/resolved/normal => resolved) - severity fields:
severity(critical/warning/info) orpriority(high => critical, low => info)
Successful processing returns:
{
"status": "success",
"alerts_created": 1,
"alerts_updated": 0,
"alerts_resolved": 0,
"incidents_created": 1,
"incidents_updated": 0
}If some alerts fail but others succeed:
statusbecomespartialerrorsincludes a list of error strings
If you see errors like sqlite3.OperationalError: no such table: alerts_alert, it typically means the test DB wasn’t created/migrated.
Make sure you’re using pytest-django and have DJANGO_SETTINGS_MODULE=config.settings (already configured in pyproject.toml).
If auto-detection can’t find a driver:
- try forcing one:
POST /alerts/webhook/alertmanager/ - confirm your payload includes the driver’s expected signature fields
The webhook view is CSRF-exempt (@csrf_exempt) to support external systems.
The alerts app integrates with the checkers app to create alerts from health check results.
The health-check-to-alert workflow is now handled by the orchestrated pipeline. Use:
# Run all checks through the pipeline
uv run python manage.py run_pipeline --checks-only
# Run specific checkers
uv run python manage.py run_pipeline --checks-only --checkers cpu memory disk
# Dry run
uv run python manage.py run_pipeline --checks-only --dry-run --json
# Custom labels and hostname
uv run python manage.py run_pipeline --checks-only --label env=production --hostname web-01
# Skip incidents
uv run python manage.py run_pipeline --checks-only --no-incidents
# Threshold overrides
uv run python manage.py run_pipeline --checks-only --warning-threshold 60 --critical-threshold 80See apps/orchestration/README.md for full run_pipeline documentation.
from apps.alerts.check_integration import CheckAlertBridge
from apps.checkers.checkers import CPUChecker
# Create a bridge instance
bridge = CheckAlertBridge(
auto_create_incidents=True, # Create incidents for critical/warning alerts
hostname="my-server", # Override hostname in labels
)
# Run a single check and create an alert
checker = CPUChecker(warning_threshold=70, critical_threshold=90)
result = checker.check()
processing_result = bridge.process_check_result(result)
print(f"Alerts created: {processing_result.alerts_created}")
print(f"Incidents created: {processing_result.incidents_created}")
# Or use the convenience method
check_result, processing_result = bridge.run_check_and_alert(
"cpu",
checker_kwargs={"warning_threshold": 70},
labels={"environment": "production"},
)
# Run multiple checks at once
from apps.alerts.check_integration import CheckAlertResult
result = bridge.run_checks_and_alert(
checker_names=["cpu", "memory", "disk"],
labels={"datacenter": "us-east-1"},
)
print(f"Checks run: {result.checks_run}")
print(f"Total alerts created: {result.alerts_created}")- Check execution: Run health checkers (CPU, memory, disk, etc.)
- Status mapping: CheckStatus is mapped to alert severity:
CRITICAL→criticalseverity,firingstatusWARNING→warningseverity,firingstatusOK→infoseverity,resolvedstatusUNKNOWN→warningseverity,firingstatus
- Alert creation: Alerts are created or updated based on fingerprint (checker + hostname)
- Incident management: Optionally create/update incidents for firing alerts
- Auto-resolution: When a check returns OK, the corresponding alert is resolved
Use cron to run checks periodically:
# Run all checks every 5 minutes
*/5 * * * * cd /path/to/project && uv run python manage.py run_pipeline --checks-only --json >> /var/log/health-checks.log 2>&1
# Run only critical checks every minute
* * * * * cd /path/to/project && uv run python manage.py run_pipeline --checks-only --checkers cpu memory --json