June 7, 2026

Email OTP Is Broken in Morocco — Here's the WhatsApp API That Actually Works

Why email OTPs fail in Morocco and MENA, and how to replace them with Wasel's WhatsApp API in 10 minutes. Drop-in REST API for developers.

whatsapp api webdev morocco otp
Email OTP Is Broken in Morocco — Here's the WhatsApp API That Actually Works

You’ve just launched your app. Sign-up flow looks clean. You fire the OTP to the user’s email.

They never see it.

Not in spam. Not in promotions. Just… gone. Or received 4 minutes later, after the code expired.

In Europe or the US, this is a minor UX annoyance. In Morocco and most of MENA, it’s a conversion killer.

Here’s why — and what you can do about it today.

Why email fails in Morocco

Morocco has ~95% WhatsApp penetration on smartphones. Every shop owner, every patient, every customer manages their life through WhatsApp. It’s open all day. It has notifications. It gets read.

Email? Most users don’t have a mail app configured on their phone. And those who do have trained themselves to ignore it — promotional tabs, spam filters, delivery delays. The few that get through land 4 hours later when the OTP has long expired.

This isn’t a Moroccan quirk. It’s true across MENA, Sub-Saharan Africa, and large parts of Southeast Asia. WhatsApp is the inbox. Email is where you store receipts you’ll never open.

WhatsApp vs SMS vs Email: honest comparison for Morocco

EmailSMSWhatsApp
Open rate (Morocco)8–14%~75%90–98%
Delivery speed0–10 minInstantInstant
Read timeHours / daysMinutesSeconds
Cost per message~$0.001~$0.04–0.07~$0.005–0.015
User frictionHigh (app switch)LowVery low
Copy code UXNoNo✅ Native button
Rich contentHTML (often clipped)Plain textTemplates + media
Works offlineNoNo
Moroccan user expectationLowMediumHigh

Honest take: SMS is still better than email in Morocco and works offline. But WhatsApp costs less than SMS, has better UX, and users are already there. The only real case for SMS is when you need guaranteed offline delivery.


What breaks in production

User signs up
→ OTP sent to email
→ User doesn't have push email configured (very common)
→ OTP expires after 5 minutes
→ User retries 2–3x
→ User abandons

Your funnel leaks at peak intent. Your email provider reports it as “delivered.” You have zero visibility.


The fix: Wasel External API

Wasel exposes a clean REST API that lets any CRM, ERP, or backend send WhatsApp template messages and OTPs — without touching the WhatsApp Business API directly.

Base URL: https://wasel-api.wasel.ma/external/v1 Auth: X-API-Key: ext_YOUR_API_KEY_HERE


1. WhatsApp OTP — drop-in email replacement

The cleanest part: you never handle the code yourself. The API generates it, sends it via WhatsApp, and validates it. Your backend just calls two endpoints.

Step 1 — Send the OTP

Terminal window
curl -s -X POST "https://wasel-api.wasel.ma/external/v1/otp/send" \
-H "X-API-Key: ext_YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"phone": "+212600000000",
"lang": "fr",
"ttl_minutes": 10,
"reference": "session_abc123"
}' | jq

Parameters:

  • phone — E.164 format (required)
  • langfr / en / ar (default: fr)
  • ttl_minutes — 1–60, default 10
  • max_attempts — 1–10, default 3
  • reference — your session/user ID (recommended, binds send → verify)

The user receives a native WhatsApp message with a one-tap “Copy code” button — no typing, no app-switching.

Step 2 — Verify the code

Terminal window
curl -s -X POST "https://wasel-api.wasel.ma/external/v1/otp/verify" \
-H "X-API-Key: ext_YOUR_API_KEY_HERE" \
-H "Content-Type: application/json" \
-d '{
"phone": "+212600000000",
"code": "847291",
"reference": "session_abc123"
}' | jq

Response reason values:

reasonmeaning
verified✅ Correct code
invalid_codeWrong — attempts counter incremented
expiredTTL passed — re-send required
max_attempts_exceededToo many wrong guesses
not_foundNo pending OTP for this phone

Check status anytime

Terminal window
curl -s "https://wasel-api.wasel.ma/external/v1/otp/status?phone=%2B212600000000&reference=session_abc123" \
-H "X-API-Key: ext_YOUR_API_KEY_HERE" | jq

Requires a key with scope otp.


2. Send a single template message

Order confirmations, appointment reminders, payment alerts — triggered directly from your CRM or ERP.

Terminal window
curl -X POST "https://wasel-api.wasel.ma/external/v1/send-template" \
-H "Content-Type: application/json" \
-H "X-API-Key: ext_YOUR_API_KEY_HERE" \
-d '{
"phone": "+212600000000",
"template_name": "order_confirmation",
"lang": "fr",
"variables": ["ORD-1001"],
"response_action_key": "order-confirm-v1",
"custom_data": {
"orderNumber": "ORD-1001",
"source": "erp"
}
}'

Two optional power fields:

  • response_action_key — binds the user’s WhatsApp reply to a workflow action (e.g. user replies “Confirmer” → triggers an automation in your system)
  • custom_data — arbitrary JSON stored with the message for downstream handling in your ERP/CRM

3. Bulk template (up to 500 recipients)

One call, personalized per recipient. No loop on your side.

Terminal window
curl -X POST "https://wasel-api.wasel.ma/external/v1/send-template-bulk" \
-H "Content-Type: application/json" \
-H "X-API-Key: ext_YOUR_API_KEY_HERE" \
-d '{
"template_name": "appointment_reminder",
"lang": "fr",
"recipients": [
{ "phone": "+212600000001", "variables": ["demain à 10h"] },
{ "phone": "+212600000002", "variables": ["demain à 14h"] }
]
}'

Error handling reference

400 invalid payload / missing fields / template not found
401 invalid or missing X-API-Key header
403 rate limit or quota exceeded
404 no OTP found for this phone (status endpoint)
429 rate limited — back off and retry
500 unexpected server error
502 WhatsApp delivery failed — retry after a few seconds

Default rate limit: 60 req/min per key. Simple exponential backoff for 429 / 502:

async function sendWithRetry(payload, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const res = await fetch('https://wasel-api.wasel.ma/external/v1/send-template', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.WASEL_API_KEY
},
body: JSON.stringify(payload)
});
if (res.status === 429 || res.status === 502) {
await new Promise(r => setTimeout(r, 1000 * 2 ** attempt));
continue;
}
return res.json();
}
throw new Error('Max retries exceeded');
}

Real-world impact

Switching from email → WhatsApp OTP on Moroccan deployments:

  • Sign-up completion rate: +35–50%
  • OTP support tickets: -70–80%
  • Time to verified: ~4 min average → ~25 seconds

Appointment reminders:

  • No-show rate: -40–50%
  • Confirmation response rate: ~5% (email) → ~68% (WhatsApp)

Compliance note

WhatsApp requires opt-in. For OTP/transactional: user providing their phone at sign-up is sufficient. For marketing: explicit checkbox required. Morocco follows CNDP regulations, broadly aligned with GDPR.


TL;DR

  1. Email OTP in Morocco/MENA has 8–14% open rate. WhatsApp: 90%+.
  2. /otp/send + /otp/verify is a drop-in replacement — you never touch the code itself.
  3. /send-template and /send-template-bulk cover every transactional notification your stack needs.
  4. response_action_key + custom_data wire WhatsApp replies back into your existing workflows.
  5. Standard REST, X-API-Key auth, 60 req/min, clean error codes.

🚀 Start free 7-day trial — no credit card

Get Started Today →

Built something with the Wasel API? Drop a comment — happy to help debug or extend the examples.