Troubleshooting Stripe and Convex
If checkout completes but access does not, assume configuration drift before you assume application logic is wrong.
Run the built-in health checks
Start here:
bun run stripe:listen:dev
bun run stripe:dev:doctor
bun run clerk:test-user:prepare:entitled
bun run stripe:dev:verifyThese commands tell you more, faster, than hand-inspecting random env files.
The most common failures
1. Missing Clerk convex JWT template
If that template is missing or misnamed, authenticated server reads can fail even while the user appears signed in.
2. Clerk issuer / Convex mismatch
If Clerk and Convex disagree on the issuer/domain, access checks can fail after payment.
3. Stripe secret drift
These values must match in both places:
.env.local- the active Convex env
The dev helper syncs both values. If the listener rotated the webhook secret, restart the app.
4. Wrong Stripe account or wrong pinned CLI profile
The app STRIPE_SECRET_KEY is the runtime source of truth. The Stripe CLI profile is the local guardrail.
In this repo, bun run stripe:dev:doctor compares the app account against the pinned Stripe CLI project ship-by-sunday. If the doctor says the pinned profile is missing or mismatched, repair it with:
stripe login --project-name ship-by-sundayIf those differ, you can see real Stripe events without the starter ever receiving a valid matching webhook.
5. Webhook endpoint not actually registered
If Stripe does not reach POST {NEXT_PUBLIC_CONVEX_SITE_URL}/stripe/webhook, checkout can finish while access grant never happens.
If the app reaches Stripe and returns 202 or 200 but nothing shows up in Convex-side observability or access state, verify whether the app also needs NEXT_PUBLIC_CONVEX_SITE_URL for HTTP actions. In this repo, .convex.cloud is not enough for observability ingest or other Convex HTTP-action endpoints when the deployment serves those on .convex.site.
6. Payment succeeded but access still did not grant
If the Stripe event exists and the buyer still cannot reach /start, check:
- whether the webhook used the current signing secret
- whether Convex accepted the same webhook secret and Stripe secret key
- whether the browser still has the pending checkout session cookie if the buyer recovered from a lost Stripe return tab
- whether the signed-in account’s verified primary email matches the Stripe checkout email before the claim step runs
Fast debug order
Use this sequence:
- run the Stripe listener
- run
stripe:dev:doctor - if
/access-requiredis visible, read the signed-in email shown on that page before assuming the webhook failed - confirm secret parity
- confirm the pinned Stripe CLI project
ship-by-sundaymatches the app account - confirm Clerk
convextemplate - confirm the app and Convex agree on both
NEXT_PUBLIC_CONVEX_URLandNEXT_PUBLIC_CONVEX_SITE_URLwhen HTTP actions are involved - rerun a real local checkout against your hosted Convex dev deployment
What success looks like
Stripe and Convex are aligned when:
- Stripe forwards
checkout.session.completed - the webhook accepts the signature
- Convex writes or updates the access grant
- the buyer leaves
/checkout/successand lands in/start