Skip to content

Alerting

Seerflow ships with a complete notification router. Every alert is matched against a list of routing rules (first match wins) and dispatched to one or more delivery targets. Each target has its own rate limit, deduplication semantics, and optional quiet hours.

Alert
│
▼
Dedup window (default 15 min, per alert_type override available)
│
▼
Routing rule match (alert_type · rule_name · entity_type · severity range)
│
▼
Per-channel dispatch (immediate or digest, with quiet-hours suppression)
│
▼
DeliveryTarget ── webhook · email · sms · telegram · whatsapp · pagerduty · otlp

All channels are configured under alerting: in seerflow.yaml. Channels not listed are inert — there is no implicit “send everywhere” behaviour.

alerting:
webhooks:
- url: ${SLACK_WEBHOOK_URL}
format: slack # slack | teams | json
min_severity: 4 # only fire for severity >= 4 (high+)
- url: ${TEAMS_WEBHOOK_URL}
format: teams
- url: https://example.com/seerflow
format: json

The json format posts the raw alert payload. slack and teams render channel-native cards including the alert title, severity, rule name, top entities, and a link to the dashboard alert detail (alerting.dashboard_url) when configured.

alerting:
pagerduty_routing_key: ${SEERFLOW_PD_KEY}

Triggers Events API v2 with severity mapped from the OCSF severity ID. The dedup_key is reused as the PagerDuty incident key, so deduplication is consistent across both sides.

alerting:
email_targets:
- name: oncall-mail
smtp_host: smtp.example.com
smtp_port: 587
use_starttls: true
smtp_user: ${SMTP_USER}
smtp_password: ${SMTP_PASSWORD}
from_address: seerflow@example.com
to_addresses:
- oncall@example.com
- secops@example.com
min_severity: 4
max_per_minute: 30 # token-bucket cap; omit for unlimited

smtp_user and smtp_password are read at startup; rotate by restarting the process.

alerting:
sms_targets:
- name: oncall-sms
account_sid: ${TWILIO_SID}
auth_token: ${TWILIO_TOKEN}
from_number: "+15551234567"
to_numbers:
- "+15557654321"
min_severity: 5
rate_per_second: 1.0
burst: 3

Use min_severity: 5 (critical) to keep SMS reserved for the loudest alerts.

alerting:
telegram_targets:
- name: oncall-telegram
bot_token: ${TELEGRAM_BOT_TOKEN}
chat_id: "-1001234567890" # group id (negative) or user id
min_severity: 4
rate_per_second: 30.0
burst: 30

Messages are rendered as MarkdownV2 and respect Telegram’s 30-msg/s burst limit by default.

alerting:
whatsapp_targets:
- name: oncall-whatsapp
phone_number_id: "1234567890"
template_name: seerflow_alert
language_code: en
access_token: ${WA_ACCESS_TOKEN}
to_numbers:
- "+15551234567"
min_severity: 5
rate_per_second: 10.0
burst: 20

The template must be pre-approved in WhatsApp Business Manager. The target carries an internal circuit breaker that opens after sustained 5xx errors and resumes once the API recovers.

Forward every alert (post-dedup) to an OpenTelemetry collector as a span. Useful for fan-out into Tempo / Jaeger / OTel-aware SIEMs.

alerting:
otlp_endpoint: otel-collector.internal:4317
otlp_protocol: grpc # grpc | http
otlp_export_interval_seconds: 5
# otlp_tls: true # default: derived from URL scheme
# otlp_tls_ca_file: /etc/ssl/private-ca.pem
# otlp_mtls_cert_file: /etc/ssl/seerflow.crt
# otlp_mtls_key_file: /etc/ssl/seerflow.key

Custom CA / mTLS files are PEM paths read once at startup. Rotation requires a restart (same semantics as Go tls.Config / Java KeyStore).

Seerflow keeps a dedup_key → last_fire_ts map per alert. Alerts with the same key inside the window are suppressed and bump a dedup_count.

alerting:
dedup_window_seconds: 900 # default 15 min
dedup_window_overrides:
- ["correlation", 300] # kill-chain alerts fire faster
- ["ueba", 1800] # UEBA: at most twice per hour

First-match-wins evaluation. Each rule has a match block (all predicates AND together) and a notify list dispatched in parallel.

alerting:
routing_rules:
- match:
alert_type: ["sigma", "correlation"]
min_severity: 5
notify:
- channel: oncall-sms
- channel: oncall-telegram
- channel: oncall-mail
mode: digest # batch into one mail
digest_window_minutes: 15
- match:
rule_name: "ssh_brute_force*" # fnmatch glob
entity_type: ipv4
notify:
- channel: oncall-telegram
- match:
alert_type: ueba
notify:
- channel: oncall-mail
mode: digest
digest_window_minutes: 60
default_routing:
action: notify # notify | drop
notify:
- channel: oncall-mail
mode: digest
digest_window_minutes: 30
PredicateTypeNotes
alert_typestring or listsigma, ml, correlation, ueba, ioc
rule_namestring (glob)fnmatch.fnmatchcase — e.g. "ssh_*"
entity_typestring or listipv4, ipv6, user, host, process, file, domain, url
min_severity / max_severityintOCSF severity ID (0–6)
ModeWhen to use
immediate (default)High-signal alerts you want pushed as they fire
digestLower-signal alerts coalesced into a single message every digest_window_minutes

Per-channel suppression windows. Severity-aware: alerts at or above min_severity still break through.

alerting:
quiet_hours_by_channel:
- ["oncall-sms", { start: "22:00", end: "07:00", min_severity: 6 }]
- ["oncall-telegram", { start: "00:00", end: "06:00", min_severity: 5 }]

Times are UTC. Windows that cross midnight (e.g. 22:00 → 07:00) are handled correctly.

Add a dashboard_url so message renderers can deep-link back to the alert detail page.

alerting:
dashboard_url: https://seerflow.example.com

Slack, Teams, email, and Telegram bodies will then include a “View in Seerflow” link to {dashboard_url}/#alert=<id>.

Every channel uses a token-bucket rate limiter. The defaults are conservative; tune rate_per_second and burst if you hit the bucket and start seeing send delays in the logs.

ChannelDefault rate_per_secondDefault burst
Emailn/a (use max_per_minute)n/a
SMS1.03
Telegram30.030
WhatsApp10.020

WhatsApp additionally maintains an internal circuit breaker that opens after sustained upstream failure.