Skip to content

Webhook

The webhook driver POSTs a JSON body to any HTTP endpoint you control. It’s the catch-all for services that accept incoming webhooks but don’t have a dedicated provider — PagerDuty, n8n, a custom server, anything that speaks HTTP. (For Discord, use the native provider instead — it sends embeds Discord actually renders.)

[[notifier]]
id = "my-hook"
type = "webhook"
url = "${WEBHOOK_URL}"
[notifier.headers]
Authorization = "Bearer ${WEBHOOK_TOKEN}"

id, type, and url are required.

KeyRequiredWhat it does
urlyesThe endpoint URL — inline, ${VAR}, or ${file:path}.
headersnoExtra HTTP headers sent with every request (e.g. auth tokens).
template_pathnoPath to a Go-template file overriding the embedded JSON payload.

Use ${...} substitution in both url and header values to pull secrets from env vars or files — storing the secret.

Pick where the URL lives and reference it with ${...} substitution.

The simplest option. Set the variable in whatever already manages your environment.

Terminal window
export WEBHOOK_URL=https://example.com/hooks/abc123
export WEBHOOK_TOKEN=my-secret-token
runwisp daemon
[[notifier]]
id = "my-hook"
type = "webhook"
url = "${WEBHOOK_URL}"
[notifier.headers]
Authorization = "Bearer ${WEBHOOK_TOKEN}"
[tasks.backup-postgres]
cron = "30 2 * * *"
notify_on_failure = ["my-hook"]
run = "..."
[[notification_route]]
match = { kind = ["run.failed", "run.timeout", "run.crashed"] }
notify = ["my-hook"]

See Per-task notifications and Notification rules for the full options.

Trigger a task you know will fail:

Terminal window
runwisp exec smoke-test

Your endpoint should receive a POST with Content-Type: application/json within a few seconds. If only the in-app bell shows, delivery failed — look for a notify.delivery_failed event in the bell for the reason.

The default template produces a flat, machine-readable JSON object. Every field uses a stable key that’s safe to parse downstream.

{
"kind": "run.failed",
"severity": "error",
"timestamp": "2025-01-15T02:30:45Z",
"task": "backup-postgres",
"title": "backup-postgres failed",
"message": "Exited with code 1 after 3m 4s.",
"trigger": "Scheduled run",
"run": {
"id": "01JXYZ...",
"exit_code": 1,
"triggered_by": "cron",
"url": "https://runwisp.example.com/tasks/backup-postgres/01JXYZ...",
"duration": "3m 4s"
},
"output_tail": "Error: connection refused\ndial tcp ...",
"reason": "exit 1",
"source": "runwisp (bright-falcon)"
}

run, output_tail, and reason are conditional — they appear only when applicable. run.url and run.duration are also conditional within the run object.

Point template_path at a Go-template file. Copy webhook.tmpl.json as your starting point. The template receives the full event struct and the same helpers available to other providers: statusEmoji, statusVerb, eventSentence, eventTrigger, runURL, outputTail, fingerprint, and more.

  • A missing or empty url.
  • A url that isn’t http:// or https://.
  • An empty key in headers.
  • An id containing : (reserved for inline target overrides) or equal to "inapp" (reserved).

Webhook notifiers do not support inline target overrides (like my-hook:something) — there’s no natural “target” to override.