Skip to content

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.

[[notifier]]
id = "slack-ops"
type = "slack"
webhook_url_env = "RUNWISP_SLACK_OPS_URL"
channel = "#ops" # optional override

id and type are required on every notifier. Exactly one of webhook_url, webhook_url_env, webhook_url_file must be set — the secret rule.

KeyRequiredWhat it does
webhook_urlone-of (see below)Inline webhook URL.
webhook_url_envone-ofName of an env var holding the URL.
webhook_url_fileone-ofPath to a file containing the URL. Relative paths resolve under the data dir.
channelnoOverride the webhook’s default. Must start with # (channel) or @ (user).
template_pathnoPath to a Go-template file overriding the embedded message format.

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

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.

Terminal window
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"

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.

There are two places this channel id can appear. Pick whichever reads better in your file.

[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.

Trigger a task you know will 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, 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.

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.

  • channel that doesn’t start with # (channel) or @ (user).
  • Two of webhook_url, webhook_url_env, webhook_url_file set at the same time, or none of them set.
  • An id containing : (reserved for inline target overrides) or equal to "inapp" (reserved).