slackapi/slack-github-action v4: Send a Slack Message From GitHub Actions
slackapi/slack-github-action v4.0.0 posts to Slack from a workflow with a bot token or an incoming webhook. YAML we ran against a real workspace, thread replies, chat.update, and the errors: true setting that stops a failed post from passing silently.
On this page
slackapi/slack-github-action is Slack's official GitHub Action for posting to Slack from a workflow. The latest release is v4.0.0 (15 July 2026, commit dcb1066). Add it as a step, give it either a bot token with method: chat.postMessage or an incoming webhook URL, both stored as repository secrets, and write the message in the payload input as YAML. Add if: failure() to post only when a build breaks, and errors: true so a Slack error fails the step: on 6 October we ran v4.0.0 against a wrong channel ID and, with the default errors: false, the step still exited 0.
How bot tokens differ from user tokens is in Slack bot token.
Four ways to post to Slack from GitHub Actions
| Way | Secret you store | Best for |
|---|---|---|
| slack-github-action with an API method | SLACK_BOT_TOKEN and a channel ID | Threads, message updates, files, any channel the bot is in |
| slack-github-action with an incoming webhook | SLACK_WEBHOOK_URL | One fixed channel, quickest setup |
curl and jq in a run step | SLACK_WEBHOOK_URL | Repos that allow no third-party actions |
| The GitHub app for Slack | None in the repo | Workflow run alerts with no YAML changes |
The action is maintained by Slack. To pin it to an exact build, as many security policies require, use the commit instead of the tag: uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0. Version 4.0.0 came out on 15 July 2026; its one breaking change is stricter YAML in payload, where a value that spans several lines must be indented under its key. Version 3 moved the action to the Node.js 24 runtime, so old self-hosted runners may need an update.
Option 1: bot token and chat.postMessage
This way can post to any channel the bot is in, reply in threads and edit its own messages.
- Create a Slack app at api.slack.com/apps, or use an existing one.
- Under OAuth & Permissions, add the bot scope
chat:write. - Click Install to Workspace and copy the Bot User OAuth Token (it starts with
xoxb-). - In GitHub, open Settings > Secrets and variables > Actions and add
SLACK_BOT_TOKEN. Add the channel ID asSLACK_CHANNEL_ID; you find it at the bottom of the channel's details panel. - In Slack, run
/invite @your-appin that channel. Without it, the call fails withnot_in_channel.
Then add the step. This workflow runs tests on every push to main and posts only if they fail:
name: CI
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- run: npm ci && npm test
- name: Tell Slack the build failed
if: failure()
uses: slackapi/slack-github-action@v4.0.0
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
errors: true
payload: |
channel: ${{ secrets.SLACK_CHANNEL_ID }}
text: ":x: ${{ github.workflow }} failed on ${{ github.ref_name }}: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
We parsed this file and its payload block with a YAML parser to check the indentation that v4 requires.
What happened when we ran v4.0.0 against Slack
On 6 October 2026 we checked out the v4.0.0 tag and ran the action's own dist/index.js with Node.js, passing the same inputs the runner passes, against our test workspace with a throwaway bot token:
| Step we ran | Result |
|---|---|
chat.postMessage with the payload above | Posted. Outputs ok=true, ts, channel_id and time |
A second step with thread_ts: set to the first ts | Posted as a thread reply |
chat.update with ts: set to the first ts | Edited the message in place |
Wrong channel ID, default errors: false | Exit code 0, output ok=false; channel_not_found appeared only in the debug log |
Wrong channel ID, errors: true | Exit code 1, An API error occurred: channel_not_found |
A payload line at the wrong indent | Exit code 1, Invalid input! Failed to parse contents of the provided payload |
The bot had the chat:write.public scope as well as chat:write, so it posted to a public channel it had never joined. With only chat:write, invite it first. We deleted the messages and the test app afterwards.
Use if: always() to post on every run, and ${{ job.status }} in the text to say success or failure.
Option 2: incoming webhook
An incoming webhook posts to one channel, chosen when you create it. No channel ID or invite is needed.
https://hooks.slack.com/services/T.../B.../..., into the repository secret SLACK_WEBHOOK_URL. - name: Post the build result to Slack
if: always()
uses: slackapi/slack-github-action@v4.0.0
with:
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
webhook-type: incoming-webhook
payload: |
text: "*${{ github.workflow }}*: ${{ job.status }}\n${{ github.event.head_commit.url }}"
A webhook cannot reply in threads or edit its message. For those, use Option 1. A third setting, webhook-type: webhook-trigger, starts a Slack Workflow Builder workflow instead of posting a message; ideas for those workflows are in Slack Workflow Builder examples.
Step-by-step setup of the webhook URL itself, including private channels and DMs, is in how to create a Slack webhook.
Option 3: curl, with no third-party action
Some organizations allow only GitHub's own actions. A run step with jq and curl, both installed on GitHub's Ubuntu runners, posts to an incoming webhook (the same call outside CI, in Python and Node too, is in Slack webhook curl example):
- name: Post to Slack with curl
if: failure()
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
STATUS: ${{ job.status }}
run: |
jq -n --arg text "$GITHUB_WORKFLOW on $GITHUB_REF_NAME: $STATUS $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" '{text: $text}' |
curl -sS --fail -X POST -H 'Content-type: application/json' --data @- "$SLACK_WEBHOOK_URL"
jq builds the JSON, so quotes in a branch name or commit message do not break it. --fail makes the step fail if Slack rejects the post. We ran the command outside GitHub with the same variables: jq printed {"text": "CI on main: failure https://github.com/acme/app/actions/runs/123"}, and a made-up webhook URL made curl exit with an error on Slack's 404, as it should.
Never appear "away" on Slack again
Cloud-based. No downloads. Works 24/7 even when your laptop is off.
Threads, updates and files
With a bot token, each step returns the message's ts as an output, so later steps can reply in a thread or edit it. Give the first step an id:
- name: Announce the deploy
id: deploy_message
uses: slackapi/slack-github-action@v4.0.0
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: |
channel: ${{ secrets.SLACK_CHANNEL_ID }}
text: "Deploy of ${{ github.sha }} started :eyes:"
- run: ./deploy.sh
- name: Reply in the thread
uses: slackapi/slack-github-action@v4.0.0
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: |
channel: ${{ secrets.SLACK_CHANNEL_ID }}
thread_ts: "${{ steps.deploy_message.outputs.ts }}"
text: "Deploy finished :rocket:"
Swap the second step's method for chat.update and use ts: instead of thread_ts: to edit the first message in place. method: files.uploadV2 with channel_id, file and filename uploads a test report; it needs the files:write scope and uses the 3-step upload that replaced files.upload.
Option 4: the GitHub app for Slack
If you only want to know when workflows finish, the GitHub app for Slack needs no YAML. Install it from the Slack Marketplace, then in a channel run:
/github subscribe owner/repo workflows:{event:"push" branch:"main"}
It posts each workflow run and updates the message as jobs finish. It cannot send custom text; for that, use one of the options above.
Common errors
| Error | Cause | Fix |
|---|---|---|
not_in_channel | The bot is not in the channel | /invite @your-app |
channel_not_found | Wrong channel ID, or a private channel the bot cannot see | Copy the ID from channel details, invite the bot |
missing_scope | The token lacks chat:write | Add the scope and reinstall the app |
invalid_auth | Wrong or revoked token | Copy the xoxb- token again into the secret |
YAML error in payload | Multi-line value not indented (v4 rule) | Indent continuation lines under the key |
| Nothing posts on failure | The step has no if: | Add if: failure() or if: always() |
| Step is green but nothing posted | errors defaults to false, so Slack errors do not fail the step | Add errors: true |
Secrets are not passed to workflows triggered by pull requests from forks, so the step fails there by design. Run it only on push or on pull requests from the same repository.
Long CI logs hit Slack text limits; the numbers and a split function are in Slack message character limit.
FAQ
What is the latest version of slack-github-action?
v4.0.0, released 15 July 2026, commit dcb1066f776dd043e64d0e8ba94ca15cc7e1875d. It requires multi-line payload values to be indented.
How do I send a Slack notification only when a GitHub workflow fails?
Add if: failure() to the Slack step. Use if: always() to post on every run.
Why is my Slack step green when nothing was posted?
The errors input defaults to false, so a Slack error such as channel_not_found does not fail the step. Set errors: true.
Can I send a Slack message from GitHub Actions without a third-party action? Yes. Use curl with a Slack incoming webhook URL in a run step, with jq to build the JSON.
Stop Jiggling Your Mouse.
Join hundreds of remote workers who never worry about their Slack status. Set it up once, stay green forever.
Related Articles
Slack Bot Scopes: The Scope Each API Method Needs, Measured on 44 Methods
We gave a Slack bot one scope, called 44 common Web API methods, and read the needed field of every missing_scope error. Then we added the scopes and called them again. The table shows which scope clears each method.
Slack File Size Limit: 1 GB Is Exactly 1,073,741,824 Bytes, Tested
We uploaded files of 50 MB, exactly 1 GiB and 1 GiB plus one byte to Slack through its upload API. The limit is 1,073,741,824 bytes, and a failed upload can still leave a broken file behind.
Slack Dark Mode: Where the Setting Is, and Which Custom Emoji Disappear
Slack's dark mode is under Preferences > Appearance > Color Mode, with Light, Dark and System. We switched modes in Slack web with three test emoji and measured which ones vanish.