Building GitHub integrations for enterprise customers? You've probably heard this before: "I installed your GitHub app weeks ago, but it's still not working."
Here's what happens: Sarah from the AppSec team clicks "Install GitHub App" for her team's repositories. Her organization requires admin approval, so the request goes to Mike in IT. A few days later, Mike reviews it, clicks "Approve," and GitHub redirects him to your app's callback URL to complete the installation.
The problem? Mike isn't Sarah. He doesn't have a Konvu account, doesn't know what this app does, and frankly doesn't care - he just did his job approving a legitimate request. He hits your login page, closes the tab, and moves on with his day.
Meanwhile, Sarah is still waiting for her integration to work, your system has no way to connect Mike's approval back to Sarah's original request, and the installation sits in limbo until someone manually intervenes.
Why GitHub App Approvals Break
This scenario creates two distinct challenges that most GitHub App developers don't anticipate:
Challenge 1: The Forgotten Request When Sarah initially requested the installation, our system had no way to persist that request. We'd receive GitHub's callback indicating "admin approval required," show Sarah a message saying "we'll handle this," and then... forget everything. No record of who requested it, which organization they wanted to connect, or what they were trying to accomplish.
Challenge 2: The Orphaned Approval Days later when Mike approved the installation, GitHub would redirect him to our callback with the installation details. But by then, we had no context. We couldn't connect this approval back to Sarah's original request or her Konvu organization. Mike would hit our login page, close the tab, and the installation would sit in GitHub limbo - technically approved but never activated in our system.
What We Built: The Missing Link
The solution required solving both sides of this equation: capturing the original request context and automatically fulfilling approved installations. Here's how we approached it, broken into three steps.
Step 1: Capture Context
The first challenge was figuring out who actually requested the installation and which organization they wanted to connect. When Sarah clicks "Install" and gets redirected back to us with a "pending approval" status, we need to capture that context.
Here's how we solve it:
- Get the user's identity: We exchange the OAuth code (included in GitHub's callback) for a user access token, then use that token to fetch the user's GitHub ID
- Find their specific request: We query GitHub's
/app/installation-requests
API, which returns all pending installation requests for our app - Match them up: Each installation request includes the requester's user ID and target organization details. We match Sarah's user ID to find her specific request and extract the target organization she wanted to connect
This gives us everything we need: who requested it (Sarah), which organization they wanted to connect (Acme Corp), and we can store this as a pending request in our database.
Step 2: Handle Approvals
When Mike (the admin) eventually approves the installation, we need to handle his redirect gracefully while still capturing the installation details. Our callback endpoint now detects whether the user is authenticated:
- If authenticated: Process normally (direct installation case)
- If unauthenticated: Show a simple "Thank you" page explaining the installation was approved, while still processing the installation data in the background
Step 3: Build the Fulfillment Loop
A background service runs every few minutes to check for newly approved installations:
// Simplified version of our fulfillment serviceasync function fulfillPendingInstallations() {// Get all installations from GitHubconst installations = await github.apps.listInstallations();// Get our pending requests from the databaseconst pendingRequests = await db.getPendingIntegrationRequests();for (const request of pendingRequests) {// Look for a matching installationconst matchingInstall = installations.find(install =>install.account.id === request.targetOrgId);if (matchingInstall) {// Create the integration in our systemawait createIntegration(request.userId, matchingInstall);await db.markRequestFulfilled(request.id);}}}
This polling approach ensures approved installations get linked back to the original requesters without requiring complex webhook handling.


Key Takeaways
If you're building GitHub integrations for enterprise customers, here's what we learned:
Don't assume direct installs: Enterprise organizations often require admin approval, so plan for it from day one. The approval workflow is fundamentally different from direct installations and needs separate handling.
Capture context early: When a user requests an installation that needs approval, immediately capture who they are and what they're trying to connect. You won't get this information later when the admin approves it.
Handle unauthenticated approvers gracefully: The person approving the installation (IT admin) is rarely the same person who requested it (developer). Don't force them through your onboarding flow - show a simple thank you page and handle the installation in the background.
Build the fulfillment loop: Approved installations don't automatically sync back to your system. You need either polling or webhooks to detect when GitHub has approved a pending installation and match it back to your stored requests.
This turned what used to be a manual support process into a fully automated workflow. Sarah gets her integration activated within minutes of Mike's approval, and our support team stopped getting "where's my GitHub integration?" tickets.