At Facets, we recently overhauled our GitHub integration strategy for our control plane product. Unlike traditional multi-tenant SaaS platforms that share a single backend, our architecture provides each customer with a dedicated control plane instance, deployed under a unique domain.

While this design ensures strong isolation and flexibility, it posed challenges when integrating with GitHub, specifically:

  1. Limited Callback URLs: GitHub Apps only support up to 10 callback URLs, which becomes problematic when each customer has a dedicated control plane instance with its own domain.

  2. Missing State in Approval Flows: When non-owners initiate installations requiring approval, GitHub doesn't preserve the state parameter in the approval callback, making it difficult to route back to the originating control plane. This is a well-documented limitation in the GitHub community.

  3. Enterprise GitHub Support: Supporting both GitHub.com and self-hosted GitHub Enterprise instances requires handling different API endpoints, authentication flows, and routing mechanisms.

This blog post details how we designed a lightweight "cross-control-plane" service that acts as an intermediary between GitHub and individual control plane instances, solving these challenges while maintaining our multi-tenant security boundaries.

The Problem with Personal Access Tokens

Initially, we relied on Personal Access Tokens (PATs) for GitHub integration. While this approach worked, it came with significant limitations:

  • PATs are tied to individual user accounts rather than organizations
  • They have indefinite lifespans unless manually rotated
  • They are less secure
  • GitHub's official guidance recommends GitHub Apps for production integration scenarios

This approach created real operational issues—for example, one of our customers lost GitHub access when the engineer who set up their integration left the company. Their PAT expired without warning, breaking their automation.

Since implementing GitHub Apps, we've eliminated common PAT-related issues:

  • No more unexpected token expirations
  • No disruptions when the PAT creator leaves the company
  • No functionality breakage due to insufficient permissions

The Challenge: GitHub Apps in a Multi-Tenant Environment

GitHub Apps offer significant advantages—granular permissions, installation-based access, and automated token rotation. However, implementing GitHub Apps in our multi-tenant architecture presented unique challenges:

The Callback URL Limitation: When registering a GitHub App, you can specify up to 10 callback URLs. With each customer having a dedicated control plane instance, this limit becomes problematic at scale. Consider our customer control plane URLs:

  • cust1.facetsapp.cloud

  •  facets.cust2.com

  •  cust3.console.facets.cloud

... and many more

The callback URL is essential as it receives the installation ID required to complete the app installation process.

Our Solution: Cross-Control-Plane Service

We designed a centralised, lightweight service (which we call the "cross-control-plane" service) that acts as an intermediary between GitHub and individual control plane instances:

The cross-control-plane service has two primary responsibilities:

  1. Accept callbacks from GitHub and redirect them to the appropriate control plane
  2. Maintain a small MongoDB database to temporarily store installation requests

Integration Flow

Below is a simplified diagram of our integration flow, showing both standard and enterprise GitHub scenarios:

standard and enterprise GitHub scenarios

Standard Flow (Owner Initiates)

1. Installation Initiation

: When a user who is an organisation owner initiates GitHub App installation from their control plane (e.g., cust1.facetsapp.cloud) we:

    • Generate a unique state parameter containing the originating control plane URL
    • Direct the user to GitHub's app installation flow with this state parameter

2. Callback Handling: GitHub redirects to our central callback endpoint with:

https://cross-control-plane.facets.cloud/github/callback?installation_id=58587767&setup_action=install&code=94251652e13379fded53&state=xyz

3. Redirection: Our cross-control-plane service:

    • Unpacks the state parameter to identify the originating control plane
    • Forwards the installation ID to the correct control plane

This simple flow works when the user initiating the installation has sufficient permissions.

Approval Flow (Non-Owner Initiates)

For organisations where a non-owner initiates the installation (e.g., from

cust3.console.facets.cloud):

1. Initial Request: GitHub sends a request callback:

https://cross-control-plane.facets.cloud/github/callback?code=94251652e13379fded53&setup_action=request&state=def

2. Database Storage: Our cross-control-plane service:

  • Exchanges the code to get the GitHub user ID

  • Calls the GitHub API to find the installation request

  • Stores in MongoDB:
{
targetId: "12345", // GitHub organization ID
custId: "default", // "default" for github.com
state: "def" // Original state parameter
}

3. Approval Callback: Later, when an organization owner approves the installation:

https://cross-control-plane.facets.cloud/github/callback?code=aafa9de8250d49d080de&installation_id=58587767&setup_action=install

4. State Recovery:

We get installation details to find the target organisation ID:

  • Look up the state in MongoDB using the composite key (targetId, custId)
  • Redirect to the original control plane with the recovered state and installation ID
  • Remove the record from MongoDB

The database schema uses a composite unique key of  (targetId, custId) to handle duplicate requests.

Enterprise GitHub Considerations

For customers using self-hosted GitHub Enterprise instances, like github.cust2.com we implemented additional workflows:

Path-Based Routing

We use path-based routing in our cross-control-plane service:

  • /github/callback- For github.com installations
  • /github/callback/cust2- For the enterprise GitHub at

Example enterprise GitHub callbacks:

https://cross-control-plane.facets.cloud/github/callback/cust2?code=88776655e13379fdaabb&setup_action=request&state=ghi

https://cross-control-plane.facets.cloud/github/callback/cust2?code=ccdd9de8250d49d0effa&installation_id=98765432&setup_action=install

In our database, we store:

{  
targetId: "67890", // Enterprise GitHub organisation ID custId: "cust2", // Enterprise customer identifier state: "ghi" // Original state parameter
}

Implementation Challenges

During implementation, we encountered several significant challenges:

1. Managing the Approval Flow

The most difficult aspect was handling the organization approval flow correctly. Specific challenges included:

  • Duplicate Requests: Multiple installation requests might be initiated for the same organization (same target ID). We needed to implement logic to handle these duplicates and maintain a single entity in our database per organization.

  • Correlation Without State: Designing a reliable method to correlate approval callbacks with the original requests without having the state parameter required careful API usage and database design.

This limitation is well-documented and remains an open issue in the GitHub community. You can follow the ongoing discussion about this challenge in this GitHub community thread.

2. Enterprise GitHub Setup

Integrating with enterprise GitHub instances presented unique challenges:

  • Each enterprise instance requires separate configuration
  • Routing callbacks correctly without explicit enterprise identifiers
  • Managing multiple sets of credentials (client IDs, secrets, and private keys)
  • Testing complex flows across various GitHub Enterprise versions

Token Management and Security

Our control plane instances use the installation ID to:

  • Generate installation access tokens (valid for one hour)
  • Cache these tokens for 59 minutes to avoid rate limits
  • Use these tokens to access the customer's blueprint repositories

Additional security measures include:

  • One-time webhook tokens to prevent replay attacks
  • OAuth user verification to ensure the installation was performed by the expected user
  • Correlation of GitHub user IDs with installation requests
  • Automatic token rotation

Key Tips for GitHub App Integration in Multi-Tenant Environments

  • Does your app support Github enterprise server? If so, once the customer creates the app in their server from the manifest, make sure you store the private key and client secret securely.

  • Not getting the code param in the callback? Check "Request user authorisation (OAuth) during installation" setting in the app configuration.

  • A user can cancel an installation request and there is no callback or webhook from GitHub for this, so if they re-request after canceling you may get duplicates in your DB - make sure you handle this!

Conclusion

Implementing GitHub Apps in a multi-tenant architecture required creative solutions to address GitHub's callback limitations. Our cross-control-plane service approach provides a scalable and secure way to integrate with GitHub while maintaining isolation between customer instances.

The lightweight design of our cross-control-plane service—focusing solely on callback handling and temporary request storage—allows us to maintain a clean separation of concerns while solving the multi-tenant callback challenge.

By sharing our approach, we hope to help other development teams facing similar challenges with multi-tenant architectures and GitHub integration.