You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django集成Twilio无法发送OTP的问题排查求助

Django集成Twilio无法发送OTP的问题排查求助

我正在自己的Django项目里用Twilio实现OTP短信验证功能,已经成功申请到了Twilio的专用手机号,但现在遇到了一个棘手的问题:OTP短信始终无法发送到用户的手机上。下面是我所有的配置和代码细节,麻烦各位帮忙看看哪里出问题了!


我的实现代码细节

1. views.py 核心代码

def send_otp_view(request):
    if request.method == "POST":
        data = json.loads(request.body)
        phone_number = data.get('phone_number')

        # 生成6位随机OTP
        otp = random.randint(100000, 999999)

        # 调用Twilio发送OTP
        try:
            client = Client(settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN)
            message = client.messages.create(
                body=f"Your OTP is {otp}. Please use this to verify your number.",
                from_=settings.TWILIO_PHONE_NUMBER,
                to=phone_number
            )

            # 将OTP和手机号存入Session
            request.session['otp'] = otp
            request.session['phone_number'] = phone_number

            return JsonResponse({'status': 'success', 'otp': otp})  # 返回OTP用于前端验证
        except Exception as e:
            return JsonResponse({'status': 'error', 'message': str(e)})

    return JsonResponse({'status': 'error', 'message': 'Invalid request'})


def address_view(request):
    if request.user.is_authenticated:
        user_addresses = Address.objects.filter(user=request.user)

        if request.method == "POST":
            # 获取表单提交的OTP
            entered_otp = request.POST.get("otp")

            # 验证OTP是否正确
            if entered_otp == str(request.session.get('otp')):
                # OTP正确,保存地址信息
                mobile = request.POST.get("mobile")
                email = request.POST.get("email")
                pin = request.POST.get("pin")
                region = request.POST.get("region")
                address = request.POST.get("address")
                landmark = request.POST.get("landmark")
                name = request.POST.get("name")

                new_address = Address.objects.create(
                    user=request.user,
                    mobile=mobile,
                    email=email,
                    pin=pin,
                    region=region,
                    address=address,
                    landmark=landmark,
                    name=name,
                )
                messages.success(request, "Address Added Successfully.")
                return redirect("core:address")
            else:
                messages.error(request, "Invalid OTP. Please try again.")
                return redirect("core:address")

        context = {
            "user_addresses": user_addresses,
        }
        return render(request, 'others/address-book.html', context)
    else:
        print("User is not authenticated")
        return redirect("core:login")

2. HTML 表单代码

<div class="gl-inline">
    <div class="u-s-m-b-30">
        <label class="gl-label" for="address-phone">PHONE *</label>
        <input 
            class="input-text input-text--primary-style" 
            type="text" 
            id="address-phone" 
            name="mobile" 
            placeholder="Enter 10-digit phone number" 
            oninput="validatePhoneNumber()" 
            maxlength="10">
        <button type="button" id="send-otp-btn" style="display:none;" onclick="sendOTP()">Send OTP</button>
    </div>
    <div id="otp-box" style="display: none;" class="u-s-m-b-30">
        <label class="gl-label" for="otp-input">ENTER OTP *</label>
        <input 
            class="input-text input-text--primary-style" 
            type="text" 
            id="otp-input" 
            name="otp" 
            placeholder="Enter OTP" 
            maxlength="6" 
            oninput="validateOTP()">
        <span id="otp-feedback" style="display: none; font-size: 14px;"></span>
    </div>
    <div class="u-s-m-b-30">
        <label class="gl-label" for="address-street">STREET ADDRESS *</label>
        <input class="input-text input-text--primary-style" type="text" id="address-street" placeholder="House Name and Street" name="address">
    </div>
</div>

3. Twilio 配置 (settings.py)

TWILIO_ACCOUNT_SID = 'YOUR_ACCOUNT_SID'
TWILIO_AUTH_TOKEN = 'YOUR_AUTH_TOKEN'
TWILIO_PHONE_NUMBER = '+1234567890'  # 带国家码的Twilio号码

4. 前端验证 JavaScript 代码

console.log("OTP");

let sentOTP = null; // 存储后端返回的OTP

function validatePhoneNumber() {
    const phoneInput = document.getElementById('address-phone');
    const otpBox = document.getElementById('otp-box');
    const sendOtpBtn = document.getElementById('send-otp-btn');

    // 手机号长度为10位时显示OTP输入框和发送按钮
    if (phoneInput.value.length === 10) {
        otpBox.style.display = 'block';
        sendOtpBtn.style.display = 'inline-block';
    } else {
        otpBox.style.display = 'none';
        sendOtpBtn.style.display = 'none';
    }
}

function sendOTP() {
    const phoneNumber = document.getElementById('address-phone').value;

    // 发送AJAX请求到后端获取并发送OTP
    fetch('/send-otp/', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-CSRFToken': getCookie('csrftoken') // 处理CSRF验证
        },
        body: JSON.stringify({ phone_number: phoneNumber })
    })
    .then(response => response.json())
    .then(data => {
        if (data.status === 'success') {
            sentOTP = data.otp; // 保存OTP用于前端预验证
            console.log("OTP sent successfully");
        } else {
            alert("Error sending OTP");
        }
    })
    .catch(error => {
        console.error('Error:', error);
        alert('Failed to send OTP');
    });
}

function validateOTP() {
    const otpInput = document.getElementById('otp-input');
    const feedback = document.getElementById('otp-feedback');

    // 前端预验证OTP是否正确
    if (otpInput.value === sentOTP) {
        feedback.style.display = 'block';
        feedback.style.color = 'green';
        feedback.textContent = 'OTP is correct!';
    } else if (otpInput.value.length === sentOTP.length) {
        feedback.style.display = 'block';
        feedback.style.color = 'red';
        feedback.textContent = 'Incorrect OTP, please try again.';
    } else {
        feedback.style.display = 'none'; // 输入未完成时隐藏提示
    }
}

// 从Cookie中获取CSRF Token的工具函数
function getCookie(name) {
    const cookieValue = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)');
    return cookieValue ? cookieValue.pop() : '';
}

我遇到的具体问题

  1. 表单能正确提交手机号,POST请求也能正常到达Django的send_otp_view视图
  2. 代码走到了Twilio的client.messages.create调用逻辑,但就是没有短信发送到目标手机
  3. Django日志里只看到一条模糊的错误:Error sending OTP: TwilioRestException: [Error message here](具体错误内容没显示全)

我已经尝试过的排查步骤

  1. 反复核对了Twilio的Account SID、Auth Token和手机号,确认都是正确的
  2. 确保接收手机号是标准E.164格式(比如+86开头的国内手机号)
  3. 确认Twilio试用账号已经在控制台验证过这个接收手机号
  4. 去Twilio的消息日志里查,完全找不到这条请求的记录
  5. 用独立Python脚本测试Twilio功能是正常的(脚本如下):
from twilio.rest import Client

client = Client('YOUR_ACCOUNT_SID', 'YOUR_AUTH_TOKEN')
message = client.messages.create(
    body="Test message",
    from_='+1234567890',
    to='+9876543210'
)
print(message.sid)

期望行为

用户输入的手机号能正常收到OTP验证短信

我的问题

到底哪里出问题了?是Twilio的账号设置有坑,还是Django的配置或者代码有遗漏?求各位大佬给点排查方向或者解决方案!


补充说明:

  • 我用的是Twilio免费试用账号
  • 接收方手机号已经在Twilio控制台完成验证
  • 我的开发环境没有网络或防火墙限制

备注:内容来源于stack exchange,提问作者Google User

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 17:19:31