[GH-ISSUE #8255] Stripe Success-URL Parameter broken #11031

Closed
opened 2026-04-13 07:24:28 -05:00 by GiteaMirror · 5 comments
Owner

Originally created by @JE4GLE on GitHub (Mar 1, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/8255

Originally assigned to: @bytaesu on GitHub.

To Reproduce

Start a subscription using:

    const { data, error } = await authClient.subscription.upgrade({
		plan: 'test',
		successUrl: 'http://localhost:5173/account/billing/success?id={CHECKOUT_SESSION_ID}',
		cancelUrl: 'http://localhost:5173/account/billing/cancel?id={CHECKOUT_SESSION_ID}',
	});

Current vs. Expected behavior

Expected

I get redirected to http://localhost:5173/account/billing/cancel?id= with my session id next

Actually

I get redirected to http://localhost:5173/account/billing/success?id=%7BCHECKOUT_SESSION_ID%7D

What version of Better Auth are you using?

1.5.0

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.3.0: Wed Jan 28 20:47:03 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T6031",
    "release": "25.3.0",
    "cpuCount": 16,
    "cpuModel": "Apple M3 Max",
    "totalMemory": "64.00 GB",
    "freeMemory": "0.56 GB"
  },
  "node": {
    "version": "v25.2.1",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.6.2"
  },
  "frameworks": [
    {
      "name": "svelte",
      "version": "^5.53.6"
    },
    {
      "name": "@sveltejs/kit",
      "version": "^2.53.4"
    }
  ],
  "databases": [
    {
      "name": "postgres",
      "version": "^3.4.8"
    },
    {
      "name": "drizzle",
      "version": "^0.45.1"
    }
  ],
  "betterAuth": {
    "version": "Unknown",
    "config": null,
    "error": "Cannot find module '$app/environment'\nRequire stack:\n- /src/lib/server/auth.ts"
  }
}

Which area(s) are affected? (Select all that apply)

Package

Additional context

The internal successUrl of the Stripe checkout is http://localhost:5173/api/auth/subscription/success?callbackURL=http%3A%2F%2Flocalhost%3A5173%2Faccount%2Fbilling%2Fsuccess%3Fid%3D%7BCHECKOUT_SESSION_ID%7D&checkoutSessionId={CHECKOUT_SESSION_ID}
It seems like Better-Auth does its own internal handling of the CHECKOUT_SESSION_ID and therefore preventing the custom success page to work

Cancel works as expected.

Originally created by @JE4GLE on GitHub (Mar 1, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/8255 Originally assigned to: @bytaesu on GitHub. ## To Reproduce Start a subscription using: ```typescript const { data, error } = await authClient.subscription.upgrade({ plan: 'test', successUrl: 'http://localhost:5173/account/billing/success?id={CHECKOUT_SESSION_ID}', cancelUrl: 'http://localhost:5173/account/billing/cancel?id={CHECKOUT_SESSION_ID}', }); ``` ## Current vs. Expected behavior ### Expected I get redirected to `http://localhost:5173/account/billing/cancel?id=` with my session id next ### Actually I get redirected to `http://localhost:5173/account/billing/success?id=%7BCHECKOUT_SESSION_ID%7D` ## What version of Better Auth are you using? 1.5.0 ## System info ```json { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.3.0: Wed Jan 28 20:47:03 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T6031", "release": "25.3.0", "cpuCount": 16, "cpuModel": "Apple M3 Max", "totalMemory": "64.00 GB", "freeMemory": "0.56 GB" }, "node": { "version": "v25.2.1", "env": "development" }, "packageManager": { "name": "npm", "version": "11.6.2" }, "frameworks": [ { "name": "svelte", "version": "^5.53.6" }, { "name": "@sveltejs/kit", "version": "^2.53.4" } ], "databases": [ { "name": "postgres", "version": "^3.4.8" }, { "name": "drizzle", "version": "^0.45.1" } ], "betterAuth": { "version": "Unknown", "config": null, "error": "Cannot find module '$app/environment'\nRequire stack:\n- /src/lib/server/auth.ts" } } ``` ## Which area(s) are affected? (Select all that apply) Package ## Additional context The internal successUrl of the Stripe checkout is `http://localhost:5173/api/auth/subscription/success?callbackURL=http%3A%2F%2Flocalhost%3A5173%2Faccount%2Fbilling%2Fsuccess%3Fid%3D%7BCHECKOUT_SESSION_ID%7D&checkoutSessionId={CHECKOUT_SESSION_ID}` It seems like Better-Auth does its own internal handling of the CHECKOUT_SESSION_ID and therefore preventing the custom success page to work Cancel works as expected.
GiteaMirror added the lockedbug labels 2026-04-13 07:24:28 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Mar 1, 2026):

This is actually expected behavior based on how Better Auth's Stripe integration is designed, though I understand it's not what you want.

What's happening: When you pass {CHECKOUT_SESSION_ID} in your successUrl, it gets URL-encoded when embedded in Better Auth's internal callback structure. The internal URL becomes:

/subscription/success?callbackURL=...%7BCHECKOUT_SESSION_ID%7D&checkoutSessionId={CHECKOUT_SESSION_ID}

Stripe only replaces the literal {CHECKOUT_SESSION_ID} in the outermost URL (the checkoutSessionId parameter), but can't see or replace the encoded version inside callbackURL.

Workaround: Better Auth currently doesn't pass the checkout session ID to your callback URL. You can work around this by:

  1. Store state in your database before initiating checkout, then retrieve it using the authenticated user's session after redirect
  2. Use the getCheckoutSessionParams hook to store custom metadata with Stripe, which you can retrieve later via the Stripe API

This could be considered a feature request - the success endpoint redirect could potentially be modified to append the checkout session ID to your callback URL automatically.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3980196880 --> @dosubot[bot] commented on GitHub (Mar 1, 2026): <!-- Answer --> This is actually expected behavior based on how Better Auth's Stripe integration is designed, though I understand it's not what you want. **What's happening:** When you pass `{CHECKOUT_SESSION_ID}` in your `successUrl`, it gets URL-encoded when embedded in Better Auth's internal callback structure. The internal URL becomes: ``` /subscription/success?callbackURL=...%7BCHECKOUT_SESSION_ID%7D&checkoutSessionId={CHECKOUT_SESSION_ID} ``` Stripe only replaces the literal `{CHECKOUT_SESSION_ID}` in the outermost URL (the `checkoutSessionId` parameter), but can't see or replace the encoded version inside `callbackURL`. **Workaround:** Better Auth currently doesn't pass the checkout session ID to your callback URL. You can work around this by: 1. **Store state in your database** before initiating checkout, then retrieve it using the authenticated user's session after redirect 2. **Use the `getCheckoutSessionParams` hook** to store custom metadata with Stripe, which you can retrieve later via the Stripe API This could be considered a feature request - the [success endpoint redirect](https://github.com/better-auth/better-auth/blob/b38eb05fc0df14bc2d0f53b8af511401f6de01c1/packages/stripe/src/routes.ts#L1781) could potentially be modified to append the checkout session ID to your callback URL automatically. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/342beb5b-db72-41bc-98c7-bf70cae7e4c4?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/342beb5b-db72-41bc-98c7-bf70cae7e4c4?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/342beb5b-db72-41bc-98c7-bf70cae7e4c4?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/342beb5b-db72-41bc-98c7-bf70cae7e4c4?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/342beb5b-db72-41bc-98c7-bf70cae7e4c4?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/342beb5b-db72-41bc-98c7-bf70cae7e4c4?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/342beb5b-db72-41bc-98c7-bf70cae7e4c4?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20is%20actually%20expected%20behavior%20based%20on%20how%20Better%20Auth%27s%20Stripe%20integration%20is%20designed%2C%20though%20I%20understand%20it%27s%20not%20what%20you%20want.%0A%0A%2A%2AWhat%27s%20happening%3A%2A%2A%20When%20you%20pass%20%60%7BCHECKOUT_SESSION_ID%7D%60%20in%20your%20%60successUrl%60%2C%20it%20gets%20URL-encoded%20when%20embedded%20in%20Better%20Auth%27s%20internal%20callback%20structure.%20The%20internal%20URL%20becomes%3A%0A%60%60%60%0A/subscription/success%3FcallbackURL%3D...%257BCHECKOUT_SESSION_ID%257D%26checkoutSessionId%3D%7BCHECKOUT_SESSION_ID%7D%0A%60%60%60%0A%0AStripe%20only%20replaces%20the%20literal%20%60%7BCHECKOUT_SESSION_ID%7D%60%20in%20the%20outermost%20URL%20%28the%20%60checkoutSessionId%60%20parameter%29%2C%20but%20can%27t%20see%20or%20replace%20the%20encoded%20version%20inside%20%60callbackURL%60.%0A%0A%2A%2AWorkaround%3A%2A%2A%20Better%20Auth%20currently%20doesn%27t%20pass%20the%20checkout%20session%20ID%20to%20your%20callback%20URL.%20You%20can%20work%20around%20this%20by%3A%0A%0A1.%20%2A%2AStore%20state%20in%20your%20database%2A%2A%20before%20initiating%20checkout%2C%20then%20retrieve%20it%20using%20the%20authenticated%20user%27s%20session%20after%20redirect%0A2.%20%2A%2AUse%20the%20%60getCheckoutSessionParams%60%20hook%2A%2A%20to%20store%20custom%20metadata%20with%20Stripe%2C%20which%20you%20can%20retrieve%20later%20via%20the%20Stripe%20API%0A%0AThis%20could%20be%20considered%20a%20feature%20request%20-%20the%20%5Bsuccess%20endpoint%20redirect%5D%28https%3A//github.com/better-auth/better-auth/blob/b38eb05fc0df14bc2d0f53b8af511401f6de01c1/packages/stripe/src/routes.ts%23L1781%29%20could%20potentially%20be%20modified%20to%20append%20the%20checkout%20session%20ID%20to%20your%20callback%20URL%20automatically.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/8255)
Author
Owner

@JE4GLE commented on GitHub (Mar 1, 2026):

@dosu None of the workarounds work. How do I, with potentially no other user information whatsoever, can get any kind of information regarding the checkout without the CHECKOUT_SESSION_ID? A solution would be for better-auth to forward the parameter to restore the default Stripe behavior. I don't see any reliable workaround for this issue.

<!-- gh-comment-id:3980514631 --> @JE4GLE commented on GitHub (Mar 1, 2026): @dosu None of the workarounds work. How do I, with potentially no other user information whatsoever, can get any kind of information regarding the checkout without the `CHECKOUT_SESSION_ID`? A solution would be for better-auth to forward the parameter to restore the default Stripe behavior. I don't see any **reliable** workaround for this issue.
Author
Owner

@dosubot[bot] commented on GitHub (Mar 1, 2026):

You're right - the current implementation simply redirects to your callback URL without appending any parameters, even though the checkoutSessionId is available at the success endpoint. There's no hook or configuration to change this behavior.

One option that might work for your case: If you're building an SPA, you can use the disableRedirect option which returns JSON instead of redirecting. Then you could retrieve the checkout session ID from the response and handle the navigation client-side.

Otherwise, this does seem like a valid feature request - Better Auth could append the checkoutSessionId to the callback URL before redirecting. The success endpoint already has this data, it just doesn't forward it. A configuration option like passCheckoutSessionId: true would restore Stripe's default behavior while maintaining backward compatibility.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3980524158 --> @dosubot[bot] commented on GitHub (Mar 1, 2026): <!-- Answer --> You're right - the current implementation simply [redirects to your callback URL without appending any parameters](https://github.com/better-auth/better-auth/blob/4904f46651deec132faf0f5a7bc24b5cb33d1337/packages/stripe/src/routes.ts#L1781), even though the `checkoutSessionId` is [available at the success endpoint](https://github.com/better-auth/better-auth/blob/4904f46651deec132faf0f5a7bc24b5cb33d1337/packages/stripe/src/routes.ts#L1650-L1651). There's no hook or configuration to change this behavior. **One option that might work for your case:** If you're building an SPA, you can use the [`disableRedirect` option](https://github.com/better-auth/better-auth/blob/4904f46651deec132faf0f5a7bc24b5cb33d1337/packages/stripe/src/routes.ts#L221-L226) which returns JSON instead of redirecting. Then you could retrieve the checkout session ID from the response and handle the navigation client-side. Otherwise, this does seem like a valid feature request - Better Auth could append the `checkoutSessionId` to the callback URL before redirecting. The [success endpoint already has this data](https://github.com/better-auth/better-auth/blob/4904f46651deec132faf0f5a7bc24b5cb33d1337/packages/stripe/src/routes.ts#L1659-L1667), it just doesn't forward it. A configuration option like `passCheckoutSessionId: true` would restore Stripe's default behavior while maintaining backward compatibility. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/8fac5a24-458e-41c5-9c00-864804e69368?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/8fac5a24-458e-41c5-9c00-864804e69368?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/8fac5a24-458e-41c5-9c00-864804e69368?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/8fac5a24-458e-41c5-9c00-864804e69368?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/8fac5a24-458e-41c5-9c00-864804e69368?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/8fac5a24-458e-41c5-9c00-864804e69368?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/8fac5a24-458e-41c5-9c00-864804e69368?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=You%27re%20right%20-%20the%20current%20implementation%20simply%20%5Bredirects%20to%20your%20callback%20URL%20without%20appending%20any%20parameters%5D%28https%3A//github.com/better-auth/better-auth/blob/4904f46651deec132faf0f5a7bc24b5cb33d1337/packages/stripe/src/routes.ts%23L1781%29%2C%20even%20though%20the%20%60checkoutSessionId%60%20is%20%5Bavailable%20at%20the%20success%20endpoint%5D%28https%3A//github.com/better-auth/better-auth/blob/4904f46651deec132faf0f5a7bc24b5cb33d1337/packages/stripe/src/routes.ts%23L1650-L1651%29.%20There%27s%20no%20hook%20or%20configuration%20to%20change%20this%20behavior.%0A%0A%2A%2AOne%20option%20that%20might%20work%20for%20your%20case%3A%2A%2A%20If%20you%27re%20building%20an%20SPA%2C%20you%20can%20use%20the%20%5B%60disableRedirect%60%20option%5D%28https%3A//github.com/better-auth/better-auth/blob/4904f46651deec132faf0f5a7bc24b5cb33d1337/packages/stripe/src/routes.ts%23L221-L226%29%20which%20returns%20JSON%20instead%20of%20redirecting.%20Then%20you%20could%20retrieve%20the%20checkout%20session%20ID%20from%20the%20response%20and%20handle%20the%20navigation%20client-side.%0A%0AOtherwise%2C%20this%20does%20seem%20like%20a%20valid%20feature%20request%20-%20Better%20Auth%20could%20append%20the%20%60checkoutSessionId%60%20to%20the%20callback%20URL%20before%20redirecting.%20The%20%5Bsuccess%20endpoint%20already%20has%20this%20data%5D%28https%3A//github.com/better-auth/better-auth/blob/4904f46651deec132faf0f5a7bc24b5cb33d1337/packages/stripe/src/routes.ts%23L1659-L1667%29%2C%20it%20just%20doesn%27t%20forward%20it.%20A%20configuration%20option%20like%20%60passCheckoutSessionId%3A%20true%60%20would%20restore%20Stripe%27s%20default%20behavior%20while%20maintaining%20backward%20compatibility.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/8255)
Author
Owner

@bytaesu commented on GitHub (Mar 12, 2026):

Hi @JE4GLE,

Thanks for letting us know. I just opened a PR!

<!-- gh-comment-id:4046553176 --> @bytaesu commented on GitHub (Mar 12, 2026): Hi @JE4GLE, Thanks for letting us know. I just opened a PR!
Author
Owner

@github-actions[bot] commented on GitHub (Mar 31, 2026):

This issue has been locked as it was closed more than 7 days ago. If you're experiencing a similar problem or you have additional context, please open a new issue and reference this one.

<!-- gh-comment-id:4165921614 --> @github-actions[bot] commented on GitHub (Mar 31, 2026): This issue has been locked as it was closed more than 7 days ago. If you're experiencing a similar problem or you have additional context, please open a new issue and reference this one.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#11031