Skip to content

Slack

The slack driver delivers via Slack’s Incoming Webhooks. One webhook URL maps to one default channel; an optional channel override lets a single webhook target multiple destinations. This page is both the field reference and the setup walkthrough.

[[notifier]]
id = "slack-ops"
type = "slack"
webhook_url_env = "RUNWISP_SLACK_OPS_URL"
channel = "#ops" # optional override
KeyTypeRequiredWhat it does
webhook_urlstringone-of (see below)Inline incoming-webhook URL.
webhook_url_envstringone-ofName of an env var holding the URL.
webhook_url_filestringone-ofPath to a file containing the URL. Relative paths resolve under the data dir.
channelstringnoOverride the default channel/user. Must start with # (channel) or @ (user).
template_pathstringnoPath to a Go-template file overriding the embedded message format.

id and type are the common fields shared with every driver. Exactly one of webhook_url, webhook_url_env, webhook_url_file must be set. The validator rejects a notifier with two of three sources to make accidents impossible. Omitting all three is also an error.

In your Slack workspace:

  1. Open api.slack.com/apps and create a new app (or pick an existing one) for the workspace you want notifications in.
  2. Under Incoming Webhooks, toggle the feature on.
  3. Click Add New Webhook to Workspace, pick the destination channel (e.g. #ops), and authorise.
  4. Copy the resulting URL. It looks like https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX.

Treat that URL as a secret — anyone with it can post to your channel.

Three options, ordered from “everywhere works” to “secret-store friendly.” Mixing two sources is a config-load error.

Terminal window
# /etc/runwisp/runwisp.env (loaded by systemd via EnvironmentFile=)
RUNWISP_SLACK_OPS_URL=https://hooks.slack.com/services/T00.../B00.../XXX...
Terminal window
sudo install -d -o runwisp -g runwisp -m 0700 /etc/runwisp/secrets
echo 'https://hooks.slack.com/services/...' \
| sudo install -m 0600 -o runwisp -g runwisp /dev/stdin /etc/runwisp/secrets/slack-ops.url

Useful when the URL flows from a tool like Vault or a secrets operator — write it to a known path, point RunWisp at the path.

The notifier supports webhook_url = "https://…" directly in runwisp.toml. Don’t. Your config probably lives in git or gets shared in a chat at some point; an inline secret will leak eventually.

[[notifier]]
id = "slack-ops"
type = "slack"
webhook_url_env = "RUNWISP_SLACK_OPS_URL"
channel = "#ops" # optional override of the webhook's default

For the file variant:

[[notifier]]
id = "slack-ops"
type = "slack"
webhook_url_file = "/etc/runwisp/secrets/slack-ops.url"
channel = "#ops"

The id is what other parts of runwisp.toml refer to. Pick something readable — slack-ops, slack-deploys, slack-marketing are all reasonable. The channel field is optional; without it the webhook posts to whatever channel was selected when the webhook was created.

Two equivalent ways. Pick whichever reads better in your file.

[tasks.backup-postgres]
cron = "30 2 * * *"
notify_on_failure = ["slack-ops"]
run = "..."

That single line desugars to a synthetic route — see Per-task notification sugar. Behind the scenes:

# Synthesised by the loader, you don't write this:
[[notification_route]]
match = { kind = ["run.failed", "run.timeout", "run.crashed"], task = "backup-postgres" }
notify = ["slack-ops", "inapp"]

The implicit "inapp" notifier means the bell in the Web UI also lights up — a redundant safety net if Slack itself is down.

Explicit route (when you want one rule covering many tasks)

Section titled “Explicit route (when you want one rule covering many tasks)”
[[notification_route]]
match = { kind = ["run.failed", "run.timeout", "run.crashed"] }
notify = ["slack-ops"]

No match.task glob = match every task. Combine with task globs for finer control:

# Backup failures specifically also page the on-call channel
[[notification_route]]
match = { kind = ["run.failed", "run.timeout", "run.crashed"], task = "backup-*" }
notify = ["slack-ops", "tg-oncall"]

The router deduplicates — if a backup failure matches both a generic route and a backup-specific route, Slack receives one message, not two.

The fastest way to verify everything is wired up: trigger a task that’s expected to fail.

Terminal window
runwisp exec smoke-test # whatever you have that exits non-zero

Within a few seconds you should see:

  • A message in #ops with the task name, end reason, and a preview of the captured stderr.
  • An entry in the Web UI’s notification bell.

Both at once is the happy path. If only the bell lights up, the Slack delivery failed — open the bell and look for an event of kind notify.delivery_failed for the underlying error (expired webhook, rate limit, network).

The default Slack message embeds the task name, the end reason, the duration, and a link back to the run in the Web UI. To customise, point at a Go-template file:

[[notifier]]
id = "slack-ops"
type = "slack"
webhook_url_env = "RUNWISP_SLACK_OPS_URL"
template_path = "/etc/runwisp/templates/slack.tmpl"

The template receives the full event struct — task name, run id, exit code, end reason, captured tail. For most teams the default is enough; the override is there for branded incident messages or team-specific routing keys.

Caught at config load:

  • channel set to a value that doesn’t start with # (channel) or @ (user).
  • All of the common provider rules apply too.