Connect GitHub with Slack
Implementation Guide
Overview: Connecting GitHub and Slack
For engineering teams operating at scale, the gap between code activity and team awareness is one of the most persistent sources of miscommunication in modern software development. GitHub is where the actual work happens — commits land, pull requests are reviewed, issues are triaged, and CI/CD pipelines execute. Slack is where the conversations about that work happen. When these two systems operate in isolation, developers are forced into a context-switching loop: they must manually relay status updates, ping reviewers, or post deployment notices, all of which are interruptions that compound across a 20- or 200-person engineering organisation.
Connecting GitHub to Slack via a programmatic integration eliminates this gap. The result is a living, real-time communication layer that reflects the exact state of your repositories directly inside the channels and threads where your teams already collaborate. This guide covers the full implementation of that connection, from OAuth scopes and webhook architecture through to advanced filtering logic and production-grade troubleshooting.
Core Prerequisites
Before beginning, ensure all of the following conditions are met. Missing any one of them is the most common reason integrations fail silently during initial setup.
GitHub Requirements:
- A GitHub account with Owner or Admin role on the target organisation or repository. Repository-level webhooks can be created by users with Admin access to that specific repo; organisation-level webhooks require Org Owner privileges.
- If using the GitHub REST API directly, you will need a Personal Access Token (PAT) — either classic or fine-grained — with the following scopes:
repo(full control of private repositories),admin:repo_hook(for managing repository webhooks), andadmin:org_hookif configuring organisation-level webhooks. - If using a GitHub App (the recommended approach for production), the App must be granted the following permissions: Repository permissions: Contents (Read), Pull requests (Read & Write), Issues (Read & Write), Checks (Read); Organisation permissions: Members (Read); Subscribe to events: push, pull_request, issues, check_run, check_suite.
- Two-factor authentication (2FA) must be enabled on the GitHub account used for API authentication — this is enforced by most enterprise GitHub organisations.
Slack Requirements:
- A Slack workspace where you hold the Workspace Admin or Owner role, or where the workspace admin has pre-authorised third-party app installations.
- A Slack App created via the Slack API portal with the following Bot Token Scopes under OAuth & Permissions:
chat:write,chat:write.public(to post in channels the bot hasn't joined),channels:read,groups:read(for private channels),im:write(for direct messages), andusers:read(to resolve GitHub usernames to Slack member IDs for @-mentions). - The Slack App must be installed into the workspace and the resulting Bot User OAuth Token (format:
xoxb-...) must be securely stored. Never commit this token to a repository. - Incoming Webhooks must be enabled on the Slack App if you are using a simplified webhook-only architecture (no bot token required in that model, but functionality is limited to a single channel).
Infrastructure Requirements:
- A publicly accessible HTTPS endpoint capable of receiving GitHub webhook POST requests. This can be an AWS Lambda function behind API Gateway, a Cloudflare Worker, a Render.com web service, or a dedicated integration platform (iPaaS). Self-signed TLS certificates will be rejected by GitHub's webhook delivery system — you must use a CA-signed certificate.
- A webhook secret (a high-entropy random string, minimum 32 characters) for validating the
X-Hub-Signature-256header on incoming GitHub payloads.
Top Enterprise Use Cases
1. Pull Request Lifecycle Notifications to Team Channels The most universally implemented use case. When a developer opens, updates, or closes a pull request, a formatted Slack message is posted to the team's engineering channel containing the PR title, author, target branch, a direct link, and a summary of changed files. This reduces the need for developers to manually announce their PRs and ensures reviewers are notified promptly without relying on GitHub's internal email notifications, which are frequently filtered or ignored.
2. Deployment and CI/CD Pipeline Alerts
By subscribing to check_run and check_suite events from GitHub Actions or a connected CI provider, your integration can post a Slack notification whenever a build passes or fails on a protected branch such as main or production. This gives the entire team — including QA and product managers — immediate visibility into release health without requiring access to GitHub itself.
3. Issue Triage Routing
When a new issue is opened and labelled (e.g., bug, priority:critical, customer-reported), the integration routes a notification to the appropriate Slack channel. A priority:critical label on a new issue might trigger a message to #incidents, while a feature-request label routes to #product-backlog. This is typically implemented with a label-based conditional filter in the middleware layer.
4. Security and Compliance Alerting
For organisations with strict branch protection requirements, the integration can monitor for force-pushes to protected branches, new collaborator additions, or changes to repository visibility (public/private). These events trigger immediate alerts to a #security-ops channel and can simultaneously create a Jira ticket or PagerDuty incident via a parallel action.
5. Daily Digest Summaries for Engineering Managers Rather than real-time noise, some teams prefer a scheduled daily digest that summarises all merged PRs, closed issues, and failed builds from the previous 24 hours. This is implemented by storing event data in an intermediate datastore (e.g., a Redis list or a simple Postgres table) and using a scheduled job (cron) to query the GitHub REST API for a consolidated report that is formatted and posted each morning.
Step-by-Step Implementation Guide
Phase 1: Configure the GitHub Webhook
Navigate to your GitHub repository settings (or organisation settings for org-level hooks) and select Webhooks, then click Add webhook. In the Payload URL field, enter the HTTPS endpoint of your middleware service — for example, https://integrate.yourdomain.com/webhooks/github. Set Content type to application/json. In the Secret field, enter your pre-generated webhook secret string. Under Which events would you like to trigger this webhook?, select Let me select individual events and enable: Pushes, Pull requests, Issues, Check runs, and Check suites. Click Add webhook. GitHub will immediately send a ping event to your endpoint; your handler must respond with HTTP 200 to confirm receipt.
To verify webhook signatures in your middleware, implement the following validation logic. Every inbound request from GitHub includes an X-Hub-Signature-256 header containing an HMAC-SHA256 digest of the raw request body, keyed with your webhook secret. Your handler must recompute this digest and compare it to the header value using a constant-time comparison function to prevent timing attacks. In Node.js, this looks like:
const crypto = require('crypto');
function verifyGitHubSignature(rawBody, signatureHeader, secret) {
const expectedSig = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signatureHeader),
Buffer.from(expectedSig)
);
}
If this check fails, return HTTP 401 immediately and do not process the payload.
Phase 2: Parse the GitHub Payload
GitHub's webhook payloads are richly structured. A pull_request event payload has the following abbreviated structure — note how nested objects carry the data you need for a useful Slack message:
{
"action": "opened",
"number": 142,
"pull_request": {
"html_url": "https://github.com/org/repo/pull/142",
"title": "feat: add OAuth2 PKCE flow to auth module",
"user": {
"login": "jsmith"
},
"head": {
"ref": "feature/pkce-auth"
},
"base": {
"ref": "main"
},
"body": "This PR implements the PKCE extension for the authorisation code flow...",
"draft": false,
"requested_reviewers": [
{ "login": "agarcia" }
],
"changed_files": 7,
"additions": 214,
"deletions": 38
},
"repository": {
"full_name": "acme-corp/auth-service"
},
"sender": {
"login": "jsmith"
}
}
Your middleware should extract action, pull_request.title, pull_request.html_url, pull_request.user.login, pull_request.base.ref, and pull_request.changed_files to construct the Slack message. You should also implement an action filter: only process opened, closed, and review_requested actions; ignore synchronize (which fires on every subsequent commit push to the PR) unless your team explicitly wants those notifications, as they are the primary source of alert fatigue.
Phase 3: Post to Slack Using Block Kit
Using Slack's plain Incoming Webhook is acceptable for simple setups, but the Slack API's chat.postMessage endpoint with Block Kit formatting produces significantly more readable and actionable notifications. The following is a complete curl example of posting a PR notification. Note the doubled single quotes throughout:
curl -X POST https://slack.com/api/chat.postMessage \
-H 'Authorization: Bearer xoxb-your-bot-token' \
-H 'Content-Type: application/json' \
-d '{
"channel": "C04XXXXXXXE",
"text": "New PR opened in acme-corp/auth-service",
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": ":pull-request: PR #142 Opened"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": "*Title:*\nfeat: add OAuth2 PKCE flow to auth module"
},
{
"type": "mrkdwn",
"text": "*Author:*\njsmith"
},
{
"type": "mrkdwn",
"text": "*Base Branch:*\nmain"
},
{
"type": "mrkdwn",
"text": "*Files Changed:*\n7 (+214 / -38)"
}
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": { "type": "plain_text", "text": "View Pull Request" },
"url": "https://github.com/org/repo/pull/142",
"style": "primary"
}
]
}
]
}'
The channel field must be the Slack channel's ID (format: C04XXXXXXXE), not its display name. Retrieve channel IDs via the conversations.list Slack API endpoint.
Phase 4: iPaaS Implementation via Make (formerly Integromat)
If you are implementing this integration via Make rather than custom code, create a new scenario with the following module chain. Begin with a Webhooks > Custom Webhook module as the trigger; copy the generated webhook URL and paste it into the GitHub webhook Payload URL field as described in Phase 1. Connect this to a JSON > Parse JSON module, using the raw body from the webhook trigger as the input string and defining a data structure that maps to the GitHub pull_request event schema. Follow this with a Router module that branches on the value of action: one branch handles opened, one handles closed (filtering further on pull_request.merged being true for merged vs. simply closed), and a third handles review_requested. Each branch terminates in a Slack > Create a Message module configured with your workspace connection, target channel, and the Block Kit JSON body constructed using Make's text-templating syntax to inject the parsed field values.
Set the scenario's error handling to use a Break directive with the Store incomplete executions option enabled, so that failed Slack delivery attempts (e.g., due to a transient API error) are retried automatically from the Make execution log.
Common Pitfalls & Troubleshooting
HTTP 401 Unauthorized from Slack API
This error means the Bot Token supplied in the Authorization header is invalid, expired, or lacks the required scopes. First, verify the token begins with xoxb- (bot token) and not xoxp- (user token) or xapp- (app-level token). Navigate to your Slack App's OAuth & Permissions page and confirm that chat:write and chat:write.public are listed under Bot Token Scopes — not under User Token Scopes. If you recently re-installed the app or rotated the token, ensure the new token value has been updated in your middleware environment variables or secrets manager. If the app was revoked by a workspace admin, you will need to re-request installation.
HTTP 403 Forbidden from GitHub Webhooks API
Returned when attempting to create or list webhooks without the admin:repo_hook scope on your PAT, or when using a GitHub App that lacks the Webhooks (Read & Write) repository permission. Regenerate your PAT with the correct scopes, or update the GitHub App's permission configuration and re-request user authorisation.
HTTP 410 Gone — GitHub Webhook Auto-Disabled
GitHub automatically disables a webhook and sets its status to inactive if your endpoint returns non-2xx responses for 5 consecutive days. This is the single most common silent failure mode. Implement an /health check on your middleware and set up an uptime monitor (e.g., Better Uptime, Checkly) on your webhook endpoint URL. Reactivate a disabled webhook via the GitHub UI (Settings > Webhooks > Edit > check Active) or via the REST API: PATCH /repos/{owner}/{repo}/hooks/{hook_id} with body {"active": true}.
HTTP 429 Too Many Requests from Slack
Slack's chat.postMessage endpoint enforces a rate limit of approximately 1 message per second per channel. In high-velocity repositories — those receiving dozens of commits per minute — your middleware can easily breach this limit during a CI/CD storm. Implement an exponential backoff queue: when a 429 is received, read the Retry-After response header (value in seconds), wait that duration, then retry. For sustained high-volume scenarios, consider batching notifications into a digest or routing them across multiple channels.
Signature Validation Failures (GitHub events rejected as unauthorised)
If your middleware is behind a reverse proxy or load balancer (e.g., nginx, AWS ALB), ensure the proxy is forwarding the raw, unmodified request body to your application. Some proxy configurations re-encode the body or strip headers, which will cause HMAC-SHA256 recomputation to produce a different hash than the one GitHub signed. Validate by logging both the received X-Hub-Signature-256 header and your locally computed hash before the comparison step.
Duplicate Slack Messages
GitHub guarantees at-least-once webhook delivery and will re-deliver events if your endpoint does not respond with 200 within 10 seconds. If your processing takes longer than this (e.g., due to a slow downstream Slack API call), GitHub will retry and you'll receive the same event twice. Implement idempotency by caching the X-GitHub-Delivery header value (a UUID unique to each delivery) in Redis or a similar store with a TTL of 24 hours, and skip processing if that UUID has already been seen.