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:0007: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.