Back to Blog
Guide

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.

Slack Green Team
September 25, 2026
October 6, 2026
6 min read
Share:
github actions
slack api
slack bot
ci notifications

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

Four ways to post to Slack from GitHub Actions: API method with a bot token, incoming webhook, curl and jq, the GitHub app for Slack
WaySecret you storeBest for
slack-github-action with an API methodSLACK_BOT_TOKEN and a channel IDThreads, message updates, files, any channel the bot is in
slack-github-action with an incoming webhookSLACK_WEBHOOK_URLOne fixed channel, quickest setup
curl and jq in a run stepSLACK_WEBHOOK_URLRepos that allow no third-party actions
The GitHub app for SlackNone in the repoWorkflow 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 as SLACK_CHANNEL_ID; you find it at the bottom of the channel's details panel.
  • In Slack, run /invite @your-app in that channel. Without it, the call fails with not_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 ranResult
    chat.postMessage with the payload abovePosted. Outputs ok=true, ts, channel_id and time
    A second step with thread_ts: set to the first tsPosted as a thread reply
    chat.update with ts: set to the first tsEdited the message in place
    Wrong channel ID, default errors: falseExit code 0, output ok=false; channel_not_found appeared only in the debug log
    Wrong channel ID, errors: trueExit code 1, An API error occurred: channel_not_found
    A payload line at the wrong indentExit code 1, Invalid input! Failed to parse contents of the provided payload
    A Slack message posted by slack-github-action v4.0.0, then edited with chat.update to read CI passed on main after a re-run, with one thread reply

    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.

  • In your Slack app, turn on Incoming Webhooks, click Add New Webhook to Workspace, and pick the channel.
  • Copy the URL, which looks like 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

How a GitHub Actions step reaches Slack: workflow step, repository secret, Slack API, channel

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

ErrorCauseFix
not_in_channelThe bot is not in the channel/invite @your-app
channel_not_foundWrong channel ID, or a private channel the bot cannot seeCopy the ID from channel details, invite the bot
missing_scopeThe token lacks chat:writeAdd the scope and reinstall the app
invalid_authWrong or revoked tokenCopy the xoxb- token again into the secret
YAML error in payloadMulti-line value not indented (v4 rule)Indent continuation lines under the key
Nothing posts on failureThe step has no if:Add if: failure() or if: always()
Step is green but nothing postederrors defaults to false, so Slack errors do not fail the stepAdd 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.

Always Active

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

Guide

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 Green Team
Guide

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 Green Team
Guide

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.

Slack Green Team