Guide de la passerelle USSD
← Retour à la documentation principale
Ce guide couvre la passerelle USSD OmniSS7, qui relie les dialogues USSD SS7/MAP aux rappels HTTP/JSON, permettant aux développeurs tiers de créer des applications USSD avec un simple point de terminaison HTTP.
Table des matières
- Aperçu
- Architecture
- Activation de la passerelle USSD
- Configuration
- Protocole de rappel HTTP
- USSD d'origine réseau (API Push)
- Cycle de vie de la session
- Gestion des erreurs
- Métriques et surveillance
- Serveur de rappel d'exemple
- Dépannage
Aperçu
La passerelle USSD gère deux directions de trafic USSD :
- D'origine mobile (entrant) — Un abonné compose un code court (par exemple,
*100#). La passerelle reçoit la MAPprocessUnstructuredSS-Request(opcode 59), la transmet à votre rappel HTTP et renvoie votre réponse via SS7. - D'origine réseau (sortant) — Votre application envoie un message USSD à un abonné via l'API REST. La passerelle envoie une MAP
unstructuredSS-Request(opcode 60) via SS7 et achemine la réponse de l'abonné vers votre rappel.
Les deux directions prennent en charge des dialogues multi-tours — des menus interactifs où l'abonné répond et reçoit des invites de suivi.
Caractéristiques clés
| Propriété | Valeur |
|---|---|
| Transport | HTTP POST synchrone par tour |
| Encodage | Alphabet par défaut GSM 7 bits (DCS 0x0F) selon 3GPP TS 23.038 |
| Longueur max du texte | 182 caractères (configurable) |
| Suivi de session | UUID généré par la passerelle par dialogue |
| Authentification | Aucune (fait confiance au réseau SS7) |
| Routage | Correspondance de préfixe de code court aux URL de rappel |
Références 3GPP
| Spécification | Pertinence |
|---|---|
| 3GPP TS 23.090 | USSD Étape 2 — architecture et procédures |
| 3GPP TS 24.090 | USSD Étape 3 — détails du protocole |
| 3GPP TS 29.002 | Protocole MAP — USSD-Arg, USSD-Res, opcodes 59/60/61 |
| 3GPP TS 23.038 | Alphabet par défaut GSM 7 bits et schéma de codage de données |
Architecture
Flux d'origine mobile (entrant)
Flux d'origine réseau (Push sortant)
Vue d'ensemble des composants
Activation de la passerelle USSD
La passerelle USSD nécessite que le mode client MAP soit activé, ainsi que son propre drapeau de fonctionnalité.
config :omniss7,
map_client_enabled: true,
ussd_gateway_enabled: true
La passerelle nécessite également une connexion M3UA fonctionnelle (voir Guide du client MAP pour la configuration M3UA).
Configuration
Paramètres de la passerelle USSD
config :omniss7,
ussd_gateway_enabled: true,
ussd_gateway: %{
# Routage par code court — correspondance de préfixe la plus longue
routes: [
%{pattern: "*100", url: "http://balance-app:9000/ussd"},
%{pattern: "*200", url: "http://topup-app:9000/ussd"},
%{pattern: "*", url: "http://default-app:9000/ussd"}
],
# Délais d'expiration de session
session_timeout_ms: 180_000, # Durée totale de la session (3 minutes)
turn_timeout_ms: 30_000, # Temps d'attente maximum pour la réponse de l'abonné par tour (30 secondes)
# Paramètres de rappel HTTP
http_timeout_ms: 5_000, # Délai d'expiration pour le POST HTTP vers votre application (5 secondes)
# Limites de texte
max_text_length: 182 # Max GSM 7 bits (tronque avec avertissement si dépassé)
}
Référence des paramètres
| Paramètre | Type | Requis | Par défaut | Description |
|---|---|---|---|---|
ussd_gateway_enabled | Booléen | Oui | false | Interrupteur principal pour la fonctionnalité de la passerelle USSD |
ussd_gateway.routes | Liste de Maps | Oui | [] | Règles de routage par préfixe de code court. Chaque entrée a pattern (chaîne de préfixe) et url (URL de rappel). La correspondance de préfixe la plus longue l'emporte. |
ussd_gateway.session_timeout_ms | Entier | Non | 180_000 | Durée maximale de la session en millisecondes. La session est terminée avec une erreur si elle est dépassée. |
ussd_gateway.turn_timeout_ms | Entier | Non | 30_000 | Temps maximum d'attente pour la réponse de l'abonné dans un dialogue multi-tours, en millisecondes. |
ussd_gateway.http_timeout_ms | Entier | Non | 5_000 | Délai d'expiration de la requête HTTP pour les rappels vers votre application, en millisecondes. Couvre à la fois le temps de connexion et le temps de réponse. |
ussd_gateway.max_text_length | Entier | Non | 182 | Nombre maximum de caractères dans une chaîne de texte USSD. Les textes dépassant cette limite sont tronqués et un avertissement est enregistré. |
Paramètres de routage
Chaque entrée dans la liste routes est une carte :
| Paramètre | Type | Requis | Description |
|---|---|---|---|
pattern | Chaîne | Oui | Préfixe de code court à correspondre. Utilisez "*" comme solution de repli. Les préfixes plus longs ont la priorité. |
url | Chaîne | Oui | URL du point de terminaison HTTP pour recevoir les POST de rappel pour les codes courts correspondants. |
Correspondance des routes
Les routes sont correspondantes par préfixe le plus long d'abord. Pour la chaîne de composition *100# :
"*100"correspond (longueur 4) — sélectionné"*10"correspond (longueur 3) — ignoré, plus court"*"correspond (longueur 1) — solution de repli
Si aucune route ne correspond, la passerelle renvoie une erreur MAP au mobile et enregistre un avertissement.
Protocole de rappel HTTP
Votre application reçoit des requêtes HTTP POST de la passerelle et répond avec des instructions JSON.
Requête de la passerelle vers votre application
Content-Type: application/json
Premier tour (initiation de session) :
{
"session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"msisdn": "+254712345678",
"type": "initiation",
"text": "*100#",
"turn": 1
}
Tours suivants (l'abonné a répondu) :
{
"session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"msisdn": "+254712345678",
"type": "response",
"text": "1",
"turn": 2
}
Champs de requête
| Champ | Type | Description |
|---|---|---|
session_id | Chaîne | UUID généré par la passerelle. Unique par dialogue USSD. Utilisez ceci pour corréler les tours. |
msisdn | Chaîne | MSISDN de l'abonné (si disponible à partir du message MAP). Peut être vide pour certains réseaux. |
type | Chaîne | "initiation" pour le premier tour, "response" pour les réponses ultérieures de l'abonné. |
text | Chaîne | La chaîne de composition (par exemple, *100#) lors de l'initiation, ou l'entrée de l'abonné (par exemple, 1) lors de la réponse. |
turn | Entier | Compteur de tours commençant à 1. S'incrémente avec chaque interaction de l'abonné. |
Réponse de votre application
Votre application doit répondre avec JSON contenant une action et un text :
Continuer (afficher le menu, attendre l'entrée de l'abonné) :
{
"action": "continue",
"text": "1. Solde\n2. Recharger\n3. Transférer"
}
Fin (afficher le message final, fermer la session) :
{
"action": "end",
"text": "Votre solde est de 5,00 $"
}
Champs de réponse
| Champ | Type | Requis | Description |
|---|---|---|---|
action | Chaîne | Oui | "continue" pour garder la session ouverte et attendre l'entrée de l'abonné, ou "end" pour afficher un message final et fermer la session. |
text | Chaîne | Oui | Texte à afficher sur le téléphone de l'abonné. La longueur maximale est régie par max_text_length (par défaut 182). Utilisez \n pour les sauts de ligne. |
USSD d'origine réseau (API Push)
Envoyez un message USSD à un abonné depuis votre application.
Point de terminaison
POST /api/ussd/send
Requête
{
"msisdn": "+254712345678",
"text": "Vous avez une facture en attente. Répondez 1 pour payer.",
"callback_url": "http://billing-app:9000/ussd"
}
Champs de requête
| Champ | Type | Requis | Description |
|---|---|---|---|
msisdn | Chaîne | Oui | MSISDN de l'abonné destinataire au format international. |
text | Chaîne | Oui | Texte USSD initial à afficher. Encodé en GSM 7 bits. |
callback_url | Chaîne | Oui | URL pour recevoir la réponse de l'abonné via le protocole de rappel standard. |
Réponse
Succès (200 OK) :
{
"session_id": "xyz-789-abc-123",
"status": "sent"
}
Réponses d'erreur :
| Statut HTTP | Corps | Cause |
|---|---|---|
| 400 | {"error": "requête invalide", "required": ["msisdn", "text", "callback_url"]} | Manque un ou plusieurs champs requis, ou le corps n'est pas un JSON valide |
| 500 | {"error": "{:gsm7_encode_failed, ...}"} | Le texte contient des caractères non présents dans l'alphabet GSM 7 bits. L'échec de l'encodage est signalé par la branche d'erreur générique, donc le statut est 500, pas 400. |
| 500 | {"error": "..."} | Toute autre erreur d'envoi (par exemple, connectivité M3UA). Le champ error contient la raison interne inspectée. |
| 503 | {"error": "passerelle USSD non activée"} | ussd_gateway_enabled est false |
Remarque sur les erreurs de validation vs. d'envoi : Les problèmes de validation de champ (clés manquantes, JSON mal formé) renvoient 400
requête invalide. Une fois la validation réussie, tout échec du chemin d'envoi — y compris les échecs d'encodage GSM 7 bits — est signalé comme 500 avec la raison inspectée dans le champerror.
Exemple cURL
curl -X POST http://localhost:8080/api/ussd/send \
-H "Content-Type: application/json" \
-d '{
"msisdn": "+254712345678",
"text": "Vous avez une facture en attente. Répondez 1 pour payer.",
"callback_url": "http://billing-app:9000/ussd"
}'
Cycle de vie de la session
Chaque dialogue USSD est suivi comme une session avec un session_id unique.
États de la session
Session à un tour
Si votre rappel renvoie "end" lors du premier tour, aucune session persistante n'est créée. La passerelle envoie un MAP End avec le résultat et renvoie immédiatement.
Session multi-tours
Si votre rappel renvoie "continue", la passerelle :
- Crée un Session GenServer enregistré dans
UssdGateway.Registry - Envoie un MAP Continue avec opcode 60 (unstructuredSS-Request) au mobile
- Attend la réponse de l'abonné (jusqu'à
turn_timeout_ms) - Transmet la réponse à votre rappel
- Répète jusqu'à ce que votre rappel renvoie
"end"ou qu'un délai d'attente se produise
Comportement de délai d'attente
| Délai d'attente | Par défaut | Effet |
|---|---|---|
| Délai d'attente de tour | 30 secondes | Si l'abonné ne répond pas dans cette fenêtre, la session est terminée avec une erreur MAP. |
| Délai d'attente de session | 3 minutes | Durée totale de la session. Termine la session indépendamment de l'activité. |
| Délai d'attente de rappel HTTP | 5 secondes | Si votre application ne répond pas à temps, la passerelle envoie une erreur MAP au mobile et termine la session. |
Gestion des erreurs
La passerelle gère les échecs avec grâce et tente toujours d'envoyer une réponse d'erreur MAP au mobile afin que l'abonné voie un message significatif plutôt qu'un délai d'attente réseau.
| Scénario | Action de la passerelle | Code d'erreur MAP |
|---|---|---|
| Délai d'attente de rappel HTTP ou 5xx | Terminer la session, envoyer MAP End avec erreur | 34 (systemFailure) |
| JSON invalide du rappel | Terminer la session, envoyer MAP End avec erreur | 34 (systemFailure) |
Le texte USSD dépasse max_text_length | Tronquer le texte, enregistrer un avertissement, continuer normalement | N/A (tronqué, pas une erreur) |
| Délai d'attente de l'abonné (pas de réponse) | Terminer la session, envoyer MAP End avec erreur | 34 (systemFailure) |
| Aucune route ne correspond au code court | Envoyer MAP End avec erreur, enregistrer un avertissement | 34 (systemFailure) |
| Échec du Session GenServer | La session meurt, l'abonné voit un délai d'attente réseau | N/A (sortie de processus) |
| Passerelle USSD non activée | Retourner Facility Not Supported | 21 (facilityNotSupported) |
Métriques et surveillance
La passerelle USSD expose des métriques Prometheus sur le point de terminaison standard /metrics (port 8080).
Métriques USSD
Métrique: ussd_requests_total
Type: Compteur
Description: Total des requêtes USSD traitées
Étiquettes:
direction—"inbound"(d'origine mobile) ou"outbound"(push d'origine réseau)
Métrique: ussd_active_sessions
Type: Jauge
Description: Déclarée comme une jauge pour le nombre de sessions USSD actives.
Avertissement: Cette jauge est déclarée mais jamais mise à jour dans la version actuelle — elle lit toujours
0. Ne comptez pas dessus pour les comptes de session en direct. Suivez l'activité de session viaussd_requests_totalà la place.
Métrique: map_request_duration_milliseconds
Type: Histogramme
Description: Durée des opérations d'envoi USSD en millisecondes
Étiquettes:
operation—"ussd_send"pour les requêtes push sortantes
Exemples de requêtes Prometheus
# Taux de requêtes USSD par direction (entrant vs sortant)
rate(ussd_requests_total[5m])
# Latence USSD sortante (p95)
histogram_quantile(0.95, rate(map_request_duration_milliseconds_bucket{operation="ussd_send"}[5m]))
Serveur de rappel d'exemple
Python (Flask)
from flask import Flask, request, jsonify
app = Flask(__name__)
sessions = {}
@app.route('/ussd', methods=['POST'])
def ussd():
data = request.json
session_id = data['session_id']
text = data['text']
turn = data['turn']
if data['type'] == 'initiation':
sessions[session_id] = {'state': 'main_menu'}
return jsonify({
'action': 'continue',
'text': 'Bienvenue!\n1. Vérifier le solde\n2. Acheter du crédit\n3. Transférer'
})
state = sessions.get(session_id, {}).get('state')
if state == 'main_menu':
if text == '1':
del sessions[session_id]
return jsonify({
'action': 'end',
'text': 'Votre solde est de 5,00 $'
})
elif text == '2':
sessions[session_id]['state'] = 'buy_airtime'
return jsonify({
'action': 'continue',
'text': 'Entrez le montant :'
})
else:
del sessions[session_id]
return jsonify({
'action': 'end',
'text': 'Option invalide. Au revoir.'
})
elif state == 'buy_airtime':
del sessions[session_id]
return jsonify({
'action': 'end',
'text': f'Vous avez acheté {text} $ de crédit. Merci!'
})
return jsonify({'action': 'end', 'text': 'Session expirée.'})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=9000)
Node.js (Express)
const express = require('express');
const app = express();
app.use(express.json());
const sessions = new Map();
app.post('/ussd', (req, res) => {
const { session_id, text, type } = req.body;
if (type === 'initiation') {
sessions.set(session_id, { state: 'main_menu' });
return res.json({
action: 'continue',
text: 'Bienvenue!\n1. Vérifier le solde\n2. Acheter du crédit'
});
}
const session = sessions.get(session_id);
if (!session) {
return res.json({ action: 'end', text: 'Session expirée.' });
}
if (session.state === 'main_menu' && text === '1') {
sessions.delete(session_id);
return res.json({ action: 'end', text: 'Votre solde est de 5,00 $' });
}
sessions.delete(session_id);
return res.json({ action: 'end', text: 'Au revoir.' });
});
app.listen(9000, () => console.log('Rappel USSD sur le port 9000'));
Dépannage
Le numéro USSD renvoie "Service non disponible"
Symptômes: L'abonné compose un code court et obtient immédiatement une erreur réseau.
Causes possibles:
ussd_gateway_enabledestfalse- Aucune route ne correspond au code court composé
- La connexion M3UA est hors service
Résolution:
- Vérifiez
ussd_gateway_enabled: truedans la configuration - Vérifiez qu'un modèle de route correspond au code court (n'oubliez pas d'inclure
*comme solution de repli) - Vérifiez l'état du pair M3UA dans l'interface Web (page des pairs)
Le rappel ne reçoit pas de requêtes
Symptômes: Les journaux de la passerelle montrent un début USSD mais votre application ne reçoit jamais le POST HTTP.
Causes possibles:
- L'URL de rappel est inaccessible depuis l'hôte OmniSS7
- Pare-feu bloquant HTTP sortant depuis OmniSS7
- Application de rappel non en cours d'exécution
Résolution:
- Testez la connectivité :
curl -v http://your-app:9000/ussddepuis l'hôte OmniSS7 - Vérifiez les règles de pare-feu pour HTTP sortant
- Vérifiez que votre application de rappel écoute sur le port configuré
Les sessions expirent prématurément
Symptômes: Les sessions multi-tours se terminent avec "systemFailure" avant que l'abonné puisse répondre.
Causes possibles:
turn_timeout_msest trop court pour votre base d'abonnéshttp_timeout_msest trop court pour le temps de traitement de votre application- Latence réseau entre OmniSS7 et votre serveur de rappel
Résolution:
- Augmentez
turn_timeout_ms(30 secondes par défaut devraient suffire pour la plupart des cas) - Augmentez
http_timeout_mssi votre application a besoin de plus de temps de traitement - Déployez le serveur de rappel près d'OmniSS7 pour réduire la latence
Erreurs d'encodage GSM 7 bits
Symptômes: Erreurs gsm7_encode_failed dans les journaux, ou une réponse 500 de /api/ussd/send dont le champ error contient {:gsm7_encode_failed, ...}.
Causes possibles:
- Le texte contient des caractères en dehors de l'alphabet par défaut GSM 7 bits (par exemple, emoji, caractères CJK)
Résolution:
- Limitez le texte USSD à l'ensemble de caractères de base GSM : lettres ASCII, chiffres, ponctuation courante, et quelques caractères grecs/nordiques
- Voir 3GPP TS 23.038 Section 6.2.1 pour le tableau complet des caractères
Documentation connexe
- Guide API — Référence complète de l'API REST (tous les points de terminaison y compris
/api/ussd/send) - Guide du client MAP — Configuration de la connexion M3UA requise pour USSD
- Référence de configuration — Tous les paramètres de configuration
- Guide des fonctionnalités communes — Interface Web, surveillance et configuration de Prometheus