L'API OTP WhatsApp pour les développeurs en 2026 — envoyer et vérifier un code, avec des exemples PHP, Laravel, Node.js et Python
Le contrat complet de l'API des codes de vérification WhatsApp : envoi, vérification, renvoi et statut, la clé publique et la signature HMAC, chaque code d'erreur et chaque limite, et le rappel de vérification vers votre serveur — avec du code prêt en PHP, Laravel, Node.js, Python et curl.
Sur cette page 11
- Avant la première ligne de code : une application OTP et deux clés
- Cinq points d'accès — tout le contrat
- L'essai le plus rapide : curl
- PHP — une seule fonction pour chaque appel
- Laravel — via Http
- Node.js — fetch, sans bibliothèque
- Python — requests
- Erreurs et limites — et que faire pour chacune
- Le rappel de vérification vers votre serveur
- Ce que vous payez
- Questions des développeurs
La réponse courte
Avec deux appels : POST /wa_otp_api/send avec le numéro de l'utilisateur renvoie un request_id, puis POST /wa_otp_api/verify avec ce request_id et le code qu'il a saisi. L'authentification se fait par une clé publique dans l'en-tête X-Public-Key, avec une signature HMAC-SHA256 facultative à l'aide du secret. Le code part de votre numéro connecté au tarif de Meta pour le pays du destinataire, ou du numéro partagé à un prix fixe par code, sans compte Meta. C'est la seule API publique pour développeurs ouverte aujourd'hui.
Aussi demandé sous la forme : «WhatsApp OTP PHP» · «WhatsApp OTP Laravel» · «WhatsApp OTP Node.js» · «OTP API» · «كيف ارسل كود تحقق على الواتساب من الموقع» · «كود OTP لا يصل» · «خدمة OTP للشركات» · «كيف أرسل OTP من API؟» · «WhatsApp OTP integration» · «WhatsApp API example» · «WhatsApp API error» · «OTP واتساب لا يصل»
Avant la première ligne de code : une application OTP et deux clés
- Créez une application OTP dans le tableau de bord
Depuis OTP dans le tableau de bord d'ArabyBot : le nom de l'application, le nom affiché dans le message, puis l'expéditeur — le numéro partagé prêt à l'emploi, ou un numéro WhatsApp connecté à votre compte. Vous fixez aussi la longueur du code, sa durée de validité et le nombre de tentatives de vérification autorisées.
- Récupérez la clé publique et le secret
La clé publique commence par
ab_otp_et voyage dans l'en-têteX-Public-Keyà chaque appel. Le secret commence parsk_otp_et n'est affiché qu'une fois à la création : gardez-le dans les variables d'environnement de votre serveur — c'est avec lui que vous signez les requêtes et que vous vérifiez notre signature sur les rappels. - Choisissez ce que vous verrouillez
Trois réglages facultatifs sur l'application : la signature des requêtes (tout appel sans X-Signature valide est refusé), les domaines autorisés pour les appels du navigateur, et les adresses IP de vos serveurs. Activez le premier et le troisième si l'appel passe par votre serveur.
Cinq points d'accès — tout le contrat
| Point d'accès | Corps | Retours |
|---|---|---|
POST /wa_otp_api/send | phone, facultatif lang, length (4–8), expiry_seconds (60–900), metadata | request_id, expires_in, expires_at |
POST /wa_otp_api/verify | request_id, code | request_id, phone, verified_at |
POST /wa_otp_api/resend | request_id | Un nouveau code, même forme que l'envoi |
GET /wa_otp_api/status/{request_id} | — | status, attempts, expires_at, verified_at |
GET /wa_otp_api/ping | — | Le nom et les réglages de l'application — pour vérifier que la clé fonctionne |
Le téléphone est au format international, chiffres uniquement ou précédés d'un + : 966501234567. Chaque erreur revient sous une seule forme : {"success":false,"error_code":"…","message":"…"} : construisez donc votre logique sur error_code, pas sur le texte du message.
L'essai le plus rapide : curl
# 1) send the code
curl -X POST https://app.arabybot.com/wa_otp_api/send \
-H "Content-Type: application/json" \
-H "X-Public-Key: ab_otp_…" \
-d '{"phone":"966501234567","lang":"ar"}'
{"success":true,"request_id":"3f6c2a9e-41b7-4c0d-9a51-7e2b8d4f1c60",
"expires_in":300,"expires_at":"2026-10-01 14:05:00"}
# 2) verify what the user typed
curl -X POST https://app.arabybot.com/wa_otp_api/verify \
-H "Content-Type: application/json" \
-H "X-Public-Key: ab_otp_…" \
-d '{"request_id":"3f6c2a9e-41b7-4c0d-9a51-7e2b8d4f1c60","code":"482913"}'
{"success":true,"request_id":"3f6c2a9e-…","phone":"966501234567",
"verified_at":"2026-10-01 14:01:12"}
PHP — une seule fonction pour chaque appel
Le cURL intégré de PHP suffit, sans bibliothèque. Gardez le request_id dans la session de l'utilisateur côté serveur, pas dans la page.
<?php
// One helper for every call. Keys come from the environment, never from the page.
function arabybot_otp(string $action, array $body): array
{
$raw = json_encode($body);
$headers = [
'Content-Type: application/json',
'X-Public-Key: ' . getenv('ARABYBOT_OTP_PUBLIC_KEY'),
];
if ($secret = getenv('ARABYBOT_OTP_SECRET')) { // only if request signing is on
$headers[] = 'X-Signature: sha256=' . hash_hmac('sha256', $raw, $secret);
}
$ch = curl_init('https://app.arabybot.com/wa_otp_api/' . $action);
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_POSTFIELDS => $raw, CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15,
]);
$res = json_decode((string) curl_exec($ch), true);
curl_close($ch);
return is_array($res) ? $res : ['success' => false, 'error_code' => 'network'];
}
session_start();
$sent = arabybot_otp('send', ['phone' => '201001234567', 'lang' => 'ar']);
$_SESSION['otp_request'] = $sent['request_id'] ?? null;
// …later, when the user submits the code
$check = arabybot_otp('verify', ['request_id' => $_SESSION['otp_request'], 'code' => $_POST['code']]);
if (! empty($check['success'])) {
// the number is verified
}
Laravel — via Http
Le client Http de Laravel écrit la requête en quelques lignes. Si vous activez la signature des requêtes, signez exactement les octets que vous envoyez : construisez le JSON vous-même et envoyez-le avec withBody, car ->post() réencode le tableau.
use Illuminate\Support\Facades\Http;
// config/services.php → 'arabybot' => ['otp_key' => env('ARABYBOT_OTP_PUBLIC_KEY')]
$otp = Http::withHeaders(['X-Public-Key' => config('services.arabybot.otp_key')])
->baseUrl('https://app.arabybot.com/wa_otp_api')
->timeout(15);
$sent = $otp->post('/send', ['phone' => $request->phone, 'lang' => 'ar'])->json();
session(['otp_request' => $sent['request_id'] ?? null]);
$check = $otp->post('/verify', [
'request_id' => session('otp_request'),
'code' => $request->code,
])->json();
if ($check['success'] ?? false) {
// verified
}
// With request signing on, sign the exact bytes you send:
// $raw = json_encode($body);
// Http::withHeaders([... 'X-Signature' => 'sha256='.hash_hmac('sha256', $raw, $secret)])
// ->withBody($raw, 'application/json')->post('https://app.arabybot.com/wa_otp_api/send');
Node.js — fetch, sans bibliothèque
import crypto from 'node:crypto';
// Node 18+ (built-in fetch). Keys from the environment.
async function otp(action, body) {
const raw = JSON.stringify(body);
const headers = {
'Content-Type': 'application/json',
'X-Public-Key': process.env.ARABYBOT_OTP_PUBLIC_KEY,
};
if (process.env.ARABYBOT_OTP_SECRET) { // only if request signing is on
headers['X-Signature'] = 'sha256=' + crypto
.createHmac('sha256', process.env.ARABYBOT_OTP_SECRET).update(raw).digest('hex');
}
const res = await fetch(`https://app.arabybot.com/wa_otp_api/${action}`,
{ method: 'POST', headers, body: raw });
return res.json();
}
const sent = await otp('send', { phone: '971501234567', lang: 'ar' });
const check = await otp('verify', { request_id: sent.request_id, code: '482913' });
console.log(check.success ? 'verified' : check.error_code);
Python — requests
import hashlib, hmac, json, os
import requests
def otp(action, body):
raw = json.dumps(body)
headers = {"Content-Type": "application/json",
"X-Public-Key": os.environ["ARABYBOT_OTP_PUBLIC_KEY"]}
secret = os.environ.get("ARABYBOT_OTP_SECRET")
if secret: # only if request signing is on
headers["X-Signature"] = "sha256=" + hmac.new(
secret.encode(), raw.encode(), hashlib.sha256).hexdigest()
return requests.post(f"https://app.arabybot.com/wa_otp_api/{action}",
data=raw, headers=headers, timeout=15).json()
sent = otp("send", {"phone": "9647701234567", "lang": "ar"})
check = otp("verify", {"request_id": sent["request_id"], "code": "482913"})
print("verified" if check.get("success") else check.get("error_code"))
Erreurs et limites — et que faire pour chacune
| error_code | HTTP | Signification et que faire |
|---|---|---|
missing_key · invalid_key | 401 | La clé publique est absente, erronée, ou l'application est en pause. |
missing_signature · bad_signature | 401 | La signature est activée et la requête n'est pas signée, ou la signature porte sur un corps différent de celui envoyé. |
origin_blocked · ip_blocked | 403 | L'appel vient d'un domaine ou d'une IP non autorisés dans les réglages de l'application. |
invalid_phone · missing_phone | 422 | Corrigez le numéro au format international avant l'envoi. |
cooldown · rate_limit_phone | 429 | Trop de requêtes pour un même numéro. Lisez retry_after en secondes et affichez un compte à rebours. |
rate_limit_app · rate_limit_app_day | 429 | La limite de l'application : 60 envois par minute et 20 000 par jour par défaut. |
invalid_code | 401 | Code erroné ; attempts_remaining l'accompagne. |
too_many_attempts | 429 | Tentatives épuisées ; demandez un nouveau code. |
expired · already_resolved | 410 | Le code a expiré, ou a déjà été utilisé. |
quota_exceeded · insufficient_balance · package_expired | 402 | Votre forfait ou votre portefeuille ne couvre pas l'envoi. |
wa_send_failed | 502 | WhatsApp a refusé l'envoi ; un prélèvement sur le numéro partagé est remboursé. |
Le rappel de vérification vers votre serveur
Si vous définissez une URL de rappel sur l'application, un événement otp.verified vous parvient dès qu'un code est vérifié, signé avec votre secret. Calculez le HMAC-SHA256 du corps tel que reçu et comparez-le à l'en-tête avant de lui faire confiance.
// The callback your server receives when a code is verified (if you set a callback URL)
POST https://your-site.example/otp-callback
X-ArabyBot-Event: otp.verified
X-ArabyBot-Signature: sha256=<HMAC-SHA256 of the raw body, keyed with your sk_otp_ secret>
{"event":"otp.verified","app_id":"12","request_id":"3f6c2a9e-…",
"phone":"966501234567","verified_at":"2026-10-01T14:01:12+00:00"}
Ce que vous payez
Sur votre propre numéro, le code part sous forme de modèle d'authentification approuvé et Meta facture le tarif du pays du destinataire : 0,0036 $ vers un numéro égyptien et 0,0107 $ vers un numéro saoudien, mesuré sur 1 147 codes. Sur le numéro partagé, c'est un prix fixe par code prélevé sur votre portefeuille (0,0300 $), sans compte Meta. Le tableau pour chaque pays arabe se trouve dans le prix du code de vérification dans chaque pays arabe.
Questions des développeurs
Ai-je besoin de mon propre numéro sur l'API WhatsApp pour envoyer le code ?
Non. Quand vous créez une application OTP, vous choisissez l'expéditeur : le numéro partagé que gère la plateforme, opérationnel en quelques minutes sans compte Meta, à prix fixe par code prélevé sur votre portefeuille, ou votre propre numéro connecté à la plateforme, qui envoie via un modèle d'authentification approuvé sur votre numéro, Meta vous facturant directement le tarif du pays du destinataire.
Je vérifie par numéro de téléphone ou par request_id ?
Par request_id, toujours. L'appel d'envoi le renvoie ; gardez-le dans la session de l'utilisateur sur votre serveur, puis envoyez-le avec le code qu'il a tapé à l'appel de vérification. Un code n'est accepté qu'une seule fois, et il est refusé après son expiration ou après la limite de tentatives que vous avez fixée pour l'application.
Combien coûte un code ?
Sur votre propre numéro : le tarif d'authentification de Meta pour le pays du destinataire — 0,0036 $ vers un numéro égyptien et 0,0107 $ vers un numéro saoudien ; le tableau complet se trouve dans le prix du code dans chaque pays arabe. Sur le numéro partagé : un prix fixe de 0,0300 $ par code. Chaque renvoi est un nouveau message facturé.
Et si l'envoi échoue ?
L'appel renvoie wa_send_failed avec le code HTTP 502 et la raison de WhatsApp, et sur le numéro partagé vous n'êtes pas facturé, car le prélèvement est remboursé automatiquement. Montrez à l'utilisateur un bouton « réessayer » après une pause ; ne renvoyez pas en boucle.
Puis-je l'appeler directement depuis le navigateur ?
Techniquement oui — l'API accepte les appels du navigateur depuis les domaines que vous autorisez dans les réglages de l'application. Mais la bonne conception fait passer l'appel par votre serveur : c'est là que vous gardez le request_id hors de portée de l'utilisateur, activez la signature des requêtes avec le secret, et limitez les appels aux adresses IP de vos serveurs.
Existe-t-il une API pour envoyer des messages WhatsApp autres que le code ?
Pas encore ouverte. L'API OTP est la seule API publique pour développeurs disponible aujourd'hui, avec les webhooks de réception des commandes et d'événements de panier et les outils de l'agent IA — détaillés dans le guide du développeur.
À lire ensuite
Créez une application OTP et envoyez le premier code
Sur le numéro partagé, le premier code arrive quelques minutes après l'inscription, sans compte Meta.