Introduction
This guide walks you through creating a GitHub app that automatically posts comments when code is pushed to your team's repositories. The app listens for push events via webhooks and can post intelligent comments about the changes, notify team members, or provide automated feedback.
What You'll Build
- A GitHub app integrated with your organization's repositories
- A webhook server that receives push notifications
- Automated comment posting logic
- Team-wide installation and configuration
Prerequisites
- A GitHub account with organization admin access (or personal account for testing)
- Basic knowledge of JavaScript/Node.js
- A server or hosting platform to run your webhook handler (Heroku, Vercel, AWS Lambda, etc.)
- Git installed locally
Step 1: Create the GitHub App
-
Navigate to GitHub App Settings
Go to your GitHub organization settings or personal account settings:
- For organizations:
https://github.com/organizations/YOUR_ORG/settings/apps - For personal accounts:
https://github.com/settings/apps
Click "New GitHub App"
- For organizations:
-
Configure Basic Information
Field Value GitHub App name Push Commenter (or your preferred name) Homepage URL Your organization website or app documentation URL Webhook URL Your server endpoint (e.g., https://your-domain.com/webhook)Webhook secret Generate a random string (save this securely!) Note: You can use a tool like
openssl rand -hex 20to generate a secure webhook secret. -
Set Repository Permissions
Under "Repository permissions", configure the following:
Permission Access Level Reason Contents Read-only Read commit content and file changes Issues Read & write Post comments on issues (if needed) Pull requests Read & write Post comments on pull requests Commit statuses Read & write Update commit status (optional) -
Subscribe to Events
Under "Subscribe to events", check:
- Push - This is the primary event for your app
- Pull request - Optional, if you want to comment on PRs when pushed
-
Set Installation Permissions
Under "Where can this GitHub App be installed?"
- Select "Only on this account" for team-only use
- Or select "Any account" if you plan to distribute it
-
Create the App
Click "Create GitHub App" at the bottom of the page.
-
Generate a Private Key
After creation, scroll down to "Private keys" and click "Generate a private key". A
.pemfile will download automatically. Store this securely - you'll need it to authenticate API requests. -
Note Your App ID
At the top of the app settings page, note your App ID. You'll need this for authentication.
Step 2: Build the Webhook Handler
Create a server that receives push events and posts comments. Here's a complete Node.js implementation:
Project Setup
mkdir github-push-commenter
cd github-push-commenter
npm init -y
npm install express @octokit/app @octokit/rest crypto
Create the Server (server.js)
const express = require('express');
const crypto = require('crypto');
const { App } = require('@octokit/app');
const { Octokit } = require('@octokit/rest');
const fs = require('fs');
const app = express();
const PORT = process.env.PORT || 3000;
// Configuration - set these as environment variables
const APP_ID = process.env.GITHUB_APP_ID;
const PRIVATE_KEY = fs.readFileSync(process.env.PRIVATE_KEY_PATH, 'utf8');
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
// Initialize GitHub App
const githubApp = new App({
appId: APP_ID,
privateKey: PRIVATE_KEY,
});
// Middleware to parse JSON with raw body for signature verification
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
}
}));
// Verify webhook signature
function verifySignature(req) {
const signature = req.headers['x-hub-signature-256'];
if (!signature) return false;
const hash = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.rawBody)
.digest('hex');
return signature === `sha256=${hash}`;
}
// Main webhook endpoint
app.post('/webhook', async (req, res) => {
// Verify the webhook signature
if (!verifySignature(req)) {
console.error('Invalid signature');
return res.status(401).send('Unauthorized');
}
const event = req.headers['x-github-event'];
const payload = req.body;
// Only handle push events
if (event === 'push') {
await handlePushEvent(payload);
}
res.status(200).send('OK');
});
// Handle push events
async function handlePushEvent(payload) {
const {
repository,
pusher,
ref,
commits,
installation,
} = payload;
console.log(`Push to ${repository.full_name} on ${ref} by ${pusher.name}`);
console.log(`Commits: ${commits.length}`);
// Get installation access token
const octokit = await githubApp.getInstallationOctokit(installation.id);
// Generate comment content based on the push
const comment = generateComment(commits, pusher, ref);
// Post comment on the latest commit
const latestCommit = commits[commits.length - 1];
try {
await octokit.repos.createCommitComment({
owner: repository.owner.login,
repo: repository.name,
commit_sha: latestCommit.id,
body: comment,
});
console.log(`Posted comment on commit ${latestCommit.id.substring(0, 7)}`);
} catch (error) {
console.error('Error posting comment:', error.message);
}
}
// Generate comment content
function generateComment(commits, pusher, ref) {
const branch = ref.replace('refs/heads/', '');
const commitCount = commits.length;
const commitWord = commitCount === 1 ? 'commit' : 'commits';
let comment = `š **Push detected!**\n\n`;
comment += `- **Author:** @${pusher.name}\n`;
comment += `- **Branch:** \`${branch}\`\n`;
comment += `- **Commits:** ${commitCount} ${commitWord}\n\n`;
comment += `### Changes:\n`;
commits.forEach((commit, index) => {
const shortSha = commit.id.substring(0, 7);
const message = commit.message.split('\n')[0]; // First line only
comment += `${index + 1}. [\`${shortSha}\`](${commit.url}) - ${message}\n`;
});
comment += `\n---\n`;
comment += `_Automated comment by Push Commenter_`;
return comment;
}
// Health check endpoint
app.get('/health', (req, res) => {
res.status(200).send('OK');
});
app.listen(PORT, () => {
console.log(`Webhook server listening on port ${PORT}`);
});
Environment Variables
Create a .env file (never commit this!):
GITHUB_APP_ID=your_app_id
PRIVATE_KEY_PATH=./path-to-your-private-key.pem
WEBHOOK_SECRET=your_webhook_secret
PORT=3000
Add .gitignore
node_modules/
.env
*.pem
Step 3: Deploy Your Webhook Handler
Your webhook handler needs to be accessible from the internet. Here are several deployment options:
Option A: Vercel (Serverless)
- Install Vercel CLI:
npm i -g vercel - Create
vercel.json:{ "version": 2, "builds": [ { "src": "server.js", "use": "@vercel/node" } ], "routes": [ { "src": "/(.*)", "dest": "server.js" } ], "env": { "GITHUB_APP_ID": "@github-app-id", "WEBHOOK_SECRET": "@webhook-secret" } } - Deploy:
vercel --prod - Add secrets:
vercel secrets add github-app-id YOUR_APP_ID - Upload private key as environment variable in Vercel dashboard
Option B: Heroku
- Install Heroku CLI and login
- Create
Procfile:web: node server.js - Create app:
heroku create your-app-name - Set config vars:
heroku config:set GITHUB_APP_ID=your_app_id heroku config:set WEBHOOK_SECRET=your_secret heroku config:set PRIVATE_KEY="$(cat your-key.pem)" - Deploy:
git push heroku main
Option C: AWS Lambda
Use the Serverless Framework or AWS SAM to deploy the Express app as a Lambda function behind API Gateway.
Update Webhook URL
After deployment, update your GitHub App's webhook URL to point to your deployed endpoint:
- Go to your GitHub App settings
- Update Webhook URL to
https://your-domain.com/webhook - Click "Save changes"
Step 4: Install the App for Your Team
-
Navigate to Installation Page
Go to your GitHub App's public page:
https://github.com/apps/YOUR_APP_NAME -
Click "Install"
You'll be prompted to choose where to install the app.
-
Select Your Organization
Choose your team's organization from the list.
-
Configure Repository Access
Choose which repositories the app can access:
- All repositories - App works on all current and future repos
- Only select repositories - Choose specific repos from a list
-
Review Permissions
Review the permissions the app will have and click "Install".
-
Authorize
You may need to authorize with your GitHub password or 2FA.
Team Member Access
Once installed on your organization, the app will automatically work for all team members when they push to the configured repositories. Individual team members don't need to install anything - the app operates at the organization level.
Step 5: Test Your App
Make a Test Push
- Clone one of your configured repositories
- Make a small change (e.g., update a README)
- Commit and push:
git add . git commit -m "Test push for GitHub app" git push origin main - Check the latest commit on GitHub - you should see a comment posted by your app
Monitor Webhook Deliveries
In your GitHub App settings, go to "Advanced" ā "Recent Deliveries" to see:
- All webhook events sent to your app
- Request and response payloads
- HTTP status codes
- Redeliver failed webhooks
Check Server Logs
Monitor your webhook handler's logs for any errors:
- Vercel:
vercel logs - Heroku:
heroku logs --tail - Local testing: View console output
Troubleshooting
| Issue | Possible Cause | Solution |
|---|---|---|
| No webhook received | Incorrect webhook URL | Verify the URL in GitHub App settings points to your deployed server |
| 401 Unauthorized | Signature verification failed | Check that WEBHOOK_SECRET matches what's configured in GitHub |
| Cannot post comments | Insufficient permissions | Ensure the app has "Read & write" access to Issues/Pull requests |
| Authentication errors | Invalid private key or App ID | Verify GITHUB_APP_ID and private key are correct |
| App not triggering on push | Not subscribed to push events | Check "Subscribe to events" includes "Push" |
| Works locally, fails in production | Environment variables not set | Verify all environment variables are configured on your hosting platform |
Debugging Tips
- Use ngrok for local testing:
ngrok http 3000creates a public URL for your local server - Enable verbose logging: Add detailed console.log statements in your webhook handler
- Test webhook signature separately: Create a test endpoint that logs the signature verification process
- Check GitHub's webhook delivery status: Failed deliveries show the error response from your server
- Validate your private key format: Ensure it's properly formatted with newlines (
\n) intact
Customization Ideas
Enhance your push commenter with these features:
1. Conditional Comments
Only comment on specific conditions:
// Only comment on main branch pushes
if (ref === 'refs/heads/main') {
await postComment(...);
}
// Only comment on large pushes
if (commits.length > 5) {
await postComment(...);
}
2. Code Analysis
Analyze commits and provide insights:
// Count files changed
const filesChanged = commits.reduce((acc, commit) => {
return acc + commit.added.length + commit.modified.length + commit.removed.length;
}, 0);
comment += `\n- **Files changed:** ${filesChanged}`;
// Detect breaking changes
const hasBreakingChange = commits.some(c =>
c.message.toLowerCase().includes('breaking')
);
if (hasBreakingChange) {
comment += `\n\nā ļø **WARNING:** This push may contain breaking changes!`;
}
3. Team Mentions
Automatically mention team members:
// Mention reviewers for production pushes
if (ref === 'refs/heads/production') {
comment += `\n\n@teamlead @devops-team Please review this production push.`;
}
4. External Integrations
Connect to other tools:
- Send Slack notifications about pushes
- Update Jira tickets based on commit messages
- Trigger CI/CD pipelines
- Log metrics to monitoring systems
5. Rich Formatting
Use GitHub's Markdown for better comments:
comment += `
## š Push Summary
| Metric | Value |
|--------|-------|
| Commits | ${commits.length} |
| Author | @${pusher.name} |
| Branch | \`${branch}\` |
### š Files Changed
${commits.map(c => c.modified.map(f => `- \`${f}\``).join('\n')).join('\n')}
`;
Security Best Practices
- Always verify webhook signatures - Never skip signature verification in production
- Keep your private key secure - Never commit it to version control, use environment variables or secret management services
- Use HTTPS only - GitHub requires webhook URLs to use HTTPS
- Rotate secrets regularly - Periodically regenerate your webhook secret and private key
- Minimize permissions - Only request the permissions your app actually needs
- Rate limit awareness - Respect GitHub's API rate limits (5,000 requests/hour for apps)
- Log security events - Monitor for unauthorized access attempts
- Validate input - Always validate payload data before processing