[GH-ISSUE #3484] authClient.emailOtp.verifyEmail() wrong endpoint: /email-verification #9619

Closed
opened 2026-04-13 05:11:04 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @arkmech on GitHub (Jul 19, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/3484

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

  1. Create Hono.js backend

Current vs. Expected behavior

Why does authClient.emailOtp.verifyEmail() keep sending to the regular endpoint /email-verification and NOT /email-otp/verify-email​ endpoint?

I keep getting METHOD NOT ALLOWED, which makes sense as /email-verification is a GET, but I am sending a POST. Because I am expecting the authClient to hit /email-otp/verify-email, which is a POST

What version of Better Auth are you using?

1.3.0

Provide environment information

- Macbook Pro M1 Max Sequoia 15.5
- Chrome Version 138.0.7204.101 (Official Build) (arm64)

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000",
  secret: process.env.BETTER_AUTH_SECRET || undefined,
  emailAndPassword: {
    enabled: true,
    autoSignIn: true,
    minPasswordLength: 8,
  },
  plugins: [
    // Authentication
    emailOTP({
      async sendVerificationOTP({ email, otp, type }) {
        if (type === "email-verification") {
          await sendEmail({
            from: process.env.FROM_EMAIL!,
            to: email,
            subject: "Example Email Verification Code",
            html: await render(
              <VerificationEmail
                to={email}
                previewText="Example Email Verification Code"
                code={otp}
              />,
            ),
          });
        } else if (type === "forget-password") {
          await sendEmail({
            from: process.env.FROM_EMAIL!,
            to: email,
            subject: "Example Password Reset Request",
            html: await render(
              <PasswordResetEmail
                to={email}
                previewText="Example Password Reset Request"
                code={otp}
              />,
            ),
          });
        }
      },
      sendVerificationOnSignUp: true,
    }),
    passkey(),
    // Authorization
    admin(),
    organization(),
    // Utility
    captcha({
      provider: "cloudflare-turnstile",
      secretKey: process.env.TURNSTILE_SECRET_KEY!,
    }),
    haveIBeenPwned(),
    openAPI(),
  ],
  trustedOrigins: [
    process.env.BETTER_AUTH_URL || "http://localhost:3000",
    ...(process.env.ALLOWED_ORIGINS?.split(",") || []),
  ],
  database: drizzleAdapter(db, {
    provider: "pg",
    schema: {
      ...authSchema,
    },
  }),
  advanced: {
    cookiePrefix: "example",
    crossSubDomainCookies: {
      enabled: true,
      domain: "example.local",
    },
  },
});


Client setup

export const authClient = createAuthClient({
  baseURL: env.PUBLIC_BETTER_AUTH_BASE_URL!,
  fetchOptions: {
    credentials: 'include',
  },
  plugins: [
    // Authentication
    emailOTPClient(),
    passkeyClient(),
    // Authorization
    adminClient(),
    organizationClient(),
  ]
})

Additional context

Auth Flow:

  1. authClient.signUp.email - works, redirect to my email-verification page which is behind auth. I see other people having flows of where people can't login until their email is verified. But I set it up, so they can, and they can only verify email when logged in. I guess they can technically verifyEmail outside of my page as I think its public endpoint. But redirects to /login cause they don't get signedIn.
  2. authClient.signIn.email - works.
  3. They cannot go to any other page in dashboard until emailIsVerified. Wrong endpoint.
  const { error } = await authClient.emailOtp.verifyEmail({
      email: user.email,
      otp: f.data.code,
    }); 
Originally created by @arkmech on GitHub (Jul 19, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/3484 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce 1. Create Hono.js backend ### Current vs. Expected behavior Why does `authClient.emailOtp.verifyEmail()` keep sending to the regular endpoint `/email-verification` and NOT `/email-otp/verify-email​` endpoint? I keep getting `METHOD NOT ALLOWED`, which makes sense as `/email-verification` is a `GET`, but I am sending a `POST`. Because I am expecting the `authClient` to hit `/email-otp/verify-email`, which is a `POST` ### What version of Better Auth are you using? 1.3.0 ### Provide environment information ```bash - Macbook Pro M1 Max Sequoia 15.5 - Chrome Version 138.0.7204.101 (Official Build) (arm64) ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ baseURL: process.env.BETTER_AUTH_URL || "http://localhost:3000", secret: process.env.BETTER_AUTH_SECRET || undefined, emailAndPassword: { enabled: true, autoSignIn: true, minPasswordLength: 8, }, plugins: [ // Authentication emailOTP({ async sendVerificationOTP({ email, otp, type }) { if (type === "email-verification") { await sendEmail({ from: process.env.FROM_EMAIL!, to: email, subject: "Example Email Verification Code", html: await render( <VerificationEmail to={email} previewText="Example Email Verification Code" code={otp} />, ), }); } else if (type === "forget-password") { await sendEmail({ from: process.env.FROM_EMAIL!, to: email, subject: "Example Password Reset Request", html: await render( <PasswordResetEmail to={email} previewText="Example Password Reset Request" code={otp} />, ), }); } }, sendVerificationOnSignUp: true, }), passkey(), // Authorization admin(), organization(), // Utility captcha({ provider: "cloudflare-turnstile", secretKey: process.env.TURNSTILE_SECRET_KEY!, }), haveIBeenPwned(), openAPI(), ], trustedOrigins: [ process.env.BETTER_AUTH_URL || "http://localhost:3000", ...(process.env.ALLOWED_ORIGINS?.split(",") || []), ], database: drizzleAdapter(db, { provider: "pg", schema: { ...authSchema, }, }), advanced: { cookiePrefix: "example", crossSubDomainCookies: { enabled: true, domain: "example.local", }, }, }); Client setup export const authClient = createAuthClient({ baseURL: env.PUBLIC_BETTER_AUTH_BASE_URL!, fetchOptions: { credentials: 'include', }, plugins: [ // Authentication emailOTPClient(), passkeyClient(), // Authorization adminClient(), organizationClient(), ] }) ``` ### Additional context **Auth Flow:** 1. `authClient.signUp.email` - works, redirect to my `email-verification` page which is behind auth. I see other people having flows of where people can't login until their email is verified. But I set it up, so they can, and they can only verify email when logged in. I guess they can technically verifyEmail outside of my page as I think its public endpoint. But redirects to `/login` cause they don't get signedIn. 2. `authClient.signIn.email` - works. 3. They cannot go to any other page in dashboard until emailIsVerified. Wrong endpoint. ```ts const { error } = await authClient.emailOtp.verifyEmail({ email: user.email, otp: f.data.code, }); ```
GiteaMirror added the locked label 2026-04-13 05:11:04 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Jul 19, 2025):

authClient.emailOtp.verifyEmail() should POST to /email-otp/verify-email if the emailOTP plugin is correctly registered on both the backend and client. If it's hitting /email-verification instead, this usually means the plugin isn't properly included in both places, or there’s a typo or version mismatch. Double-check that emailOTP (server) and emailOTPClient (client) are both present in your plugins arrays, and that you’ve restarted both server and client after changes. There’s no config option to change the endpoint path—mapping is automatic and based on plugin registration. Tests confirm that with the right setup, verifyEmail() targets the correct endpoint. If you’re still seeing this, try isolating the issue in a minimal setup to spot any integration bugs.
Reference: plugin endpoint mapping and tests

To reply, just mention @dosu.


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

<!-- gh-comment-id:3092591417 --> @dosubot[bot] commented on GitHub (Jul 19, 2025): <!-- Answer --> authClient.emailOtp.verifyEmail() should POST to /email-otp/verify-email if the emailOTP plugin is correctly registered on both the backend and client. If it's hitting /email-verification instead, this usually means the plugin isn't properly included in both places, or there’s a typo or version mismatch. Double-check that emailOTP (server) and emailOTPClient (client) are both present in your plugins arrays, and that you’ve restarted both server and client after changes. There’s no config option to change the endpoint path—mapping is automatic and based on plugin registration. Tests confirm that with the right setup, verifyEmail() targets the correct endpoint. If you’re still seeing this, try isolating the issue in a minimal setup to spot any integration bugs. [Reference: plugin endpoint mapping and tests](https://github.com/better-auth/better-auth/blob/79629fa3514a13ed2851bc1a83d11249539a2d22/packages/better-auth/src/plugins/email-otp/index.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/4d0a40cc-bc91-40e8-8ff5-844fc6c1c455?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/4d0a40cc-bc91-40e8-8ff5-844fc6c1c455?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/4d0a40cc-bc91-40e8-8ff5-844fc6c1c455?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/4d0a40cc-bc91-40e8-8ff5-844fc6c1c455?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/4d0a40cc-bc91-40e8-8ff5-844fc6c1c455?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/4d0a40cc-bc91-40e8-8ff5-844fc6c1c455?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/4d0a40cc-bc91-40e8-8ff5-844fc6c1c455?feedback_type=other)</sup>&nbsp;&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/3484)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9619