Slack
The Slack driver posts via Slack’s Incoming Webhooks. One
webhook URL maps to one default channel. An optional channel
override lets a single webhook target several destinations.
Fields
Section titled “Fields”[[notifier]]id = "slack-ops"type = "slack"webhook_url_env = "RUNWISP_SLACK_OPS_URL"channel = "#ops" # optional overrideid and type are required on every notifier. Exactly one of
webhook_url, webhook_url_env, webhook_url_file must be set —
the secret rule.
| Key | Required | What it does |
|---|---|---|
webhook_url | one-of (see below) | Inline webhook URL. |
webhook_url_env | one-of | Name of an env var holding the URL. |
webhook_url_file | one-of | Path to a file containing the URL. Relative paths resolve under the data dir. |
channel | no | Override the webhook’s default. Must start with # (channel) or @ (user). |
template_path | no | Path to a Go-template file overriding the embedded message format. |
1. Create the webhook in Slack
Section titled “1. Create the webhook in Slack”In your Slack workspace:
- Open api.slack.com/apps and create a new app (or pick an existing one) for the workspace you want notifications in.
- Under Incoming Webhooks, toggle the feature on.
- Click Add New Webhook to Workspace, pick the destination
channel (e.g.
#ops), and authorise. - Copy the URL. It looks like
https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX.
Treat the URL as a secret — anyone with it can post to your channel.
2. Store the URL
Section titled “2. Store the URL”Pick one of the three options below. Setting more than one is a config-load error.
The simplest option for any deployment — Docker, systemd, bare metal. Set the variable in whatever already manages your environment.
export RUNWISP_SLACK_OPS_URL=https://hooks.slack.com/services/T00.../B00.../XXX...runwisp daemon[[notifier]]id = "slack-ops"type = "slack"webhook_url_env = "RUNWISP_SLACK_OPS_URL"channel = "#ops"Useful when a secrets manager (Vault agent, sops, Docker
secrets at /run/secrets/...) writes the URL to a known
path for you. Relative paths resolve under the data dir.
mkdir -p ~/.config/runwispchmod 0700 ~/.config/runwispprintf '%s\n' 'https://hooks.slack.com/services/...' > ~/.config/runwisp/slack-ops.urlchmod 0600 ~/.config/runwisp/slack-ops.url[[notifier]]id = "slack-ops"type = "slack"webhook_url_file = "/home/you/.config/runwisp/slack-ops.url"channel = "#ops"The notifier accepts webhook_url = "https://…" directly.
Avoid it — config files are often committed to git or shared
in chat, and an inline URL will leak.
[[notifier]]id = "slack-ops"type = "slack"webhook_url = "https://hooks.slack.com/services/T.../B.../X..."channel = "#ops"The id is what other parts of runwisp.toml refer to. Pick
something readable — slack-ops, slack-deploys, slack-marketing
all work. Without channel, the webhook posts to whatever channel
was selected when it was created.
3. Route failures to it
Section titled “3. Route failures to it”There are two places this channel id can appear. Pick whichever reads better in your file.
On one task
Section titled “On one task”[tasks.backup-postgres]cron = "30 2 * * *"notify_on_failure = ["slack-ops"]run = "..."That single line is enough. The bell receives the same event by default,
so if the Slack request fails you still see the failure in the bell. See
Per-task notifications for the full list of
options, including notify_on_success and inline channel overrides.
In a notification rule (one rule covering many tasks)
Section titled “In a notification rule (one rule covering many tasks)”[[notification_route]]match = { kind = ["run.failed", "run.timeout", "run.crashed"] }notify = ["slack-ops"]Omit match.task and the rule matches every task. Add a match.task
pattern for finer control:
# Backup failures also send to the on-call channel[[notification_route]]match = { kind = ["run.failed", "run.timeout", "run.crashed"], task = "backup-*" }notify = ["slack-ops", "tg-oncall"]The router removes duplicates — if a backup failure matches both a generic rule and a backup-specific rule, the channel receives one message.
4. Test it
Section titled “4. Test it”Trigger a task you know will fail:
runwisp exec smoke-test # whatever you have that exits non-zeroWithin a few seconds you should see a message in #ops with the task
name, end reason, and a preview of the captured stderr, plus a new
row in the Web UI’s bell. If only the bell shows a row, Slack
delivery failed — open the bell and look for a
notify.delivery_failed event with the underlying reason.
Customising the message
Section titled “Customising the message”Point template_path at a Go-template file to override the embedded
message format. The default template is JSON for Slack’s Block Kit:
{ "blocks": [ { "type": "header", "text": { "type": "plain_text", "text": "{{ emoji .Severity }} {{ .TaskName }} — {{ .Kind }}" } }, { "type": "section", "text": { "type": "mrkdwn", "text": "*Reason:* {{ jsonStr .Reason }}\n*Severity:* `{{ .Severity }}`" } }{{- if .Run }}, { "type": "context", "elements": [ { "type": "mrkdwn", "text": "Run `{{ .Run.ID }}` triggered by `{{ .Run.TriggeredBy }}` at {{ timeRFC .Timestamp }}" } ] }{{- end }} ]}Copy this as your starting point and modify what you need. The template receives the full event struct — task name, run id, exit code, end reason, captured tail.
What the loader rejects
Section titled “What the loader rejects”channelthat doesn’t start with#(channel) or@(user).- Two of
webhook_url,webhook_url_env,webhook_url_fileset at the same time, or none of them set. - An
idcontaining:(reserved for inline target overrides) or equal to"inapp"(reserved).
Where to next
Section titled “Where to next”- Telegram — same shape, a different chat platform.
- Notification rules — one rule covering many tasks.
- Notifications model — coalescing, delivery-failure handling, the trust model.