Skip to content

SMS test OTP not honored with Twilio Verify provider #45832

@RestartDK

Description

@RestartDK

Bug report

  • I confirm this is a bug with Supabase, not with my own application.
  • I confirm I have searched the Docs, GitHub Discussions, and Discord.

Describe the bug

Supabase hosted Auth appears to ignore configured SMS test OTP entries when Phone Auth is configured with sms_provider = twilio_verify.

The test OTP entry is visible through the Supabase Management API but /auth/v1/otp does not appear to use the test OTP path, and /auth/v1/verify rejects the configured test code with otp_expired.

This reproduces with raw Supabase Auth REST calls, outside our React Native app and outside supabase-js.

Auth config summary from Management API:

sms_provider: twilio_verify
phone_enabled: true
phone_autoconfirm: true
sms_test_otp_valid_until: 2027-06-30...
sms_test_otp contains: <phone digits without leading +>=000000

Example format:

12125550124=000000

But verifying 000000 fails with:

{
  "error_code": "otp_expired",
  "msg": "Token has expired or is invalid"
}

To Reproduce

  1. In a hosted Supabase project, enable Phone Auth.

  2. Configure SMS provider as Twilio Verify.

  3. Add a test OTP entry in the dashboard:

    12125550124=000000

    The dashboard requires E.164 without the leading +.

  4. Set sms_test_otp_valid_until to a future date.

  5. Confirm via Management API that the config exists and is not expired.

  6. Request an OTP:

    curl -X POST "https://<project-ref>.supabase.co/auth/v1/otp" \
      -H "apikey: <publishable-or-anon-key>" \
      -H "Authorization: Bearer <publishable-or-anon-key>" \
      -H "Content-Type: application/json" \
      -d '{"phone":"+12125550124","create_user":false}'
  7. Verify using the configured test OTP:

    curl -X POST "https://<project-ref>.supabase.co/auth/v1/verify" \
      -H "apikey: <publishable-or-anon-key>" \
      -H "Authorization: Bearer <publishable-or-anon-key>" \
      -H "Content-Type: application/json" \
      -d '{"phone":"+12125550124","token":"000000","type":"sms"}'
  8. Observe that verification fails with 403 otp_expired.

We also tested phone: "12125550124" without the leading +; it fails the same way.

Expected behavior

Supabase should match the incoming phone number +12125550124 to the configured test OTP entry 12125550124=000000, bypass Twilio Verify, and return a valid Auth session.

The OTP request should appear to use the test OTP path. Based on observed/test behavior, I expected /auth/v1/otp to return something like:

message_id: "test-otp"

Screenshots

Can provide privately if useful.

System information

  • OS: macOS 26.4.1
  • Browser: Chrome
  • Version of supabase-js: app uses @supabase/supabase-js ^2.90.1
  • Version of Node.js: v24.11.1

Additional context

None.

Extra checks performed:

  • Direct REST reproduction rules out React Native/app code.
  • The app uses normal Supabase phone OTP APIs:
    • signInWithOtp({ phone, options: { shouldCreateUser } })
    • verifyOtp({ phone, token, type: "sms" })
  • Management API confirms the test OTP entry exists and has not expired.
  • A no-op Management API PATCH on staging re-saving the same sms_test_otp and sms_test_otp_valid_until succeeded but did not fix runtime behavior.
  • Trying : as the delimiter was rejected by Supabase validation. The API/dashboard requires <phone-number>=<code> and “E.164 without the leading + sign”.
  • /auth/v1/otp returned message_id: null, not message_id: "test-otp", which suggests hosted Auth is not applying the test OTP path at send time either.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions