التطوير

OTP WhatsApp avec Symfony (PHP) : tutoriel intégration

12 دقيقة قراءة9 شتنبر 2025

Tutoriel intégration OTP WhatsApp dans une application Symfony : bundles, services, webhooks, tests et bonnes pratiques PHP.

OTP WhatsApp avec Symfony (PHP) : tutoriel intégration

Symfony reste l'un des frameworks PHP les plus utilisés dans l'écosystème backend marocain : ERP internes, portails e-commerce sur mesure, applications bancaires web. Intégrer un OTP WhatsApp dans une application Symfony demande une architecture propre : séparation des responsabilités, gestion des webhooks, robustesse face aux erreurs Meta. Voici un tutoriel structuré pour Symfony 6 ou 7.

Prérequis

  • Une application Symfony 6.x ou 7.x fonctionnelle.
  • Un compte BSP WhatsApp Business (ou accès Cloud API Meta direct).
  • Un template Authentication approuvé Meta contenant la variable {{1}} pour le code OTP.
  • Un endpoint HTTPS accessible depuis Internet (indispensable pour les webhooks Meta).

Étape 1 : structurer le bundle OTP

Créer un sous-dossier dédié dans src/ permet une évolution propre. La structure typique :

  • src/Otp/Service/WhatsappOtpSender.php : envoi du code via l'API BSP.
  • src/Otp/Service/OtpGenerator.php : génération du code aléatoire.
  • src/Otp/Service/OtpVerifier.php : vérification du code saisi.
  • src/Otp/Entity/OtpChallenge.php : entité Doctrine persistant les défis OTP.
  • src/Otp/Controller/OtpController.php : endpoints REST pour émission et vérification.
  • src/Otp/Controller/WhatsappWebhookController.php : réception des statuts de livraison.

Étape 2 : le service d'envoi

Le service WhatsappOtpSender encapsule l'appel HTTP vers le BSP. Recommandations clés :

  • Utiliser le composant HttpClient de Symfony, jamais cURL nu ou file_get_contents.
  • Injecter le token d'API via une variable d'environnement (WHATSAPP_BSP_TOKEN) et jamais en dur.
  • Configurer un timeout de 5 à 10 secondes pour éviter les blocages en cas de latence BSP.
  • Logger chaque appel avec un identifiant de corrélation (trace_id) pour faciliter le debug.
  • Retourner un objet typé (DTO) plutôt qu'un tableau brut, pour bénéficier de l'autocomplétion.

Structure d'une requête WhatsApp Cloud API

La charge JSON envoyée à Meta contient : messaging_product (whatsapp), to (numéro E.164, ex : 212661234567), type (template), template avec name, language et components. Le composant body transmet la valeur de la variable {{1}}, le composant button injecte le code pour le bouton Copier le code.

Étape 3 : la génération et la persistance

Le service OtpGenerator produit un code numérique de 6 chiffres via random_int() (PHP 7+, cryptographiquement sûr). Le service persiste ensuite un enregistrement OtpChallenge avec :

  • Le numéro cible.
  • Le hash du code (jamais en clair — SHA-256 salé, ou mieux, bcrypt).
  • La date de création.
  • La date d'expiration (typiquement +5 minutes).
  • Le nombre de tentatives de vérification (limite : 3 à 5).
  • Le statut (pending, verified, expired).

Étape 4 : la vérification

Le service OtpVerifier reçoit le numéro et le code saisi par l'utilisateur. Il :

  1. Charge le dernier OtpChallenge non expiré pour ce numéro.
  2. Vérifie le hash du code saisi contre le hash stocké.
  3. Incrémente les tentatives et invalide après 3 échecs.
  4. Marque le défi comme verified en cas de succès.
  5. Retourne un résultat typé (succès, expiré, verrouillé, erroné).

Étape 5 : le webhook de statut

Meta envoie des webhooks POST sur votre endpoint dès qu'un statut change (sent, delivered, read, failed). Le contrôleur doit :

  • Vérifier la signature HMAC SHA-256 dans le header X-Hub-Signature-256 pour authentifier l'origine Meta.
  • Répondre immédiatement 200 OK à Meta (sinon Meta réessaie et sature la file).
  • Déléguer le traitement métier à un Messenger handler asynchrone (RabbitMQ, Redis Streams ou Doctrine transport).
  • Journaliser chaque événement avec le message_id, le statut et le timestamp.

Le traitement asynchrone permet notamment de déclencher le SMS Failback si le statut failed arrive rapidement.

Étape 6 : les endpoints REST

Deux routes principales exposées via OtpController :

  • POST /api/otp/send : reçoit le numéro, valide (regex E.164), génère et envoie le code, retourne un challenge_id.
  • POST /api/otp/verify : reçoit le challenge_id et le code, retourne le résultat.

Protéger ces endpoints avec un rate limiter (composant Symfony natif) est indispensable : maximum 3 envois par numéro par 15 minutes, maximum 10 requêtes par IP par minute.

Étape 7 : les tests

Tests unitaires

Mocker le HttpClient pour tester le comportement de WhatsappOtpSender sans appel réseau. Tester la génération, le hash et la vérification en isolation.

Tests d'intégration

Utiliser la WebTestCase Symfony pour tester le parcours complet : POST /send → base à jour → POST /verify → succès. Un mock BSP en fixture facilite ces tests.

Tests E2E

Contre le sandbox Meta, avec un numéro de test vérifié, valider que le flux complet fonctionne bout à bout, incluant la réception du webhook.

Bonnes pratiques additionnelles

  • Rotation des tokens BSP tous les 90 jours minimum.
  • Alerting sur taux d'échec supérieur à 5 % sur 15 minutes glissantes.
  • Purge des OtpChallenge expirés via une commande Symfony planifiée (Cron).
  • Métriques Prometheus exposées pour suivi opérationnel.
  • Documentation OpenAPI des endpoints via nelmio/api-doc-bundle.

Conclusion

Une intégration OTP WhatsApp dans Symfony ne se résume pas à un appel API. La qualité tient dans la séparation des responsabilités, la gestion rigoureuse des webhooks, la robustesse face aux erreurs Meta et la protection contre les abus. Suivre ces principes garantit une brique OTP fiable, maintenable et prête pour la production marocaine à haut volume.

جرّب Adrixis ميتا مجانًا. سبعة أيام، بدون بطاقة.