Table des matières :
Ce que l’API SMS OVHcloud couvre (et ce qu’elle ne couvre pas)
Authentification OVH API : signature, horodatage, rotation et erreurs classiques
Envoyer un SMS « répondable » : émetteur, corrélation et stockage technique
Callbacks temps réel : webhooks, idempotence, sécurité et observabilité
Mode Pull (polling) : robuste, incrémental, et compatible prod
Intégration PrestaShop (8.1/9.x) : module Symfony, files, et workflow “conversationnel”
Ce que l’API SMS OVHcloud couvre (et ce qu’elle ne couvre pas)
L’API SMS OVHcloud est utilisable pour du SMS transactionnel (OTP, confirmation de commande, livraison) comme pour du SMS « conversationnel » (réponses client, support). Le point qui piège souvent : envoyer un SMS n’implique pas automatiquement de pouvoir recevoir une réponse. La réception dépend du type d’émetteur (alphanumérique vs numéro), de l’option “réponse” activée côté service, et des contraintes opérateurs (certains pays bloquent les réponses sur certains routes).
Pour parler proprement, on distingue trois flux : (1) MO (Mobile Originated) = SMS entrants (réponses), (2) MT (Mobile Terminated) = SMS sortants, (3) DLR (Delivery Receipt) = accusés de réception opérateur (livré, expiré, rejeté, etc.). La plupart des intégrations se contentent du MT ; dès qu’on veut de la conversation (MO) ou de la traçabilité (DLR), il faut choisir une stratégie : callbacks temps réel (webhooks) quand c’est disponible et fiable, ou mode Pull (polling) pour maîtriser la récupération.
Pour cadrer rapidement, voici une grille de lecture opérationnelle (utile en phase de conception et de debug) :
| Flux | Qui initie ? | À quoi ça sert en pratique | Erreurs fréquentes |
|---|---|---|---|
| MT (sortant) | Votre application | OTP, confirmation de commande, rappel RDV | « Envoyé » ≠ « livré » ; pas de corrélation fiable sans stockage |
| DLR (accusé opérateur) | Opérateur / agrégateur | Mesurer la délivrabilité, déclencher un fallback (email/WhatsApp) | Croire que “DELIVERED” = “lu” ; ignorer les statuts intermédiaires |
| MO (entrant) | Client | STOP/opt-out, réponse support, confirmation simple (« 1 » / « OUI ») | Sender non répondable ; messages entrants non rattachés à une conversation |
Deux nuances importantes, notamment en Europe/France où les opérateurs et les politiques anti-spoofing sont strictes :
Le “sender” visible (ex.
MA-BOUTIQUE) peut être purement “branding”. Même si l’utilisateur répond, la réponse ne “revient” pas forcément à votre système si vous n’avez pas un numéro répondable derrière (long number/virtual number) et un routage MO activé.La disponibilité des MO varie selon les pays et les routes. Sur des campagnes multi-pays (France + Belgique + Suisse par exemple), vous pouvez avoir un comportement hétérogène : réponses possibles dans un pays, non possibles dans l’autre, ou possibles mais avec latence/délivrabilité différentes. C’est une raison classique de basculer vers un design “callback + pull de secours”.
Côté conformité, ne mélangez pas technique et juridique : la capacité à recevoir une réponse ne vous dispense pas des mentions obligatoires, opt-in, STOP, conservation, etc. Pour le cadre RGPD/consentement/mentions, il est plus sain de s’appuyer sur une check-list dédiée, par exemple : SMS conversationnel : conformité RGPD, opt-in et mentions STOP.
Authentification OVH API : signature, horodatage, rotation et erreurs classiques
L’API OVHcloud n’est pas une simple “API key”. Elle repose sur un triplet Application Key (AK) / Application Secret (AS) / Consumer Key (CK) et une signature par requête. En production, le vrai risque n’est pas l’implémentation initiale, c’est la dérive : horloges décalées, secrets exposés dans des logs, rotation non prévue, ou code qui signe l’URL différemment de ce que le serveur attend (http/https, querystring réordonnée, body JSON normalisé différemment).
La signature OVH est basée sur un hash du secret + éléments de la requête (méthode, URL, body, timestamp). Vous devez contrôler l’horodatage (NTP sur vos serveurs) et implémenter une stratégie de retry uniquement sur erreurs réseau/5xx (et pas sur 4xx “signature invalid”). Concrètement, si vous êtes sur VPS/serveur dédié, mettez NTP/chrony en place et surveillez la dérive ; les symptômes sont des 401/403 aléatoires difficiles à reproduire.
Points de contrôle concrets (ceux qui évitent 80% des tickets “ça marche en dev mais pas en prod”) :
URL signée strictement identique à celle envoyée : même schéma (
https), même host, même chemin, et querystring identique.Body exactement identique : si vous signez une chaîne JSON, ne la “reformatez” pas ensuite. Gardez une variable
$bodyunique, signée puis envoyée.Horloge serveur stable : un NTP correct côté app + une surveillance (ex. alerte si dérive > quelques secondes).
Séparation des environnements : clés différentes pour dev/staging/prod ; sinon un script de test peut polluer la prod.
Exemple PHP (PHP 8.2+) de construction de signature (pseudo-code volontairement explicite) :
$method = 'POST';
$url = 'https://api.ovh.com/1.0/sms/{serviceName}/jobs';
$body = json_encode($payload, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
$timestamp = time();
$toSign = $appSecret.'+'.$consumerKey.'+'.$method.'+'.$url.'+'.$body.'+'.$timestamp;
$signature = '$1$'.sha1($toSign);
$headers = [
'X-Ovh-Application: '.$appKey,
'X-Ovh-Consumer: '.$consumerKey,
'X-Ovh-Signature: '.$signature,
'X-Ovh-Timestamp: '.$timestamp,
'Content-Type: application/json',
];
Pour éviter d’exposer ces clés : stockez-les dans un secret manager (Vault, SOPS, variables d’environnement chiffrées, etc.) et appliquez un principe de moindre privilège. Deux bonnes pratiques supplémentaires, souvent oubliées :
Ne jamais logger les headers signés en niveau INFO/DEBUG en prod (le risque, c’est l’export vers un SIEM ou un outil de support).
Prévoir la rotation : documentez un runbook (“on régénère AK/AS/CK, on déploie, on invalide l’ancien”) et testez-le avant d’en avoir besoin.
Le sujet dépasse l’API SMS mais les mêmes mécaniques s’appliquent à tout endpoint sensible : journalisez proprement (sans secrets) et appliquez des headers de sécurité côté endpoint interne quand vous exposez des callbacks ; voir HTTP Security Headers en PHP : guide CSP, HSTS, X-Frame-Options.
Enfin, si vous voulez réduire la surface d’erreur (signature, timestamps, etc.), la bibliothèque officielle est un choix raisonnable : https://github.com/ovh/php-ovh (elle n’annule pas les besoins de NTP et de gestion des secrets, mais limite les erreurs d’implémentation).
Envoyer un SMS « répondable » : émetteur, corrélation et stockage technique
Pour recevoir des réponses, le paramètre déterminant n’est pas “j’ai envoyé un SMS”, mais “j’ai envoyé un SMS avec un sender qui supporte la réponse”. Typiquement :
Sender alphanumérique (ex:
MA-BOUTIQUE) : très bon pour l’image/transactionnel, mais souvent non répondable.Numéro long / virtuel / court fourni par le service : souvent répondable (MO), avec des contraintes par pays.
Dans l’API SMS OVHcloud, l’envoi passe généralement par un endpoint de type /sms/{serviceName}/jobs. Le payload doit inclure au minimum le message et la liste des destinataires. Pour un usage “conversationnel”, ajoutez une corrélation interne : ne comptez pas sur le texte du client pour recoller un fil de discussion. Stockez (a) l’ID job/provider, (b) le MSISDN normalisé (E.164), (c) une clé conversation, (d) le contexte métier (commande, panier, ticket).
Sur le MSISDN, visez une normalisation E.164 (ex. +336xxxxxxxx) et appliquez-la partout (outbound, inbound, dédup, recherche). Cela vous évite les doublons 06.. vs +33.. et les bugs de routage quand une même personne répond depuis un format différent. Référence normative : https://www.itu.int/rec/T-REC-E.164
Une matrice simple pour choisir le sender (utile quand vous avez à arbitrer “image de marque” vs “réponse”) :
| Objectif | Sender conseillé | Réponses (MO) | Commentaire |
|---|---|---|---|
| OTP / alerting pur | Alphanumérique | Rarement | Priorité à l’identification ; si besoin de retour, prévoyez un autre canal |
| Livraison avec confirmation | Numéro répondable | Oui (si route OK) | Permet “OK” / “NON” ; exige ingestion MO solide |
| Support client | Numéro répondable dédié | Oui | Idéalement un numéro par marque/pays pour éviter les collisions |
Techniquement, le piège le plus courant est l’encodage et la segmentation. Si vous basculez en UCS-2 (accents/emoji), la longueur par segment chute fortement ; ça impacte le coût et la délivrabilité (multi-segments). Donc : normalisez vos templates (GSM-7 si possible), mesurez le taux de segmentation, et loggez segments_count côté applicatif. Repères utiles (valeurs standard) :
GSM-7 : 160 caractères (un seul segment), puis 153 par segment concaténé.
UCS-2 : 70 caractères (un seul segment), puis 67 par segment concaténé.
Sur un run de 10 000 SMS, un passage moyen de 1,1 segment à 1,8 segment change complètement l’économie (facturation) et peut dégrader la latence (plus de fragments à transmettre). Un réflexe simple : au moment du rendu template, calculez une estimation “GSM-7 vs UCS-2” et alertez si un message “transactionnel” dépasse 1 segment.
Pour la persistance, ne faites pas “juste une table messages”. Faites une table outbox (SMS à envoyer) + une table provider_events (DLR, MO) + une table conversations (état métier). Ce pattern évite les incohérences quand votre code d’envoi échoue après avoir mis à jour la commande (ou l’inverse). Exemple de champs qui aident vraiment en exploitation (sans sur-modéliser) :
outbox:id,conversation_id,msisdn_e164,body,encoding,segments_estimated,status,provider_job_id,last_error,created_at,sent_atprovider_events:provider_event_id(unique),provider_message_id,type(DLR|MO),payload_json,received_atconversations:id,customer_id,order_id(nullable),state,last_inbound_at,optout_at(nullable)
Si vous utilisez Redis pour découpler l’envoi (queue courte, retries), voyez la config serveur/extension : Redis PHP : activer l’extension, choisir les bases et surveiller l’usage et, pour rester cohérent avec l’écosystème boutique, configurer Redis avec PrestaShop sur VPS/serveur dédié.
Callbacks temps réel : webhooks, idempotence, sécurité et observabilité
Quand OVHcloud peut pousser un événement (réponse SMS entrante, DLR, etc.), vous basculez sur un modèle “webhook”. La définition est simple mais a une conséquence opérationnelle directe : « Webhooks are user-defined HTTP callbacks » (Wikipedia, entrée “Webhook”). Dit autrement : vous opérez une API publique qui recevra des requêtes externes, potentiellement bruitées, répétées, et parfois hors ordre.
Le point non négociable : l’idempotence. Un callback peut être renvoyé (retry) si votre endpoint répond trop lentement, ou si un intermédiaire réseau coupe. L’IETF définit l’idempotence ainsi : “A request method is considered ‘idempotent’ if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request.” (RFC 9110, HTTP Semantics). Même si votre webhook arrive en POST, vous devez traiter l’événement comme idempotent : utilisez un identifiant provider (messageId/eventId) et une contrainte unique en base, ou un cache de déduplication court (Redis SETNX TTL).
En production, l’idempotence doit aussi couvrir les cas “bizarres” :
Réception hors ordre : un DLR peut arriver après un timeout applicatif ; un MO peut arriver avant que votre outbox ait été “marquée sent”.
Événements incomplets : payload partiel, champs manquants, encodage inattendu.
Répétitions : même événement renvoyé 2 à 5 fois (rare, mais ça arrive) → votre pipeline ne doit pas “rejouer” un changement d’état métier.
Sur la sécurité, évitez les “IP allowlist” comme seul contrôle (trop fragile). Préférez une combinaison :
URL avec token non devinable (
/webhook/ovh/sms/{token}) + rotation,vérification d’un header secret (si OVHcloud permet de configurer un secret),
rate limiting et protections L7.
Ajoutez aussi des garde-fous simples, qui coûtent peu et évitent des incidents :
Limiter la taille des payloads acceptés (ex. 64–256 KB max) pour réduire les risques de DoS applicatif.
Vérifier le
Content-Typeattendu (ou gérer proprement JSON vs form-data).Répondre vite et toujours avec un code clair (
200si accepté,4xxsi rejet définitif). En cas de doute sur le comportement de retry du provider, documentez ce que vous considérez comme “réessayable” (souvent5xx) vs “non réessayable” (4xx).
Pour le rate limiting au niveau reverse-proxy, un setup HAProxy est pertinent, surtout si vous hébergez plusieurs endpoints. Voir : HAProxy reverse proxy : terminaison TLS, rate limiting et supervision Prometheus et, si vous industrialisez la gouvernance d’API, API Gateway : authentification, rate limiting et routage des microservices.
Côté implémentation (Symfony / PrestaShop 8.1+), votre endpoint doit répondre vite (objectif < 200 ms), mettre l’événement dans une file (DB outbox, Redis stream, queue), et traiter asynchrone. Exemple minimal (contrôleur Symfony, PHP 8.2) :
#[Route('/webhook/ovh/sms/{token}', methods: ['POST'])]
public function ovhSmsWebhook(string $token, Request $request): Response
{
if (!hash_equals($_ENV['OVH_WEBHOOK_TOKEN'], $token)) {
return new Response('forbidden', 403);
}
$raw = $request->getContent();
$payload = json_decode($raw, true) ?: $request->request->all();
$eventId = $payload['eventId'] ?? $payload['messageId'] ?? null;
if (!$eventId) {
return new Response('bad request', 400);
}
// Dédup : contrainte unique en base OU Redis SETNX TTL.
$this->eventIngestor->enqueue($eventId, $payload);
return new Response('ok', 200);
}
Mini-scénario “réel” (et typiquement français/UE, car l’opt-out est un sujet sensible) : un client répond STOP à un message. Même si votre SMS initial était transactionnel, votre système doit savoir router ce message entrant vers une logique d’opt-out (si vous mélangez transactionnel et marketing, vous devez cloisonner les consentements). Concrètement : vous ingérez le MO, vous détectez le mot-clé, vous mettez à jour un flag optout_at, et vous empêchez les futurs envois marketing—sans bloquer l’envoi d’un OTP strictement nécessaire si votre politique le justifie et le documente.
Pour l’observabilité, ne vous contentez pas d’un log texte. Produisez des métriques : webhook_requests_total, webhook_dedup_hits_total, sms_incoming_total, dlr_status_total{status=...}, queue_lag_seconds. Si vous êtes déjà sur une stack Prometheus/Grafana, c’est du basique ; sinon, mettez au moins des compteurs applicatifs et des corrélations. (Pour la partie Prometheus côté infra, vous avez un point d’entrée reproductible : Prometheus sur Kubernetes OVHcloud : déployer kube-prometheus-stack avec Helm.)
Mode Pull (polling) : robuste, incrémental, et compatible prod
Le mode Pull est souvent méprisé, mais il résout des problèmes concrets : pas besoin d’exposer un endpoint public, pas de gestion de retries entrants, et une surface d’attaque réduite. En contrepartie, vous introduisez de la latence (dépendant de la fréquence de poll) et vous consommez du quota API. En pratique, pour une boutique e-commerce, un poll toutes les 30–60 secondes sur les SMS entrants est généralement acceptable (SLA métier), alors qu’un callback peut être requis pour du support “quasi live”.
La règle de base : poll incrémental. Ne faites pas “GET toutes les conversations” puis filtrez côté code. Utilisez soit une pagination stable (offset/limit), soit un “since” (timestamp), soit un curseur (dernier ID traité), selon ce que l’API expose. Maintenez un état last_seen_id ou last_seen_timestamp en base (ou Redis persistant), et traitez dans l’ordre. Si l’API ne garantit pas l’ordre, triez côté applicatif et gardez une fenêtre de recouvrement (ex: repoller les 2 dernières minutes + dédup).
Deux détails qui rendent le Pull “prod-proof” dans un contexte boutique :
Verrou de concurrence : assurez-vous qu’un seul worker poll à la fois (lock Redis, lock DB, ou flock), sinon vous créez des doublons artificiels.
Rattrapage : si votre worker est down 30 minutes, il doit pouvoir rattraper proprement sans exploser le temps de traitement ni saturer l’API (pagination + backoff).
Pour absorber les pannes, implémentez :
backoff exponentiel en cas de 429/5xx,
circuit breaker (arrêt temporaire si erreur persistante),
dead-letter (événements impossibles à parser) pour ne pas bloquer le pipeline.
Le pattern opérationnel simple : une commande CLI (Symfony Console) + cron systemd, ou un worker long (supervisord). Si vous avez déjà un outillage CLI PrestaShop, restez cohérent : Commandes CLI PrestaShop : liste, catégories et options d’aide.
Exemple de boucle Pull (pseudo-code) :
$cursor = $stateStore->get('ovh_sms_incoming_cursor');
$page = $ovhApi->getIncoming(['afterId' => $cursor, 'limit' => 100]);
foreach ($page as $msg) {
$id = $msg['id'];
if ($dedup->alreadyProcessed($id)) {
continue;
}
$handler->handleIncoming($msg);
$cursor = max($cursor, $id);
}
$stateStore->set('ovh_sms_incoming_cursor', $cursor);
Dans un contexte PrestaShop à fort trafic, stocker l’état en Redis peut être pratique, mais attention : Redis n’est pas “magique”, il faut surveiller la mémoire, les expirations et la persistance selon votre usage. Vous avez déjà une base méthodologique côté infra : Cache PrestaShop : Varnish, Redis, Memcached et OPcache côté serveur.
Astuce de design : même si vous partez “full Pull” au début, prévoyez une interface interne (SmsEventIngestor) qui accepte des événements “comme si” c’était du webhook. Le jour où vous activez les callbacks, vous changez la source (Push vs Pull) sans réécrire tout le pipeline métier.
Intégration PrestaShop (8.1/9.x) : module Symfony, files, et workflow “conversationnel”
Pour PrestaShop 8.1 et 9.x (Symfony 6.4), le cœur n’apporte pas un bus d’événements “SMS” ni une infra de queue native. Donc si vous voulez une intégration fiable, vous devez l’architecturer dans le module : services Symfony, persistance, et exécution asynchrone. Une base propre : un module au format moderne (services.yml, controllers, commands), en suivant les bonnes pratiques : Module PrestaShop 9 : structure, services et bonnes pratiques Symfony.
Le design qui tient en prod pour “réponses SMS” :
Outbound : quand un événement métier arrive (commande validée), vous écrivez dans une table
ps_sms_outbox(étatpending).Sender worker : une commande CLI lit
pending, envoie via l’API SMS OVHcloud, met à joursent+provider_job_id.Inbound : via callback ou Pull, vous ingérez dans
ps_sms_incomingavec contrainte unique sur l’ID provider.Router : un routeur métier rattache l’inbound à une conversation et déclenche l’action (création ticket, changement d’état, opt-out).
Ce découplage est utile parce que PrestaShop n’est pas un moteur de messaging. Il vous laisse beaucoup de liberté, mais il ne vous protège pas des erreurs d’architecture (double envoi, pertes d’événements). La discipline “outbox + dédup” est ce qui évite les incidents qui coûtent cher : SMS envoyés deux fois lors d’un timeout PHP-FPM, ou réponses clients perdues car votre controller a crashé après avoir répondu 200.
Un exemple “conversationnel” simple, souvent utile en e-commerce :
Vous envoyez : « Votre colis est expédié. Répondez 1 si tout est OK à la réception, 2 si problème. »
Si
1: vous loggez une confirmation et vous pouvez déclencher une demande d’avis (si consentement).Si
2: vous créez un ticket SAV, et vous attachez automatiquementorder_id, transporteur, tracking, etc.
Techniquement, ce scénario n’est fiable que si :
vous corrélez le MO à la bonne commande (via conversation_id ou mapping MSISDN ↔ dernière commande),
vous appliquez un délai de validité (ex. “réponses prises en compte pendant 7 jours”), sinon une réponse tardive peut rouvrir un workflow clos,
vous gérez les réponses “hors format” (texte libre) en fallback vers le support.
Enfin, n’oubliez pas la partie “exploitation”. Les SMS sont un canal critique : votre runbook doit préciser (a) où voir les erreurs (logs), (b) comment rejouer une outbox sans renvoyer en double (idempotence), (c) comment basculer temporairement en Pull si les callbacks sont instables, (d) comment appliquer du rate limiting si vous êtes sous attaque. Sur la brique hébergement/sécurité, les mêmes fondamentaux s’appliquent : durcissement, sauvegardes, surveillance, et gestion des incidents. Si vous cherchez une grille de lecture côté ops, vous avez un point d’appui : Maintenance PrestaShop : managed services, sécurité serveur, sauvegardes chiffrées.
Références externes utiles (à lire avant d’implémenter “au feeling”) :
Console et exploration des endpoints OVHcloud : https://api.ovh.com/console/
Bibliothèque officielle OVH en PHP (si vous préférez éviter de signer à la main) : https://github.com/ovh/php-ovh
RFC 9110 — HTTP Semantics (idempotence, etc.) : https://www.rfc-editor.org/rfc/rfc9110
Définition “Webhook” (pour cadrer le modèle) : https://en.wikipedia.org/wiki/Webhook
Recommandation ITU-T E.164 (format international des numéros) : https://www.itu.int/rec/T-REC-E.164




