Подробный гайд: отправка алертов из Alertmanager через webhook в JSON
1. Как это работает
Alertmanager получает алерты из Prometheus, группирует их и отправляет в настроенные приёмники (receivers). Один из встроенных типов приёмников — webhook_configs. Он отправляет HTTP POST-запрос с телом в формате JSON на указанный вами эндпоинт.
Prometheus → Alertmanager → HTTP POST (JSON) → ваш сервис
Формат полезной нагрузки (payload) фиксированный — вы не можете кастомизировать тело запроса через шаблоны напрямую (в отличие от, например, telegram_configs или email_configs). Если нужен кастомный формат — ставят промежуточный шлюз.
2. Настройка alertmanager.yml
Минимальная конфигурация
global:
resolve_timeout: 5m
route:
receiver: 'default-webhook'
group_by: ['alertname', 'job']
group_wait: 30s # сколько ждать перед первой отправкой группы
group_interval: 5m # как часто отправлять обновления по группе
repeat_interval: 4h # как часто повторять активные алерты
receivers:
- name: 'default-webhook'
webhook_configs:
- url: 'http://my-service:8080/alertmanager/webhook'
send_resolved: true
Полные параметры webhook_configs
| Параметр | Описание | По умолчанию |
|---|---|---|
url |
URL эндпоинта | обязателен |
send_resolved |
Отправлять ли статус resolved |
false |
max_alerts |
Макс. алертов в одном запросе (0 = без лимита) | 0 |
http_config |
Настройки HTTP-клиента | — |
Пример с аутентификацией и TLS
receivers:
- name: 'secure-webhook'
webhook_configs:
- url: 'https://hooks.example.com/alertmanager'
send_resolved: true
max_alerts: 10
http_config:
basic_auth:
username: 'alertmanager'
password: 'secret123'
# или Bearer-токен:
# authorization:
# type: 'Bearer'
# credentials: 'my-token-here'
tls_config:
insecure_skip_verify: false
ca_file: '/etc/ssl/certs/ca.crt'
3. Формат JSON-payload (версия 4)
Каждый запрос — это один объект со следующей структурой:
{
"version": "4",
"groupKey": "{}:{alertname=\"HighCPUUsage\"}",
"truncatedAlerts": 0,
"status": "firing",
"receiver": "default-webhook",
"groupLabels": {
"alertname": "HighCPUUsage"
},
"commonLabels": {
"alertname": "HighCPUUsage",
"severity": "warning",
"job": "node-exporter"
},
"commonAnnotations": {
"summary": "Высокая загрузка CPU",
"description": "CPU выше 90% на {{ $labels.instance }}"
},
"externalURL": "http://alertmanager:9093",
"alerts": [
{
"status": "firing",
"labels": {
"alertname": "HighCPUUsage",
"severity": "warning",
"job": "node-exporter",
"instance": "server-01:9100"
},
"annotations": {
"summary": "Высокая загрузка CPU на server-01",
"description": "CPU выше 90% на сервере server-01"
},
"startsAt": "2026-10-11T14:23:05.123Z",
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "http://prometheus:9090/graph?g0.expr=...",
"fingerprint": "a1b2c3d4e5f67890"
}
]
}
Разбор полей
Корневой уровень
| Поле | Тип | Описание |
|---|---|---|
version |
string | Версия формата. Актуальная — "4" |
groupKey |
string | Уникальный ключ группы алертов |
truncatedAlerts |
int | Сколько алертов было отброшено из-за max_alerts |
status |
string | Общий статус группы: "firing" или "resolved" |
receiver |
string | Имя приёмника из конфига |
groupLabels |
object | Метки, по которым сгруппированы алерты |
commonLabels |
object | Метки, общие для всех алертов в группе |
commonAnnotations |
object | Аннотации, общие для всех алертов в группе |
externalURL |
string | URL Alertmanager (для ссылок в уведомлениях) |
Массив alerts[]
| Поле | Тип | Описание |
|---|---|---|
status |
string | Статус конкретного алерта |
labels |
object | Все метки алерта |
annotations |
object | Все аннотации алерта |
startsAt |
string (RFC 3339) | Время начала |
endsAt |
string (RFC 3339) | Время окончания (0001-01-01T00:00:00Z если ещё активен) |
generatorURL |
string | Ссылка на выражение в Prometheus UI |
fingerprint |
string | Уникальный отпечаток алерта (для дедупликации) |
Важно: в одном запросе может быть несколько алертов в массиве alerts — это зависит от настройки group_by в маршруте.
4. Пример правила алерта в Prometheus
groups:
- name: node-alerts
rules:
- alert: HighCPUUsage
expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 90
for: 5m
labels:
severity: warning
team: infrastructure
annotations:
summary: "Высокая загрузка CPU на {{ $labels.instance }}"
description: "CPU выше 90% более 5 минут на сервере {{ $labels.instance }}"
- alert: InstanceDown
expr: up == 0
for: 1m
labels:
severity: critical
team: infrastructure
annotations:
summary: "Инстанс {{ $labels.instance }} недоступен"
description: "{{ $labels.instance }} ({{ $labels.job }}) не отвечает более 1 минуты"
5. Примеры обработчиков
5.1. Простой сервер на Python (Flask)
from flask import Flask, request, jsonify
import logging
from datetime import datetime
app = Flask(__name__)
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s')
logger = logging.getLogger(__name__)
@app.route('/alertmanager/webhook', methods=['POST'])
def webhook():
data = request.get_json(force=True)
if not data:
return jsonify({"error": "empty payload"}), 400
version = data.get("version", "unknown")
status = data.get("status")
receiver = data.get("receiver")
group_key = data.get("groupKey")
alerts = data.get("alerts", [])
logger.info(f"=== Alertmanager webhook ===")
logger.info(f"Version: {version}, Status: {status}, Receiver: {receiver}")
logger.info(f"Group Key: {group_key}")
logger.info(f"Alerts count: {len(alerts)}")
for i, alert in enumerate(alerts):
alert_status = alert.get("status")
labels = alert.get("labels", {})
annotations = alert.get("annotations", {})
starts_at = alert.get("startsAt")
fingerprint = alert.get("fingerprint")
logger.info(f" --- Alert #{i+1} ---")
logger.info(f" Status: {alert_status}")
logger.info(f" Fingerprint: {fingerprint}")
logger.info(f" Labels: {labels}")
logger.info(f" Annotations: {annotations}")
logger.info(f" Started at: {starts_at}")
# Пример: извлечение конкретного поля
alertname = labels.get("alertname", "unknown")
severity = labels.get("severity", "unknown")
instance = labels.get("instance", "unknown")
summary = annotations.get("summary", "")
logger.info(f" → [{severity.upper()}] {alertname} on {instance}: {summary}")
return jsonify({"status": "ok"}), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
5.2. Простой сервер на Go (net/http)
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
type AlertmanagerPayload struct {
Version string `json:"version"`
GroupKey string `json:"groupKey"`
TruncatedAlerts int `json:"truncatedAlerts"`
Status string `json:"status"`
Receiver string `json:"receiver"`
GroupLabels map[string]string `json:"groupLabels"`
CommonLabels map[string]string `json:"commonLabels"`
CommonAnnotations map[string]string `json:"commonAnnotations"`
ExternalURL string `json:"externalURL"`
Alerts []Alert `json:"alerts"`
}
type Alert struct {
Status string `json:"status"`
Labels map[string]string `json:"labels"`
Annotations map[string]string `json:"annotations"`
StartsAt string `json:"startsAt"`
EndsAt string `json:"endsAt"`
GeneratorURL string `json:"generatorURL"`
Fingerprint string `json:"fingerprint"`
}
func webhookHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var payload AlertmanagerPayload
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
log.Printf("Error decoding JSON: %v", err)
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
log.Printf("=== Alertmanager Webhook ===")
log.Printf("Status: %s | Receiver: %s | Alerts: %d",
payload.Status, payload.Receiver, len(payload.Alerts))
for _, alert := range payload.Alerts {
log.Printf(" [%s] %s (instance: %s, fingerprint: %s)",
alert.Status,
alert.Labels["alertname"],
alert.Labels["instance"],
alert.Fingerprint,
)
log.Printf(" Summary: %s", alert.Annotations["summary"])
}
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "OK")
}
func main() {
http.HandleFunc("/alertmanager/webhook", webhookHandler)
log.Println("Listening on :8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
5.3. Пример на Node.js (Express)
const express = require('express');
const app = express();
app.use(express.json());
app.post('/alertmanager/webhook', (req, res) => {
const { version, status, receiver, alerts = [] } = req.body;
console.log(`[${new Date().toISOString()}] Alertmanager webhook`);
console.log(` Version: ${version}, Status: ${status}, Receiver: ${receiver}`);
alerts.forEach((alert, i) => {
const { alertname, severity, instance } = alert.labels;
const { summary } = alert.annotations;
console.log(` Alert #${i + 1}: [${alert.status}] ${alertname} (${severity}) on ${instance}`);
console.log(` Summary: ${summary}`);
console.log(` Started: ${alert.startsAt}`);
});
res.status(200).json({ status: 'ok' });
});
app.listen(8080, () => console.log('Webhook listener on :8080'));
6. Группировка и жизненный цикл
Как работает group_by
route:
group_by: ['alertname', 'cluster']
- Все алерты с одинаковыми значениями
alertnameиclusterпопадают в один запрос в массивalerts. - Если сработали 5 алертов
InstanceDownна разных серверах иgroup_by: ['alertname']— придёт один запрос с массивом из 5 алертов.
Тайминги отправки
| Параметр | Что делает | Типичное значение |
|---|---|---|
group_wait |
Задержка перед первой отправкой новой группы (накапливает алерты) | 30s |
group_interval |
Интервал между повторными отправками при изменении группы | 5m |
repeat_interval |
Интервал повторной отправки неизменных активных алертов | 4h |
Жизненный цикл алерта
Алерт сработал (firing)
→ Alertmanager ждёт group_wait
→ POST с status="firing", alerts[].status="firing"
→ Если алерт повторяется — повторная отправка через repeat_interval
→ Алерт затух (resolved)
→ POST с status="resolved", alerts[].status="resolved"
(только если send_resolved: true)
7. Продвинутые сценарии
7.1. Маршрутизация разных алертов на разные вебхуки
route:
receiver: 'default'
routes:
- match:
severity: critical
receiver: 'critical-webhook'
group_wait: 10s
repeat_interval: 1h
- match:
severity: warning
receiver: 'warning-webhook'
repeat_interval: 12h
receivers:
- name: 'critical-webhook'
webhook_configs:
- url: 'http://oncall-service:9090/critical'
send_resolved: true
- name: 'warning-webhook'
webhook_configs:
- url: 'http://dashboard-service:9090/warnings'
send_resolved: false
7.2. Ограничение количества алертов
webhook_configs:
- url: 'http://my-service/webhook'
max_alerts: 20 # не более 20 алертов в одном запросе
Если алертов больше — лишние будут отброшены, а поле truncatedAlerts покажет сколько.
7.3. Дедупликация на стороне обработчика
Используйте поле fingerprint для определения уникального алерта:
seen_alerts = {}
def handle_alert(alert):
fp = alert["fingerprint"]
status = alert["status"]
if status == "firing":
if fp not in seen_alerts:
seen_alerts[fp] = alert
# Отправить уведомление
elif status == "resolved":
if fp in seen_alerts:
del seen_alerts[fp]
# Отправить уведомление о восстановлении
7.4. Кастомный формат через прокси-шлюз
Поскольку Alertmanager отправляет фиксированный формат, для кастомизации ставят промежуточный сервис:
Alertmanager → JSON → Прокси-шлюз → Кастомный формат → Конечный сервис
Пример прокси на Python, который преобразует алерт в произвольный JSON:
@app.route('/alertmanager/webhook', methods=['POST'])
def transform_and_forward():
data = request.get_json()
for alert in data.get("alerts", []):
custom_payload = {
"alert_name": alert["labels"].get("alertname"),
"severity": alert["labels"].get("severity", "unknown"),
"message": alert["annotations"].get("summary", ""),
"server": alert["labels"].get("instance", ""),
"state": alert["status"],
"timestamp": alert["startsAt"],
"link": alert.get("generatorURL", ""),
}
# Отправка в нужный сервис в своём формате
requests.post(
"https://internal-api.company.com/incidents",
json=custom_payload,
headers={"Authorization": "Bearer xxx"},
)
return jsonify({"status": "ok"}), 200
8. Отладка и проверка
8.1. Быстрая проверка через curl
Отправьте тестовый алерт вручную, чтобы проверить ваш обработчик без Alertmanager:
curl -X POST http://localhost:8080/alertmanager/webhook \
-H "Content-Type: application/json" \
-d '{
"version": "4",
"groupKey": "{}:{alertname=\"TestAlert\"}",
"truncatedAlerts": 0,
"status": "firing",
"receiver": "test",
"groupLabels": {"alertname": "TestAlert"},
"commonLabels": {"alertname": "TestAlert", "severity": "info"},
"commonAnnotations": {"summary": "Тестовый алерт"},
"externalURL": "http://alertmanager:9093",
"alerts": [
{
"status": "firing",
"labels": {
"alertname": "TestAlert",
"severity": "info",
"instance": "test-server:9100"
},
"annotations": {
"summary": "Это тестовый алерт",
"description": "Проверка webhook-обработчика"
},
"startsAt": "2026-10-11T12:00:00.000Z",
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "http://prometheus:9090/graph?g0.expr=up",
"fingerprint": "abc123def456"
}
]
}'
8.2. Использование amtool для тестов
# Отправить тестовый алерт в Alertmanager
amtool alert add TestAlert \
alertname="TestAlert" severity="info" instance="test:9100" \
--annotation=summary="Тестовый алерт" \
--alertmanager.url=http://localhost:9093
8.3. Проверка конфига
# Валидация синтаксиса
amtool check-config /etc/alertmanager/alertmanager.yml
# Или через Docker
docker run --rm -v $(pwd)/alertmanager.yml:/etc/alertmanager.yml \
prom/alertmanager amtool check-config /etc/alertmanager.yml
8.4. Логи и метрики
В логах Alertmanager ищите:
level=info msg="Notify success" receiver="default-webhook"
level=error msg="Notify failed" receiver="default-webhook"
Alertmanager метрики для мониторинга:
alertmanager_notifications_total{receiver="default-webhook"}
alertmanager_notifications_failed_total{receiver="default-webhook"}
9. Типичные ошибки и решения
| Проблема | Причина | Решение |
|---|---|---|
| Обработчик не получает алерты | Неверный url или сетевые ограничения |
Проверить доступность: curl -X POST http://... |
Нет resolved уведомлений |
send_resolved: false |
Установить send_resolved: true |
| Дубликаты алертов | Обработчик не учитывает repeat_interval |
Использовать fingerprint для дедупликации |
Пустой alerts[] |
Неверный group_by или match |
Проверить маршруты и группировку |
| Обработчик падает при большом количестве алертов | Слишком большая группа | Настроить max_alerts или уменьшить group_by |
Content-Type не парсится |
Обработчик ожидает application/x-www-form-urlencoded |
Alertmanager шлёт application/json — убедиться, что обработчик парсит JSON |
10. Чек-лист настройки
- [ ] Определены правила алертов в Prometheus (
rules.yml) - [ ] В
alertmanager.ymlнастроенrouteсgroup_by, таймингами - [ ] Создан
receiverсwebhook_configs - [ ] Указан корректный
url - [ ] При необходимости настроена аутентификация (
basic_auth/authorization) - [ ] Установлен
send_resolved: trueесли нужны уведомления о восстановлении - [ ] Обработчик принимает
POSTи парситapplication/json - [ ] Обработчик возвращает
200 OK(иначе Alertmanager будет повторять) - [ ] Проверена валидация конфига:
amtool check-config - [ ] Проведён тестовый прогон через
curlилиamtool - [ ] Настроены метрики/логи для мониторинга доставки
Мы делимся этой технической информацией, чтобы помочь вам в решении задач — используйте её с пониманием. Статья носит рекомендательный характер, поэтому, пожалуйста, применяйте описанные методы осмотрительно.