DeepWiki

04.c - Payment-to-Installation-Linking

Relevant source files

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.


The payment-to-installation linking system bridges two separate workflows that occur in sequence:

  1. Payment Flow: User pays $10 → Stripe redirects to success page with session_id
  2. 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.

Sources: app/success/page.tsx L10-L70

app/api/auth/github/callback/route.ts L1-L149

CLAUDE.md L44-L50


The OAuth state parameter serves dual purposes:

  1. CSRF Protection: Random token prevents unauthorized OAuth initiations
  2. Session Correlation: Embeds session_id for 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')

The encoded state is stored in the github_oauth_state cookie during OAuth initiation:

Cookie NameValueExpiryPurpose
github_oauth_stateBase64-encoded JSON with sessionId1 hourCSRF protection + session correlation
github_access_tokenUser OAuth token24 hoursUser dashboard access (set in callback)

Sources: app/api/auth/github/callback/route.ts L23-L37

CLAUDE.md L48-L50


The success page receives the session_id from Stripe's redirect and embeds it in the GitHub OAuth initiation URL.

app/success/page.tsx L10-L19

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.

app/success/page.tsx L54-L59

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

CLAUDE.md L47-L48


The OAuth callback receives the state parameter from GitHub and decodes it to extract the original session_id.

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.

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").

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:

FieldSourcePurpose
Stripe Session IDDecoded from state cookieLinks to Stripe payment
Installation IDGitHub callback URL parameterLinks to GitHub App installation
GitHub UsernameGitHub API user responseIdentifies customer account
GitHub EmailGitHub API user responseCustomer contact (may be null)
GitHub User IDGitHub API user responseUnique GitHub identifier
User NameGitHub API user responseCustomer display name
Setup ActionGitHub callback URL parameterInstall type (usually "install")
TimestampSystem timeWhen 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


The callback sends a structured notification to ntfy.sh that includes a "Match ID" for quick correlation.

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'

app/api/auth/github/callback/route.ts L114-L132

The ntfy notification includes:

FieldDescription
Installation IDFull installation_id from GitHub
CustomerEmail or username
Match IDLast 12 chars of session_id
TimeNew York timezone timestamp
Automation messageInstructions 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


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_id in 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


Since the system lacks a database, the owner must manually correlate payments with installations using logs.

  1. By Session ID: Search Vercel logs for the full session_id from Stripe dashboard
  2. By Match ID: Search for the last 12 characters if full ID is unavailable
  3. By Timestamp: Cross-reference payment time with log timestamps
  4. By Customer Email: Search for GitHub email or username

Sources: CLAUDE.md L44-L50

app/api/auth/github/callback/route.ts L99-L137


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')

app/api/auth/github/callback/route.ts L39-L50

The callback uses a try-catch block to safely decode the state:

  1. Convert base64 string to Buffer: Buffer.from(state, 'base64')
  2. Convert Buffer to UTF-8 string: .toString('utf-8')
  3. Parse JSON string: JSON.parse(...)
  4. 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


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.

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

CLAUDE.md L48-L49


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
  • stripeSessionId is set to null
  • OAuth flow continues
  • Log entry shows "NOT PROVIDED" for Stripe Session ID
  • Installation still functions but requires manual matching via timestamp

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

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


The state parameter serves as a CSRF token by:

  1. Being randomly generated during OAuth initiation
  2. Stored server-side in httpOnly cookie
  3. Verified in callback against GitHub's response
  4. Single-use (deleted after verification)
AttributeValuePurpose
httpOnlytruePrevents JavaScript access
secureproductionHTTPS-only in production
path/Accessible to all routes
maxAgeVariableLimits exposure window

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

CLAUDE.md L113-L116


The linking mechanism depends on:

  • Stripe Checkout (4.1): Generates initial session_id
  • Success Page (2.2): Captures and forwards session_id

The mechanism enables:

  • Installation Token Generation (6.2): Uses installation_id to create tokens
  • Repository Cloning (5.2): Requires correlated installation_id

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

Ask Devin about godeep.wiki-jb

04.c - Payment-to-Installation-Linking | DeepWiki | godeep.wiki