Skip to content

Notification Sink Reference

Each notification sink reads its configuration from a Kubernetes Secret and delivers notifications to an external system. See the Notifications Guide for setup instructions and usage patterns.

Sink Types

Sink Type Destination Protocol
slack Slack HTTPS
telegram Telegram HTTPS
webhook Webhook HTTPS

Slack

Sink type: slack

Delivers messages via Slack Incoming Webhooks.

Formatting Message Text (Slack)

When format: json is configured, message content is rendered using Slack message formatting semantics (mrkdwn/plain_text). See Slack docs: Formatting message text.

Configuration

The configuration must be in a JSON object stored under config key in secret reference:

{
  "webhook_url": "<webhook_url>",
  "bot_token": "<bot_token>",
  "channel_id": "<channel_id>",
  "format": "<format>",
  "block_layout": "<block_layout>",
  "max_targets": "<max_targets>",
  "additional_scopes": "<additional_scopes>",
  "time_display": "<time_display>",
  "timezone": "<timezone>",
  "time_layout": "<time_layout>",
  "delivery_mode": "<delivery_mode>",
  "rate_limit": {
    "rate": "<rate>",
    "unit": "<unit>",
    "burst": "<burst>"
  }
}
Field Type Required Description
webhook_url string Yes WebhookURL is the Slack Incoming Webhook URL. Mutually exclusive with bot_token and channel_id, required when delivery_mode=channel.
bot_token string Yes BotToken is the Slack Bot token used for Web API delivery mode. Mutually exclusive with webhook_url, required when delivery_mode=thread.
channel_id string Yes ChannelID is the Slack channel ID used for Web API delivery mode. Mutually exclusive with webhook_url, required when delivery_mode=thread.
format string No Format controls Slack payload mode. Supported values: text (message text only), and json (Slack blocks payload, using preset layouts or custom templates).
block_layout string No BlockLayout selects the preset JSON layout used when format=json and no custom JSON template is provided (or parsing fails). Supported values: default, compact, auto. For ExecutionProgress, default and compact suppress non-terminal updates (Pending, Running) and only send terminal updates (Completed, Failed, Aborted). Use auto for full progress streaming
max_targets int No MaxTargets limits target lines in preset JSON layouts. It defaults to 8, which is enough to show all targets in most cases while keeping the message concise.
additional_scopes []string No AdditionalScopes appends additional scope fields to the scope context. Account and Cluster are always included by default. Supported: environment (alias: env), region, project, provider, connector, account, cluster.
time_display string No TimeDisplay controls how preset JSON layouts render context time. Supported values: - slack_dynamic (default): Slack date token rendered in each viewer's locale/timezone. - fixed: rendered with Timezone + TimeLayout. - utc: rendered in UTC with TimeLayout.
timezone string No Timezone is an IANA timezone name (for example, Asia/Jakarta) used only when TimeDisplay is fixed. Defaults to UTC in fixed mode.
time_layout string No TimeLayout is Go time layout used by fixed/utc displays. Defaults to Mon, 02 Jan 2006 15:04:05 MST.
delivery_mode string No DeliveryMode controls message grouping behavior. Supported values: - channel (default): each event posts as standalone channel message. - thread: root message is treated as a live status card and updated on every delivered event for the same plan/cycle, while event entries are posted as thread replies (including Start). Root status is monotonic per sink+plan+cycle+operation: once terminal (Success/Failure), late non-terminal events (ExecutionProgress, Recovery, PhaseChange, Start) do not downgrade root status back to in-progress, though they are still posted as thread replies. In thread mode, templateRef/custom templates are intentionally ignored: the sink always uses built-in, opinionated thread layouts so context and status progression remain consistent across root updates and replies. Recommendation: include ExecutionProgress in onEvents so root status moves continuously across execution; otherwise root updates only on subscribed events.
rate_limit object No RateLimit controls the rate limiting for this specific sink instance. Used to prevent burst traffic from overwhelming Slack's API limits (1 request per second per channel with burst tolerance). If not specified, uses default rate of 50 req/minute with burst of 75 (aligned with Web API Tier 3-4). Reference: https://docs.slack.dev/apis/web-api/rate-limits/
rate float64 No Rate is the sustained rate limit (e.g. 2.0 for 2 req/unit). Default: 50.0
unit string No Unit is the time unit for Rate: "second" or "minute". Default: "minute"
burst int No Burst is the maximum number of requests allowed in a burst. Default: 75

Default Template

{{ if eq .Event "Start" -}}
{{ if eq .Operation "shutdown" -}}
:arrow_forward: *Hibernation Starting* ({{ len .Targets }} targets)
{{ else -}}
:arrow_forward: *Wake-Up Starting* ({{ len .Targets }} targets)
{{ end -}}
{{ else if eq .Event "Success" -}}
{{ if eq .Operation "shutdown" -}}
:white_check_mark: *Hibernation Completed*
{{ else -}}
:white_check_mark: *Wake-Up Completed*
{{ end -}}
{{ else if eq .Event "Failure" -}}
{{ if eq .Operation "shutdown" -}}
:red_circle: *Hibernation Failed*
{{ else -}}
:red_circle: *Wake-Up Failed*
{{ end -}}
{{ else if eq .Event "Recovery" -}}
{{ if eq .Operation "shutdown" -}}
:recycle: *Hibernation Retrying* (attempt {{ .RetryCount }})
{{ else -}}
:recycle: *Wake-Up Retrying* (attempt {{ .RetryCount }})
{{ end -}}
{{ else if eq .Event "ExecutionProgress" -}}
{{ if .TargetExecution -}}
:gear: *Target Progress:* {{ .TargetExecution.Name }} ({{ .TargetExecution.Executor }}) → `{{ .TargetExecution.State }}`{{ if .TargetExecution.Message }} — {{ .TargetExecution.Message }}{{ end }}
{{ end -}}
{{ else -}}
:information_source: *Phase Change*
{{ end -}}
*Plan:* {{ .Plan.Name }}
*Namespace:* {{ .Plan.Namespace }}
*Phase:* {{ .Phase }}
*Operation:* {{ .Operation | default "N/A" }}
{{ if .PreviousPhase -}}
*Previous Phase:* {{ .PreviousPhase }}
{{ end -}}
{{ if .ErrorMessage -}}
*Error:* {{ .ErrorMessage }}
{{ end -}}
*Timestamp:* {{ .Timestamp | date "2006-01-02 15:04:05 MST" }}
{{ if .Targets -}}
*Targets:*
{{ range .Targets -}}
• {{ .Name }} ({{ .Executor }}): {{ .State }}{{ if .Connector.AccountID }} | Account: {{ .Connector.AccountID }}{{ end }}{{ if .Connector.ClusterName }} | Cluster: {{ .Connector.ClusterName }}{{ end }}{{ if .Connector.Region }} | Region: {{ .Connector.Region }}{{ end }}
{{ end -}}
{{ end }}

Telegram

Sink type: telegram

Delivers messages via the Telegram Bot API.

Escaping Reserved Characters (Telegram)

The Telegram Bot API requires certain characters to be escaped depending on the parse_mode. Two helper functions are available in all templates:

  • escapeHTML — use when parse_mode is HTML (the default). Escapes <, >, &, and ".
  • escapeMarkdown — use when parse_mode is MarkdownV2. Escapes _, *, [, ], (, ), ~, `, >, #, +, -, =, |, {, }, ., !.

Always pipe dynamic values through the appropriate escape function in custom Telegram templates, otherwise Telegram will reject the message.

Configuration

The configuration must be in a JSON object stored under config key in secret reference:

{
  "token": "<token>",
  "chat_id": "<chat_id>",
  "parse_mode": "<parse_mode>",
  "rate_limit": {
    "rate": "<rate>",
    "unit": "<unit>",
    "burst": "<burst>"
  }
}
Field Type Required Description
token string Yes Token is the Telegram Bot API token.
chat_id string Yes ChatID is the target chat ID (numeric ID or channel username like "@mychannel").
parse_mode *string No ParseMode is the message parse mode (MarkdownV2 or HTML), defaults to HTML if not specified.
rate_limit object No RateLimit controls the rate limiting for this specific sink instance. Used to prevent burst traffic from overwhelming Telegram's API limits. If not specified, uses default rate of 5 req/sec with burst of 10.
rate float64 No Rate is the sustained rate limit (e.g. 5.0 for 5 req/unit). Default: 5.0
unit string No Unit is the time unit for Rate: "second" or "minute". Default: "second"
burst int No Burst is the maximum number of requests allowed in a burst. Default: 10

Default Template

{{ if eq .Event "Start" -}}
{{ if eq .Operation "shutdown" -}}
▶️ <b>Hibernation Starting</b> ({{ len .Targets }} targets)
{{ else -}}
▶️ <b>Wake-Up Starting</b> ({{ len .Targets }} targets)
{{ end -}}
{{ else if eq .Event "Success" -}}
{{ if eq .Operation "shutdown" -}}
✅ <b>Hibernation Completed</b>
{{ else -}}
✅ <b>Wake-Up Completed</b>
{{ end -}}
{{ else if eq .Event "Failure" -}}
{{ if eq .Operation "shutdown" -}}
🔴 <b>Hibernation Failed</b>
{{ else -}}
🔴 <b>Wake-Up Failed</b>
{{ end -}}
{{ else if eq .Event "Recovery" -}}
{{ if eq .Operation "shutdown" -}}
♻️ <b>Hibernation Retrying</b> (attempt {{ .RetryCount }})
{{ else -}}
♻️ <b>Wake-Up Retrying</b> (attempt {{ .RetryCount }})
{{ end -}}
{{ else if eq .Event "ExecutionProgress" -}}
{{ if .TargetExecution -}}
⚙️ <b>Target Progress:</b> {{ .TargetExecution.Name | escapeHTML }} ({{ .TargetExecution.Executor | escapeHTML }}) → <code>{{ .TargetExecution.State | escapeHTML }}</code>{{ if .TargetExecution.Message }} — {{ .TargetExecution.Message | escapeHTML }}{{ end }}
{{ end -}}
{{ else -}}
ℹ️ <b>Phase Change</b>
{{ end -}}
<b>Plan:</b> {{ .Plan.Name | escapeHTML }}
<b>Namespace:</b> {{ .Plan.Namespace | escapeHTML }}
<b>Phase:</b> {{ .Phase | escapeHTML }}
<b>Operation:</b> {{ .Operation | default "N/A" | escapeHTML }}
{{ if .PreviousPhase -}}
<b>Previous Phase:</b> {{ .PreviousPhase | escapeHTML }}
{{ end -}}
{{ if .ErrorMessage -}}
<b>Error:</b> {{ .ErrorMessage | escapeHTML }}
{{ end -}}
<b>Timestamp:</b> {{ .Timestamp | date "2006-01-02 15:04:05 MST" | escapeHTML }}
{{ if .Targets -}}
<b>Targets:</b>
{{ range .Targets -}}
• {{ .Name | escapeHTML }} ({{ .Executor | escapeHTML }}): {{ .State | escapeHTML }}{{ if .Connector.AccountID }} | Account: {{ .Connector.AccountID | escapeHTML }}{{ end }}{{ if .Connector.ClusterName }} | Cluster: {{ .Connector.ClusterName | escapeHTML }}{{ end }}{{ if .Connector.Region }} | Region: {{ .Connector.Region | escapeHTML }}{{ end }}
{{ end -}}
{{ end }}

Webhook

Sink type: webhook

Delivers notifications as a JSON POST request to any HTTP endpoint. Useful for custom alerting pipelines, incident management tools, or internal APIs.

Custom Templates with Webhooks

If enable_renderer is false (the default), templateRef has no effect. The receiver gets the raw structured payload and can format it however it likes. Set enable_renderer: true when you want the controller to pre-render a human-readable message.

Configuration

The configuration must be in a JSON object stored under config key in secret reference:

{
  "url": "<url>",
  "headers": {},
  "enable_renderer": false
}
Field Type Required Description
url string Yes URL is the endpoint to POST notifications to.
headers map[string]string No Headers are additional HTTP headers to include in the request.
enable_renderer bool No EnableRenderer when true, renders the payload through the template engine and includes the result in the "rendered" field of the JSON body.

Default Template

[{{ .Event }}] {{ .Operation }} — {{ .Plan.Namespace }}/{{ .Plan.Name }} | Phase: {{ .Phase }}{{ if .TargetExecution }} | Target: {{ .TargetExecution.Name }} ({{ .TargetExecution.Executor }}) → {{ .TargetExecution.State }}{{ end }}{{ if .ErrorMessage }} | Error: {{ .ErrorMessage }}{{ end }}

Payload

{
  "context": {
    "plan": {
      "name": "<name>",
      "namespace": "<namespace>",
      "labels": {},
      "annotations": {}
    },
    "event": "<event>",
    "timestamp": "<timestamp>",
    "phase": "<phase>",
    "previousPhase": "<previousPhase>",
    "operation": "<operation>",
    "cycleId": "<cycleId>",
    "targets": [
      {
        "name": "<name>",
        "executor": "<executor>",
        "state": "<state>",
        "message": "<message>",
        "connector": {
          "kind": "<kind>",
          "name": "<name>",
          "provider": "<provider>",
          "accountId": "<accountId>",
          "projectId": "<projectId>",
          "region": "<region>",
          "clusterName": "<clusterName>"
        }
      }
    ],
    "targetExecution": {
      "name": "<name>",
      "executor": "<executor>",
      "state": "<state>",
      "message": "<message>",
      "connector": {
        "kind": "<kind>",
        "name": "<name>",
        "provider": "<provider>",
        "accountId": "<accountId>",
        "projectId": "<projectId>",
        "region": "<region>",
        "clusterName": "<clusterName>"
      }
    },
    "errorMessage": "<errorMessage>",
    "retryCount": 0,
    "sinkName": "<sinkName>",
    "sinkType": "<sinkType>"
  },
  "rendered": "<rendered>"
}
Field Type Description
context object Context carries the structured notification event data as a webhook-specific DTO.
context.plan object Plan carries plan metadata.
context.plan.name string Name is the plan name.
context.plan.namespace string Namespace is the Kubernetes namespace.
context.plan.labels map[string]string Labels are the plan labels.
context.plan.annotations map[string]string Annotations are the plan annotations.
context.event string Event is the hook point that triggered this notification (e.g., "Start", "Failure").
context.timestamp string Timestamp is when the event occurred.
context.phase string Phase is the plan phase after the transition.
context.previousPhase string PreviousPhase is the plan phase before the transition (empty on Start).
context.operation string Operation is the current operation: "Hibernate" or "WakeUp".
context.cycleId string CycleID is the current execution cycle identifier.
context.targets object[] Targets holds per-target execution state (available on Success/Failure).
context.targets[].name string Name is the target name.
context.targets[].executor string Executor is the executor type (e.g., "rds", "eks").
context.targets[].state string State is the execution state (e.g., "Completed", "Failed").
context.targets[].message string Message provides details for the target's execution state.
context.targets[].connector object Connector carries resolved connector metadata.
context.targets[].connector.kind string Kind is the connector type: "CloudProvider" or "K8SCluster".
context.targets[].connector.name string Name is the connector resource name.
context.targets[].connector.provider string Provider is the cloud provider type (e.g., "aws", "gcp").
context.targets[].connector.accountId string AccountID is the cloud account identifier, it is relevant for AWS cloud provider.
context.targets[].connector.projectId string ProjectID is the cloud project identifier, it is relevant for GCP cloud provider.
context.targets[].connector.region string Region is the cloud region.
context.targets[].connector.clusterName string ClusterName is the Kubernetes cluster name.
context.targetExecution object TargetExecution holds the individual target whose execution state just changed. Populated only for ExecutionProgress events.
context.targetExecution.name string Name is the target name.
context.targetExecution.executor string Executor is the executor type (e.g., "rds", "eks").
context.targetExecution.state string State is the execution state (e.g., "Completed", "Failed").
context.targetExecution.message string Message provides details for the target's execution state.
context.targetExecution.connector object Connector carries resolved connector metadata.
context.targetExecution.connector.kind string Kind is the connector type: "CloudProvider" or "K8SCluster".
context.targetExecution.connector.name string Name is the connector resource name.
context.targetExecution.connector.provider string Provider is the cloud provider type (e.g., "aws", "gcp").
context.targetExecution.connector.accountId string AccountID is the cloud account identifier, it is relevant for AWS cloud provider.
context.targetExecution.connector.projectId string ProjectID is the cloud project identifier, it is relevant for GCP cloud provider.
context.targetExecution.connector.region string Region is the cloud region.
context.targetExecution.connector.clusterName string ClusterName is the Kubernetes cluster name.
context.errorMessage string ErrorMessage provides error details (Failure/Recovery only).
context.retryCount int32 RetryCount is the current retry attempt number (Recovery/Failure only).
context.sinkName string SinkName is the human-readable name of the sink being dispatched to.
context.sinkType string SinkType is the sink provider type (e.g., "slack", "telegram", "webhook").
rendered string Rendered is the template-rendered message string. Omitted when enable_renderer is false or unset.