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 whenparse_modeisHTML(the default). Escapes<,>,&, and".escapeMarkdown— use whenparse_modeisMarkdownV2. 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:
| 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. |