signal-gateway
An extensible monitoring tool, bridging alerting and monitoring systems with Signal messenger.
Overview
signal-gateway receives alerts and log messages from various sources and forwards them to Signal
messenger via signal-cli. It supports:
- Alertmanager webhooks - Receive Prometheus alerts and forward them to Signal
- JSON log streams - Accept JSON logs over TCP/UDP
- Syslog (RFC 5424) - Accept syslog messages over TCP/UDP
signal-gateway also allows you to define filtering and rate limiting schemes to decide if and when an
error log should be escalated to an alert and forwarded, while avoiding alert fatigure.
It also retains a buffer of recent logs to send as context.
Beyond simple forwarding, it enables admins to query the system interactively.
- Prometheus querying - With access to the prometheus query API, you can query metrics and generate plots directly from signal.
- AI integration - Can't remember PromQL syntax or the names of your metrics? Connect it to claude, and ask claude to generate plots for you. Claude also sees the log messages, retains context on the system, and can help you troubleshoot.
Additionally, admins can send "commands" with semantics interpreted by your services elsewhere in the cluster, if you configure this.
-
Secured by Signal - Signal messages are a form of authenticated encryption, tied to your device. You can take the safety numbers from the app and put them in the
signal-gatewayconfig. Then, even if your phone number is simjacked, and you don't have registration lock enabled, an attacker won't be able to send commands that are accepted by thesignal-gateway. -
Extensible
The project is designed as both a library and a binary. You can either use the configurable binary (signal-gateway-bin) that is offered as a default, or use the library signal-gateway
and customize it for your needs. This allows you to add custom handling for admin commands, expose additional tools to the AI integration, and so on.
Requirements
- signal-cli running in JSON-RPC daemon mode
- You can configure it to listen on TCP, or on a unix domain socket
- A registered Signal account.
- It's best to use a new number that you aren't already using with signal, such as a google voice number.
- For security, you should enable registration lock on this number, as well as your personal number.
Quickstart
-
Start signal-cli in JSON-RPC mode:
signal-cli -a +15551234567 daemon --tcp 127.0.0.1:7583 -
Run signal-gateway:
signal-gateway \ --signal-cli-tcp-addr 127.0.0.1:7583 \ --signal-account +15551234567 \ --signal-admins '["your-uuid-here"]' -
Configure Alertmanager to send webhooks to
http://localhost:8000/alert
Configuration
signal-gateway supports hierarchical config, and can read config values from CLI arguments, environment variables, or a TOML config file,
or combinations thereof. See --help for details.
Use --config-file path/to/config.toml to load from a file.
Example TOML configuration:
http_listen_addr = "0.0.0.0:8000"
signal_account = "+15551234567"
signal_cli_tcp_addr = "127.0.0.1:7583"
# Admin UUIDs mapped to their safety numbers (empty list means no verification)
[signal_admins]
"12345678-1234-1234-1234-123456789abc" = []
# Optional: send alerts to a group instead of individual admins
# alert_group_id = "base64-encoded-group-id"
# Optional: Prometheus for /query, /plot, /alerts commands
[prometheus]
prometheus_url = "http://172.31.10.138:9090"
[prometheus.plot]
timezone = "US/Mountain"
# Syslog listener (optional)
[syslog]
listen_addr = "0.0.0.0:1514"
# JSON log listener (optional)
[json]
listen_addr = "0.0.0.0:5000"
Run signal-gateway --help for all available options.
Log handler
TODO
# Log handler configuration
[log_handler]
# Overall rate limit: max 1 alert per 10m from same source location (suppress 2nd+)
overall_limits = [
{ threshold = "< 2 / 10m", by_source_location = true }
]
# Log formatting
[log_handler.log_format]
format_module = true
format_source_location = true
# Single route matching errors, with burst detection for noisy patterns
# (only alert if pattern occurs 2+ times in 10m)
[[log_handler.route]]
alert_level = "error"
limits = [
{ threshold = ">= 2 / 10m", module_equals = "ws", msg_contains = "WebSocket protocol error: Connection reset without closing handshake" },
{ threshold = ">= 2 / 10m", module_equals = "ws", msg_contains = "did not respond to ping, closing stream" },
{ threshold = ">= 2 / 10m", module_equals = "ws", msg_contains = "IO error: peer closed connection without sending TLS close_notify" },
{ threshold = ">= 2 / 10m", module_equals = "main", msg_contains = "error sending request for url" },
]
Claude
TODO
[claude]
api_key_file = "creds/anthropic_api_key"
system_prompt_files = ["system_prompt.md", "metrics_prompt.md"]
claude_model = "claude-sonnet-4-5-20250929"
[claude.compaction]
prompt_file = "compaction_prompt.md"
model = "claude-sonnet-4-5-20250929"
max_tokens = 2048
trigger_chars = 50000
License
MIT or Apache 2 at your option.