Unable to Send OTP via Twilio in Django
I'm working on implementing an OTP system in my Django project using Twilio. I've successfully obtained a Twilio phone number, but the OTP message is not being sent to the user's mobile number. Below are the details of my setup:
Code Implementation
views.py
:
def send_otp_view(request):
if request.method == "POST":
data = json.loads(request.body)
phone_number = data.get('phone_number')
# Generate a random 6-digit OTP
otp = random.randint(100000, 999999)
# Send OTP via Twilio (or any other SMS provider)
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
)
# Optionally, store the OTP in a session or database
request.session['otp'] = otp
request.session['phone_number'] = phone_number
return JsonResponse({'status': 'success', 'otp': otp}) # Return OTP for validation
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":
# Get the OTP from the form
entered_otp = request.POST.get("otp")
# Check if the OTP matches
if entered_otp == str(request.session.get('otp')):
# Proceed with saving the address if OTP is correct
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 Form:
<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 (settings.py
):
TWILIO_ACCOUNT_SID = 'YOUR_ACCOUNT_SID'
TWILIO_AUTH_TOKEN = 'YOUR_AUTH_TOKEN'
TWILIO_PHONE_NUMBER = '+1234567890' # Twilio number with country code
4. Frontend Validation (JavaScript):
console.log("OTP");
let sentOTP = null; // Store the sent OTP
function validatePhoneNumber() {
const phoneInput = document.getElementById('address-phone');
const otpBox = document.getElementById('otp-box');
const sendOtpBtn = document.getElementById('send-otp-btn');
// Check if the phone number is 10 digits long
if (phoneInput.value.length === 10) {
otpBox.style.display = 'block'; // Show OTP box
sendOtpBtn.style.display = 'inline-block'; // Show OTP button
} else {
otpBox.style.display = 'none'; // Hide OTP box
sendOtpBtn.style.display = 'none'; // Hide OTP button
}
}
function sendOTP() {
const phoneNumber = document.getElementById('address-phone').value;
// Send an AJAX request to the Django view to generate and send OTP
fetch('/send-otp/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken') // Handle CSRF token for security
},
body: JSON.stringify({ phone_number: phoneNumber })
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
sentOTP = data.otp; // Save the OTP for later validation
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');
// Compare entered OTP with the sent 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'; // Hide feedback when incomplete
}
}
// Utility to get the CSRF token from cookies
function getCookie(name) {
const cookieValue = document.cookie.match('(^|;)\\s*' + name + '\\s*=\\s*([^;]+)');
return cookieValue ? cookieValue.pop() : '';
}
Problem:
- The form submits the correct mobile number, and the POST request reaches the Django view.
- Twilio's
Client
andmessages.create
methods are invoked, but no SMS is sent. - I see the following error in my Django logs:
Error sending OTP: TwilioRestException: [Error message here]
What I've Tried:
- Verified that the Twilio credentials (Account SID, Auth Token, and Phone Number) are correct.
- Ensured the phone number is in E.164 format with the correct country code.
- Confirmed that the Twilio trial account has verified the recipient's phone number.
- Checked Twilio message logs, but no entries appear for the failed message.
- Tested the Twilio functionality with a standalone script, which works:
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)
Expected Behavior: The OTP message should be sent to the entered mobile number.
Question: What could be causing the issue, and how can I resolve it? Could it be related to my Twilio account setup or Django configuration? Any help or insights would be appreciated!
Additional Notes:
- I'm using a Twilio trial account.
- The recipient's phone number is verified in Twilio's dashboard.
- There are no network or firewall restrictions in my setup.
Key improvements and troubleshooting steps:
1. Phone Number Formatting:
-Added proper phone number validation and formatting using the phonenumbers library
-Ensures numbers are in E.164 format required by Twilio
-Handles different country codes properly
from django.http import JsonResponse
from django.conf import settings
from twilio.rest import Client
from twilio.base.exceptions import TwilioRestException
import random
import json
import phonenumbers
def format_phone_number(phone_number, country_code='US'):
try:
parsed_number = phonenumbers.parse(phone_number, country_code)
if not phonenumbers.is_valid_number(parsed_number):
return None
return phonenumbers.format_number(parsed_number,
phonenumbers.PhoneNumberFormat.E164)
except phonenumbers.NumberParseException:
return None
def send_otp_view(request):
if request.method != "POST":
return JsonResponse({'status': 'error', 'message': 'Invalid
request method'})
try:
data = json.loads(request.body)
phone_number = data.get('phone_number')
if not phone_number:
return JsonResponse({'status': 'error', 'message': 'Phone
number is required'})
formatted_number = format_phone_number(phone_number)
if not formatted_number:
return JsonResponse({
'status': 'error',
'message': 'Invalid phone number format. Please include
country code.'
})
otp = str(random.randint(100000, 999999))
try:
client = Client(settings.TWILIO_ACCOUNT_SID,
settings.TWILIO_AUTH_TOKEN)
message = client.messages.create(
body=f"Your verification code is: {otp}",
from_=settings.TWILIO_PHONE_NUMBER,
to=formatted_number
)
request.session['otp'] = otp
request.session['phone_number'] = formatted_number
return JsonResponse({
'status': 'success',
'message': 'OTP sent successfully',
'otp': otp
})
except TwilioRestException as e:
print(f"Twilio Error: {str(e)}")
error_message = 'Failed to send OTP. '
if e.code == 20003:
error_message += 'Authentication failed. Check Twilio
credentials.'
elif e.code == 21211:
error_message += 'Invalid phone number format.'
elif e.code == 21608:
error_message += 'Phone number not verified in trial
account.'
else:
error_message += str(e)
return JsonResponse({'status': 'error', 'message':
error_message})
except Exception as e:
return JsonResponse({'status': 'error', 'message': f'Server error:
{str(e)}'})
2. Error Handling:
-Added specific error handling for common Twilio error codes
-Provides clear error messages for debugging
-Properly catches and reports authentication issues
3. Security Improvements:
-Better session handling for OTP storage
-More robust input validation
-Proper error message sanitization
To implement this solution:
# Install required packages:
pip install twilio phonenumbers
# In your frontend JavaScript, modify the error handling:
function sendOTP() {
const phoneNumber = document.getElementById('address-phone').value;
// Add country code if not provided by user
const formattedNumber = phoneNumber.startsWith('+') ? phoneNumber :
`+${phoneNumber}`;
fetch('/send-otp/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
},
body: JSON.stringify({ phone_number: formattedNumber })
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
alert('OTP sent successfully!');
} else {
alert(`Error: ${data.message}`);
}
})
.catch(error => {
console.error('Error:', error);
alert('Failed to send OTP. Please try again.');
});
}
Additional troubleshooting steps:
# Check your Twilio Console to ensure:
-Your account is active and has sufficient credits
-The recipient's number is verified (required for trial accounts)
-Your Twilio phone number is capable of sending SMS
# Verify your Django settings:
-All Twilio credentials are correctly set
-DEBUG mode is enabled during testing
-Your server has internet access to reach Twilio's API