Skip to content

Remove dead Stripe webhook and refresh session after checkout#17

Open
chaance wants to merge 2 commits into
mainfrom
devin/1784313298-remove-dead-stripe-webhook
Open

Remove dead Stripe webhook and refresh session after checkout#17
chaance wants to merge 2 commits into
mainfrom
devin/1784313298-remove-dead-stripe-webhook

Conversation

@chaance

@chaance chaance commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

scripts/setup.ts created a Stripe webhook endpoint (subscribed to checkout.session.completed) pointed at <convex-deployment>.site/stripe-webhook, but convex/http.ts only ever registers a /workos-webhook route — there is no /stripe-webhook handler (git history confirms one was never committed). So setup wired up a dead Stripe endpoint, and the code implied you must run your own Stripe listener.

Entitlement sync does not depend on that webhook. With the WorkOS Stripe integration enabled, WorkOS listens to Stripe and provisions entitlements onto the org's access token once stripeCustomerId is set on the org (already done in src/app/api/subscribe/route.ts). The app's only responsibility is to refresh the session after checkout so new entitlements appear in the token immediately rather than on next login.

This PR removes the misleading dead webhook and makes the post-checkout refresh explicit.

Changes

  • scripts/setup.ts: Removed setupStripeWebhook() and its call. It also set STRIPE_API_KEY in Convex (read by convex/stripe.ts), so that one step is preserved in a small setStripeApiKeyInConvex() helper. STRIPE_WEBHOOK_SECRET is no longer set (only the dead handler would have used it). The .cloud.site webhookUrl derivation is kept — it still feeds setupWorkOSWebhook().

    - await setupStripeWebhook(STRIPE_API_KEY, webhookUrl + '/stripe-webhook');
    + await setStripeApiKeyInConvex(STRIPE_API_KEY);
      await setupWorkOSWebhook(WORKOS_API_KEY, webhookUrl + '/workos-webhook');
  • src/app/api/subscribe/route.ts: Added a comment explaining WorkOS provisions entitlements automatically (no app-side Stripe webhook), and tagged the checkout success_url with a marker:

    - success_url: `${process.env.NEXT_PUBLIC_BASE_URL}/dashboard`,
    + success_url: `${process.env.NEXT_PUBLIC_BASE_URL}/dashboard?checkout=success`,
  • src/app/dashboard/page.tsx + new checkout-success-refresh.tsx: When ?checkout=success is present, refresh the AuthKit session so new entitlements land in the token now.

    • refreshAuthkitSession calls refreshSession(), which sets cookies — that throws during a Server Component render ("Cookies can only be modified in a Server Action or Route Handler"). The existing repo pattern (src/app/dashboard/audit-logs/page.tsx) invokes it from a client component in useEffect, so I matched that: a small CheckoutSuccessRefresh client component runs the action on mount, then router.replace('/dashboard') + router.refresh() to drop the marker and re-render with fresh entitlements. Verified refreshAuthkitSession() returns Promise<string> (a stringified session) and takes no args.
  • README.md: Added one line — entitlement sync is handled by WorkOS; the only app responsibility is refreshing the session after checkout; no Stripe webhook required for entitlements.

Incidental (unrelated) build fix

next build was already failing on main (verified on a clean tree) with an error in src/app/router/route.ts:

cannot reassign to a variable declared with `const`

session was destructured with const then reassigned under a @ts-expect-error. Turbopack treats const reassignment as a hard error, so the build was red. Changed const { session }let { session } and removed the now-unused @ts-expect-error directive. This is unrelated to the webhook work but was necessary to get a green lint/build. Happy to split it out if preferred.

Validation

  • eslint — passes
  • tsc --noEmit — passes
  • next build — passes (with env vars set)

Optional follow-up (not required for entitlements)

If app-side Stripe events are ever wanted (owning your own DB records, receipts, dunning, etc.), you could add a real /stripe-webhook Convex HTTP route and re-introduce the setup step + STRIPE_WEBHOOK_SECRET. Note convex/stripe.ts already contains an unused verifyStripeWebhook action that such a handler would use. This is explicitly not required for entitlements and intentionally left out of this PR.

Docs: https://workos.com/docs/authkit/add-ons/stripe · https://workos.com/docs/user-management/entitlements

Link to Devin session: https://app.devin.ai/sessions/0b653638165f422a9cb0808ecc0aff04
Requested by: @chaance

The setup script created a Stripe webhook endpoint subscribed to
checkout.session.completed pointed at <deployment>.site/stripe-webhook,
but convex/http.ts only registers /workos-webhook. There is no
/stripe-webhook handler, so that endpoint was dead.

Entitlement sync does not depend on it: WorkOS provisions entitlements
onto the org access token via the Stripe integration once the org has a
stripeCustomerId. The app only needs to refresh the session after
checkout so new entitlements appear immediately.

- setup.ts: drop setupStripeWebhook(); keep STRIPE_API_KEY set in Convex
- subscribe route: tag success_url with ?checkout=success
- dashboard: refresh AuthKit session when ?checkout=success
- router/route.ts: fix pre-existing const reassignment breaking build

Co-Authored-By: chance <chance.strickland@gmail.com>
@chaance chaance self-assigned this Jul 17, 2026
@devin-ai-integration

Copy link
Copy Markdown
Original prompt from chance

SYSTEM:
=== BEGIN THREAD HISTORY ===
Devin AI (U08G1NMS6AV): New Assistant Thread

Devin (U08G1NMS6AV): Hi! I'm Devin. How can I help you today?

<most_recent_message>
Chance Strickland (U07ABH2GKTL): Fix the dead Stripe webhook in workos/next-b2b-starter-kit

You are working on the workos/next-b2b-starter-kit repo (Next.js + Convex + Stripe + WorkOS AuthKit). Open a PR against main.

Background / confirmed findings (verify before changing):
scripts/setup.ts calls setupStripeWebhook(), which creates a Stripe webhook endpoint subscribed to checkout.session.completed, pointed at &amp;lt;convex-deployment&amp;gt;.site/stripe-webhook.
convex/http.ts only ever registers a /workos-webhook route (handling WorkOS user.* and organization.* events). There is no /stripe-webhook handler — git history confirms one was never committed. So the Stripe webhook created during setup points at a route that doesn't exist and is effectively dead.
• Entitlement sync does NOT depend on this webhook. With the WorkOS Stripe (Entitlements) integration enabled, WorkOS listens to Stripe via Stripe Connect and provisions entitlements onto the org's access token. The app's only responsibility is to refresh the session after checkout so new entitlements appear in the token immediately (otherwise they appear on next login/refresh). Confirm against the current docs: <https://workos.com/docs/authkit/add-ons/stripe|workos.com/docs/authkit/add-ons/stripe> and <https://workos.com/docs/user-management/entitlements|workos.com/docs/user-management/entitlements>
• The working billing flow lives in src/app/api/subscribe/route.ts: it creates the org, creates a Stripe customer, writes stripeCustomerId back onto the WorkOS org (the step that enables entitlement provisioning), then creates a Checkout Session redirecting to /dashboard. A refreshAuthkitSession.ts server action already exists in src/actions/.
Goal: Remove the misleading dead Stripe webhook and make the post-che... (8379 chars truncated...)

@devin-ai-integration

Copy link
Copy Markdown

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@greptile-apps

greptile-apps Bot commented Jul 17, 2026

Copy link
Copy Markdown

Greptile Summary

This PR removes a dead Stripe webhook setup (the /stripe-webhook Convex route was never implemented) and instead wires up a proper post-checkout session refresh so new WorkOS entitlements appear in the access token immediately. It also fixes a pre-existing Turbopack build failure caused by a const reassignment in the router route.

  • scripts/setup.ts: setupStripeWebhook() is removed and replaced with a focused setStripeApiKeyInConvex() helper; STRIPE_WEBHOOK_SECRET is no longer set since no app-side Stripe webhook handler exists.
  • src/app/dashboard/checkout-success-refresh.tsx + page.tsx: A new CheckoutSuccessRefresh client component runs refreshAuthkitSession() in a useEffect on mount when ?checkout=success is present, then calls router.replace('/dashboard') + router.refresh() to drop the marker and re-render with fresh entitlements.
  • src/app/router/route.ts: const { session } changed to let { session } and the now-unnecessary @ts-expect-error directive removed, resolving the Turbopack build failure.

Confidence Score: 5/5

Safe to merge — removes genuinely dead code, adds a well-guarded post-checkout session refresh, and fixes a pre-existing Turbopack build failure.

All six changed files are straightforward and correct. The new CheckoutSuccessRefresh component handles cancellation and failure gracefully. The searchParams type matches the Next.js 15 async API. The setup script correctly preserves the Stripe API key step while discarding the webhook wiring that pointed at a non-existent handler. No logic gaps were found across the changed paths.

No files require special attention.

Important Files Changed

Filename Overview
src/app/dashboard/checkout-success-refresh.tsx New client component that refreshes the AuthKit session on mount when arriving from a successful checkout; uses a cancellation flag to prevent navigation after unmount, and deliberately drops the URL marker even on refresh failure to avoid retry loops.
src/app/dashboard/page.tsx Adds searchParams prop (correctly typed as Promise for Next.js 15+) and conditionally renders CheckoutSuccessRefresh when ?checkout=success is present; admin guard remains in place before the param is read.
scripts/setup.ts Removes the dead setupStripeWebhook() call and extracts STRIPE_API_KEY persistence into a focused setStripeApiKeyInConvex() helper; STRIPE_WEBHOOK_SECRET is no longer set since no app-side Stripe webhook handler exists.
src/app/router/route.ts Fixes Turbopack build failure by changing const to let for session reassignment and removing the now-unnecessary @ts-expect-error directive.
src/app/api/subscribe/route.ts Adds ?checkout=success to the Stripe Checkout success_url so the dashboard can detect the return and trigger a session refresh; a clarifying comment explains that no app-side Stripe webhook is needed for entitlements.
README.md Adds a short note clarifying that WorkOS handles entitlement sync and no app-side Stripe webhook is required.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant Stripe
    participant Dashboard as /dashboard (Server)
    participant Client as CheckoutSuccessRefresh (Client)
    participant Action as refreshAuthkitSession (Server Action)
    participant WorkOS

    User->>Stripe: Completes checkout
    Stripe->>User: "Redirect to /dashboard?checkout=success"
    User->>Dashboard: "GET /dashboard?checkout=success"
    Dashboard->>Dashboard: withAuth() – verify admin session
    Dashboard->>Dashboard: "searchParams → checkout === 'success'"
    Dashboard->>User: Render page + mount CheckoutSuccessRefresh
    Client->>Action: refreshAuthkitSession()
    Action->>WorkOS: "refreshSession({ ensureSignedIn: true })"
    WorkOS-->>Action: New session with entitlements in token
    Action-->>Client: JSON.stringify(session)
    Client->>User: "router.replace('/dashboard') — drops ?checkout=success"
    Client->>Dashboard: router.refresh() — re-render with fresh session
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant Stripe
    participant Dashboard as /dashboard (Server)
    participant Client as CheckoutSuccessRefresh (Client)
    participant Action as refreshAuthkitSession (Server Action)
    participant WorkOS

    User->>Stripe: Completes checkout
    Stripe->>User: "Redirect to /dashboard?checkout=success"
    User->>Dashboard: "GET /dashboard?checkout=success"
    Dashboard->>Dashboard: withAuth() – verify admin session
    Dashboard->>Dashboard: "searchParams → checkout === 'success'"
    Dashboard->>User: Render page + mount CheckoutSuccessRefresh
    Client->>Action: refreshAuthkitSession()
    Action->>WorkOS: "refreshSession({ ensureSignedIn: true })"
    WorkOS-->>Action: New session with entitlements in token
    Action-->>Client: JSON.stringify(session)
    Client->>User: "router.replace('/dashboard') — drops ?checkout=success"
    Client->>Dashboard: router.refresh() — re-render with fresh session
Loading

Reviews (2): Last reviewed commit: "Handle refresh failure in CheckoutSucces..." | Re-trigger Greptile

Comment thread src/app/dashboard/checkout-success-refresh.tsx
Co-Authored-By: chance <chance.strickland@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants