π How to Create Your Own GitHub App (Step-by-Step Guide) β
Author: Mark Wayne B. Menorca
Organization: Cloudmateria / Wayne Enterprise Solution Corporation
Last Updated: October 2025
π§ Overview β
A GitHub App lets you integrate automation, CI/CD, bots, or custom workflows directly into GitHub β with granular permissions and event-based webhooks.
You can use a GitHub App to:
- Automate code scanning or validation
- Manage issues, pull requests, and commits
- Add custom status checks or CI gates
- Integrate with your internal systems (like Slack, Notion, or servers)
- Implement custom rules (e.g., prevent committing
.envfiles)
In this guide, weβll build a real GitHub App from scratch β deploy it, and make it perform automated tasks (like scanning for .env files) using Node.js + Express + Octokit.
π§© Prerequisites β
Before you start, make sure you have:
- A GitHub account or organization
- Node.js β₯ 18 installed
- Basic knowledge of JavaScript and npm
- A server or cloud platform (Render, Railway, Vercel, or VPS) with HTTPS
π§± Step 1: Create a New GitHub App β
Go to your GitHub Developer Settings page:
π https://github.com/settings/apps/newFill in the required information:
| Field | Description | Example |
|---|---|---|
| GitHub App name | A unique name for your app | Cloudmateria EnvGuard |
| Homepage URL | Your appβs homepage | https://yourapp.onrender.com |
| Callback URL | Required, even if unused (OAuth placeholder) | https://yourapp.onrender.com/auth |
| Webhook URL | The URL where GitHub will send webhook events | https://yourapp.onrender.com/github/webhook |
| Webhook Secret | Random secure string for verifying authenticity | gh_secret_12345 |
π Permissions β
Under Repository Permissions, choose what your app needs access to:
| Permission | Access Level | Purpose |
|---|---|---|
| Checks | Read & Write | To post Check Runs (status checks) |
| Issues | Read & Write | To create or comment on issues |
| Contents | Read-only | To analyze commits and file changes |
| Pull requests | Read-only | To respond to PRs |
| Metadata | Read-only | Always required |
π Subscribe to Events β
Scroll to Subscribe to Events, and check:
- β
push - β
pull_request
Click Create GitHub App.
π Step 2: Generate Your App Credentials β
After creating the app:
Download Private Key (.pem)
This is your appβs authentication key (used to sign JWTs).
Save it securely β never commit it to GitHub.Note down these values:
- App ID
- Client ID
- Webhook Secret
- Your downloaded
github-app.pem
βοΈ Step 3: Deploy a Simple Node.js Server β
Letβs build the backend to receive webhooks and process them.
1οΈβ£ Initialize project β
mkdir my-github-app
cd my-github-app
npm init -y
npm install express @octokit/app @octokit/webhooks dotenv
2οΈβ£ Create .env β
GITHUB_APP_ID=123456
GITHUB_WEBHOOK_SECRET=your_secret
PORT=3000
3οΈβ£ Create server.js β
import express from "express";
import { App } from "@octokit/app";
import { Webhooks } from "@octokit/webhooks";
import dotenv from "dotenv";
dotenv.config();
const app = express();
app.use(express.json());
// ποΈ Private key (load from environment)
const privateKey = process.env.GITHUB_PRIVATE_KEY?.replace(/\\n/g, "\n");
const githubApp = new App({
appId: process.env.GITHUB_APP_ID,
privateKey,
});
const webhooks = new Webhooks({
secret: process.env.GITHUB_WEBHOOK_SECRET,
});
// β
Webhook route (modern Node 18+ compatible)
app.post("/github/webhook", async (req, res) => {
try {
await webhooks.verifyAndReceive({
id: req.headers["x-github-delivery"],
name: req.headers["x-github-event"],
signature: req.headers["x-hub-signature-256"],
payload: req.body,
});
res.status(200).send("Webhook received β
");
} catch (error) {
console.error("β Webhook verification failed:", error.message);
res.status(400).send("Invalid signature");
}
});
// π§© Example event listener
webhooks.on(["push", "pull_request"], async ({ payload }) => {
console.log("π¦ Received event:", payload.action || "push");
});
app.listen(process.env.PORT || 3000, () => {
console.log(`π GitHub App listening on port ${process.env.PORT || 3000}`);
});
π§ Step 4: Configure Your App URLs β
In your GitHub App settings, go to General β Webhook:
| Field | Value |
|---|---|
| Webhook URL | https://yourapp.onrender.com/github/webhook |
| Webhook Secret | same as .env |
| Callback URL | https://yourapp.onrender.com/auth (placeholder) |
βοΈ Step 5: Deploy to Render (or another host) β
Render Example: β
- Go to Render.com
- Click New β Web Service
- Connect your GitHub repository
- Build Command β
npm install
Start Command βnode server.js - Add Environment Variables:
GITHUB_APP_IDGITHUB_WEBHOOK_SECRETGITHUB_PRIVATE_KEY(paste full PEM content)
Render automatically gives you a live HTTPS URL like:
https://yourapp.onrender.com
π§ͺ Step 6: Test It β
- Go to your GitHub App β Install App
- Install it on a test repository
- Push a commit or open a PR
- Check your Render logs β you should see:
π¦ Received event: push Webhook received β
π§± Step 7: Add Functionality β
Now that your app receives webhooks, you can extend it!
Example: Create an Issue Automatically β
webhooks.on("push", async ({ payload }) => {
const { repository, installation } = payload;
const owner = repository.owner.login;
const repo = repository.name;
const octokit = await githubApp.getInstallationOctokit(installation.id);
await octokit.request("POST /repos/{owner}/{repo}/issues", {
owner,
repo,
title: "π¨ New Push Event Detected",
body: "This issue was created automatically by your GitHub App.",
});
console.log(`π§Ύ Issue created in ${owner}/${repo}`);
});
π Step 8: Add a Custom Status Check β
await octokit.request("POST /repos/{owner}/{repo}/check-runs", {
owner,
repo,
name: "Custom Validation Check",
head_sha: payload.after,
status: "completed",
conclusion: "success",
output: {
title: "β
Custom Check Passed",
summary: "Everything looks good!",
},
});
This will appear under the Checks tab in the pull request.
π§Ύ Step 9: Enforce Merge Rules β
In your repository:
- Go to Settings β Branches β Branch Protection Rules
- Enable:
- β Require status checks to pass before merging
- β Select your appβs check (e.g. βCustom Validation Checkβ)
- Save β GitHub now blocks merges until your appβs check passes.
π§Ή Step 10: Secure and Maintain β
| Task | Frequency | Description |
|---|---|---|
| π Rotate PEM key | Every 6β12 months | Regenerate from GitHub App settings |
| π§° Monitor logs | Weekly | Check webhook delivery reports |
| π« Never commit PEM | Always | Add to .gitignore |
| π Use environment secrets | Always | Store credentials in Render/Host env vars |
| π§© Test webhook delivery | On new deploy | Use GitHub βRecent Deliveriesβ panel |
π§ Example .gitignore β
# Node and credentials
node_modules/
.env
*.pem
π§Ύ Common Issues β
| Error | Cause | Solution |
|---|---|---|
Invalid signature | Webhook secret mismatch | Ensure GitHub App secret matches .env |
argument handler must be a function | Old middleware version | Use verifyAndReceive() instead |
Required status check missing | Wrong check name in protection rules | Update rule to match your check name |
| Webhook not received | Wrong webhook URL | Use full HTTPS Render URL + /github/webhook |
β Summary β
| Step | Description |
|---|---|
| 1οΈβ£ | Create a new GitHub App |
| 2οΈβ£ | Set permissions & webhook events |
| 3οΈβ£ | Download private key (.pem) |
| 4οΈβ£ | Build Node.js backend with Octokit |
| 5οΈβ£ | Deploy on Render or Railway |
| 6οΈβ£ | Install the App on repositories |
| 7οΈβ£ | Add automation: issues, checks, alerts |
| 8οΈβ£ | Add branch protection for enforced security |
π References β
π Conclusion β
Creating your own GitHub App allows you to automate your workflows securely and at scale.
Whether itβs running checks, creating issues, or scanning commits, a self-hosted GitHub App gives you complete control over your development pipeline β with zero dependency on external services.
Once youβve mastered this foundation, you can extend your app with:
- AI-driven code reviews
- Automated pull request templates
- Secret scanning (like EnvGuard)
- Slack or Discord notifications
Build once β automate forever. βοΈπ‘