This guide explains how to configure alert routing to destinations like Slack and Discord.
- Go to Slack incoming webhooks
- Create a Slack app and enable incoming webhooks
- Add a webhook to your channel
- Copy the webhook URL
destinations:
- name: slack-alerts
type: slack
webhook_url: "${SLACK_WEBHOOK_URL}"
template:
content: |
{"text": "Alert: {{ status }} - {{ labels.alertname }}\n{{ annotations.summary }}"}Block Kit provides rich formatting:
destinations:
- name: slack-blockkit
type: slack
webhook_url: "${SLACK_WEBHOOK_URL}"
template:
content: |
{"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "{{ labels.alertname }}"
}
},
{
"type": "section",
"fields": [
{"type": "mrkdwn", "text": "*Severity:* {{ labels.severity | upper }}"},
{"type": "mrkdwn", "text": "*Status:* {{ status | upper }}"}
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "{{ annotations.summary }}"
}
}
]}- Go to your Discord server settings
- Go to Integrations → Webhooks
- Create a new webhook
- Select a channel
- Copy the webhook URL
destinations:
- name: discord-alerts
type: discord
webhook_url: "${DISCORD_WEBHOOK_URL}"
template:
content: |
{"content": "🚨 **{{ status | upper }}** {{ labels.alertname }}\n{{ annotations.summary }}"}Embeds provide rich formatting:
destinations:
- name: discord-embed
type: discord
webhook_url: "${DISCORD_WEBHOOK_URL}"
template:
content: |
{
"embeds": [{
"title": "{{ labels.alertname }}",
"description": "{{ annotations.summary }}",
"color": 16711680 if status == 'firing' else 65280,
"fields": [
{"name": "Severity", "value": "{{ labels.severity | upper }}"},
{"name": "Status", "value": "{{ status | upper }}"}
]
}]
}groups:
- name: unique-group-name
destinations: [slack-alerts, discord-oncall]
match:
- type: label_equals
label: namespace
values: [team-a, team-b]Group Fields:
name: Unique identifierdestinations: List of destination namesmatch: List of match rules (OR logic)
Label Equals:
match:
- type: label_equals
label: namespace
values: [team-a, team-b]Matches: namespace == "team-a" OR namespace == "team-b"
Label Contains:
match:
- type: label_contains
label: namespace
values: [team]Matches: namespace contains "team"
Label Matches (Regex):
match:
- type: label_matches
label: container
pattern: ".*-db-.*"Matches: container =~ ".*-db-.*" (e.g., postgres-db-main)
Label Not Equals:
match:
- type: label_not_equals
label: namespace
values: [kube-system, kube-public]Matches: namespace != "kube-system" AND namespace != "kube-public"
Label Not Contains:
match:
- type: label_not_contains
label: namespace
values: [test, dev]Matches: namespace does not contain "test" AND namespace does not contain "dev"
Label Not Matches:
match:
- type: label_not_matches
label: alertname
pattern: "Test.*"Matches: alertname !~ "Test.*"
Always Match:
match:
- type: always_matchMatches all alerts (catch-all)
Hermes uses OR logic - an alert matches if ANY match rule matches:
groups:
- name: team-a-or-critical
match:
- type: label_equals
label: namespace
values: [team-a]
- type: label_equals
label: severity
values: [critical]This group matches alerts where:
namespace == "team-a"ORseverity == "critical"
groups:
- name: team-a
destinations: [slack-team-a]
match:
- type: label_equals
label: namespace
values: [team-a, team-production, team-staging]groups:
- name: critical-alerts
destinations: [slack-critical, discord-oncall]
match:
- type: label_equals
label: severity
values: [critical]
- name: warning-alerts
destinations: [slack-warning]
match:
- type: label_equals
label: severity
values: [warning]groups:
- name: database-alerts
destinations: [slack-db-team]
match:
- type: label_matches
label: alertname
pattern: ".*Database.*|.*Postgres.*|.*MySQL.*"groups:
- name: production-alerts
destinations: [slack-production]
match:
- type: label_equals
label: environment
values: [production]
- name: staging-alerts
destinations: [slack-staging]
match:
- type: label_equals
label: environment
values: [staging]groups:
- name: production-critical
destinations: [slack-oncall]
match:
- type: label_equals
label: environment
values: [production]
- type: label_equals
label: severity
values: [critical]Matches production alerts OR critical alerts.
groups:
- name: non-kube-system
destinations: [slack-alerts]
match:
- type: label_not_equals
label: namespace
values: [kube-system, kube-public]destinations:
- name: slack-team
type: slack
webhook_url: "${SLACK_TEAM_WEBHOOK_URL}"
- name: discord-oncall
type: discord
webhook_url: "${DISCORD_ONCALL_WEBHOOK_URL}"
groups:
- name: critical-alerts
destinations: [slack-team, discord-oncall]
match:
- type: label_equals
label: severity
values: [critical]Critical alerts go to both Slack and Discord.
settings:
fingerprint_strategy: "auto"
deduplication_ttl: 300
destinations:
- name: slack-team-a
type: slack
webhook_url: "${SLACK_TEAM_A_WEBHOOK_URL}"
template:
content: |
{"blocks": [
{
"type": "header",
"text": {"type": "plain_text", "text": "{{ labels.alertname }}"}
},
{
"type": "section",
"fields": [
{"type": "mrkdwn", "text": "*Severity:* {{ labels.severity | upper }}"},
{"type": "mrkdwn", "text": "*Namespace:* {{ labels.namespace }}"}
]
},
{
"type": "section",
"text": {"type": "mrkdwn", "text": "{{ annotations.summary }}"}
}
]}
- name: discord-oncall
type: discord
webhook_url: "${DISCORD_ONCALL_WEBHOOK_URL}"
template:
content: |
{"content": "🚨 {{ labels.alertname }}\n{{ annotations.summary }}"}
groups:
- name: team-a-alerts
destinations: [slack-team-a]
match:
- type: label_equals
label: namespace
values: [team-a]
- name: critical-alerts
destinations: [slack-team-a, discord-oncall]
match:
- type: label_equals
label: severity
values: [critical]
- name: database-alerts
destinations: [slack-team-a]
match:
- type: label_matches
label: alertname
pattern: ".*Database.*"- Routing and Groups - How OR routing works
- Templating - Customizing message formats
- Examples - Complete configuration examples