Send Discord Notifications from GitHub Actions CI/CD
Learn how to send Discord webhook notifications from GitHub Actions workflows. Includes examples for build status, deployment alerts, and custom embeds.
Why Send Discord Notifications from GitHub Actions?
GitHub Actions is a powerful CI/CD platform, but its native notifications are limited to email and GitHub’s UI. Discord webhooks let you send real-time build status, deployment alerts, and test results directly to your team’s Discord server.
This guide shows you how to integrate Discord webhooks into your GitHub Actions workflows with practical examples you can copy and adapt.
Prerequisites
You need:
- A GitHub repository with Actions enabled
- A Discord webhook URL (Server Settings → Integrations → Webhooks → New Webhook)
- Basic familiarity with YAML workflow syntax
Method 1: Using curl (No Dependencies)
The simplest approach uses curl to send a POST request directly from your workflow:
name: Build and Notify
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build project
run: npm run build
- name: Notify Discord
if: always()
run: |
curl -H "Content-Type: application/json" \
-d '{"content": "Build completed for `${{ github.repository }}`"}' \
${{ secrets.DISCORD_WEBHOOK_URL }}
Important: Store your webhook URL as a GitHub secret (Settings → Secrets and variables → Actions → New repository secret). Never hardcode webhook URLs in your workflow files.
Method 2: Send Build Status with Embeds
Plain text notifications work, but embeds provide richer formatting with colors, fields, and status indicators:
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tests
id: tests
run: npm test
- name: Discord notification
if: always()
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL }}
run: |
if [ "${{ steps.tests.outcome }}" == "success" ]; then
COLOR=5763719
STATUS="Success"
EMOJI=":white_check_mark:"
else
COLOR=15548997
STATUS="Failed"
EMOJI=":x:"
fi
curl -H "Content-Type: application/json" \
-d "{
\"embeds\": [{
\"title\": \"$EMOJI CI Pipeline - $STATUS\",
\"description\": \"Tests completed for \`${{ github.repository }}\`\",
\"color\": $COLOR,
\"fields\": [
{\"name\": \"Branch\", \"value\": \"\`${{ github.ref_name }}\`\", \"inline\": true},
{\"name\": \"Commit\", \"value\": \"\`${{ github.sha }}\`\", \"inline\": true},
{\"name\": \"Author\", \"value\": \"${{ github.actor }}\", \"inline\": true}
],
\"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}]
}" \
$DISCORD_WEBHOOK
This workflow:
- Runs tests on every push and pull request
- Sends a green embed on success, red on failure
- Includes branch, commit SHA, and author information
- Links directly to the workflow run
Method 3: Using a Dedicated Action
For cleaner workflows, use a pre-built action like sarisia/actions-status-discord:
name: Deploy Production
on:
push:
tags:
- 'v*'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Deploy to production
run: |
echo "Deploying version ${{ github.ref_name }}..."
# Your deployment commands here
- name: Discord notification
if: always()
uses: sarisia/actions-status-discord@v1
with:
webhook: ${{ secrets.DISCORD_WEBHOOK_URL }}
status: ${{ job.status }}
title: "Production Deployment"
description: "Version `${{ github.ref_name }}` deployed"
color: 0x5865F2
username: "Deploy Bot"
This action automatically handles:
- Status detection (success/failure/cancelled)
- Color coding based on status
- Timestamp formatting
- Error handling
Method 4: Conditional Notifications (Failures Only)
To reduce noise, send notifications only when builds fail:
name: Continuous Integration
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Test
run: npm test
- name: Notify on failure
if: failure()
run: |
curl -H "Content-Type: application/json" \
-d "{
\"content\": \"@here Build failed!\",
\"embeds\": [{
\"title\": \":x: Build Failed\",
\"description\": \"The build for \`${{ github.repository }}\` has failed.\",
\"color\": 15548997,
\"fields\": [
{\"name\": \"Branch\", \"value\": \"\`${{ github.ref_name }}\`\", \"inline\": true},
{\"name\": \"Triggered by\", \"value\": \"${{ github.actor }}\", \"inline\": true}
],
\"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}]
}" \
${{ secrets.DISCORD_WEBHOOK_URL }}
The if: failure() condition ensures the notification step only runs when a previous step fails.
Method 5: Multi-Job Workflow with Summary
For complex workflows with multiple jobs, send a summary notification at the end:
name: Full Pipeline
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run lint
build:
runs-on: ubuntu-latest
needs: [test, lint]
steps:
- uses: actions/checkout@v4
- run: npm run build
notify:
runs-on: ubuntu-latest
needs: [test, lint, build]
if: always()
steps:
- name: Send summary
env:
TEST_STATUS: ${{ needs.test.result }}
LINT_STATUS: ${{ needs.lint.result }}
BUILD_STATUS: ${{ needs.build.result }}
run: |
curl -H "Content-Type: application/json" \
-d "{
\"embeds\": [{
\"title\": \"Pipeline Summary\",
\"description\": \"Full pipeline completed for \`${{ github.repository }}\`\",
\"color\": 5793266,
\"fields\": [
{\"name\": \"Tests\", \"value\": \"$TEST_STATUS\", \"inline\": true},
{\"name\": \"Linting\", \"value\": \"$LINT_STATUS\", \"inline\": true},
{\"name\": \"Build\", \"value\": \"$BUILD_STATUS\", \"inline\": true}
]
}]
}" \
${{ secrets.DISCORD_WEBHOOK_URL }}
This pattern:
- Runs test, lint, and build jobs in parallel (test and lint) or sequentially (build waits for both)
- Collects results from all jobs
- Sends a single summary notification with all job statuses
Best Practices
Use GitHub Secrets: Always store webhook URLs in repository secrets, never in workflow files.
Add if: always(): Without this condition, notification steps won’t run if previous steps fail.
Include Links: Add the workflow run URL so team members can quickly jump to logs.
Avoid Spam: Use if: failure() or if: github.ref == 'refs/heads/main' to limit notifications to important events.
Test Locally: Use the Discord Webhook Builder to design and test your embed layouts before adding them to workflows.
Troubleshooting
Webhook returns 400 Bad Request: Check your JSON syntax. Use a JSON validator or test the payload with curl locally first.
Notifications not appearing: Verify the webhook URL is correct and the webhook hasn’t been deleted in Discord.
Rate limits: Discord allows 30 requests per minute per webhook. If you hit this limit, add delays between notifications or batch multiple updates into a single message.
Next Steps
You now have multiple methods for sending Discord notifications from GitHub Actions. For more complex embed designs, try our free Discord Webhook Builder to visually create messages, then copy the JSON into your workflows.
discord-webhook.com also offers scheduled messages, thread and forum support, polls, and interactive buttons with actions — all configurable through the visual builder without writing code.
Pull Request Notifications
Beyond push-based builds, you can trigger Discord notifications on pull request events such as opened, synchronized, or merged. This is useful for keeping your team informed about code review activity without requiring everyone to watch GitHub constantly. The workflow below fires on PR events and sends an embed containing the PR title, author, and diff statistics, linking directly to the pull request for quick access.
name: PR Notification
on:
pull_request:
types: [opened, synchronize, closed]
jobs:
notify-pr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get diff stats
id: diff
run: |
STATS=$(git diff --shortstat origin/${{ github.base_ref }}...HEAD)
echo "stats=$STATS" >> $GITHUB_OUTPUT
- name: Notify Discord
run: |
if [ "${{ github.event.action }}" == "closed" ] && [ "${{ github.event.pull_request.merged }}" == "true" ]; then
COLOR=5763719
ACTION="Merged"
elif [ "${{ github.event.action }}" == "opened" ]; then
COLOR=5793266
ACTION="Opened"
else
COLOR=16776960
ACTION="Updated"
fi
curl -H "Content-Type: application/json" \
-d "{
\"embeds\": [{
\"title\": \"PR $ACTION: ${{ github.event.pull_request.title }}\",
\"description\": \"${{ steps.diff.outputs.stats }}\",
\"color\": $COLOR,
\"fields\": [
{\"name\": \"Author\", \"value\": \"${{ github.event.pull_request.user.login }}\", \"inline\": true},
{\"name\": \"Base\", \"value\": \"\`${{ github.base_ref }}\`\", \"inline\": true},
{\"name\": \"Head\", \"value\": \"\`${{ github.head_ref }}\`\", \"inline\": true}
],
\"url\": \"${{ github.event.pull_request.html_url }}\"
}]
}" \
${{ secrets.DISCORD_WEBHOOK_URL }}
The color-coded status changes based on the PR action — green for merged, yellow for updates, and blue for newly opened pull requests. This gives reviewers immediate visual feedback in the Discord channel.
Scheduled Reports
Cron-triggered workflows let you send periodic summaries to Discord without any manual intervention. This is ideal for daily standups, weekly activity digests, or repository health dashboards. Using GitHub’s schedule event, you can collect repository statistics via the GitHub API and format them into a structured embed that arrives at a consistent time each day or week.
name: Weekly Repo Summary
on:
schedule:
- cron: '0 9 * * 1' # Every Monday at 9:00 UTC
jobs:
weekly-report:
runs-on: ubuntu-latest
steps:
- name: Gather stats
id: stats
env:
GH_TOKEN: ${{ github.token }}
run: |
COMMITS=$(gh api repos/${{ github.repository }}/commits --jq 'length' -q 'since=7 days ago' 2>/dev/null || echo "0")
OPEN_ISSUES=$(gh api repos/${{ github.repository }}/issues --jq '[.[] | select(.pull_request == null)] | length')
OPEN_PRS=$(gh api repos/${{ github.repository }}/pulls --jq 'length')
echo "commits=$COMMITS" >> $GITHUB_OUTPUT
echo "issues=$OPEN_ISSUES" >> $GITHUB_OUTPUT
echo "prs=$OPEN_PRS" >> $GITHUB_OUTPUT
- name: Send report
run: |
curl -H "Content-Type: application/json" \
-d "{
\"embeds\": [{
\"title\": \"Weekly Repository Summary\",
\"description\": \"Activity report for \`${{ github.repository }}\`\",
\"color\": 5793266,
\"fields\": [
{\"name\": \"Recent Commits\", \"value\": \"${{ steps.stats.outputs.commits }}\", \"inline\": true},
{\"name\": \"Open Issues\", \"value\": \"${{ steps.stats.outputs.issues }}\", \"inline\": true},
{\"name\": \"Open PRs\", \"value\": \"${{ steps.stats.outputs.prs }}\", \"inline\": true}
],
\"footer\": {\"text\": \"Generated on $(date -u +%Y-%m-%d)\"}
}]
}" \
${{ secrets.DISCORD_WEBHOOK_URL }}
Use embed formatting to customize the report layout with additional fields like top contributors, merged PRs, or release notes. Scheduled reports reduce the need for manual check-ins and ensure your team stays aligned on project progress.
Matrix Builds with Notifications
When your CI pipeline tests across multiple operating systems, language versions, or configurations using a matrix strategy, you often want a single aggregated notification rather than one per combination. The pattern below uses a separate notify job that depends on the matrix job, collects results, and posts a consolidated summary to Discord.
name: Matrix CI
on:
push:
branches: [main]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm ci && npm test
notify:
runs-on: ubuntu-latest
needs: [test]
if: always()
steps:
- name: Aggregate and notify
run: |
RESULT="${{ needs.test.result }}"
if [ "$RESULT" == "success" ]; then
COLOR=5763719
MSG="All matrix combinations passed."
else
COLOR=15548997
MSG="One or more matrix combinations failed."
fi
curl -H "Content-Type: application/json" \
-d "{
\"embeds\": [{
\"title\": \"Matrix Build: $RESULT\",
\"description\": \"$MSG\",
\"color\": $COLOR,
\"fields\": [
{\"name\": \"OS Targets\", \"value\": \"ubuntu, windows, macos\", \"inline\": true},
{\"name\": \"Node Versions\", \"value\": \"18, 20, 22\", \"inline\": true},
{\"name\": \"Combinations\", \"value\": \"9 total\", \"inline\": true}
],
\"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}]
}" \
${{ secrets.DISCORD_WEBHOOK_URL }}
Using fail-fast: false ensures all combinations run even if one fails, giving you a complete picture. The notify job then reports the overall matrix result. Combine this with automation patterns to route failure notifications to specific channels based on which platform or version failed.
Security Considerations
Webhook URLs are effectively bearer tokens — anyone with the URL can post messages to your channel. Follow these practices to keep your pipeline secure:
Protect webhook secrets: Always store webhook URLs in GitHub repository or organization secrets. Never print them in logs. Use add-mask in workflow steps if you dynamically construct URLs to prevent accidental exposure in action output.
Minimize GITHUB_TOKEN permissions: When your notification step accesses the GitHub API (for example, to fetch PR details or repository stats), use the permissions key at the job level to restrict the token to read-only access on only the resources it needs.
Audit and rotate: Periodically review which workflows use your webhook secrets and rotate webhook URLs if team members leave or if you suspect a URL has been exposed. Discord lets you regenerate a webhook URL without deleting the webhook itself, preserving channel configuration.
Restrict workflow triggers: Use branch protection rules alongside on.push.branches filters to ensure only trusted code paths can trigger notification workflows, preventing forks or unauthorized branches from posting to your channels.
Related Articles
- Send Discord Webhooks Using curl (Command Line Guide) — Complete guide to sending Discord webhook messages with curl from the command line
- Discord Webhook Notifications and Automation - Complete Guide — Automate Discord notifications for server monitoring, CI/CD pipelines, and e-commerce alerts
- Send Discord Webhooks from JavaScript (Node.js & Browser) — Learn how to send webhook messages using JavaScript with Node.js and browser fetch API
- Discord Webhook Scheduled Messages — Automate recurring messages and timed notifications with scheduled webhooks
Try it in our tool
Open Discord Webhook Builder