[GH-ISSUE #4702] Allow better integration with SMS OTP providers for phone number login #27350

Open
opened 2026-04-17 18:18:18 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @jinsley8 on GitHub (Sep 16, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/4702

Originally assigned to: @ping-maxwell on GitHub.

Is this suited for github?

  • Yes, this is suited for github

Need the ability to fully utilize third-party SMS OTP services like Twilio Verify, which already generates the OTP.

Right now, Better Auth generates the OTP, so Twilio has to use "Custom Verification Code" mode, which significantly increases costs.

If you don't enable "Custom Verification Code" then Better Auth and Twilio generate their own OTP numbers which don't match so verification fails.

Describe the solution you'd like

Allow Twilio Verify to full handle OTP generation and sending. Currently it has to be switched to "Custom Verification Code" mode.

Normally, Twilio charges $0.05 per successful verification. By switching to "Custom Verification Code" to allow Better Auth to generate the OTP, the pricing structure changes to $0.05 per ATTEMPT and disables the fraud protection.

Describe alternatives you've considered

Twilio Verify but this requires "Custom Verification Code" to be enables which increases the price.

Image

Additional context

I successfully setup Twilio to work but it will be too expensive in "Custom Verification Code" mode to pay per verification attempt instead of per successful verification.

// auth.ts
export const auth = betterAuth({
    plugins: [
        phoneNumber({
            otpLength: 6,
            expiresIn: 900, // 15 minutes
            sendOTP: async ({ phoneNumber, code }, request) => {
               // Use Twilio service if credentials are provided
               if (config.twilioApiKey && config.twilioApiKeySecret && config.twilioVerifyServiceSid) {
                   await sendPhoneOTP(phoneNumber, {
                       apiKey: config.twilioApiKey,
                       apiKeySecret: config.twilioApiKeySecret,
                       verifyServiceSid: config.twilioVerifyServiceSid,
                   });
               } else {
                   // Fallback for development or when Twilio is not configured
                   console.log(`📱 Development OTP for ${phoneNumber}: ${code}`);
               }
           },
           signUpOnVerification: {
               getTempEmail: (phoneNumber) => {
                   const digits = phoneNumber.replace(/\D/g, '');
                   return `phone-${digits}@domain.app`;
               },
               getTempName: (phoneNumber) => {
                   return phoneNumber;
               },
            },
        }),
    ]
})
// twilio-service.ts
interface TwilioConfig {
  apiKey: string;
  apiKeySecret: string;
  verifyServiceSid: string;
}

interface TwilioVerifyParams {
  phoneNumber: string;
  code: string;
}

/**
 * Send OTP via Twilio Verify
 */
export async function sendPhoneOTP(
  phoneNumber: string,
  config: TwilioConfig,
): Promise<void> {
  try {
    const twilio = (await import("twilio")).default;
    const client = twilio(config.apiKey, config.apiKeySecret);

    // Use test number in development, actual number in production
    const targetNumber = process.env.NODE_ENV === "development"
      ? "+15011234567"  // Twilio's test number
      : phoneNumber;

    if (process.env.NODE_ENV === "development") {
      console.log(`📱 [DEV] Sending OTP to test number: ${targetNumber}`);
      console.log(`🔑 [DEV] Use any 6-digit code (e.g., 123456) to verify`);
    }

    const verification = await client.verify.v2
      .services(config.verifyServiceSid)
      .verifications.create({
        to: targetNumber,
        channel: "sms",
        locale: "en",
      });

    console.log(
      `Verification sent to ${targetNumber}, SID: ${verification.sid}`,
    );
  } catch (error) {
    console.error("Failed to send phone OTP:", error);
    throw new Error(
      `Failed to send verification SMS: ${
        error instanceof Error ? error.message : "Unknown error"
      }`,
    );
  }
}

/**
 * Verify OTP code via Twilio Verify
 */
export async function verifyPhoneOTP(
  { phoneNumber, code }: TwilioVerifyParams,
  config: TwilioConfig,
): Promise<boolean> {
  try {
    const twilio = (await import("twilio")).default;
    const client = twilio(config.apiKey, config.apiKeySecret);

    // Use test number in development
    const targetNumber = process.env.NODE_ENV === "development"
      ? "+15011234567"
      : phoneNumber;

    if (process.env.NODE_ENV === "development") {
      console.log(`📱 [DEV] Verifying OTP for test number: ${targetNumber}`);
      console.log(`🔑 [DEV] Code: ${code}`);
    }

    const verificationCheck = await client.verify.v2
      .services(config.verifyServiceSid)
      .verificationChecks.create({
        to: targetNumber,
        code: code,
      });

    const isApproved = verificationCheck.status === "approved";

    if (process.env.NODE_ENV === "development") {
      console.log(`✅ [DEV] Verification result: ${isApproved ? "SUCCESS" : "FAILED"}`);
    }

    return isApproved;
  } catch (error) {
    console.error("Failed to verify phone OTP:", error);
    return false;
  }
}
Originally created by @jinsley8 on GitHub (Sep 16, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/4702 Originally assigned to: @ping-maxwell on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### Is your feature request related to a problem? Please describe. Need the ability to fully utilize third-party SMS OTP services like Twilio Verify, which already generates the OTP. Right now, Better Auth generates the OTP, so Twilio has to use "Custom Verification Code" mode, which significantly increases costs. If you don't enable "Custom Verification Code" then Better Auth and Twilio generate their own OTP numbers which don't match so verification fails. ### Describe the solution you'd like Allow Twilio Verify to full handle OTP generation and sending. Currently it has to be switched to "Custom Verification Code" mode. Normally, Twilio charges $0.05 per successful verification. By switching to "Custom Verification Code" to allow Better Auth to generate the OTP, the pricing structure changes to $0.05 per ATTEMPT and disables the fraud protection. ### Describe alternatives you've considered Twilio Verify but this requires "[Custom Verification Code](https://www.twilio.com/docs/verify/api/customization-options)" to be enables which increases the price. <img width="586" height="534" alt="Image" src="https://github.com/user-attachments/assets/03d020c7-5084-4519-8279-fd7b0e5b5a82" /> ### Additional context I successfully setup Twilio to work but it will be too expensive in "Custom Verification Code" mode to pay per verification attempt instead of per successful verification. ```js // auth.ts export const auth = betterAuth({ plugins: [ phoneNumber({ otpLength: 6, expiresIn: 900, // 15 minutes sendOTP: async ({ phoneNumber, code }, request) => { // Use Twilio service if credentials are provided if (config.twilioApiKey && config.twilioApiKeySecret && config.twilioVerifyServiceSid) { await sendPhoneOTP(phoneNumber, { apiKey: config.twilioApiKey, apiKeySecret: config.twilioApiKeySecret, verifyServiceSid: config.twilioVerifyServiceSid, }); } else { // Fallback for development or when Twilio is not configured console.log(`📱 Development OTP for ${phoneNumber}: ${code}`); } }, signUpOnVerification: { getTempEmail: (phoneNumber) => { const digits = phoneNumber.replace(/\D/g, ''); return `phone-${digits}@domain.app`; }, getTempName: (phoneNumber) => { return phoneNumber; }, }, }), ] }) ``` ```js // twilio-service.ts interface TwilioConfig { apiKey: string; apiKeySecret: string; verifyServiceSid: string; } interface TwilioVerifyParams { phoneNumber: string; code: string; } /** * Send OTP via Twilio Verify */ export async function sendPhoneOTP( phoneNumber: string, config: TwilioConfig, ): Promise<void> { try { const twilio = (await import("twilio")).default; const client = twilio(config.apiKey, config.apiKeySecret); // Use test number in development, actual number in production const targetNumber = process.env.NODE_ENV === "development" ? "+15011234567" // Twilio's test number : phoneNumber; if (process.env.NODE_ENV === "development") { console.log(`📱 [DEV] Sending OTP to test number: ${targetNumber}`); console.log(`🔑 [DEV] Use any 6-digit code (e.g., 123456) to verify`); } const verification = await client.verify.v2 .services(config.verifyServiceSid) .verifications.create({ to: targetNumber, channel: "sms", locale: "en", }); console.log( `Verification sent to ${targetNumber}, SID: ${verification.sid}`, ); } catch (error) { console.error("Failed to send phone OTP:", error); throw new Error( `Failed to send verification SMS: ${ error instanceof Error ? error.message : "Unknown error" }`, ); } } /** * Verify OTP code via Twilio Verify */ export async function verifyPhoneOTP( { phoneNumber, code }: TwilioVerifyParams, config: TwilioConfig, ): Promise<boolean> { try { const twilio = (await import("twilio")).default; const client = twilio(config.apiKey, config.apiKeySecret); // Use test number in development const targetNumber = process.env.NODE_ENV === "development" ? "+15011234567" : phoneNumber; if (process.env.NODE_ENV === "development") { console.log(`📱 [DEV] Verifying OTP for test number: ${targetNumber}`); console.log(`🔑 [DEV] Code: ${code}`); } const verificationCheck = await client.verify.v2 .services(config.verifyServiceSid) .verificationChecks.create({ to: targetNumber, code: code, }); const isApproved = verificationCheck.status === "approved"; if (process.env.NODE_ENV === "development") { console.log(`✅ [DEV] Verification result: ${isApproved ? "SUCCESS" : "FAILED"}`); } return isApproved; } catch (error) { console.error("Failed to verify phone OTP:", error); return false; } } ```
GiteaMirror added the credentials label 2026-04-17 18:18:18 -05:00
Author
Owner

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

Better Auth's phone number plugin always generates and verifies OTPs internally, so there's currently no way to delegate OTP generation and verification to external providers like Twilio Verify in standard mode. The sendOTP callback only lets you control how the internally generated code is sent, not how it's created or verified. This means you have to use Twilio's "Custom Verification Code" mode, which is more expensive and disables fraud protection [docs].

To support full delegation, the plugin would need new extension points—like a verifyOTP override—so you could plug in external verification logic and let Twilio handle both code generation and verification. The current architecture doesn't expose hooks or middleware for this, so it would require a significant refactor [plugin code].

If you want to pursue this, the maintainers would need to discuss adding a new API surface to allow external OTP provider integration. There are no recent changes or open discussions about this feature in the repo.

To reply, just mention @dosu.


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

<!-- gh-comment-id:3299410637 --> @dosubot[bot] commented on GitHub (Sep 16, 2025): <!-- Answer --> Better Auth's phone number plugin always generates and verifies OTPs internally, so there's currently no way to delegate OTP generation and verification to external providers like Twilio Verify in standard mode. The sendOTP callback only lets you control how the internally generated code is sent, not how it's created or verified. This means you have to use Twilio's "Custom Verification Code" mode, which is more expensive and disables fraud protection [[docs](https://github.com/better-auth/better-auth/blob/4df178463eeeced7972c9a59f902b4bbf55db30b/docs/content/docs/plugins/phone-number.mdx)]. To support full delegation, the plugin would need new extension points—like a `verifyOTP` override—so you could plug in external verification logic and let Twilio handle both code generation and verification. The current architecture doesn't expose hooks or middleware for this, so it would require a significant refactor [[plugin code](https://github.com/better-auth/better-auth/blob/4df178463eeeced7972c9a59f902b4bbf55db30b/packages/better-auth/src/plugins/phone-number/index.ts)]. If you want to pursue this, the maintainers would need to discuss adding a new API surface to allow external OTP provider integration. There are no recent changes or open discussions about this feature in the repo. <!-- 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/82ac3809-603b-4ff7-bd2b-41ad659cc42b?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/82ac3809-603b-4ff7-bd2b-41ad659cc42b?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/82ac3809-603b-4ff7-bd2b-41ad659cc42b?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/82ac3809-603b-4ff7-bd2b-41ad659cc42b?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/82ac3809-603b-4ff7-bd2b-41ad659cc42b?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/82ac3809-603b-4ff7-bd2b-41ad659cc42b?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/82ac3809-603b-4ff7-bd2b-41ad659cc42b?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/4702)
Author
Owner

@Alvi24 commented on GitHub (Oct 19, 2025):

better twilio integration would be very useful (https://github.com/better-auth/better-auth/pull/4102)

<!-- gh-comment-id:3419504385 --> @Alvi24 commented on GitHub (Oct 19, 2025): better twilio integration would be very useful (https://github.com/better-auth/better-auth/pull/4102)
Author
Owner

@dosubot[bot] commented on GitHub (Jan 18, 2026):

Hi, @jinsley8. I'm Dosu, and I'm helping the better-auth team manage their backlog and am marking this issue as stale.

Issue Summary:

  • You requested full integration with third-party SMS OTP providers like Twilio Verify to offload OTP generation and verification.
  • The current phone number plugin handles OTPs internally, requiring Twilio's more expensive "Custom Verification Code" mode.
  • Enabling full delegation to third-party services would require significant refactoring and new API extension points.
  • Another user expressed interest and referenced a related pull request (#4102).
  • There have been no recent updates or discussions advancing this feature.

Next Steps:

  • Please let me know if this feature is still relevant to your use case with the latest version of better-auth by commenting on this issue.
  • If I don’t hear back within 7 days, I will automatically close this issue to keep the backlog manageable.

Thanks for your understanding and contribution!

<!-- gh-comment-id:3765450794 --> @dosubot[bot] commented on GitHub (Jan 18, 2026): Hi, @jinsley8. I'm [Dosu](https://dosu.dev), and I'm helping the better-auth team manage their backlog and am marking this issue as stale. **Issue Summary:** - You requested full integration with third-party SMS OTP providers like Twilio Verify to offload OTP generation and verification. - The current phone number plugin handles OTPs internally, requiring Twilio's more expensive "Custom Verification Code" mode. - Enabling full delegation to third-party services would require significant refactoring and new API extension points. - Another user expressed interest and referenced a related pull request (#4102). - There have been no recent updates or discussions advancing this feature. **Next Steps:** - Please let me know if this feature is still relevant to your use case with the latest version of better-auth by commenting on this issue. - If I don’t hear back within 7 days, I will automatically close this issue to keep the backlog manageable. Thanks for your understanding and contribution!
Author
Owner

@enisze commented on GitHub (Jan 23, 2026):

would be nicee!

<!-- gh-comment-id:3790946390 --> @enisze commented on GitHub (Jan 23, 2026): would be nicee!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#27350