Canonical guide for configuring and debugging the GitHub App installation callback flow.
GitHub App installations use the Callback URL from your GitHub App settings (not a redirect_uri in code):
1. User clicks "Install GitHub App" in the frontend
2. Frontend calls POST /auth/github/app/install/start
3. Backend returns an installation URL with state
4. User completes installation on GitHub
5. GitHub redirects to: {CALLBACK_URL}?installation_id=...&state=...
6. Backend handles GET /auth/github/app/install/callback
7. Backend redirects to FRONTEND_BASE_URL (e.g. /dashboard?github_app_installed=true)
- Go to GitHub → Settings → Developer settings → GitHub Apps → Grainlify
- Open Identifying and authorizing users
- Set Callback URL to your backend URL:
Production:
https://api.your-domain.com/auth/github/app/install/callback
Local development (with HTTPS tunnel):
https://your-tunnel.example.com/auth/github/app/install/callback
Requirements:
- Must be HTTPS (GitHub rejects HTTP callbacks)
- Path must be exactly
/auth/github/app/install/callback - No trailing slash
- Must be reachable from the public internet
- Click Update GitHub App so GitHub verifies the URL.
In Post Installation:
| Setting | Recommended value |
|---|---|
| Setup URL (optional) | Leave empty (or match the callback URL exactly) |
| Redirect on update | Checked |
If Setup URL points elsewhere, GitHub may redirect there instead of your callback.
If GitHub reports the app can only be installed by the creator:
- Open GitHub App settings → Where can this GitHub App be installed?
- Change from Only on this account to Any account
- Click Update GitHub App
A private (non-marketplace) app can still be installable by any account when this setting is enabled.
# Where users land after a successful installation
FRONTEND_BASE_URL=https://your-frontend.example.com
# Public backend URL (must match the callback host)
PUBLIC_BASE_URL=https://api.your-domain.com- Confirm Callback URL is set in GitHub App settings and matches
PUBLIC_BASE_URL - Confirm the backend is running and the tunnel (if local) is active
- Check backend logs for
GitHub App installation startedandGitHub App installation callback received - Uninstall the app from the org and try again (GitHub skips redirect if already installed)
- Ensure Setup URL is empty
curl -I "https://api.your-domain.com/auth/github/app/install/callback?installation_id=test&state=test"A 200, 302, or JSON error about invalid state confirms the route is reachable.
Usually means:
- User cancelled installation on GitHub
- Callback URL was opened directly (not from GitHub)
- Setup URL misconfiguration
Complete the installation flow again from the frontend without cancelling.
If your dev tunnel URL changes:
- Update GitHub App Callback URL
- Update
PUBLIC_BASE_URLin.env - Click Update GitHub App
- Callback URL set in GitHub App settings (HTTPS, correct path)
- Setup URL empty or matches callback URL
- App installable on Any account (if needed for other users)
-
PUBLIC_BASE_URLandFRONTEND_BASE_URLconfigured - Backend running; tunnel active for local dev
-
curltest to callback URL succeeds - Installation logs show callback received