Channels
Slack notifications
Post Ray notifications to a Slack channel through an incoming webhook. Slack app setup, config, mrkdwn content, pacing, escaping and errors.
The slack_webhook channel posts messages to a Slack channel through a Slack incoming webhook URL. The webhook URL decides the channel, so there is no per-message recipient. API calls go to https://ray-api.gege.mn with Authorization: Bearer $RAY_API_KEY.
| Channel type | slack_webhook |
| Template kind | slack_text |
| Recipient | {} |
| Pacing | 1 message per second per webhook URL |
| Suppression | None |
What you need from Slack
- Go to https://api.slack.com/apps and click Create New App, From scratch. Name it (for example
Ray alerts) and pick your workspace. - In the app settings open Incoming Webhooks and turn Activate Incoming Webhooks on.
- Click Add New Webhook to Workspace, choose the channel to post to, and allow.
- Copy the webhook URL. It looks like
https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX.
Each webhook posts to one channel. To post to several channels, create one webhook (and one Ray channel config) per channel. Treat the URL as a secret: anyone with it can post to the channel.
Channel config
Create the config in the dashboard: Channels, New channel, Slack webhook. The URL is stored encrypted; after saving, the dashboard only shows its host.
| Field | Required | Notes |
|---|---|---|
| Name | yes | Internal label, for example eng-alerts. |
Webhook URL (webhookUrl) |
yes | Must be on hooks.slack.com. Any other host is refused when sending (the delivery fails as failed_terminal). When editing, leave blank to keep the stored URL. |
Find the config's id with GET /channels; its kind is slack_webhook.
Template content
| Field | Type | Notes |
|---|---|---|
text |
string, 1 to 40,000 | Slack mrkdwn: *bold*, _italic_, ~strike~, <https://example.com|link text>, line breaks with \n. |
Escaping: in param values Ray replaces &, < and > with &, < and >. A value can't inject a link (<https://...|...>) or a mention (<!channel>, <@U123>), but *, _ and ~ in a value still format, because Slack has no escape for them. Put links and mentions in the template text, not in params.
Recipient
Always an empty object:
{}
Unknown keys are ignored.
Send a message
curl -X POST https://ray-api.gege.mn/send \
-H "Authorization: Bearer $RAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channelConfigId": "7c8d9e0f-1a2b-4c3d-9e4f-5a6b7c8d9e0f",
"content": {
"text": "*{{service}}* error rate is {{rate}}%\n<https://status.example.com|Open status page>"
},
"params": { "service": "billing-api", "rate": 4.2 },
"recipient": {}
}'
async function slackAlert(service: string, rate: number) {
const res = await fetch("https://ray-api.gege.mn/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RAY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
channelConfigId: "7c8d9e0f-1a2b-4c3d-9e4f-5a6b7c8d9e0f",
templateId: "1d2e3f4a-5b6c-4d7e-8f9a-0b1c2d3e4f5a", // a slack_text template
params: { service, rate },
recipient: {},
}),
});
if (res.status !== 202) throw new Error(`Ray ${res.status}: ${await res.text()}`);
}
A slack_text template for the same message:
{
"name": "Error rate alert",
"channelKind": "slack_text",
"content": { "text": "*{{service}}* error rate is {{rate}}%\n<https://status.example.com|Open status page>" },
"logTitle": "Error rate alert",
"logDescription": "{{service}} error rate {{rate}}%",
"publish": true
}
Slack returns no message id, so the delivery row's providerMessageId is slack-<rowId>. delivered means Slack answered 200 ok.
Pacing
Slack allows about one message per second per incoming webhook, so Ray sends at most one message per second per webhook URL. Bursts queue up and drain at that rate; two Ray configs with the same URL share the pace. Use targets[] only if you really mean to post the same message several times; for alerting at volume, aggregate messages first.
Errors
Errors appear on the delivery row as providerError: { "name": "SlackWebhookError", "message": "Slack webhook failed: <status> <statusText> <body>" }.
| Result | Cases |
|---|---|
failed_terminal (not retried) |
Any HTTP 4xx except 429, such as invalid_payload, no_service (webhook revoked or app removed), channel_not_found, channel_is_archived, action_prohibited; a webhook URL that isn't on hooks.slack.com or isn't a valid URL |
failed_retryable (retried) |
HTTP 429 (rate limited), HTTP 5xx, network errors and timeouts |
Common fixes:
no_serviceor404: the webhook was revoked or the app uninstalled. Create a new webhook and update the config.channel_is_archived: unarchive the channel or create a webhook for another channel.refusing to POST to non-Slack host: the configured URL isn't ahooks.slack.comwebhook.
Request errors from POST /send are listed in Errors.