Below is a complete, production-ready OTP implementation guide using PHP + MySQL + Text.lk SMS Gateway in Sri Lanka
OTP (One-Time Password) verification is commonly used for:
- User registration
- Login verification
- Password reset
- Sensitive actions (payments, profile changes)
In this tutorial, you’ll learn step-by-step how to implement an OTP system using:
- PHP (backend)
- MySQL (database)
- Text.lk SMS Gateway
OTP Flow Overview
- User enters mobile number
- System generates a random OTP
- OTP is stored in the database (hashed)
- OTP is sent via Text.lk SMS
- User enters OTP
- OTP is verified & expired OTPs rejected
Project Structure
otp-system/
├── config.php
├── db.php
├── send_otp.php
├── verify_otp.php
├── textlk.php
├── index.php
└── verify.phpStep 1: Create MySQL Table
CREATE TABLE otp_verifications (
id INT AUTO_INCREMENT PRIMARY KEY,
mobile VARCHAR(15) NOT NULL,
otp_hash VARCHAR(255) NOT NULL,
expires_at DATETIME NOT NULL,
is_verified TINYINT(1) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);✅ Security tip: Store OTP as a hash, not plain text.
Step 2: Configuration (config.php)
<?php
define('TEXTLK_API_KEY', 'YOUR_TEXTLK_API_KEY');
define('TEXTLK_SENDER_ID', 'TEXTLK');
define('TEXTLK_API_URL', 'https://app.text.lk/api/v3/sms/send');
define('OTP_EXPIRY_MINUTES', 5);Step 3: Database Connection (db.php)
<?php
$conn = new mysqli("localhost", "db_user", "db_pass", "db_name");
if ($conn->connect_error) {
die("Database connection failed");
}Step 4: Text.lk SMS Sender (textlk.php)
<?php
function sendSMS($mobile, $message) {
$payload = [
"api_key" => TEXTLK_API_KEY,
"sender_id" => TEXTLK_SENDER_ID,
"to" => $mobile,
"message" => $message
];
$ch = curl_init(TEXTLK_API_URL);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}Step 5: Generate & Send OTP (send_otp.php)
<?php
require 'config.php';
require 'db.php';
require 'textlk.php';
$mobile = $_POST['mobile'];
// Generate OTP
$otp = rand(100000, 999999);
$otpHash = password_hash($otp, PASSWORD_DEFAULT);
$expiresAt = date('Y-m-d H:i:s', strtotime('+'.OTP_EXPIRY_MINUTES.' minutes'));
// Save OTP
$stmt = $conn->prepare("
INSERT INTO otp_verifications (mobile, otp_hash, expires_at)
VALUES (?, ?, ?)
");
$stmt->bind_param("sss", $mobile, $otpHash, $expiresAt);
$stmt->execute();
// Send SMS
$message = "Your OTP code is $otp. Valid for 5 minutes.";
sendSMS($mobile, $message);
echo json_encode(["status" => "success", "message" => "OTP sent"]);Step 6: Verify OTP (verify_otp.php)
<?php
require 'db.php';
$mobile = $_POST['mobile'];
$otp = $_POST['otp'];
$stmt = $conn->prepare("
SELECT id, otp_hash, expires_at
FROM otp_verifications
WHERE mobile = ? AND is_verified = 0
ORDER BY id DESC LIMIT 1
");
$stmt->bind_param("s", $mobile);
$stmt->execute();
$result = $stmt->get_result();
if ($row = $result->fetch_assoc()) {
if (strtotime($row['expires_at']) < time()) {
echo json_encode(["status" => "error", "message" => "OTP expired"]);
exit;
}
if (password_verify($otp, $row['otp_hash'])) {
$update = $conn->prepare("UPDATE otp_verifications SET is_verified = 1 WHERE id = ?");
$update->bind_param("i", $row['id']);
$update->execute();
echo json_encode(["status" => "success", "message" => "OTP verified"]);
} else {
echo json_encode(["status" => "error", "message" => "Invalid OTP"]);
}
} else {
echo json_encode(["status" => "error", "message" => "OTP not found"]);
}Step 7: Frontend (Simple Example)
OTP Request (index.php)
<form method="post" action="send_otp.php">
<input type="text" name="mobile" placeholder="07XXXXXXXX" required>
<button type="submit">Send OTP</button>
</form>OTP Verify (verify.php)
<form method="post" action="verify_otp.php">
<input type="text" name="mobile" required>
<input type="text" name="otp" placeholder="Enter OTP" required>
<button type="submit">Verify</button>
</form>Security Best Practices
✔ OTP expiry (5 minutes max)
✔ Hash OTP in database
✔ Limit OTP requests per number
✔ Prevent brute force attempts
✔ Use HTTPS always
✔ Delete old OTP records via cron
Optional: Auto Cleanup (Cron Job)
DELETE FROM otp_verifications
WHERE expires_at < NOW();Run every hour via cron.
🎯 Use Cases
- User registration
- Login verification
- Password reset
- Transaction confirmation
- Admin action confirmation
🚀 Production Enhancements
- Redis for OTP storage (high traffic)
- Rate-limit OTP requests
- Device fingerprinting
- Webhook-based delivery reports from Text.lk
🏁 Conclusion
You now have a secure, scalable OTP system using PHP, MySQL, and Text.lk SMS that’s suitable for:
- SaaS platforms
- Banking & fintech
- Sri Lanka–based applications