Bienvenue sur AfriGate
AfriGate est une passerelle de paiement qui permet aux marchands d'accepter des paiements Mobile Money et d'effectuer des transferts partout en Afrique. Intégrez une seule fois, touchez 16 pays.
Encaisser des paiements
Acceptez le Mobile Money de vos clients
Envoyer des transferts
Versez des fonds à vos bénéficiaires
Remboursements
Remboursez un paiement en totalité ou en partie
Webhooks
Notifications d'événements en temps réel
Parcours d'intégration
GET /v1/payment-methods Étape 1 — en premierRécupérez les pays/opérateurs disponibles pour votre compte (avec la disponibilité en temps réel) et affichez-les à votre client. Ne codez jamais cette liste en dur. Voir Méthodes de paiement.
POST /v1/payments Étape 2Créez le paiement avec l'operator + le country choisis par le client et le channel correspondant au flux de l'opérateur. Voir Flux de paiement.
Webhook Étape 3Recevez le résultat final (payment.completed) sur votre callbackUrl, puis confirmez via GET /v1/payments/{token}.
Flux de paiement
Marchand AfriGate Opérateur Mobile Money
| | |
|-- POST /v1/payments ----->| |
|<-- { token, status } ----| |
| |-- Routage + Push USSD ------>|
| | |
| |<-- Résultat (callback) ------|
|<-- Webhook (callbackUrl) -| |
| | |
|-- GET /v1/payments/{token} ->| |
|<-- { status: "success" } ---| | Flux de transfert
Marchand AfriGate Opérateur Mobile Money
| | |
|-- POST /v1/transfers ---->| |
|<-- { token, status } ----| |
| |-- Routage + Envoi ---------->|
| | |
| |<-- Résultat (callback) ------|
|<-- Webhook (callbackUrl) -| | Authentification
Clés API
L'authentification Afrigate repose sur une paire de clés : une clé publique (pk_) et une clé privée (sk_, parfois appelée « secret »). Les deux sont générées ensemble depuis votre tableau de bord marchand et doivent toujours être envoyées ensemble dans chaque requête.
C'est le même principe que les paires utilisées par d'autres plateformes :
| Plateforme | Clé publique | Clé privée |
|---|---|---|
| Afrigate | pk_live_... | sk_live_... |
| AWS | Access Key ID | Secret Access Key |
| OAuth 2.0 | client_id | client_secret |
| Stripe | pk_live_... | sk_live_... |
Clé publique (pk_) | Clé privée (sk_) | |
|---|---|---|
| Rôle | Identifie votre compte marchand | Prouve que vous êtes le propriétaire du compte |
| Analogie | Nom d'utilisateur | Mot de passe |
| Sensible ? | Non, peut être connue de tiers | Oui, strictement confidentielle |
| Stockage | Variable d'environnement back-end (acceptable) | Gestionnaire de secrets uniquement |
| Affichée dans le tableau de bord ? | À tout moment | Une seule fois, à la génération |
La clé publique (pk_)
- À quoi elle sert : c'est votre identifiant. Lorsqu'Afrigate reçoit une requête, cette valeur lui indique « de quel marchand parle-t-on ? ».
- Format :
pk_{env}_{24 caractères aléatoires}, ex.pk_live_mZWbtIV-ll-_0tNSSAxXV4fW. - Sensibilité : non sensible en elle-même. Connaître uniquement la clé publique d'un marchand ne permet aucune action — comme connaître un nom d'utilisateur sans le mot de passe.
- Stockage : peut être stockée en clair dans votre code back-end (variable d'environnement, fichier de configuration). Évitez toutefois de la placer dans du code front-end / mobile : non pas par risque de compromission, mais pour ne pas l'exposer inutilement dans les logs ou les outils de débogage du navigateur.
La clé privée (sk_)
- À quoi elle sert : c'est votre mot de passe. Elle prouve que vous êtes bien propriétaire de la clé publique correspondante.
- Format :
sk_{env}_{32 caractères aléatoires}, ex.sk_live_bleW2QUnMZ4Z9RPMuNLyAwGJ2egoP7JN. - Sensibilité : strictement confidentielle. Quiconque obtient la paire
pk_+sk_peut créer des paiements et des transferts en votre nom, déplacer vos fonds ou consulter votre historique de transactions. - Stockage : côté serveur uniquement, dans un gestionnaire de secrets (AWS Secrets Manager, HashiCorp Vault, fichier
.envnon commité, variable d'environnement CI). Jamais dans du front-end, du mobile ou un dépôt Git public. - Récupération : la clé privée n'est affichée qu'une seule fois, à la création/rotation. Afrigate ne la stocke pas en clair (hash argon2). En cas de perte, vous devez en générer une nouvelle via le tableau de bord.
Combiner les deux dans une requête
Concaténez la clé publique et la clé privée avec un : entre les deux, et placez le résultat après Bearer dans le header Authorization :
Authorization: Bearer {publicKey}:{privateKey} Exemple complet :
Authorization: Bearer pk_live_mZWbtIV-ll-_0tNSSAxXV4fW:sk_live_bleW2QUnMZ4Z9RPMuNLyAwGJ2egoP7JN Paires par environnement
Chaque environnement possède sa propre paire — une clé sandbox ne fonctionne pas en production, et inversement.
| Environnement | Clé publique | Clé privée | Usage |
|---|---|---|---|
| Production | pk_live_... | sk_live_... | Transactions réelles, mouvements de fonds réels |
| Sandbox | pk_test_... | sk_test_... | Tests, aucun mouvement de fonds réel, solde sandbox de 50 000 unités inclus (dans la devise de votre pays) |
En cas de fuite de votre clé privée
- Connectez-vous au tableau de bord.
- Effectuez une rotation de la clé compromise — l'ancienne clé privée est révoquée immédiatement.
- Mettez à jour la nouvelle paire dans vos back-ends.
- Vérifiez votre historique de transactions sur la période suspecte (paiements ou transferts illégitimes).
Permissions
| Permission | Description |
|---|---|
payment:read | Consulter les paiements |
payment:write | Créer/annuler des paiements |
transfer:read | Consulter les transferts |
transfer:write | Créer des transferts |
Les permissions par clé ne sont pas encore appliquées
Ces scopes décrivent le modèle cible, mais la gateway ne les applique pas pour le moment. Aujourd'hui, toute paire de clés API valide dispose d'un accès complet — lecture et écriture, paiements et transferts. La restriction fine par clé est prévue mais pas encore active : considérez donc chaque clé comme pleinement privilégiée et protégez la sk_ en conséquence.
Headers injectés
Après validation de la clé API, les headers suivants sont ajoutés automatiquement :
| Header | Description |
|---|---|
X-Merchant-ID | Votre identifiant marchand (UUID) |
X-Merchant-Code | Votre code marchand |
X-Key-Type | live ou test |
X-Request-ID | Identifiant unique de la requête |
Environnements
| Environnement | URL de base |
|---|---|
| Production | https://prod.afrigate.dev |
| Sandbox | https://sandbox.afrigate.dev |
Mode sandbox
Utilisez l'environnement sandbox pour vos tests. Aucun argent réel n'est déplacé. Passez en production lorsque vous êtes prêt à démarrer.
Méthodes de paiement
Étape 1 — obligatoire avant tout paiement
Appelez toujours GET /v1/payment-methods pour construire l'écran de paiement présenté à votre client. Ne codez jamais en dur la liste des opérateurs/pays : elle dépend de vos pays activés et des maintenances en temps réel. Afficher un opérateur indisponible (ou non activé) revient à créer un paiement voué à l'échec.
Cet endpoint renvoie une réponse propre à votre compte marchand : seuls vos pays activés et leurs opérateurs sont retournés, et chaque opérateur porte un indicateur isAvailable qui tient compte des maintenances en cours (opérateur ou gateway). C'est la source de vérité pour ne montrer à votre client que ce qu'il peut réellement utiliser. Le tableau des pays ci-dessous n'est qu'un aperçu indicatif — GET /v1/payment-methods fait foi.
Authentification : clé publique uniquement
Contrairement aux paiements/transferts (qui exigent la paire pk:sk), cet endpoint en lecture seule s'authentifie avec votre clé publique seule — vous pouvez l'appeler sans risque depuis un back-end léger.
Headers
| Header | Requis | Description |
|---|---|---|
Authorization | Requis | Bearer {publicKey} — votre pk_… seule (sans :sk_) |
Content-Type | Optionnel | application/json |
cURL curl https://prod.afrigate.dev/v1/payment-methods \
-H "Authorization: Bearer pk_live_mZWbtIV-ll-_0tNSSAxXV4fW"
Réponse 200 OK
JSON {
"data": {
"countries": [
{
"countryCode": "CI",
"countryName": "Cote d'Ivoire",
"flag": "https://d37zkt40qskmxk.cloudfront.net/flags/ci.svg",
"currency": "XOF",
"paymentMethods": [
{
"name": "Wave",
"category": "mobile_money",
"logo": "https://d37zkt40qskmxk.cloudfront.net/operators/wave.png",
"isAvailable": true
},
{
"name": "Orange Money",
"category": "mobile_money",
"logo": "https://d37zkt40qskmxk.cloudfront.net/operators/orange.png",
"isAvailable": true
},
{
"name": "MTN Mobile Money",
"category": "mobile_money",
"logo": "https://d37zkt40qskmxk.cloudfront.net/operators/mtn.png",
"isAvailable": false
}
]
}
]
}
}
Champs
Champ Description countries[]Un objet par pays activé sur votre compte countries[].countryCodeCode pays ISO 3166-1 alpha-2 (majuscules) countries[].countryNameNom du pays (peut être null) countries[].flagURL du drapeau (peut être null) countries[].currencyDevise ISO 4217 du pays (XOF, XAF, …) paymentMethods[].nameNom d'affichage de l'opérateur (ex. Wave, Orange Money) paymentMethods[].categorymobile_money, card ou fintech_wallet paymentMethods[].logoURL du logo de l'opérateur (peut être null) paymentMethods[].isAvailablefalse si l'opérateur (ou la gateway qui le sert) est en maintenance active — masquez-le ou grisez-le
isAvailable: false est temporaire (maintenance). Ne retirez pas l'opérateur de votre interface, grisez-le simplement. Le name d'affichage n'est pas le code à envoyer dans operator — consultez le tableau des opérateurs pour le code unifié (wave, orange, momo, …).
Pays & opérateurs
AfriGate est connecté dans les pays ci-dessous. Pour chaque paiement/transfert, vous envoyez country (ISO 3166-1 alpha-2) + operator (code unifié) + currency (la devise du pays) ; AfriGate route automatiquement vers le bon PSP. La liste à jour propre à votre compte (pays activés + disponibilité en temps réel) est toujours donnée par GET /v1/payment-methods.
| Pays | Code | Devise | Opérateurs |
|---|---|---|---|
| Bénin | BJ | XOF | Moov, MTN |
| Botswana | BW | BWP | Voucher |
| Cameroun | CM | XAF | MTN, Orange Money |
| Côte d'Ivoire | CI | XOF | Orange Money, Wave, Moov, MTN |
| Gambie | GM | GMD | Wave, Afrimoney, Qmoney |
| Ghana | GH | GHS | MTN, Vodafone, AirtelTigo |
| Kenya | KE | KES | M-Pesa, Airtel |
| Liberia | LR | LRD | MTN, Orange Money, Lonestar |
| Nigeria | NG | NGN | Opay, Palmpay |
| Ouganda | UG | UGX | MTN, Airtel |
| Sénégal | SN | XOF | Orange Money, Wave, Free Money |
| Sierra Leone | SL | SLE | Afrimoney, Orange Money |
| Tanzanie | TZ | TZS | Halopesa, M-Pesa, Tigo, Airtel |
La currency envoyée doit être la devise du pays — ex. BWP pour le Botswana, XOF pour le Sénégal/la Côte d'Ivoire/le Bénin, XAF pour le Cameroun. Le nom d'opérateur ci-dessus est le nom d'affichage ; le code en minuscules pour operator (wave, orange, momo, moov) se trouve dans le tableau des opérateurs. Les devises sans décimales (XOF, XAF, GNF, BIF, RWF, KMF, DJF) n'acceptent que des montants entiers — les frais et montants nets sont également arrondis à l'unité ; les autres devises conservent 2 décimales.
Spécificités par opérateur
La façon dont le client autorise le paiement varie selon l'opérateur. Le champ channel (et parfois un champ supplémentaire) pilote ce flux — détaillé dans Flux de paiement. Principaux cas :
| Opérateur / pays | Flux | Ce que VOUS devez faire |
|---|---|---|
| Wave — SN, GM, CI, SL | Redirect | channel: "REDIRECT" → redirigez le client vers redirectUrl (https://pay.afrigate.dev/{token}) |
| Orange Money — CI | OTP direct | Le client compose #144*82#, vous communique le code → envoyez-le dans otp avec channel: "OTP" |
| Orange Money — SN | Redirect | channel: "REDIRECT" (imposé) |
| Botswana (Voucher) | Voucher | Le client achète un voucher et vous communique son PIN → envoyez-le dans metadata.voucherPin |
| Opay / Palmpay — NG | Redirect | channel: "REDIRECT" → redirigez vers redirectUrl |
| MTN (MoMo), Moov, M-Pesa, Tigo Pesa, Halopesa, Airtel | Push / STK | channel: "PUSH" (par défaut) → le client valide l'invite avec son PIN |
Paiements Paiements (Collect)
Initiez un encaissement depuis le compte mobile money d'un client vers votre compte marchand.
Créer un paiement
POST /v1/payments Headers requis
Header Requis Description AuthorizationRequis Bearer {keyId}:{secret} — voir Authentification X-Idempotency-KeyRequis UUID unique pour éviter les doublons Content-TypeRequis application/json
Corps de la requête
JSON {
"amount": 5000,
"currency": "XOF",
"paymentMethod": "MOBILE_MONEY",
"operator": "orange",
"country": "CI",
"customer": {
"phone": "+2250700000000",
"name": "Jean Kouassi",
"email": "jean@example.com"
},
"successUrl": "https://mysite.com/payment/success",
"failedUrl": "https://mysite.com/payment/failed",
"callbackUrl": "https://mysite.com/webhooks/afrigate",
"merchantTransactionId": "ORDER-12345",
"feeBearer": "merchant",
"channel": "PUSH",
"designation": "Online purchase",
"description": "Order #12345",
"metadata": {
"orderId": "12345",
"customField": "value"
}
}
Champs
amount number RequisMontant dans l'unité mineure de la devise (minimum 1). Pour les devises sans décimales (XOF, XAF, GNF, BIF, RWF, KMF, DJF), le montant doit être un entier (sans décimales) — envoyez 5000, pas 5000.00. Les autres devises (GHS, KES, NGN, BWP, TZS, UGX…) utilisent 2 décimales.
currency string RequisCode devise ISO 4217 (ex. XOF, XAF, GHS)
paymentMethod string RequisMéthode de paiement (ex. MOBILE_MONEY)
operator string RequisCode opérateur unifié. Voir le tableau des opérateurs
country string RequisCode pays ISO 3166-1 alpha-2 (ex. CI, SN, GH)
customer.phone string RequisNuméro de téléphone du client. Validé par pays (indicatif + longueur exacte) ; format international E.164 (+225…, recommandé) ou format national accepté. Doit correspondre au country. Voir Format des numéros de téléphone
customer.name string OptionnelNom du client
customer.email string OptionnelE-mail du client
successUrl string RequisURL de redirection après un paiement réussi
failedUrl string RequisURL de redirection après un échec
callbackUrl string RequisURL de réception des webhooks
merchantTransactionId string OptionnelVotre référence interne
feeBearer string OptionnelQui paie les frais : merchant (par défaut) ou customer
channel string OptionnelPUSH (par défaut), OTP, USSD, QRCODE, REDIRECT, DIRECT. Voir Flux de paiement
otp string OptionnelCode d'autorisation / OTP que le client génère auprès de son opérateur (ex. Orange Money via USSD). Envoyez-le avec channel: "OTP" pour autoriser le paiement directement, sans page de redirection (20 caractères max). Voir Flux de paiement
metadata object OptionnelDonnées supplémentaires renvoyées dans les webhooks. Porte également le PIN du voucher pour les paiements par voucher prépayé au Botswana : metadata.voucherPin — voir Flux de paiement
Réponse 201 Created
JSON {
"success": true,
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"token": "pay_xK9mN2pQ",
"merchantId": "m_abc123",
"amount": 5000,
"currency": "XOF",
"feeAmount": 150,
"netAmount": 4850,
"status": "initiated",
"paymentMethod": "MOBILE_MONEY",
"operator": "orange",
"country": "CI",
"redirectUrl": "https://pay.afrigate.dev/a1b2c3...",
"expiresAt": "2024-01-15T15:30:00.000Z",
"createdAt": "2024-01-15T15:00:00.000Z"
}
}
URL de redirection
Le champ redirectUrl n'est présent qu'avec channel: REDIRECT (opérateurs à redirection — Wave, Orange Money SN, Opay, Palmpay). Il vaut toujours https://pay.afrigate.dev/{token} (page hébergée par AfriGate). Redirigez toujours le client vers cette URL : la page AfriGate le renvoie ensuite automatiquement vers la page de l'opérateur. Pour les autres canaux (PUSH, OTP, …), ce champ est absent. Voir Flux de paiement.
Consulter un paiement
GET/v1/payments/{token} Renvoie la même structure que la réponse de création.
Lister les paiements
GET/v1/payments Paramètre Type Description statusstring Filtrer par statut fromstring Date de début (ISO 8601) tostring Date de fin (ISO 8601) limitnumber Nombre de résultats (par défaut : 20) offsetnumber Décalage pour la pagination
Annuler un paiement
POST/v1/payments/{token}/cancel JSON { "reason": "Customer changed their mind" }
Seuls les paiements au statut initiated ou pending peuvent être annulés.
Cycle de vie d'un paiement
Machine à états initiated --> pending --> processing --> success
--> failed
--> expired
--> cancelled
success --> refunded (remboursement total)
--> partially_refunded (remboursement partiel)
Statut Description Terminal initiatedPaiement créé, en attente de traitement Non pendingEn cours de routage vers l'opérateur Non processingL'opérateur traite la transaction Non successPaiement réussi Oui failedPaiement échoué Oui cancelledAnnulé par le marchand Oui expiredDélai dépassé (15 min pour les flux REDIRECT, 30 min pour les flux push/momo) Oui refundedRemboursé en totalité Oui partially_refundedRemboursé partiellement Oui
Paiements Opérateurs & flux de paiement
La façon dont le client autorise un paiement dépend de l'opérateur (operator) et du pays (country). Le champ channel pilote ce flux. Il existe quatre flux, et envoyer le mauvais channel pour un opérateur peut faire échouer silencieusement la transaction chez certains PSP.
Règle d'or : vous n'avez jamais à choisir le PSP (Flutterwave, Wave, Payaza, 54pay…). Vous envoyez uniquement operator + country ; AfriGate route vers le bon connecteur. Vous ne choisissez que le channel selon les flux ci-dessous.
Flux 1 — Redirect (Wave, Orange Money SN, Opay, Palmpay)
Le client est redirigé vers une page de paiement où il autorise le débit auprès de son opérateur.
- Envoyez
channel: "REDIRECT". - La réponse
201 contient redirectUrl = https://pay.afrigate.dev/{token} (page hébergée par AfriGate). Redirigez toujours le client vers cette URL ; la page AfriGate le renvoie ensuite vers l'opérateur. Vous ne manipulez jamais l'URL brute de l'opérateur. - Rien à collecter (pas d'OTP), rien d'envoyé sur le téléphone.
- Le résultat final arrive via le webhook (
payment.completed) ; vous pouvez aussi interroger GET /v1/payments/{token}.
Opérateur operatorcountryNote Wave waveSN, CI, GM, SLREDIRECT imposé Orange Money Sénégal orangeSNREDIRECT imposé Opay opayNGREDIRECT imposé Palmpay palmpayNGREDIRECT imposé
Pour tous les opérateurs à redirection, REDIRECT est le seul canal supporté et imposé. Tout autre channel est rejeté avec un 400 (Operator '<x>' only supports channel REDIRECT).
JSON — Wave Sénégal POST /v1/payments
{
"amount": 5000,
"currency": "XOF",
"paymentMethod": "MOBILE_MONEY",
"operator": "wave",
"country": "SN",
"channel": "REDIRECT",
"customer": { "phone": "+221770000000", "name": "Awa Diop" },
"successUrl": "https://mysite.com/ok",
"failedUrl": "https://mysite.com/ko",
"callbackUrl": "https://mysite.com/webhooks/afrigate",
"merchantTransactionId": "ORDER-12345"
}
Flux 2 — OTP direct (Orange Money CI)
Le client génère un code auprès de son opérateur, vous le collectez sur votre interface et l'envoyez dans le champ otp avec channel: "OTP". Le paiement est autorisé directement, sans redirection.
Étapes côté client — Orange Money Côte d'Ivoire :
- Le client compose sur son téléphone :
#144*82#. - Il reçoit un code de paiement (OTP) par SMS.
- Il vous communique ce code sur votre interface (page de paiement, application, TPE…).
- Vous appelez
POST /v1/payments avec channel: "OTP" et otp: "<code>".
- Champ
otp : chaîne, 20 caractères max. - Aucun
redirectUrl n'est renvoyé. Le résultat final arrive via le webhook.
JSON — Orange Money CI POST /v1/payments
{
"amount": 5000,
"currency": "XOF",
"paymentMethod": "MOBILE_MONEY",
"operator": "orange",
"country": "CI",
"channel": "OTP",
"otp": "123456",
"customer": { "phone": "+2250700000000", "name": "Jean Kouassi" },
"successUrl": "https://mysite.com/ok",
"failedUrl": "https://mysite.com/ko",
"callbackUrl": "https://mysite.com/webhooks/afrigate"
}
Si vous ne pouvez pas collecter l'OTP (ex. flux sans interaction), Orange CI accepte également channel: "REDIRECT" en solution de repli.
Flux 3 — Push / STK (MTN MoMo, Moov, …)
Une invite de validation est envoyée (push) sur le téléphone du client ; il la valide avec son PIN. Rien à rediriger, rien à collecter.
- Envoyez
channel: "PUSH" (valeur par défaut si channel est omis). - Le résultat final arrive via le webhook.
JSON — MTN Mobile Money CI POST /v1/payments
{
"amount": 5000,
"currency": "XOF",
"paymentMethod": "MOBILE_MONEY",
"operator": "momo",
"country": "CI",
"channel": "PUSH",
"customer": { "phone": "+2250500000000", "name": "Ama Kone" },
"successUrl": "https://mysite.com/ok",
"failedUrl": "https://mysite.com/ko",
"callbackUrl": "https://mysite.com/webhooks/afrigate"
}
Flux 4 — Voucher / prépayé (Botswana)
Le client achète un voucher (jeton prépayé) dans un point de vente ou une application, obtient un PIN et vous le communique. Vous envoyez ce PIN dans l'objet metadata (metadata.voucherPin). Il n'y a ni redirection ni push : le PIN seul autorise le débit.
- Botswana (
country: "BW", devise BWP) : PIN dans metadata.voucherPin. - Le résultat final arrive via le webhook (
payment.completed) ; vous pouvez aussi interroger GET /v1/payments/{token}. Aucun redirectUrl n'est renvoyé.
JSON — Voucher Botswana POST /v1/payments
{
"amount": 100,
"currency": "BWP",
"paymentMethod": "MOBILE_MONEY",
"operator": "voucher",
"country": "BW",
"customer": { "phone": "+26771000000", "name": "Kgomotso M." },
"successUrl": "https://mysite.com/ok",
"failedUrl": "https://mysite.com/ko",
"callbackUrl": "https://mysite.com/webhooks/afrigate",
"metadata": { "voucherPin": "12345678" }
}
Récapitulatif
Opérateur operatorcountryFlux channelLe client… Wave waveSN, CI, GM, SLRedirect REDIRECT (imposé dans tous les pays)est redirigé vers redirectUrl Orange Money orangeSNRedirect REDIRECT (imposé)est redirigé vers redirectUrl Opay opayNGRedirect REDIRECT (imposé)est redirigé vers redirectUrl Palmpay palmpayNGRedirect REDIRECTest redirigé vers redirectUrl Orange Money orangeCIOTP direct OTP + champ otpcompose #144*82#, vous communique le code MTN MoMo momoCI, …Push PUSH (par défaut)valide l'invite avec son PIN Moov moovCI, BJPush PUSH (par défaut)valide l'invite avec son PIN Voucher voucherBWVoucher — (PIN via metadata.voucherPin) achète un voucher, vous communique le PIN
En cas de doute, interrogez d'abord GET /v1/payment-methods pour connaître les opérateurs actifs/disponibles, puis appliquez le channel du tableau ci-dessus. Un mauvais channel peut faire échouer le paiement sans message explicite (sauf pour les opérateurs REDIRECT uniquement, qui renvoient un 400 explicite).
Disbursement Transferts
Envoyez des fonds depuis votre compte marchand vers le compte mobile money d'un bénéficiaire.
Créer un transfert
POST/v1/transfers JSON {
"amount": 10000,
"currency": "XOF",
"paymentMethod": "MOBILE_MONEY",
"operator": "momo",
"country": "CI",
"recipient": {
"phone": "+2250700000000",
"name": "Awa Traore",
"email": "awa@example.com"
},
"callbackUrl": "https://mysite.com/webhooks/afrigate",
"merchantTransactionId": "TRANSFER-789",
"designation": "Supplier payment",
"metadata": { "invoiceId": "789" }
}
Champs
amount number RequisMontant (minimum 1). Les devises sans décimales (XOF, XAF, GNF, BIF, RWF, KMF, DJF) exigent un entier ; les autres devises utilisent 2 décimales.
currency string RequisCode devise ISO 4217
operator string RequisCode opérateur unifié. Voir le tableau des opérateurs
country string RequisCode pays ISO 3166-1 alpha-2
recipient.phone string RequisNuméro de téléphone du bénéficiaire. Validé par pays (indicatif + longueur exacte) ; format E.164 (+225…) ou national accepté. Doit correspondre au country. Voir Format des numéros de téléphone
recipient.name string OptionnelNom du bénéficiaire
callbackUrl string OptionnelURL du webhook
metadata object OptionnelDonnées supplémentaires
Cycle de vie d'un transfert
Statut Description Terminal initiatedTransfert créé Non pendingEn cours de routage Non processingL'opérateur traite la transaction Non successFonds envoyés au bénéficiaire Oui failedTransfert échoué Oui expiredDélai dépassé (10 min par défaut) Oui
Remboursements
Remboursez tout ou partie d'un paiement réussi.
POST/v1/payments/{paymentToken}/refund JSON {
"amount": 2500,
"currency": "XOF",
"refundType": "partial",
"reason": "Product returned"
}
amount number RequisMontant à rembourser
currency string RequisDevise (doit correspondre à celle du paiement)
refundType string Optionnelfull ou partial (détecté automatiquement si omis)
reason string OptionnelMotif du remboursement (500 caractères max)
Règles de remboursement
- Seuls les paiements au statut
success peuvent être remboursés - Le montant ne peut pas dépasser le montant remboursable restant
- Un remboursement total passe le statut à
refunded - Un remboursement partiel passe le statut à
partially_refunded
Lister les remboursements
GET/v1/payments/{paymentToken}/refunds Checkout Session
Le checkout est une page de paiement hébergée par AfriGate. Utilisez-la pour offrir une expérience de paiement clé en main.
Consulter la session
GET/v1/checkout/{token} Vérifier le statut
GET/v1/checkout/{token}/status Statut Description pendingEn attente d'une action du client processingPaiement en cours redirectLe client doit être redirigé (operatorRedirectUrl présent) successPaiement réussi (successUrl présent) failedPaiement échoué (failedUrl présent) expiredSession expirée
Intégration Webhooks
Les webhooks vous notifient en temps réel des changements de statut de vos transactions.
Événements
Événement Déclencheur payment.completedLe paiement atteint un statut terminal (success, failed, cancelled, expired) ou est remboursé (refunded, partially_refunded) transfer.completedLe transfert atteint un statut terminal
Les remboursements ne déclenchent pas d'événement distinct : ils réutilisent payment.completed avec le nouveau statut du paiement (refunded / partially_refunded).
Format du payload
JSON {
"event": "payment.completed",
"data": {
"token": "pay_xK9mN2pQ",
"merchantId": "m_abc123",
"amount": "5000",
"currency": "XOF",
"status": "success",
"completedAt": "2024-01-15T15:05:00.000Z"
},
"timestamp": "2024-01-15T15:05:01.000Z"
}
Payload de remboursement
Un remboursement n'émet pas son propre événement : il réutilise payment.completed avec le nouveau status du paiement — refunded (total) ou partially_refunded (partiel). Le token est celui du paiement (pas du remboursement), et l'objet data contient des champs supplémentaires propres au remboursement :
JSON {
"event": "payment.completed",
"data": {
"token": "pay_xK9mN2pQ",
"merchantId": "m_abc123",
"status": "partially_refunded",
"refundId": "ref_a1b2c3d4",
"paymentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"amount": "2500",
"currency": "XOF",
"refundType": "partial"
},
"timestamp": "2024-01-15T16:00:00.000Z"
}
Headers des webhooks
Header Description X-Webhook-Request-IdIdentifiant unique de la livraison (UUID) X-Webhook-TimestampHorodatage en millisecondes (epoch) X-Webhook-SignatureSignature HMAC-SHA256
Vérification de la signature
Node.js const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, timestamp, secret) {
const content = `${timestamp}.${JSON.stringify(payload)}`;
const expected = crypto
.createHmac('sha256', secret)
.update(content)
.digest('hex');
const receivedSig = signature.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(receivedSig)
);
}
app.post('/webhooks/afrigate', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const timestamp = req.headers['x-webhook-timestamp'];
// Vérifier que l'horodatage est récent (< 5 minutes)
const age = Date.now() - parseInt(timestamp);
if (age > 5 * 60 * 1000) {
return res.status(400).json({ error: 'Timestamp too old' });
}
if (!verifyWebhookSignature(req.body, signature, timestamp, WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { event, data } = req.body;
console.log(`Event: ${event}, Status: ${data.status}`);
res.status(200).json({ received: true });
});
Python import hmac, hashlib, time, json
def verify_webhook(payload, signature, timestamp, secret):
age = int(time.time() * 1000) - int(timestamp)
if age > 5 * 60 * 1000:
return False
content = f"{timestamp}.{json.dumps(payload, separators=(',', ':'))}"
expected = hmac.new(
secret.encode(), content.encode(), hashlib.sha256
).hexdigest()
received = signature.replace("sha256=", "")
return hmac.compare_digest(expected, received)
Politique de retry
Chaque webhook est livré avec un POST initial plus jusqu'à 5 nouvelles tentatives (6 tentatives max). Le délai d'attente de la requête est de 10 secondes par tentative. Les nouvelles tentatives suivent un backoff exponentiel (2, 4, 8, 16, 32 secondes) :
Tentative Délai avant la tentative Cumulé 1 (initiale) Immédiat 0 s 2 (retry 1) 2 s 2 s 3 (retry 2) 4 s 6 s 4 (retry 3) 8 s 14 s 5 (retry 4) 16 s 30 s 6 (retry 5) 32 s 62 s
Tous les échecs ne sont pas réessayés
Seuls les échecs transitoires sont réessayés : réponses 5xx, délais de connexion dépassés (statut 0) et 408 / 425 / 429. Les réponses 4xx permanentes (400, 401, 403, 404, 422) ne sont pas réessayées — la livraison est immédiatement marquée FAILED. Renvoyez un 2xx pour accuser réception ; ne renvoyez un 5xx que si vous souhaitez qu'AfriGate réessaie.
Bonnes pratiques
- Répondez
200 OK immédiatement, traitez de manière asynchrone - Utilisez
X-Webhook-Request-Id pour dédupliquer - Vérifiez toujours la signature
- Rejetez les webhooks datant de plus de 5 minutes
- Confirmez le statut via
GET /v1/payments/{token}
Idempotence
Pour éviter les transactions en double, incluez un header X-Idempotency-Key unique.
Header X-Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Scénario Comportement Nouvelle clé Transaction créée normalement Clé existante Réponse d'origine renvoyée (pas de doublon) Durée de vie de la clé 24 heures
Requis pour : POST /v1/payments et POST /v1/transfers
Rate limiting
Les requêtes sont limitées par marchand sur une fenêtre fixe de 60 secondes. La limite diffère selon l'environnement :
Environnement Limite Fenêtre Production 2 500 requêtes 60 secondes Sandbox 500 requêtes 60 secondes
Headers de réponse
Header Description X-RateLimit-LimitNombre max de requêtes dans la fenêtre (2500 en production, 500 en sandbox) X-RateLimit-RemainingRequêtes restantes dans la fenêtre courante X-RateLimit-ResetTimestamp Unix de réinitialisation de la fenêtre — utilisez-le pour temporiser
Il n'y a pas de header HTTP Retry-After — utilisez X-RateLimit-Reset pour décider quand réessayer. Dans le corps du 429, retry_after correspond à la longueur de la fenêtre (60 secondes), pas à un compte à rebours en temps réel.
Dépassement 429
JSON {
"success": false,
"data": null,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded",
"details": { "limit": 2500, "window": 60, "retry_after": 60 }
},
"meta": {
"request_id": "req_...",
"timestamp": "2024-01-15T15:00:00.000Z"
}
}
Intégration Tests en sandbox
L'environnement sandbox (https://sandbox.afrigate.dev) accepte des numéros de téléphone de test qui simulent un paiement réussi ou échoué sans solliciter le véritable opérateur mobile money. Aucun fonds réel n'est déplacé et aucune action de l'utilisateur n'est requise.
Solde sandbox initial
À la création de votre compte marchand, votre portefeuille sandbox est crédité de 50 000 unités dans la devise de votre pays (ex. 50 000 XOF pour un marchand ivoirien, 50 000 NGN pour un marchand nigérian) afin que vous puissiez tester les transferts (disbursements) immédiatement, sans dépôt préalable. Ce solde simulé est décrémenté à chaque transfert simulé réussi et n'a aucun impact en production.
Numéros de test par pays
Pour déclencher un résultat simulé, utilisez ces numéros dans customer.phone (paiement) ou recipient.phone (transfert), avec le country correspondant.
Pays Code Numéro succès Numéro échec Sénégal SN+221700000001+221700000002 Côte d'Ivoire CI+225700000001+225700000002 Bénin BJ+229700000001+229700000002 Nigeria NG+234700000001+234700000002 Ghana GH+233700000001+233700000002 Botswana BW+267700000001+267700000002 Cameroun CM+237700000001+237700000002 Gambie GM+220700000001+220700000002 Kenya KE+254700000001+254700000002 Liberia LR+231700000001+231700000002 Sierra Leone SL+232700000001+232700000002 Tanzanie TZ+255700000001+255700000002 Ouganda UG+256700000001+256700000002
Requête prête à l'emploi (par pays)
Pour chaque pays : un operator représentatif, le channel correspondant, la devise et le numéro succès pour customer.phone. Remplacez-le par le numéro échec (…002) pour simuler un échec. L'operator peut être n'importe lequel disponible pour le pays (voir GET /v1/payment-methods).
Pays countrycurrencyoperator (ex.)channelcustomer.phone (succès) Sénégal SNXOFwaveREDIRECT+221700000001 Côte d'Ivoire CIXOFmomoPUSH+225700000001 Bénin BJXOFmoovPUSH+229700000001 Nigeria NGNGNopayREDIRECT+234700000001 Ghana GHGHSmomoPUSH+233700000001 Botswana BWBWPvoucherPUSH *+267700000001 Cameroun CMXAFmomoPUSH+237700000001 Gambie GMGMDwaveREDIRECT+220700000001 Kenya KEKESmpesaPUSH+254700000001 Liberia LRLRDmomoPUSH+231700000001 Sierra Leone SLSLEorangePUSH+232700000001 Tanzanie TZTZSmpesaPUSH+255700000001 Ouganda UGUGXmomoPUSH+256700000001
En sandbox, le simulateur ne regarde que country + customer.phone : il renvoie success/failed après ~5 s quel que soit l'opérateur. Le channel doit néanmoins rester cohérent (les opérateurs REDIRECT comme wave SN / opay rejettent tout autre canal avec un 400).
* Botswana (voucher) : en sandbox, aucun PIN n'est requis (le simulateur l'ignore). En production, envoyez le PIN dans metadata.voucherPin — voir Flux de paiement.
Le numéro et le country doivent correspondre exactement. Tout autre numéro est traité comme un appel réel vers l'opérateur sandbox.
Exemple : paiement réussi
cURL curl -X POST https://sandbox.afrigate.dev/v1/payments \
-H "Authorization: Bearer pk_test_xxxxxxxxxxxxxxxxxxxxxxxx:sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "X-Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"currency": "XOF",
"paymentMethod": "MOBILE_MONEY",
"operator": "wave",
"country": "SN",
"customer": {
"phone": "+221700000001",
"name": "Test Success"
},
"successUrl": "https://mysite.com/payment/success",
"failedUrl": "https://mysite.com/payment/failed",
"callbackUrl": "https://mysite.com/webhooks/afrigate",
"merchantTransactionId": "TEST-SUCCESS-001"
}'
Renvoie 201 Created, identique à un paiement réel (status: "pending"). Après environ 5 secondes, votre callbackUrl reçoit le webhook :
JSON {
"event": "payment.completed",
"data": {
"token": "pay_xK9mN2pQ",
"merchantId": "m_abc123",
"amount": "5000",
"currency": "XOF",
"status": "success",
"completedAt": "2026-05-03T15:00:05.000Z"
},
"timestamp": "2026-05-03T15:00:06.000Z"
}
GET /v1/payments/{token} renvoie alors status: "success".
Exemple : paiement échoué
Mêmes paramètres avec le numéro échec du pays (+221700000002 pour SN). Le webhook reçu après ~5 s :
JSON {
"event": "payment.completed",
"data": {
"token": "pay_xK9mN2pQ",
"amount": "5000",
"currency": "XOF",
"status": "failed",
"completedAt": "2026-05-03T15:00:05.000Z"
},
"timestamp": "2026-05-03T15:00:06.000Z"
}
Transferts
Mêmes numéros pour recipient.phone. Le webhook transfer.completed arrive après ~5 s avec status: "success" ou status: "failed".
Remboursements
Les remboursements sont synchrones et ne passent pas par un opérateur : il n'y a ni numéro de test ni délai. Pour tester, créez un paiement réussi (numéro succès ci-dessus), attendez le status: "success", puis appelez POST /v1/payments/{token}/refund. Le paiement passe immédiatement à refunded (total) ou partially_refunded (partiel), et un webhook payment.completed est émis. Le fonctionnement est identique pour tous les pays.
Canal REDIRECT
Avec channel: "REDIRECT", le simulateur ne génère pas d'URL de redirection. La session de checkout passe directement de pending à success ou failed après ~5 s. Un front-end qui interroge GET /v1/checkout/{token} recevra :
JSON {
"token": "pay_xK9mN2pQ",
"status": "success",
"successUrl": "https://mysite.com/payment/success"
}
Limitations
- Sandbox uniquement. En production, ces numéros n'ont aucun effet particulier.
- Aucune action côté utilisateur. Pas de SMS, pas de page de paiement. Pour tester la véritable expérience utilisateur (Wave, OTP Orange, etc.), utilisez vos identifiants PSP sandbox avec un autre numéro.
- Délai fixe de 5 secondes entre l'initialisation et le webhook. Les opérateurs réels prennent de quelques secondes à plusieurs minutes.
Référence Codes d'erreur
Format des erreurs
AfriGate renvoie deux formats d'erreur différents selon l'endroit où l'erreur est levée. Prenez les deux en compte lors du parsing des erreurs.
1. Erreurs gateway — authentification, rate limit, timeout, service indisponible
Levées par la gateway elle-même. error est un objet avec un code machine stable (majuscules), un message et des details optionnels, le tout dans l'enveloppe standard :
JSON {
"success": false,
"data": null,
"error": {
"code": "INVALID_API_KEY",
"message": "Invalid or expired API key",
"details": {}
},
"meta": {
"request_id": "req_...",
"timestamp": "2024-01-15T15:00:00.000Z"
}
}
2. Erreurs métier — paiements, transferts, remboursements
Levées par le service de transactions et transmises telles quelles. C'est le format NestJS — pas de success, pas d'objet error, pas de details, seulement un statut HTTP et un message en texte brut :
JSON {
"statusCode": 400,
"message": "Merchant not active",
"error": "Bad Request"
}
Un code machine stable n'est fiable que pour les erreurs gateway. Les erreurs métier ne portent qu'un statut HTTP et un message lisible (à l'exception des deux codes structurés invalid_phone et blocked_number) — basez votre logique sur le statut HTTP, pas sur le parsing de la chaîne du message.
Erreurs gateway
Codes machine en majuscules renvoyés dans le champ error.code de l'enveloppe gateway ci-dessus.
Code HTTP Description MISSING_AUTH401 Header Authorization manquant INVALID_API_KEY401 Clé API invalide ou expirée IP_NOT_ALLOWED403 IP de la requête absente de la liste blanche du marchand PERMISSION_DENIED403 La clé ne dispose pas de la permission requise (défini mais pas encore appliqué) RATE_LIMIT_EXCEEDED429 Limite de requêtes dépassée GATEWAY_TIMEOUT504 Le service en amont n'a pas répondu à temps SERVICE_UNAVAILABLE503 Service en amont temporairement indisponible INTERNAL_SERVER_ERROR500 Erreur interne inattendue
Les erreurs ci-dessous sont des erreurs métier (voir format n° 2). À l'exception de invalid_phone et blocked_number (codes structurés), elles ne portent aucun code machine — seulement un statut HTTP et le texte exact du message indiqué ci-dessous. Basez votre logique sur le statut HTTP ; la chaîne du message est informative et peut changer.
Erreurs paiement - POST /v1/payments
HTTP Message / code Signification 400 Merchant not activeLe compte marchand n'est pas actif 400 blocked_number (code structuré)Le numéro du client (customer.phone) est bloqué 400 invalid_phone (code structuré)customer.phone n'est pas un numéro valide pour le country (indicatif ou longueur incorrects) — voir Format des numéros de téléphone 400 Operator '<x>' only supports channel REDIRECTUn canal autre que REDIRECT a été envoyé pour un opérateur REDIRECT uniquement (Wave, Orange Money SN, Opay, Palmpay) 404 Payment not foundToken de paiement introuvable
Erreurs annulation - POST /v1/payments/{token}/cancel
HTTP Message / code Signification 404 Payment not foundToken de paiement introuvable 400 Wrong merchantLe paiement n'appartient pas à ce marchand 400 Cannot cancelLe paiement est dans un statut terminal (success, failed, cancelled, expired)
Erreurs transfert - POST /v1/transfers
HTTP Message / code Signification 400 Not activeLe compte marchand n'est pas actif 400 blocked_number (code structuré)Le numéro du bénéficiaire (recipient.phone) est bloqué 400 invalid_phone (code structuré)recipient.phone n'est pas un numéro valide pour le country (indicatif ou longueur incorrects) — voir Format des numéros de téléphone 400 Exceeds single limitLe montant dépasse la limite par transaction 400 Exceeds daily limitLimite cumulée journalière dépassée 400 Exceeds monthly limitLimite cumulée mensuelle dépassée 400 Count limit reachedNombre maximal de transferts journaliers atteint 404 Not foundToken de transfert introuvable
Erreurs remboursement - POST /v1/payments/{token}/refund
HTTP Message / code Signification 404 Payment not foundToken de paiement introuvable 400 Wrong merchantLe paiement n'appartient pas à ce marchand 400 Cannot refund payment in status "<x>"Seuls les paiements success (ou partially_refunded) peuvent être remboursés ; <x> est le statut actuel 400 Max refundable: <n> <currency>Le montant dépasse le montant remboursable restant (<n>)
Opérateurs par pays
Le champ operator est obligatoire. Utilisez le code unifié (minuscules) correspondant au pays et à l'opérateur ciblés. La combinaison operator + country détermine le fournisseur exact.
Côte d'Ivoire CI - XOF
Code Opérateur Flux Canal waveWave REDIRECT REDIRECT orangeOrange Money OTP direct OTP (+ otp) — ou REDIRECT momoMTN Mobile Money PUSH PUSH moovMoov Money REDIRECT REDIRECT (par défaut)
Sénégal SN - XOF
Code Opérateur Flux Canal orangeOrange Money REDIRECT REDIRECT (imposé) waveWave REDIRECT REDIRECT (imposé) freeFree Money OTP OTP (+ otp)
Nigeria NG - NGN
Code Opérateur Flux Canal opayOpay REDIRECT REDIRECT (imposé) palmpayPalmpay REDIRECT REDIRECT
Ghana GH - GHS
Code Opérateur Flux Canal momoMTN Mobile Money OTP / USSD OTP / REDIRECT vodafoneVodafone Cash OTP / USSD OTP / REDIRECT airteltigoAirtelTigo OTP / USSD OTP / REDIRECT
Botswana BW - BWP
Code Opérateur Flux Canal voucherVoucher (prépayé) VOUCHER PIN dans metadata.voucherPin
Kenya KE - KES
Code Opérateur Flux Canal mpesa (alias safaricom)M-Pesa (Safaricom) PUSH PUSH airtelAirtel Money PUSH PUSH
Tanzanie TZ - TZS
Code Opérateur Flux Canal mpesa (alias vodacom)M-Pesa (Vodacom) PUSH PUSH tigo (alias tigopesa)Tigo Pesa PUSH PUSH halopesa (alias halotel)Halopesa PUSH PUSH airtelAirtel Money PUSH PUSH
Ouganda UG - UGX
Code Opérateur Flux Canal momo (alias mtn)MTN Mobile Money PUSH PUSH airtelAirtel Money PUSH PUSH
Autres pays (Bénin, Cameroun, Gambie, Liberia, Sierra Leone) : consultez le tableau des pays. Les opérateurs Mobile Money classiques (MTN momo, Moov moov, Orange orange…) utilisent le flux Push (channel: "PUSH", par défaut) ; Wave utilise Redirect (imposé). La liste à jour pour votre compte est donnée par GET /v1/payment-methods.
Légende des codes unifiés
Code Opérateur momo (alias mtn)MTN Mobile Money orange (alias om)Orange Money moovMoov Money waveWave freeFree Money (Tigo) vodafoneVodafone Cash (Ghana) airteltigoAirtelTigo (Ghana) mpesa (alias safaricom)M-Pesa (Kenya ; Tanzanie via Vodacom) airtelAirtel Money (Kenya, Ouganda, Tanzanie) tigo (alias tigopesa)Tigo Pesa (Tanzanie) halopesa (alias halotel)Halopesa (Tanzanie) opayOpay (Nigeria) palmpayPalmpay (Nigeria) voucherVoucher — prépayé (Botswana)
Le code unifié est le même quel que soit le pays. Par exemple, orange désigne Orange Money aussi bien en Côte d'Ivoire qu'au Sénégal. C'est la combinaison operator + country qui détermine l'opérateur exact.
Canaux de paiement
Canal Description PUSHL'opérateur envoie une invite de validation au client, qui la valide sur son téléphone (par défaut) OTPLe client génère un code auprès de son opérateur (USSD) et vous l'envoyez dans le champ otp → le paiement est autorisé directement, sans page de redirection. Voir Flux de paiement USSDLe client compose un code USSD manuellement QRCODEPaiement par scan d'un QR code REDIRECTLa réponse de création contient redirectUrl = https://pay.afrigate.dev/{token}. Redirigez toujours le client vers cette page AfriGate ; elle le renvoie ensuite vers l'opérateur (Wave, Orange SN, Opay, Palmpay) DIRECTDébit direct (selon les accords avec l'opérateur)
Limites & délais
Délais d'expiration
Type Par défaut Paiement — flux REDIRECT 15 minutes Paiement — flux push / momo 30 minutes Transfert 10 minutes
Contraintes
Contrainte Valeur Montant minimum 1 unité de devise Longueur de la devise 3 caractères Longueur du pays 2 caractères Motif d'annulation/remboursement 500 caractères max Durée de vie de l'idempotence 24 heures Tentatives de webhook 6 max (1 initiale + 5 retries) Tolérance d'horodatage des webhooks 5 minutes
Référence Format des numéros de téléphone
customer.phone (paiement) et recipient.phone (transfert) sont validés par pays à la création : le numéro doit être un numéro mobile valide pour le country de la requête (indicatif + longueur exacte). Vous pouvez envoyer le format international E.164 (+225…, recommandé) ou le format national (01…) — dans les deux cas, le numéro doit appartenir au country (un numéro +233 envoyé avec country: "CI" est rejeté). Sinon : 400 invalid_phone.
Le tableau ci-dessous indique l'indicatif et le nombre de chiffres nationaux (la partie après l'indicatif) attendus par pays :
Pays Code Indicatif Chiffres (national) Exemple (E.164) Bénin BJ+229 10 +2290195123456 Botswana BW+267 8 +26771123456 Cameroun CM+237 9 +237671234567 Côte d'Ivoire CI+225 10 +2250123456789 Gambie GM+220 7 +2203012345 Ghana GH+233 9 +233231234567 Kenya KE+254 9 +254712123456 Liberia LR+231 9 +231770123456 Nigeria NG+234 10 +2348021234567 Sénégal SN+221 9 +221701234567 Sierra Leone SL+232 8 +23225123456 Tanzanie TZ+255 9 +255621234567 Ouganda UG+256 9 +256712345678
La validation s'appuie sur les règles de numérotation officielles de chaque pays (longueurs et préfixes mobiles) — un numéro trop court/long ou avec un mauvais indicatif est refusé avant tout débit ou routage, ce qui évite un échec silencieux côté opérateur.