stripe: authclient uses the wrong request method for billing-portal #1909

Closed
opened 2026-03-13 09:12:13 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @danielepintore on GitHub (Sep 12, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

When i run this code:

const { data, error } = await authClient.subscription.billingPortal();

it generates a GET request instead of a POST so i get 404 not found. I have to manually edit fetchOptions to use a POST request. Here the docs page.

fetchOptions: {
  method: "POST"
}}) 
"@better-auth/stripe": "^1.3.9",
"better-auth": "^1.3.9",
"stripe": "^18.5.0",

Current vs. Expected behavior

Generate a POST request instead of a GET

What version of Better Auth are you using?

1.3.9

System info

{
  "system": {
    "platform": "linux",
    "arch": "x64",
    "version": "#1 SMP PREEMPT_DYNAMIC Thu, 04 Sep 2025 17:54:55 +0000",
    "release": "6.12.45-1-lts",
    "cpuCount": 12,
    "cpuModel": "13th Gen Intel(R) Core(TM) i7-1355U",
    "totalMemory": "31.02 GB",
    "freeMemory": "12.04 GB"
  },
  "node": {
    "version": "v24.7.0",
    "env": "development"
  },
  "packageManager": {
    "name": "npm",
    "version": "11.6.0"
  },
  "frameworks": [
    {
      "name": "react",
      "version": "^19.1.1"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    },
    {
      "name": "drizzle",
      "version": "^0.44.4"
    }
  ],
  "betterAuth": {
    "version": "0.0.1",
    "config": null
  }
}

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

Client

Auth config (if applicable)

// Client Auth
export const authClient = createAuthClient({
	baseURL: envClient.APP_URL,
    plugins: [
        stripeClient({
            subscription: true
        })
    ],
});

------------------------------------------------------------

// Server auth
...
    plugins: [
        stripe({
            stripeClient,
            stripeWebhookSecret: env.STRIPE.STRIPE_WEBHOOK_SECRET,
            createCustomerOnSignUp: true,
            subscription: {
                enabled: true,
                plans: [
                    {
                        name: basicPlan.id,
                        priceId: env.STRIPE.STRIPE_SUBSCRIPTION_PRICE_ID,
                        freeTrial: {
                            days: basicPlan.trialPeriodDays || 0,
                        },
                    },
                ],
            },
        }),
        reactStartCookies()
    ],
....

Additional context

No response

Originally created by @danielepintore on GitHub (Sep 12, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce When i run this code: ``` const { data, error } = await authClient.subscription.billingPortal(); ``` it generates a GET request instead of a POST so i get 404 not found. I have to manually edit fetchOptions to use a POST request. [Here](https://www.better-auth.com/docs/plugins/stripe#creating-billing-portal-sessions) the docs page. ``` fetchOptions: { method: "POST" }}) ``` ``` "@better-auth/stripe": "^1.3.9", "better-auth": "^1.3.9", "stripe": "^18.5.0", ``` ### Current vs. Expected behavior Generate a POST request instead of a GET ### What version of Better Auth are you using? 1.3.9 ### System info ```bash { "system": { "platform": "linux", "arch": "x64", "version": "#1 SMP PREEMPT_DYNAMIC Thu, 04 Sep 2025 17:54:55 +0000", "release": "6.12.45-1-lts", "cpuCount": 12, "cpuModel": "13th Gen Intel(R) Core(TM) i7-1355U", "totalMemory": "31.02 GB", "freeMemory": "12.04 GB" }, "node": { "version": "v24.7.0", "env": "development" }, "packageManager": { "name": "npm", "version": "11.6.0" }, "frameworks": [ { "name": "react", "version": "^19.1.1" } ], "databases": [ { "name": "pg", "version": "^8.16.3" }, { "name": "drizzle", "version": "^0.44.4" } ], "betterAuth": { "version": "0.0.1", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Client ### Auth config (if applicable) ```typescript // Client Auth export const authClient = createAuthClient({ baseURL: envClient.APP_URL, plugins: [ stripeClient({ subscription: true }) ], }); ------------------------------------------------------------ // Server auth ... plugins: [ stripe({ stripeClient, stripeWebhookSecret: env.STRIPE.STRIPE_WEBHOOK_SECRET, createCustomerOnSignUp: true, subscription: { enabled: true, plans: [ { name: basicPlan.id, priceId: env.STRIPE.STRIPE_SUBSCRIPTION_PRICE_ID, freeTrial: { days: basicPlan.trialPeriodDays || 0, }, }, ], }, }), reactStartCookies() ], .... ``` ### Additional context _No response_
GiteaMirror added the plugin label 2026-03-13 09:12:13 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Sep 12, 2025):

Hi @danielepintore! I'm Dosu and I’m helping the better-auth team.

This is a known limitation: the client-side method for creating Stripe billing portal sessions (authClient.subscription.billingPortal()) defaults to a GET request unless you manually set fetchOptions.method to POST. The server-side endpoint is correctly configured to expect POST, but there’s currently no built-in config or automatic way to make the client use POST for this action without the manual workaround you described. This behavior is confirmed in recent code and merged PRs, and there’s no documentation or config option to change it yet [PR #4108].

For now, you’ll need to keep using fetchOptions: { method: "POST" } when calling this method. If you want a more elegant solution, it would require a feature update to allow specifying HTTP methods per client action.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Sep 12, 2025): <!-- Greeting --> Hi @danielepintore! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> This is a known limitation: the client-side method for creating Stripe billing portal sessions (`authClient.subscription.billingPortal()`) defaults to a GET request unless you manually set `fetchOptions.method` to POST. The server-side endpoint is correctly configured to expect POST, but there’s currently no built-in config or automatic way to make the client use POST for this action without the manual workaround you described. This behavior is confirmed in recent code and merged PRs, and there’s no documentation or config option to change it yet [[PR #4108](https://github.com/better-auth/better-auth/pull/4108)]. For now, you’ll need to keep using `fetchOptions: { method: "POST" }` when calling this method. If you want a more elegant solution, it would require a feature update to allow specifying HTTP methods per client action. <!-- 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/d9301c55-9dd1-4cd9-9f7e-f44d99460bbe?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/d9301c55-9dd1-4cd9-9f7e-f44d99460bbe?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/d9301c55-9dd1-4cd9-9f7e-f44d99460bbe?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/d9301c55-9dd1-4cd9-9f7e-f44d99460bbe?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/d9301c55-9dd1-4cd9-9f7e-f44d99460bbe?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/d9301c55-9dd1-4cd9-9f7e-f44d99460bbe?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/d9301c55-9dd1-4cd9-9f7e-f44d99460bbe?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&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/4611)
Author
Owner

@danielepintore commented on GitHub (Sep 12, 2025):

@dosu Why it defaults to a GET if the backend only supports POST requests?

@danielepintore commented on GitHub (Sep 12, 2025): @dosu Why it defaults to a GET if the backend only supports POST requests?
Author
Owner

@dosubot[bot] commented on GitHub (Sep 12, 2025):

It defaults to GET because the client-side plugin doesn't explicitly set the HTTP method for the billingPortal action. In the code, only certain endpoints (like /subscription/restore) are mapped to POST, while others—including billingPortal—fall back to GET by default. There's no mechanism that automatically syncs the backend's required HTTP method (POST) to the client, so unless you manually override it with fetchOptions, it stays as GET. This is a limitation of the current client plugin design, not a deliberate choice for billingPortal specifically.
You can see this in the Stripe client implementation, where only some paths are mapped to POST and the rest default to GET unless specified otherwise (source).

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

@dosubot[bot] commented on GitHub (Sep 12, 2025): <!-- Answer --> It defaults to GET because the client-side plugin doesn't explicitly set the HTTP method for the billingPortal action. In the code, only certain endpoints (like `/subscription/restore`) are mapped to POST, while others—including billingPortal—fall back to GET by default. There's no mechanism that automatically syncs the backend's required HTTP method (POST) to the client, so unless you manually override it with `fetchOptions`, it stays as GET. This is a limitation of the current client plugin design, not a deliberate choice for billingPortal specifically. You can see this in the Stripe client implementation, where only some paths are mapped to POST and the rest default to GET unless specified otherwise [(source)](https://github.com/better-auth/better-auth/blob/a5391f8909ee91beae65bac78697df2cf6810fc7/packages/stripe/src/client.ts). <!-- 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/17d190d4-4c55-42f3-8b87-ca7d2e3662c5?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/17d190d4-4c55-42f3-8b87-ca7d2e3662c5?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/17d190d4-4c55-42f3-8b87-ca7d2e3662c5?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/17d190d4-4c55-42f3-8b87-ca7d2e3662c5?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/17d190d4-4c55-42f3-8b87-ca7d2e3662c5?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/17d190d4-4c55-42f3-8b87-ca7d2e3662c5?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/17d190d4-4c55-42f3-8b87-ca7d2e3662c5?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&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/4611)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1909