أكثر 5 أخطاء يقع فيها المطورون عند تكامل WhatsApp OTP (وكيف تتجنبها)
من صيغ أرقام الهاتف الخاطئة إلى غياب معالجات الـ Webhook — هذه أكثر المشكلات شيوعاً التي يقع فيها المطورون عند تكامل WhatsApp OTP مع Nabda API، وكيفية حلها بدقة.
تكامل WhatsApp OTP مع Nabda عملية مباشرة — لكن بعض الأخطاء المتكررة قد تكلفك ساعات من البحث عن المشكلة. سواء كنت تبني تكاملاً جديداً أو تراجع تكاملاً قائماً، هذا الدليل يغطي أكثر 5 مشكلات نراها في تذاكر الدعم وكيف تتجنبها.
#1 — صيغة رقم الهاتف خاطئة
أكثر الأخطاء شيوعاً: إرسال رقم هاتف بدون رمز الدولة، أو ببدء الرقم بصفر بدلاً من رمز الدولة. يتطلب Nabda OTP صيغة E.164 — أي أن الرقم يجب أن يبدأ بـ + متبوعاً برمز الدولة.
// ❌ 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 جميع رموز دول منطقة الشرق الأوسط وشمال أفريقيا. العراق: +964، سوريا: +963، مصر: +20، السعودية: +966، الإمارات: +971. تحقق دائماً من الصيغة على السيرفر قبل الإرسال.
#2 — استخدام توكن API خاطئ
يمتلك Nabda OTP نوعين من التوكنات: توكن الحساب العام وتوكن Bearer الخاص بالـ Instance. إرسال الرسائل يتطلب توكن الـ Instance — الذي يُحصل عليه عبر استدعاء select-instance. استخدام التوكن العام سيُرجع دائماً خطأ 401 Unauthorized.
// ❌ 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 بأمان على السيرفر وتجديده بعد كل استدعاء لـ select-instance. لا تعرضه أبداً في كود الواجهة الأمامية أو السجلات.
#3 — تخزين OTP بنص صريح على جهاز العميل
تخزين الـ OTP في localStorage أو ملف تعريف ارتباط (Cookie)، أو إرساله في استجابة الـ API، يُعدّ خطأً أمنياً فادحاً. أي شخص لديه وصول إلى أدوات المطور في المتصفح يمكنه رؤيته وإعادة استخدامه. قم دائماً بتوليد الـ OTP وتشفيره والتحقق منه على السيرفر فقط.
// ❌ 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;
}استخدم تشفيراً مشفراً (SHA-256) مع سر خاص بالسيرفر. خزّن الـ Hash في قاعدة البيانات — وليس الـ OTP بنصه الصريح. بهذه الطريقة، حتى في حال اختراق قاعدة البيانات لن يُكشف أي شيء قابل للاستخدام.
#4 — غياب وقت انتهاء صلاحية الـ OTP أو تحديد معدل المحاولات
الـ OTP بدون وقت انتهاء صلاحية يظل صالحاً إلى الأبد — مما يفتح الباب لهجمات القوة الغاشمة. وبدون تحديد معدل المحاولات، يمكن للمهاجم تجربة جميع الأكواد الستة أرقام الممكنة (مليون كود) في دقائق. كلا الحمايتين ضروريتان.
// ❌ 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 بين 3 و8 دقائق. قيّد محاولات التحقق بـ 5 محاولات كل 15 دقيقة لكل مستخدم. بعد 5 محاولات فاشلة، ألغِ الـ OTP واطلب إنشاء كود جديد.
#5 — عدم مراقبة حالة اتصال الـ Instance
يتطلب Instance الخاص بـ Nabda OTP جلسة واتساب متصلة. إذا انتهت صلاحية QR Code أو انقطع الاتصال بالهاتف، سيتوقف الـ Instance عن العمل — وستتوقف رسائل OTP عن الوصول بصمت تام. بدون Webhook، لن تعلم بذلك إلا بعد أن يبدأ المستخدمون بالشكوى.
// ❌ 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);
});قم دائماً بإعداد Webhook على Instance الخاص بك في Nabda. راقب أحداث instance.disconnected وأخطر فريقك فوراً. فكر في إعداد مراقبة تلقائية تفحص صحة الـ Instance كل بضع دقائق.
قائمة تحقق سريعة قبل الإطلاق
- أرقام الهاتف تستخدم صيغة E.164 مع رمز الدولة (+964، +963، +20...)
- توكن الـ Instance يُجلب عبر select-instance — وليس توكن الحساب العام
- يتم تشفير الـ OTP على السيرفر، ولا يُخزّن كنص صريح أو يُرسل للعميل
- وقت انتهاء الصلاحية محدد (3-8 دقائق) ومحاولات التحقق مقيّدة (5 محاولات كحد أقصى)
- Webhook مُعدّ لمراقبة حالة الـ Instance وأحداث توصيل الرسائل
ابنِ تكاملك بشكل صحيح من البداية
أنشئ Instance مجانياً على Nabda OTP واتبع هذه الممارسات الصحيحة منذ اليوم الأول.
إنشاء Instance مجاني