04.c - Payment-to-Installation-Linking
Relevant source files
Purpose and ScopeLink copied!
This document explains the mechanism that correlates Stripe payment transactions with GitHub App installations in godeep.wiki. Since the system does not use a database, this linking is achieved through a cookie-based state management system combined with structured logging. This correlation is critical for the owner to identify which GitHub repositories belong to which paying customers.
For information about the payment initiation process, see 4.1. For details on GitHub OAuth callback processing, see 3.3. For the overall payment processing architecture, see 4.
OverviewLink copied!
The payment-to-installation linking system bridges two separate workflows that occur in sequence:
- Payment Flow: User pays $10 → Stripe redirects to success page with
session_id - GitHub Flow: User connects GitHub → GitHub redirects to callback with
installation_id
The session_id serves as the correlation key that links these two events. The system must preserve this identifier across the OAuth flow and associate it with the installation_id when received.
Correlation TimelineLink copied!
Sources: app/success/page.tsx L10-L70
app/api/auth/github/callback/route.ts L1-L149
Cookie-Based State EncodingLink copied!
State Parameter StructureLink copied!
The OAuth state parameter serves dual purposes:
- CSRF Protection: Random token prevents unauthorized OAuth initiations
- Session Correlation: Embeds
session_idfor retrieval in callback
The state is encoded as a base64 JSON string containing the session_id:
// Encoding structure (not actual code from repo)const stateData = { sessionId: "cs_test_a1b2c3d4..."}const stateParam = Buffer.from(JSON.stringify(stateData)).toString('base64')
Cookie StorageLink copied!
The encoded state is stored in the github_oauth_state cookie during OAuth initiation:
| Cookie Name | Value | Expiry | Purpose |
|---|---|---|---|
github_oauth_state | Base64-encoded JSON with sessionId | 1 hour | CSRF protection + session correlation |
github_access_token | User OAuth token | 24 hours | User dashboard access (set in callback) |
Sources: app/api/auth/github/callback/route.ts L23-L37
Success Page: Session ID CaptureLink copied!
The success page receives the session_id from Stripe's redirect and embeds it in the GitHub OAuth initiation URL.
URL Parameter FlowLink copied!
The success page receives session_id as a URL search parameter from Stripe's checkout success redirect. If session_id is missing, the page redirects to the home page.
GitHub Connection ButtonLink copied!
The "Connect GitHub" button constructs a link to /api/auth/github?session_id=${session_id}, passing the session identifier as a query parameter to the OAuth initiation endpoint.
Diagram: Success Page Data Flow
Sources: app/success/page.tsx L10-L71
OAuth Callback: State DecodingLink copied!
The OAuth callback receives the state parameter from GitHub and decodes it to extract the original session_id.
State Verification ProcessLink copied!
app/api/auth/github/callback/route.ts L23-L37
The callback retrieves the github_oauth_state cookie and compares it against the state parameter received from GitHub. If they don't match, the request is rejected with a 400 status code. After successful verification, the cookie is deleted.
Session ID ExtractionLink copied!
app/api/auth/github/callback/route.ts L39-L50
The callback attempts to decode the base64-encoded state parameter and parse it as JSON to extract the sessionId field. If decoding fails, the error is logged but the OAuth flow continues (the session_id will simply be logged as "NOT PROVIDED").
Correlation Logging StructureLink copied!
app/api/auth/github/callback/route.ts L99-L111
When installation_id is present, the callback logs a complete correlation record to Vercel logs (stdout) with all relevant identifiers:
| Field | Source | Purpose |
|---|---|---|
Stripe Session ID | Decoded from state cookie | Links to Stripe payment |
Installation ID | GitHub callback URL parameter | Links to GitHub App installation |
GitHub Username | GitHub API user response | Identifies customer account |
GitHub Email | GitHub API user response | Customer contact (may be null) |
GitHub User ID | GitHub API user response | Unique GitHub identifier |
User Name | GitHub API user response | Customer display name |
Setup Action | GitHub callback URL parameter | Install type (usually "install") |
Timestamp | System time | When correlation occurred |
Diagram: Callback State Decoding
Sources: app/api/auth/github/callback/route.ts L23-L50
app/api/auth/github/callback/route.ts L99-L111
ntfy Notification FormatLink copied!
The callback sends a structured notification to ntfy.sh that includes a "Match ID" for quick correlation.
Match ID GenerationLink copied!
app/api/auth/github/callback/route.ts L118
The Match ID is the last 12 characters of the session_id, providing a shorter identifier for manual lookup:
const sessionShort = stripeSessionId ? stripeSessionId.slice(-12) : 'Not linked'
Notification Payload StructureLink copied!
app/api/auth/github/callback/route.ts L114-L132
The ntfy notification includes:
| Field | Description |
|---|---|
| Installation ID | Full installation_id from GitHub |
| Customer | Email or username |
| Match ID | Last 12 chars of session_id |
| Time | New York timezone timestamp |
| Automation message | Instructions for next steps |
The notification uses high priority and emoji tags (white_check_mark, rocket) to make it visually distinct.
Sources: app/api/auth/github/callback/route.ts L114-L137
Logging Output FormatLink copied!
Console Log StructureLink copied!
When a new GitHub App installation is linked, the callback outputs a formatted log block to stdout:
================================================================================
🎉 NEW GITHUB APP INSTALLATION
================================================================================
Stripe Session ID: cs_test_a1b2c3d4e5f6g7h8i9j0k1l2
Installation ID: 12345678
GitHub Username: john-doe
GitHub Email: john@example.com
GitHub User ID: 98765432
User Name: John Doe
Setup Action: install
Timestamp: 2024-01-15T10:30:45.123Z
================================================================================
This structured format enables:
- Manual correlation: Owner searches Stripe dashboard for
session_id - Repository access: Owner uses
installation_idin admin panel (6.2) - Customer identification: Multiple identifiers (username, email, user ID)
- Audit trail: Timestamp for tracking processing timeline
Sources: app/api/auth/github/callback/route.ts L99-L111
Manual Correlation WorkflowLink copied!
Since the system lacks a database, the owner must manually correlate payments with installations using logs.
Correlation StepsLink copied!
Log Search StrategyLink copied!
- By Session ID: Search Vercel logs for the full
session_idfrom Stripe dashboard - By Match ID: Search for the last 12 characters if full ID is unavailable
- By Timestamp: Cross-reference payment time with log timestamps
- By Customer Email: Search for GitHub email or username
Sources: CLAUDE.md L44-L50
app/api/auth/github/callback/route.ts L99-L137
State Encoding Implementation DetailsLink copied!
Base64 EncodingLink copied!
The state parameter uses standard base64 encoding of a JSON object:
// Conceptual structure (actual implementation in /api/auth/github)const stateObject = { sessionId: session_id }const stateParam = Buffer.from(JSON.stringify(stateObject)).toString('base64')
Decoding ProcessLink copied!
app/api/auth/github/callback/route.ts L39-L50
The callback uses a try-catch block to safely decode the state:
- Convert base64 string to Buffer:
Buffer.from(state, 'base64') - Convert Buffer to UTF-8 string:
.toString('utf-8') - Parse JSON string:
JSON.parse(...) - Extract sessionId field:
stateData.sessionId
If any step fails, the error is logged but the OAuth flow continues with stripeSessionId = null, which gets logged as "NOT PROVIDED".
Sources: app/api/auth/github/callback/route.ts L39-L50
Cookie LifecycleLink copied!
Creation and StorageLink copied!
The github_oauth_state cookie is created by the /api/auth/github endpoint (not included in provided files but referenced in CLAUDE.md) when the OAuth flow initiates.
Expiration and DeletionLink copied!
app/api/auth/github/callback/route.ts L37
After successful state verification, the callback immediately deletes the cookie using cookieStore.delete("github_oauth_state"). This ensures:
- Single-use protection: State cannot be reused
- Security: Expired states don't persist in browser
- Clean state: No cookie pollution between OAuth attempts
The 1-hour expiry provides a window for users to complete the GitHub OAuth flow without rushing.
Sources: app/api/auth/github/callback/route.ts L23-L37
Error HandlingLink copied!
Missing Session IDLink copied!
If the state decoding fails or session_id is not present:
app/api/auth/github/callback/route.ts L40-L50
- Error is logged to console
stripeSessionIdis set tonull- OAuth flow continues
- Log entry shows "NOT PROVIDED" for Stripe Session ID
- Installation still functions but requires manual matching via timestamp
Invalid State ParameterLink copied!
app/api/auth/github/callback/route.ts L32-L35
If state verification fails (cookie doesn't match URL parameter):
- Request returns 400 status code with "Invalid state" message
- OAuth flow terminates
- User must restart from success page
Missing Installation IDLink copied!
If installation_id is not present in the callback URL:
- No correlation log is generated
- User token is still stored for dashboard access
- This occurs when user completes OAuth but skips app installation
Sources: app/api/auth/github/callback/route.ts L32-L50
app/api/auth/github/callback/route.ts L99-L138
Security ConsiderationsLink copied!
CSRF ProtectionLink copied!
The state parameter serves as a CSRF token by:
- Being randomly generated during OAuth initiation
- Stored server-side in httpOnly cookie
- Verified in callback against GitHub's response
- Single-use (deleted after verification)
Cookie SecurityLink copied!
| Attribute | Value | Purpose |
|---|---|---|
httpOnly | true | Prevents JavaScript access |
secure | production | HTTPS-only in production |
path | / | Accessible to all routes |
maxAge | Variable | Limits exposure window |
State TamperingLink copied!
The base64 encoding provides no cryptographic security—it's merely serialization. However:
- Attacker cannot forge state without access to the server-generated cookie
- State verification requires exact match with server-stored value
- Even if attacker decodes state, they cannot modify the cookie
Sources: app/api/auth/github/callback/route.ts L23-L37
Relationship to Other SystemsLink copied!
Payment Processing IntegrationLink copied!
The linking mechanism depends on:
- Stripe Checkout (4.1): Generates initial
session_id - Success Page (2.2): Captures and forwards
session_id
GitHub Integration DependenciesLink copied!
The mechanism enables:
- Installation Token Generation (6.2): Uses
installation_idto create tokens - Repository Cloning (5.2): Requires correlated
installation_id
Admin WorkflowLink copied!
The correlation output feeds directly into:
- Manual Repository Access (6.3): Owner uses logged
installation_id - Customer Identification: Multiple identifiers enable customer lookup
Sources: CLAUDE.md L44-L50
Refresh this wiki
Last indexed: 23 November 2025 (922b35)
On this page
- Payment-to-Installation Linking
- Purpose and Scope
- Overview
- Correlation Timeline
- Cookie-Based State Encoding
- State Parameter Structure
- Cookie Storage
- Success Page: Session ID Capture
- URL Parameter Flow
- GitHub Connection Button
- OAuth Callback: State Decoding
- State Verification Process
- Session ID Extraction
- Correlation Logging Structure
- ntfy Notification Format
- Match ID Generation
- Notification Payload Structure
- Logging Output Format
- Console Log Structure
- Manual Correlation Workflow
- Correlation Steps
- Log Search Strategy
- State Encoding Implementation Details
- Base64 Encoding
- Decoding Process
- Cookie Lifecycle
- Creation and Storage
- Expiration and Deletion
- Error Handling
- Missing Session ID
- Invalid State Parameter
- Missing Installation ID
- Security Considerations
- CSRF Protection
- Cookie Security
- State Tampering
- Relationship to Other Systems
- Payment Processing Integration
- GitHub Integration Dependencies
- Admin Workflow
Ask Devin about godeep.wiki-jb