Geliştiricilerin Yaptığı En Yaygın 5 WhatsApp OTP Hatası (ve Nasıl Düzeltilir)
Yanlış telefon numarası formatlarından eksik webhook işleyicilerine kadar — Nabda API ile WhatsApp OTP entegrasyonunda geliştiricilerin en sık karşılaştığı sorunlar ve bunların tam çözümleri.
Nabda ile WhatsApp OTP entegrasyonu oldukça basittir — ancak birkaç tekrarlayan hata saatlerce hata ayıklamanıza neden olabilir. İster yeni bir entegrasyon kuruyor olun ister mevcut birini denetliyor olun, bu rehber destek taleplerinde en çok gördüğümüz 5 sorunu ve bunlardan nasıl kaçınılacağını ele alıyor.
#1 — Yanlış Telefon Numarası Formatı
En yaygın hata: ülke kodu olmadan telefon numarası göndermek veya ülke kodu yerine sıfırla başlamak. Nabda OTP, E.164 formatı gerektirir — yani numara + işaretiyle ve ardından ülke koduyla başlamalıdır.
// ❌ Wrong — missing country code
const phone = "07701234567";
await fetch("https://api.nabdaotp.com/api/v1/messages/send", {
body: JSON.stringify({ phone, message: "Your OTP: 123456" })
});// ✅ Correct — E.164 format
const phone = "+9647701234567"; // Iraq
// const phone = "+9639XXXXXXXX"; // Syria
// const phone = "+201XXXXXXXXX"; // Egypt
await fetch("https://api.nabdaotp.com/api/v1/messages/send", {
body: JSON.stringify({ phone, message: "Your OTP: 123456" })
});Nabda OTP tüm MENA ülke kodlarını destekler. Irak: +964, Suriye: +963, Mısır: +20, Suudi Arabistan: +966, BAE: +971. Göndermeden önce her zaman sunucu tarafında formatı doğrulayın.
#2 — Yanlış API Token Kullanımı
Nabda OTP'nin iki tür token'ı vardır: genel hesap token'ınız ve instance kapsamlı Bearer token. Mesaj göndermek için instance token'ı gereklidir — select-instance çağrısıyla elde edilir. Genel token kullanmak her zaman 401 Unauthorized hatası döndürür.
// ❌ Wrong — using global Nabda token, not instance token
const res = await fetch("https://api.nabdaotp.com/api/v1/messages/send", {
headers: { "Authorization": `Bearer ${globalApiToken}` }
});
// Result: 401 Unauthorized// ✅ Correct — select instance first, then use instance token
const selectRes = await fetch(
"https://api.nabdaotp.com/api/v1/auth/select-instance",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ instanceId: "your-instance-id" })
}
);
const { accessToken } = await selectRes.json();
// Now use accessToken for messages
const res = await fetch("https://api.nabdaotp.com/api/v1/messages/send", {
headers: { "Authorization": `Bearer ${accessToken}` }
});Instance token'ı arka uçta güvenli şekilde saklanmalı ve her select-instance çağrısından sonra yenilenmelidir. Asla frontend kodunda veya loglarda açığa çıkarmayın.
#3 — OTP'yi İstemci Tarafında Düz Metin Olarak Saklamak
OTP'yi localStorage'da, bir cookie'de saklamak veya API yanıtında geri göndermek kritik bir güvenlik hatasıdır. Tarayıcı geliştirici araçlarına erişimi olan herkes bunu görüp yeniden kullanabilir. OTP'leri her zaman arka uçta oluşturun, şifreleyin ve doğrulayın.
// ❌ Wrong — storing plain OTP and verifying on the client
const otp = "482917";
localStorage.setItem("otp", otp); // NEVER DO THIS
// Client-side verification — easily bypassed
if (userInput === localStorage.getItem("otp")) {
allowLogin();
}// ✅ Correct — hash on backend, verify on backend
import crypto from "crypto";
const SECRET = process.env.OTP_SECRET;
const otp = generateOTP(); // e.g. "482917"
const hash = crypto.createHash("sha256")
.update(otp + SECRET)
.digest("hex");
// Store hash + expiry in DB, never the plain OTP
await db.otpStore.set(userId, { hash, expiresAt: Date.now() + 5 * 60 * 1000 });
// On verification — backend only
function verifyOTP(input, storedHash) {
const inputHash = crypto.createHash("sha256")
.update(input + SECRET).digest("hex");
return inputHash === storedHash;
}Sunucu tarafı bir gizli anahtarla birlikte kriptografik hash (SHA-256) kullanın. Hash'i veritabanında saklayın — düz OTP'yi asla değil. Bu sayede bir veritabanı sızıntısında bile kullanılabilir hiçbir şey açığa çıkmaz.
#4 — OTP Doğrulamasında Süre Sonu veya Hız Sınırlaması Olmaması
Süre sonu olmayan bir OTP sonsuza kadar geçerlidir — bu da kaba kuvvet saldırılarına kapı açar. Hız sınırlaması olmadan, saldırgan dakikalar içinde 6 haneli tüm olası kodları (1.000.000 kod) deneyebilir. Her iki koruma da zorunludur.
// ❌ Wrong — no expiry, no rate limiting
// OTP stored forever, brute-forceable
app.post("/verify", (req, res) => {
const { otp } = req.body;
if (otp === storedOtp) { // stored without expiry
res.json({ success: true });
}
});// ✅ Correct — expiry check + rate limiting
const MAX_ATTEMPTS = 5;
const OTP_TTL_MS = 5 * 60 * 1000; // 5 minutes
app.post("/verify", rateLimiter({ max: MAX_ATTEMPTS, window: "15m" }), async (req, res) => {
const record = await db.otpStore.get(userId);
if (!record) return res.status(400).json({ error: "OTP expired or not found" });
if (Date.now() > record.expiresAt) return res.status(400).json({ error: "OTP expired" });
if (!verifyOTP(req.body.otp, record.hash)) return res.status(400).json({ error: "Invalid OTP" });
await db.otpStore.delete(userId); // single-use
res.json({ success: true });
});OTP geçerlilik süresini 3-8 dakika olarak ayarlayın. Kullanıcı başına 15 dakikalık pencerede doğrulama denemelerini 5 ile sınırlayın. 5 başarısız denemeden sonra OTP'yi geçersiz kılın ve yeni bir tane isteyin.
#5 — Instance Bağlantı Durumunu İzlememek
Nabda OTP instance'ı bağlı bir WhatsApp oturumu gerektirir. QR kodu süresi dolarsa veya telefon bağlantısı kesilirse instance çevrimdışı olur — ve OTP'ler sessizce iletilmeyi durdurur. Webhook olmadan, kullanıcılar şikayet etmeye başlayana kadar bunun farkına varmazsınız.
// ❌ Wrong — not handling instance disconnection
// Instance QR expires or WhatsApp disconnects — messages silently fail
// No alert, no fallback, users never receive their OTP// ✅ Correct — configure webhook + monitor instance status
// 1. Set up webhook on Nabda dashboard or via API:
await fetch("https://api.nabdaotp.com/api/v1/instances/webhook", {
method: "PATCH",
headers: { "Authorization": `Bearer ${instanceToken}` },
body: JSON.stringify({
webhookUrl: "https://yourdomain.com/nabda/webhook",
webhookEnabled: true
})
});
// 2. Handle events in your webhook handler:
app.post("/nabda/webhook", (req, res) => {
const { event, status } = req.body;
if (event === "instance.disconnected") {
// Alert your team — re-scan QR code required
notifyTeam("Nabda instance disconnected — OTP delivery paused");
}
if (event === "message.failed") {
// Log and retry or fallback to SMS
handleDeliveryFailure(req.body);
}
res.sendStatus(200);
});Nabda instance'ınıza her zaman bir webhook yapılandırın. instance.disconnected olaylarını dinleyin ve ekibinizi anında uyarın. Her birkaç dakikada bir instance sağlığını kontrol eden otomatik bir monitör kurmayı düşünün.
Yayına Almadan Önce Hızlı Kontrol Listesi
- Telefon numaraları ülke koduyla E.164 formatını kullanıyor (+964, +963, +20...)
- Instance token'ı select-instance üzerinden alınıyor — genel hesap token'ı değil
- OTP'ler sunucu tarafında şifreleniyor, düz metin olarak saklanmıyor veya istemciye gönderilmiyor
- Geçerlilik süresi ayarlandı (3-8 dk) ve doğrulama hız sınırlandı (maks. 5 deneme)
- Instance durumunu ve mesaj iletim olaylarını izlemek için Webhook yapılandırıldı
Entegrasyonunuzu baştan doğru yapın
Ücretsiz Nabda OTP instance'ınızı oluşturun ve bu en iyi uygulamaları ilk günden itibaren takip edin.
Ücretsiz Instance Oluştur