Learn how to send unlimited OTP messages without paying a single penny using WhatsApp Business API
WhatsApp Business API offers 4 message categories for different use cases:
Per message
β Available in Pakistan
When user initiates
β Available in Pakistan
Per OTP message
β Too expensive!
Per message
β Not for OTP
In Pakistan, the Authentication category (designed for OTP) costs PKR 25 per message - which is unaffordable for most businesses. The Utility category at PKR 1.6 is cheaper, but cannot be used for authentication messages.
We'll use the SERVICE category which is completely FREE when the customer initiates the conversation! This is 100% legal and compliant with WhatsApp's policies.
When a customer initiates the conversation (by sending the first message), WhatsApp allows you to respond within a 24-hour window using the Service category at no cost. This is exactly how banks and other services already operate!
When your customer fills out a form (name, email, mobile), create a special WhatsApp link that:
https://wa.me/92313XXXXXXX?text=otp
92313XXXXXXX = Your WhatsApp Business number (with country code)?text=otp = Pre-filled message textConfigure your webhook to receive incoming WhatsApp messages:
Settings β Webhook Configurationhttps://yourdomain.com/webhook.phpGo directly to: https://wa.sendpk.com/dashboard/profile_webhook.php
When a customer sends a message, WhatsApp sends a JSON payload to your webhook:
{
"object": "whatsapp_business_account",
"entry": [{
"id": "40752625244XXX",
"changes": [{
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "92313XXXXX",
"phone_number_id": "43001968685XXX"
},
"contacts": [{
"profile": {
"name": "Mubashar Shahzad"
},
"wa_id": "92300XXXXX"
}],
"messages": [{
"from": "92300XXXX",
"id": "wamid.HBgMOTIzMDAxNjU0MzIxwA==",
"timestamp": "1769151710",
"text": {
"body": "otp"
},
"type": "text"
}]
},
"field": "messages"
}]
}]
}
Create a webhook.php file to process incoming messages and send OTP:
<?php
// ===================================================
// STEP 1: Read incoming WhatsApp webhook data
// ===================================================
$input = file_get_contents("php://input");
$data = json_decode($input, true);
// Optional: Log all incoming webhooks for debugging
file_put_contents(
"webhook_log.txt",
date('Y-m-d H:i:s') . " " . $input . PHP_EOL,
FILE_APPEND
);
// ===================================================
// STEP 2: Extract message from webhook data
// ===================================================
if (isset($data['entry'][0]['changes'][0]['value']['messages'][0])) {
$message = $data['entry'][0]['changes'][0]['value']['messages'][0];
// Only process text messages
if ($message['type'] === 'text') {
// Get sender's number and message content
$from = $message['from']; // e.g., "92300XXXXXXX"
$text = trim($message['text']['body']); // e.g., "otp"
// ===================================================
// STEP 3: Check if message is OTP request
// ===================================================
if (strtolower($text) === 'otp') {
// Generate random 6-digit OTP code
$otp_code = rand(100000, 999999);
// ===================================================
// STEP 4: Store OTP in database (IMPORTANT!)
// ===================================================
// Connect to your database
// $conn = mysqli_connect("localhost", "user", "pass", "db");
// Store OTP with expiry time
// $expiry = date('Y-m-d H:i:s', strtotime('+5 minutes'));
// $sql = "INSERT INTO otp_codes (mobile, otp_code, created_at, expires_at)
// VALUES ('$from', '$otp_code', NOW(), '$expiry')";
// mysqli_query($conn, $sql);
// ===================================================
// STEP 5: Prepare WhatsApp message payload
// ===================================================
$payload = [
[
"mobile" => $from, // Send to customer's number
"type" => "text", // Message type
"text" => [
"body" => "Your OTP code is: $otp_code\n\nThis code will expire in 5 minutes.\n\nDo not share this code with anyone."
]
]
];
// ===================================================
// STEP 6: Send OTP via WhatsApp (FREE!)
// ===================================================
$api_url = "https://wa.sendpk.com/api/send.php";
$api_key = "YOUR_API_KEY_HERE"; // Get from wa.sendpk.com
// Construct final API URL
$url = $api_url . "?api_key=" . $api_key .
"&free_form=" . urlencode(json_encode($payload));
// Send the request
$response = file_get_contents($url);
// Optional: Log the response
file_put_contents(
"otp_sent_log.txt",
date('Y-m-d H:i:s') . " Sent OTP $otp_code to $from - Response: $response" . PHP_EOL,
FILE_APPEND
);
}
}
}
// ===================================================
// STEP 7: Always respond with 200 OK
// ===================================================
http_response_code(200);
echo "OK";
?>
Store generated OTPs in your database so you can verify them later:
CREATE TABLE otp_codes (
id INT AUTO_INCREMENT PRIMARY KEY,
mobile VARCHAR(20) NOT NULL,
otp_code VARCHAR(6) NOT NULL,
created_at DATETIME NOT NULL,
expires_at DATETIME NOT NULL,
verified TINYINT(1) DEFAULT 0,
INDEX idx_mobile (mobile),
INDEX idx_otp (otp_code)
);
<?php
// Database connection
$conn = mysqli_connect("localhost", "username", "password", "database");
// Store OTP
$mobile = $from; // From webhook
$otp_code = rand(100000, 999999);
$expires_at = date('Y-m-d H:i:s', strtotime('+5 minutes'));
$sql = "INSERT INTO otp_codes (mobile, otp_code, created_at, expires_at)
VALUES (?, ?, NOW(), ?)";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, "sss", $mobile, $otp_code, $expires_at);
mysqli_stmt_execute($stmt);
?>
When the user enters the OTP code, verify it against your database:
<?php
// verify_otp.php
$conn = mysqli_connect("localhost", "username", "password", "database");
// Get submitted data
$mobile = $_POST['mobile']; // e.g., "92300XXXXXXX"
$submitted_otp = $_POST['otp']; // e.g., "123456"
// Query database for valid OTP
$sql = "SELECT * FROM otp_codes
WHERE mobile = ?
AND otp_code = ?
AND verified = 0
AND expires_at > NOW()
ORDER BY created_at DESC
LIMIT 1";
$stmt = mysqli_prepare($conn, $sql);
mysqli_stmt_bind_param($stmt, "ss", $mobile, $submitted_otp);
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
if (mysqli_num_rows($result) > 0) {
// OTP is valid!
// Mark as verified
$sql_update = "UPDATE otp_codes SET verified = 1 WHERE mobile = ? AND otp_code = ?";
$stmt_update = mysqli_prepare($conn, $sql_update);
mysqli_stmt_bind_param($stmt_update, "ss", $mobile, $submitted_otp);
mysqli_stmt_execute($stmt_update);
echo json_encode([
"success" => true,
"message" => "Mobile number verified successfully!"
]);
} else {
// Invalid or expired OTP
echo json_encode([
"success" => false,
"message" => "Invalid or expired OTP code"
]);
}
?>
Here's how everything works together in a user registration form:
<!DOCTYPE html>
<html>
<head>
<title>Registration with WhatsApp OTP</title>
</head>
<body>
<h2>User Registration</h2>
<form id="registrationForm">
<label>Name:</label>
<input type="text" name="name" required><br><br>
<label>Email:</label>
<input type="email" name="email" required><br><br>
<label>Mobile (with country code):</label>
<input type="text" name="mobile" id="mobile" placeholder="92300XXXXXXX" required><br><br>
<!-- Button to request OTP via WhatsApp -->
<button type="button" onclick="requestOTP()">
π± Verify via WhatsApp
</button>
</form>
<!-- OTP Verification Section (shown after requesting OTP) -->
<div id="otpSection" style="display:none; margin-top: 20px;">
<h3>Enter OTP Code</h3>
<input type="text" id="otpCode" placeholder="Enter 6-digit code" maxlength="6">
<button onclick="verifyOTP()">Verify OTP</button>
</div>
<script>
function requestOTP() {
var mobile = document.getElementById('mobile').value;
if (!mobile) {
alert('Please enter your mobile number');
return;
}
// Redirect to WhatsApp with pre-filled "otp" text
var whatsappURL = 'https://wa.me/92313XXXXXXX?text=otp';
window.open(whatsappURL, '_blank');
// Show OTP input section
document.getElementById('otpSection').style.display = 'block';
alert('Please send the message in WhatsApp. You will receive your OTP code shortly!');
}
function verifyOTP() {
var mobile = document.getElementById('mobile').value;
var otp = document.getElementById('otpCode').value;
// Send AJAX request to verify OTP
fetch('verify_otp.php', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: 'mobile=' + mobile + '&otp=' + otp
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('β
' + data.message);
// Continue with registration...
} else {
alert('β ' + data.message);
}
});
}
</script>
</body>
</html>
Save PKR 25 per OTP compared to Authentication category
Send unlimited OTP messages without worrying about costs
100% compliant with WhatsApp's terms of service
Instant delivery through WhatsApp's infrastructure
Remember that the FREE service window lasts for 24 hours from when the customer sends the first message. You can send multiple messages within this window at no cost!
By using the Service category approach, you can send FREE WhatsApp OTP messages to your customers without any cost per message. This method is:
Questions or need help? Feel free to reach out for technical support and implementation assistance.