Guide · Verified October 31, 2026
Slack target guide
Short answer: A Hooks Slack destination posts either to an incoming webhook URL already bound to a channel, or to chat.postMessage with a bot token and channel id so one app can reach many channels.
Slack reports most failures as HTTP 200 with `"ok": false`, so the destination reads the body and not just the status. Templates render `text` and, in chat mode, a Block Kit `blocks` array; both use the same event fields as every other target.
1. Choose a mode and prepare Slack
Incoming webhook: create a Slack app, enable Incoming Webhooks, Add New Webhook to Workspace, and copy the URL bound to one channel. chat.postMessage: add the chat:write scope, install the app, copy the bot token (xoxb-...), invite the bot to the channel, and use the channel id (C0123456789) so renames do not break delivery.
2. Store the credential as a project secret
Settings → Security → Project secrets → add SLACK_WEBHOOK_URL (the full webhook URL) or SLACK_BOT_TOKEN. Configs accept only {{secret.NAME}} references; literal values are rejected, and the Authorization header is always recorded as [redacted].
Destination config (incoming webhook)
{
"mode": "webhook",
"webhookUrl": "{{secret.SLACK_WEBHOOK_URL}}",
"text": "New {{event.type}}: {{event.subject.title}}\n{{event.subject.url}}"
}3. Write text and Block Kit templates
`text` defaults to the event title and URL; in chat mode it doubles as the notification fallback when `blocks` is set. `blocks` renders a JSON array of Block Kit blocks — invalid JSON, or JSON that is not an array, fails the delivery with INVALID_SLACK_BLOCKS and nothing is sent. A channel override exists only in chat mode; incoming webhooks ignore it.
Destination config (chat.postMessage)
{
"mode": "chat",
"token": "{{secret.SLACK_BOT_TOKEN}}",
"channel": "C0123456789",
"text": "New {{event.type}}: {{event.subject.title}}",
"blocks": "[{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"*{{event.subject.title}}*\\n{{event.subject.url}}\"}}]"
}4. Read the response correctly
`{"ok": true}` and the plain `ok` body of incoming webhooks succeed. `{"ok": false, "error": "channel_not_found"}` fails permanently with SLACK_ERROR naming the Slack error, while `ratelimited` retries as SLACK_RATE_LIMITED. HTTP 429 honors Retry-After; 408, 425, and 5xx retry; 3xx is blocked as REDIRECT_BLOCKED. On success the attempt keeps Slack's response body, including `ts`.
Frequently asked questions
Incoming webhook or chat.postMessage?
Incoming webhooks are one channel per URL and need no bot setup. chat.postMessage needs a bot token and chat:write but lets one app post to any channel it was invited to, and supports Block Kit with a fallback text.
Why did a Slack delivery fail with HTTP 200?
Slack's Web API reports errors in the body: {"ok": false, "error": "..."}. Hooks checks the body, so a 200 with ok:false is recorded as failed (SLACK_ERROR) or retried when Slack says ratelimited.
What does INVALID_SLACK_BLOCKS mean?
The rendered `blocks` field was not a valid JSON array. Render only JSON from the template, escape quotes inside strings, and keep a plain `text` fallback.
Can one destination post to several channels?
Not directly — a destination has one config. Use chat mode and one destination per channel, or route to a lower-level HTTP target that posts to chat.postMessage itself.
Reference
- chat.postMessagehttps://docs.slack.dev/reference/methods/chat.postMessage
- Incoming webhookshttps://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/
- Block Kithttps://docs.slack.dev/block-kit/
- Payload templateshttps://hooks.events/docs/templates.md