G-SMS.

G-SMS · Cas d’usage

Notifications transactionnelles par SMS au Cameroun

Confirmer une commande, accuser réception d’un paiement Mobile Money, annoncer une livraison : les messages que le client attend. Voici comment les envoyer, ce qu’ils coûtent par commande, et pourquoi un SMS transactionnel n’est pas un SMS marketing.

Le problème : le client paie, puis il ne sait plus rien

Un paiement Mobile Money est validé en quelques secondes, mais côté client rien ne le confirme hormis le SMS de l’opérateur, qui ne dit pas ce qui a été acheté. L’e-mail de confirmation part dans une boîte relevée une fois par semaine, quand elle l’est. Le message WhatsApp suppose une connexion data active, ce qui n’est pas acquis en dehors des grandes villes.

Le SMS, lui, arrive sur tous les téléphones, y compris les modèles sans écran tactile, sans connexion data et sans application installée. C’est le seul canal dont vous pouvez supposer qu’il fonctionne chez tous vos clients.

Ce que coûte le silence

Le client qui ne reçoit pas de confirmation appelle. Sur 300 commandes par mois, si un tiers des acheteurs passe un appel de trois minutes pour savoir où en est sa commande, cela représente cinq heures de téléphone mensuelles pour votre équipe — et autant d’appels qui n’aboutissent pas quand la ligne est occupée.

S’y ajoute le litige classique : « j’ai payé, je n’ai rien reçu ». Sans trace envoyée au client au moment de l’encaissement, la discussion se règle à votre désavantage, parfois par un remboursement qui n’avait pas lieu d’être.

La solution en trois étapes

1. Choisir les événements qui méritent un message

Un SMS par changement d’état qui modifie quelque chose pour le client, pas un de plus. Pour une boutique en ligne : commande confirmée, paiement encaissé, colis remis au livreur. Tout le reste appartient à votre interface, pas au téléphone de l’acheteur.

Deux familles de messages obéissent à d’autres règles et ont leur propre page : les codes de vérification (OTP), qui exigent une durée de validité et un plafond de renvois, et les rappels de rendez-vous, où c’est l’heure d’envoi qui fait tout.

2. Envoyer, avec une référence dérivée de l’événement

<?php

$payload = [
    'to'              => '+237690000000',
    'text'            => 'Commande CM-10432 confirmée. Montant : 24 500 F. '
                       . 'Livraison prévue le 14/09. Boutique Akwa.',
    // Dérivée de l'événement métier : rejouable sans risque de double envoi.
    'clientReference' => 'commande-10432-confirmation',
];

$ch = curl_init('https://api.sms.gworldship.com/v1/sms');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 10,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer gsk_votre_cle_api',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS     => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);

$body   = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$sms = json_decode($body, true);

if ($status === 202) {
    enregistrerEnvoi($sms['messageId'], $sms['priceXaf'], $sms['balanceAfterXaf']);
} elseif ($status === 409) {
    // Référence déjà utilisée : $sms EST le message envoyé la première fois,
    // pas un objet d'erreur. Rien n'a été envoyé ni débité une seconde fois.
    enregistrerEnvoi($sms['messageId'], 0, null);
} else {
    error_log('G-SMS ' . $status . ' ' . ($sms['code'] ?? 'INTERNAL_ERROR'));
}

La réponse 202 contient messageId, segments, priceXaf et balanceAfterXaf. Les champs nuls sont absents de la réponse : testez leur présence plutôt que leur valeur.

3. Suivre la livraison par webhook

<?php
// POST https://votre-boutique.cm/webhooks/gsms

$raw       = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_GSMS_TIMESTAMP'] ?? '';
$signature = $_SERVER['HTTP_X_GSMS_SIGNATURE'] ?? '';

// Fenêtre de tolérance : 300 secondes de part et d'autre.
if (abs(time() - (int) $timestamp) > 300) { http_response_code(400); exit; }

$expected = 'sha256=' . hash_hmac('sha256', $raw . '.' . $timestamp, $secret);

// hash_equals : comparaison à temps constant, obligatoire ici.
if (!hash_equals($expected, $signature)) { http_response_code(401); exit; }

$event = json_decode($raw, true);

// Livraison « au moins une fois » : le même eventId peut revenir. Dédupliquez.
if (dejaTraite($event['eventId'])) { http_response_code(200); exit; }
marquerTraite($event['eventId']);

majStatutNotification(
    $event['data']['clientReference'],   // commande-10432-confirmation
    $event['data']['status'],            // SENT, DELIVERED ou FAILED
    $event['data']['failureReason'] ?? null
);

http_response_code(200);   // tout code 2xx vaut accusé de réception

Corps reçu, au changement de statut :

POST /webhooks/gsms
X-GSms-Event: sms.sent
X-GSms-Timestamp: 1789203247
X-GSms-Signature: sha256=9f1c…
X-GSms-Delivery: 2c81a0f4-1d33-4f7e-a0d6-6b5d9a2e7c14

{
  "eventId": "0f3a6b18-72c4-4d0e-8f2b-1a9c4e5d7b60",
  "type": "sms.sent",
  "occurredAt": "2026-09-12T09:16:31Z",
  "applicationId": "boutique-akwa",
  "data": {
    "messageId": "b7d1f0c2-5a3e-4c19-9f0a-2d6e8c41b3aa",
    "clientReference": "commande-10432-confirmation",
    "to": "+237690000000",
    "operator": "ORANGE",
    "segments": 1,
    "priceXaf": 5,
    "status": "DELIVERED",
    "failureReason": null
  }
}
Malgré son nom, applicationId contient le slug de l’application, pas son identifiant technique. Et les réessais reprennent à 1 minute, 5 minutes, 30 minutes, 2 heures, 12 heures, puis toutes les 12 heures jusqu’à l’abandon au bout de 48 heures : un endpoint qui répond lentement sera resollicité. Détail des statuts et du webhook →

Le coût réel, en francs CFA

Hypothèse : boutique en ligne, 300 commandes par mois, trois messages par commande. Tarif moyen pondéré de 5,30 F, obtenu avec 85 % d’envois vers Orange et MTN à 5 F et 15 % vers Camtel et Nexttel à 7 F.

MessageVolume mensuelCoût mensuel
Commande confirmée3001 590 F
Paiement encaissé3001 590 F
Colis remis au livreur3001 590 F
Total900 SMS4 770 F

Soit 15,90 F par commande informée de bout en bout. À comparer aux cinq heures de téléphone mensuelles qu’elles remplacent.

Écrire un message qui tient dans un segment

La facturation se fait au segment : 160 caractères en texte standard (GSM-7), 153 par segment au-delà. Un seul caractère hors de cet alphabet fait basculer tout le message en UCS-2, limité à 70 caractères — et le prix double dès la 71e.

TexteLongueurEncodageSegments
Paiement de 24 500 F reçu. Commande CM-10432 confirmée. Merci.62UCS-21, avec 8 caractères de marge
Paiement de 24 500 F encaissé. Commande CM-10432 confirmée. Merci.66GSM-71, avec 94 caractères de marge

Le « ç » minuscule ne fait pas partie de l’alphabet GSM-7, contrairement à « é », « è » et « à ». Deux mots synonymes, deux encodages, et un budget qui peut doubler sans que rien ne le signale. Tester un message dans le calculateur →

Ces quatre tailles de segment sont des paramètres du service, pas des constantes figées : l’API retourne toujours le nombre de segments réellement facturés.

Bonnes pratiques

  • Nommez-vous dans le texte. L’expéditeur affiché est un numéro : sans votre nom, le message est indéchiffrable.
  • Une référence client dérivée de l’événement métier, du type commande-10432-confirmation, jamais un identifiant aléatoire : c’est ce qui rend un rejeu inoffensif.
  • N’envoyez jamais depuis le parcours de paiement. Mettez l’envoi en file côté application ; une page de validation ne doit pas attendre un appel HTTP externe.
  • Respectez les horaires — rien entre 21 h et 7 h, sauf si l’événement vient du client lui-même : un paiement à 23 h mérite sa confirmation immédiate.
  • Conservez le messageId à côté de la commande : c’est ce qui permet de répondre à « je n’ai rien reçu » autrement que par une hypothèse.
  • Surveillez balanceAfterXaf à chaque envoi et déclenchez une alerte bien avant zéro : un solde épuisé répond 402 et la notification est perdue.
  • Un timeout n’est pas un échec. Réessayez avec la même référence : si le premier appel avait abouti, l’API répond 409 avec le message initial au lieu d’en envoyer un second.

Ce que G-SMS ne fait pas

  • Pas de campagnes marketing. Il n’y a pas de nom d’expéditeur personnalisé : une promotion envoyée depuis un numéro inconnu est perçue comme un spam, et vous n’avez aucun moyen de vous identifier au premier coup d’œil. Le produit est conçu pour le message attendu.
  • Pas de réception de SMS. Le client ne peut ni répondre, ni envoyer « STOP ». C’est un choix de conception définitif — et une raison de plus de s’en tenir aux notifications liées à une action du client.
  • Pas d’envoi par lot. Un appel HTTP par destinataire, dans la limite de 10 requêtes par seconde et par clé. Au-delà, l’API répond 429 avec un en-tête Retry-After.
  • Pas de modèles de message côté serveur. Le texte est composé par votre application : c’est elle qui connaît la langue du client et le contenu de sa commande.
📵 L’expéditeur vu par le destinataire est le numéro de la SIM d’envoi, pas un nom de marque : G-SMS est conçu pour les SMS transactionnels (codes OTP, notifications), pas pour les campagnes marketing.

Questions fréquentes

Combien coûte une notification par SMS au Cameroun ?

5 F CFA par segment vers Orange et MTN, 7 F vers Camtel et Nexttel. Une confirmation de commande courte tient dans un segment : comptez 5 à 7 F par message, sans abonnement.

Puis-je envoyer des offres promotionnelles avec G-SMS ?

Non. Le destinataire voit le numéro de la SIM d’envoi et non un nom de marque : une promotion arrivant d’un numéro inconnu est lue comme un spam. G-SMS est conçu pour les messages attendus par le client.

Comment savoir si le client a bien reçu le message ?

Chaque changement de statut est poussé vers votre serveur par un webhook signé, et le statut reste consultable à tout moment sur GET /v1/sms/{messageId}. DELIVERED signifie que l’opérateur a confirmé la remise.

Prêt à envoyer ton premier SMS ?

Crée un compte, reçois 100 F de crédit et une clé API immédiate.

Créer un compte