This guide walks you through creating and configuring a GitHub App for Grainlify.
- Go to: GitHub → Settings → Developer settings → GitHub Apps
- Click "New GitHub App"
Note: The app will be "private" by default, which is fine. Private apps can still be installed by users - they just won't appear in the GitHub Marketplace. This is actually recommended for most use cases.
GitHub App name:
Grainlify
(Or your preferred name - this is what users will see)
Description:
Grainlify helps maintainers manage open-source projects, track contributions, and connect with contributors. Install this app to sync your repositories and manage issues and pull requests.Homepage URL:
https://grainlify.com
(Or your production domain)
Callback URL:
https://your-backend-domain.com/auth/github/app/install/callback
Important: Replace your-backend-domain.com with your actual backend URL (e.g., https://api.grainlify.com or your Railway/Heroku URL)
https://abc123.loclx.io/auth/github/app/install/callback
(Replace abc123.loclx.io with your actual tunnel URL)
See Installation Callbacks for the canonical callback troubleshooting guide.
Expire user authorization tokens:
- ✅ Checked (Recommended: enables refresh tokens)
Request user authorization (OAuth) during installation:
- ⬜ Unchecked (Not needed for our use case)
Enable Device Flow:
- ⬜ Unchecked (Not needed)
Setup URL (optional):
https://your-backend-domain.com/auth/github/app/install/callback
- Option 1 (Recommended): Leave this EMPTY - GitHub will use the "Callback URL" from "Identifying and authorizing users" section
- Option 2: Set it to the same callback URL as above (must match exactly)
- Don't set it to frontend URL - This can cause
missing_installation_iderrors
Redirect on update:
- ✅ Checked (Recommended: redirect when repos are added/removed)
Active:
- ✅ Checked
Webhook URL:
https://your-backend-domain.com/webhooks/github
Important: Replace your-backend-domain.com with your actual backend URL
localhost. You MUST use a tunneling service:
Option 1: Using loclx (Recommended)
# Install loclx if not already installed
# Then create a tunnel
loclx tunnel http --to localhost:8080
# This will give you a URL like: https://abc123.loclx.io
# Use this for webhook URL:
https://abc123.loclx.io/webhooks/githubOption 2: Using ngrok
# Install ngrok, then:
ngrok http 8080
# Use the HTTPS URL provided:
https://abc123.ngrok.io/webhooks/githubImportant:
- Use the HTTPS URL (not HTTP)
- The tunnel must be running when GitHub tries to deliver webhooks
- Update the webhook URL in GitHub App settings whenever you restart the tunnel (URL changes)
Secret:
<Generate a NEW random secret string>
Generate a new random secret:
openssl rand -hex 32Or use any secure random string generator:
- Should be at least 32 characters
- Random and unpredictable
- Different from your OAuth Client Secret
Example output:
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6
Save this value - you'll need it for GITHUB_WEBHOOK_SECRET environment variable.
Why a separate secret?
- Webhook secret verifies that webhook requests are actually from GitHub
- OAuth Client Secret is for user authentication flows
- They serve different security purposes
Contents:
- Read-only ✅
- Description: "Access commits, branches, and code to sync project data"
Issues:
- Read & write ✅
- Description: "Manage contributor applications and issue assignments"
Pull requests:
- Read-only ✅
- Description: "Track contributions and project activity"
Metadata:
- Read-only ✅ (Usually auto-selected)
- Description: "Access repository metadata"
Members:
- Read-only ✅
- Description: "Verify organization membership and roles"
None required - Leave all as "No access"
Based on the permissions selected, subscribe to:
- ✅ Installation - "Installation created, deleted, or new permissions accepted"
- ✅ Installation repositories - "Repositories added or removed from installation"
- ✅ Issues - "Issue opened, edited, deleted, transferred, pinned, unpinned, closed, reopened, assigned, unassigned, labeled, unlabeled, locked, unlocked, milestoned, or demilestoned"
- ✅ Pull request - "Pull request opened, edited, closed, merged, synchronized, ready for review, locked, unlocked, a pull request review was requested, or a review request was removed"
- ✅ Push - "One or more commits pushed to a repository"
- ✅ Repository - "Repository created, deleted, archived, unarchived, publicized, or privatized"
- ✅ Any account - "Allow this GitHub App to be installed by any user or organization"
DO NOT select:
- ❌ Only on this account - This restricts installation to only the app creator
Why: If you select "Only on this account", other users won't be able to install the app. You MUST select "Any account" to allow your users to install it.
Click "Create GitHub App"
-
Generate a private key
- Click "Generate a private key" button
- Download the
.pemfile (e.g.,grainlify.private-key.pem) ⚠️ Important: This file can only be downloaded once! Save it securely.
-
Base64 encode the private key
# On macOS/Linux: base64 -i grainlify.private-key.pem # Or using cat and base64: cat grainlify.private-key.pem | base64 # The output will be a long base64 string - copy the entire output
-
Save the base64 string - You'll need this for
GITHUB_APP_PRIVATE_KEYenvironment variable
When is this needed?
- Only if organizations that install your app have IP allow lists enabled
- Usually NOT required for most use cases
- Skip this step if you're unsure
If you need to add IPs:
- For local development: Skip (tunnels use dynamic IPs)
- For production: Add your server's IP addresses
- If using Railway/Heroku: They provide IP ranges (check their docs)
- If using your own server: Add your server's public IP
- Format:
192.168.1.1/32(single IP) or192.168.1.0/24(IP range)
Example for Railway:
IP address: 0.0.0.0/0
Description: Allow all (if your hosting provider doesn't provide specific IPs)
Note: Most hosting providers (Railway, Heroku, AWS, etc.) don't require this unless you're behind a specific firewall.
After creating the app, save these values:
-
App ID (numeric, e.g.,
123456)- Found in the app settings page
- Save for
GITHUB_APP_IDenvironment variable
-
App slug (e.g.,
grainlify)- Found in the URL:
github.com/apps/grainlify - Save for
GITHUB_APP_SLUGenvironment variable
- Found in the URL:
-
Private Key (base64 encoded)
- From Step 4 above
- Save for
GITHUB_APP_PRIVATE_KEYenvironment variable
Add these to your backend .env file or deployment platform:
# GitHub App Configuration
GITHUB_APP_ID=123456
GITHUB_APP_SLUG=grainlify
GITHUB_APP_PRIVATE_KEY=<base64-encoded-private-key>
# Webhook Secret (from Step 2)
GITHUB_WEBHOOK_SECRET=<your-webhook-secret>
# Public Base URL (for webhook URL)
PUBLIC_BASE_URL=https://your-backend-domain.com
# Frontend Base URL (for redirects)
FRONTEND_BASE_URL=https://grainlify.comAfter deploying your backend:
- Go back to your GitHub App settings
- Update the Callback URL to match your production URL:
https://your-production-backend.com/auth/github/app/install/callback - Update the Webhook URL to match your production URL:
https://your-production-backend.com/webhooks/github
cd backend
go run ./cmd/api
# Or use air for auto-reload:
make devUsing loclx:
# Start tunnel (keep this running)
loclx tunnel http --to localhost:8080
# You'll get output like:
# Forwarding https://abc123.loclx.io -> http://localhost:8080Using ngrok:
ngrok http 8080
# You'll get output like:
# Forwarding https://abc123.ngrok.io -> http://localhost:8080-
Go to your GitHub App settings
-
Update Callback URL to:
https://abc123.loclx.io/auth/github/app/install/callback(Replace with your actual tunnel URL)
-
Update Webhook URL to:
https://abc123.loclx.io/webhooks/github -
Click "Update GitHub App"
- Keep both backend and tunnel running
- Go to your frontend and click "Add a repository"
- Click "Install GitHub App"
- You should be redirected to GitHub
- After installation, GitHub will redirect back to your callback URL
⚠️ Tunnel URL changes each time you restart the tunnel (with free services)⚠️ Keep tunnel running while testing - if it stops, webhooks will fail- ✅ For production, use a permanent domain (Railway, Heroku, etc.)
- ✅ Production URLs don't change, so webhooks work reliably
- GitHub App created
- App ID saved
- App slug saved
- Private key generated and base64 encoded
- Webhook secret generated
- Callback URL configured
- Webhook URL configured
- Permissions set correctly
- Events subscribed
- Environment variables set
- Backend deployed with new environment variables
- Test installation flow
- Ensure the callback URL exactly matches what's in GitHub App settings
- Check for trailing slashes
- Verify HTTPS (required for production)
- Verify webhook URL is accessible
- Check webhook secret matches
GITHUB_WEBHOOK_SECRET - Ensure backend is running and
/webhooks/githubendpoint exists
- Verify
GITHUB_APP_IDandGITHUB_APP_SLUGare correct - Check private key is properly base64 encoded
- Ensure backend has access to GitHub API
After setup:
- Test the installation flow from the frontend
- Verify repositories are synced after installation
- Check webhook deliveries in GitHub App settings
- Monitor backend logs for any errors
Last Updated: 2025-01-01