EN FR
Nous contacter
Démarrage

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.

Parcours d'intégration

GET /v1/payment-methods Étape 1 — en premier

Ré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 2

Cré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 3

Recevez le résultat final (payment.completed) sur votre callbackUrl, puis confirmez via GET /v1/payments/{token}.

Flux de paiement

Schéma du flux
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

Schéma du flux
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 :

PlateformeClé publiqueClé privée
Afrigatepk_live_...sk_live_...
AWSAccess Key IDSecret Access Key
OAuth 2.0client_idclient_secret
Stripepk_live_...sk_live_...
Clé publique (pk_)Clé privée (sk_)
RôleIdentifie votre compte marchandProuve que vous êtes le propriétaire du compte
AnalogieNom d'utilisateurMot de passe
Sensible ?Non, peut être connue de tiersOui, strictement confidentielle
StockageVariable d'environnement back-end (acceptable)Gestionnaire de secrets uniquement
Affichée dans le tableau de bord ?À tout momentUne 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 .env non 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 :

Header
Authorization: Bearer {publicKey}:{privateKey}

Exemple complet :

Header
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.

EnvironnementClé publiqueClé privéeUsage
Productionpk_live_...sk_live_...Transactions réelles, mouvements de fonds réels
Sandboxpk_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

  1. Connectez-vous au tableau de bord.
  2. Effectuez une rotation de la clé compromise — l'ancienne clé privée est révoquée immédiatement.
  3. Mettez à jour la nouvelle paire dans vos back-ends.
  4. Vérifiez votre historique de transactions sur la période suspecte (paiements ou transferts illégitimes).

Permissions

PermissionDescription
payment:readConsulter les paiements
payment:writeCréer/annuler des paiements
transfer:readConsulter les transferts
transfer:writeCré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 :

HeaderDescription
X-Merchant-IDVotre identifiant marchand (UUID)
X-Merchant-CodeVotre code marchand
X-Key-Typelive ou test
X-Request-IDIdentifiant unique de la requête

Environnements

EnvironnementURL de base
Productionhttps://prod.afrigate.dev
Sandboxhttps://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.

Paiements

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.

GET/v1/payment-methods

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

HeaderRequisDescription
AuthorizationRequisBearer {publicKey} — votre pk_… seule (sans :sk_)
Content-TypeOptionnelapplication/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

ChampDescription
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, …).

Référence

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.

PaysCodeDeviseOpérateurs
BéninBJXOFMoov, MTN
BotswanaBWBWPVoucher
CamerounCMXAFMTN, Orange Money
Côte d'IvoireCIXOFOrange Money, Wave, Moov, MTN
GambieGMGMDWave, Afrimoney, Qmoney
GhanaGHGHSMTN, Vodafone, AirtelTigo
KenyaKEKESM-Pesa, Airtel
LiberiaLRLRDMTN, Orange Money, Lonestar
NigeriaNGNGNOpay, Palmpay
OugandaUGUGXMTN, Airtel
SénégalSNXOFOrange Money, Wave, Free Money
Sierra LeoneSLSLEAfrimoney, Orange Money
TanzanieTZTZSHalopesa, 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 / paysFluxCe que VOUS devez faire
Wave — SN, GM, CI, SLRedirectchannel: "REDIRECT" → redirigez le client vers redirectUrl (https://pay.afrigate.dev/{token})
Orange Money — CIOTP directLe client compose #144*82#, vous communique le code → envoyez-le dans otp avec channel: "OTP"
Orange Money — SNRedirectchannel: "REDIRECT" (imposé)
Botswana (Voucher)VoucherLe client achète un voucher et vous communique son PIN → envoyez-le dans metadata.voucherPin
Opay / Palmpay — NGRedirectchannel: "REDIRECT" → redirigez vers redirectUrl
MTN (MoMo), Moov, M-Pesa, Tigo Pesa, Halopesa, AirtelPush / STKchannel: "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

HeaderRequisDescription
AuthorizationRequisBearer {keyId}:{secret} — voir Authentification
X-Idempotency-KeyRequisUUID unique pour éviter les doublons
Content-TypeRequisapplication/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 Requis

Montant 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 Requis

Code devise ISO 4217 (ex. XOF, XAF, GHS)

paymentMethod string Requis

Méthode de paiement (ex. MOBILE_MONEY)

operator string Requis

Code opérateur unifié. Voir le tableau des opérateurs

country string Requis

Code pays ISO 3166-1 alpha-2 (ex. CI, SN, GH)

customer.phone string Requis

Numé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 Optionnel

Nom du client

customer.email string Optionnel

E-mail du client

successUrl string Requis

URL de redirection après un paiement réussi

failedUrl string Requis

URL de redirection après un échec

callbackUrl string Requis

URL de réception des webhooks

merchantTransactionId string Optionnel

Votre référence interne

feeBearer string Optionnel

Qui paie les frais : merchant (par défaut) ou customer

channel string Optionnel

PUSH (par défaut), OTP, USSD, QRCODE, REDIRECT, DIRECT. Voir Flux de paiement

otp string Optionnel

Code 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 Optionnel

Donné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ètreTypeDescription
statusstringFiltrer par statut
fromstringDate de début (ISO 8601)
tostringDate de fin (ISO 8601)
limitnumberNombre de résultats (par défaut : 20)
offsetnumberDé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)
StatutDescriptionTerminal
initiatedPaiement créé, en attente de traitementNon
pendingEn cours de routage vers l'opérateurNon
processingL'opérateur traite la transactionNon
successPaiement réussiOui
failedPaiement échouéOui
cancelledAnnulé par le marchandOui
expiredDélai dépassé (15 min pour les flux REDIRECT, 30 min pour les flux push/momo)Oui
refundedRemboursé en totalitéOui
partially_refundedRemboursé partiellementOui
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érateuroperatorcountryNote
WavewaveSN, CI, GM, SLREDIRECT imposé
Orange Money SénégalorangeSNREDIRECT imposé
OpayopayNGREDIRECT imposé
PalmpaypalmpayNGREDIRECT 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 :

  1. Le client compose sur son téléphone : #144*82#.
  2. Il reçoit un code de paiement (OTP) par SMS.
  3. Il vous communique ce code sur votre interface (page de paiement, application, TPE…).
  4. 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érateuroperatorcountryFluxchannelLe client…
WavewaveSN, CI, GM, SLRedirectREDIRECT (imposé dans tous les pays)est redirigé vers redirectUrl
Orange MoneyorangeSNRedirectREDIRECT (imposé)est redirigé vers redirectUrl
OpayopayNGRedirectREDIRECT (imposé)est redirigé vers redirectUrl
PalmpaypalmpayNGRedirectREDIRECTest redirigé vers redirectUrl
Orange MoneyorangeCIOTP directOTP + champ otpcompose #144*82#, vous communique le code
MTN MoMomomoCI, …PushPUSH (par défaut)valide l'invite avec son PIN
MoovmoovCI, BJPushPUSH (par défaut)valide l'invite avec son PIN
VouchervoucherBWVoucher— (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 Requis

Montant (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 Requis

Code devise ISO 4217

operator string Requis

Code opérateur unifié. Voir le tableau des opérateurs

country string Requis

Code pays ISO 3166-1 alpha-2

recipient.phone string Requis

Numé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 Optionnel

Nom du bénéficiaire

callbackUrl string Optionnel

URL du webhook

metadata object Optionnel

Données supplémentaires

Cycle de vie d'un transfert

StatutDescriptionTerminal
initiatedTransfert crééNon
pendingEn cours de routageNon
processingL'opérateur traite la transactionNon
successFonds envoyés au bénéficiaireOui
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 Requis

Montant à rembourser

currency string Requis

Devise (doit correspondre à celle du paiement)

refundType string Optionnel

full ou partial (détecté automatiquement si omis)

reason string Optionnel

Motif 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
StatutDescription
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énementDé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

HeaderDescription
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) :

TentativeDélai avant la tentativeCumulé
1 (initiale)Immédiat0 s
2 (retry 1)2 s2 s
3 (retry 2)4 s6 s
4 (retry 3)8 s14 s
5 (retry 4)16 s30 s
6 (retry 5)32 s62 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énarioComportement
Nouvelle cléTransaction créée normalement
Clé existanteRé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 :

EnvironnementLimiteFenêtre
Production2 500 requêtes60 secondes
Sandbox500 requêtes60 secondes

Headers de réponse

HeaderDescription
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.

PaysCodeNuméro succèsNuméro échec
SénégalSN+221700000001+221700000002
Côte d'IvoireCI+225700000001+225700000002
BéninBJ+229700000001+229700000002
NigeriaNG+234700000001+234700000002
GhanaGH+233700000001+233700000002
BotswanaBW+267700000001+267700000002
CamerounCM+237700000001+237700000002
GambieGM+220700000001+220700000002
KenyaKE+254700000001+254700000002
LiberiaLR+231700000001+231700000002
Sierra LeoneSL+232700000001+232700000002
TanzanieTZ+255700000001+255700000002
OugandaUG+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).

Payscountrycurrencyoperator (ex.)channelcustomer.phone (succès)
SénégalSNXOFwaveREDIRECT+221700000001
Côte d'IvoireCIXOFmomoPUSH+225700000001
BéninBJXOFmoovPUSH+229700000001
NigeriaNGNGNopayREDIRECT+234700000001
GhanaGHGHSmomoPUSH+233700000001
BotswanaBWBWPvoucherPUSH *+267700000001
CamerounCMXAFmomoPUSH+237700000001
GambieGMGMDwaveREDIRECT+220700000001
KenyaKEKESmpesaPUSH+254700000001
LiberiaLRLRDmomoPUSH+231700000001
Sierra LeoneSLSLEorangePUSH+232700000001
TanzanieTZTZSmpesaPUSH+255700000001
OugandaUGUGXmomoPUSH+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.

CodeHTTPDescription
MISSING_AUTH401Header Authorization manquant
INVALID_API_KEY401Clé API invalide ou expirée
IP_NOT_ALLOWED403IP de la requête absente de la liste blanche du marchand
PERMISSION_DENIED403La clé ne dispose pas de la permission requise (défini mais pas encore appliqué)
RATE_LIMIT_EXCEEDED429Limite de requêtes dépassée
GATEWAY_TIMEOUT504Le service en amont n'a pas répondu à temps
SERVICE_UNAVAILABLE503Service en amont temporairement indisponible
INTERNAL_SERVER_ERROR500Erreur 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

HTTPMessage / codeSignification
400Merchant not activeLe compte marchand n'est pas actif
400blocked_number (code structuré)Le numéro du client (customer.phone) est bloqué
400invalid_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
400Operator '<x>' only supports channel REDIRECTUn canal autre que REDIRECT a été envoyé pour un opérateur REDIRECT uniquement (Wave, Orange Money SN, Opay, Palmpay)
404Payment not foundToken de paiement introuvable

Erreurs annulation - POST /v1/payments/{token}/cancel

HTTPMessage / codeSignification
404Payment not foundToken de paiement introuvable
400Wrong merchantLe paiement n'appartient pas à ce marchand
400Cannot cancelLe paiement est dans un statut terminal (success, failed, cancelled, expired)

Erreurs transfert - POST /v1/transfers

HTTPMessage / codeSignification
400Not activeLe compte marchand n'est pas actif
400blocked_number (code structuré)Le numéro du bénéficiaire (recipient.phone) est bloqué
400invalid_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
400Exceeds single limitLe montant dépasse la limite par transaction
400Exceeds daily limitLimite cumulée journalière dépassée
400Exceeds monthly limitLimite cumulée mensuelle dépassée
400Count limit reachedNombre maximal de transferts journaliers atteint
404Not foundToken de transfert introuvable

Erreurs remboursement - POST /v1/payments/{token}/refund

HTTPMessage / codeSignification
404Payment not foundToken de paiement introuvable
400Wrong merchantLe paiement n'appartient pas à ce marchand
400Cannot refund payment in status "<x>"Seuls les paiements success (ou partially_refunded) peuvent être remboursés ; <x> est le statut actuel
400Max 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

CodeOpérateurFluxCanal
waveWaveREDIRECTREDIRECT
orangeOrange MoneyOTP directOTP (+ otp) — ou REDIRECT
momoMTN Mobile MoneyPUSHPUSH
moovMoov MoneyREDIRECTREDIRECT (par défaut)

Sénégal SN - XOF

CodeOpérateurFluxCanal
orangeOrange MoneyREDIRECTREDIRECT (imposé)
waveWaveREDIRECTREDIRECT (imposé)
freeFree MoneyOTPOTP (+ otp)

Nigeria NG - NGN

CodeOpérateurFluxCanal
opayOpayREDIRECTREDIRECT (imposé)
palmpayPalmpayREDIRECTREDIRECT

Ghana GH - GHS

CodeOpérateurFluxCanal
momoMTN Mobile MoneyOTP / USSDOTP / REDIRECT
vodafoneVodafone CashOTP / USSDOTP / REDIRECT
airteltigoAirtelTigoOTP / USSDOTP / REDIRECT

Botswana BW - BWP

CodeOpérateurFluxCanal
voucherVoucher (prépayé)VOUCHERPIN dans metadata.voucherPin

Kenya KE - KES

CodeOpérateurFluxCanal
mpesa (alias safaricom)M-Pesa (Safaricom)PUSHPUSH
airtelAirtel MoneyPUSHPUSH

Tanzanie TZ - TZS

CodeOpérateurFluxCanal
mpesa (alias vodacom)M-Pesa (Vodacom)PUSHPUSH
tigo (alias tigopesa)Tigo PesaPUSHPUSH
halopesa (alias halotel)HalopesaPUSHPUSH
airtelAirtel MoneyPUSHPUSH

Ouganda UG - UGX

CodeOpérateurFluxCanal
momo (alias mtn)MTN Mobile MoneyPUSHPUSH
airtelAirtel MoneyPUSHPUSH

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

CodeOpé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

CanalDescription
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

TypePar défaut
Paiement — flux REDIRECT15 minutes
Paiement — flux push / momo30 minutes
Transfert10 minutes

Contraintes

ContrainteValeur
Montant minimum1 unité de devise
Longueur de la devise3 caractères
Longueur du pays2 caractères
Motif d'annulation/remboursement500 caractères max
Durée de vie de l'idempotence24 heures
Tentatives de webhook6 max (1 initiale + 5 retries)
Tolérance d'horodatage des webhooks5 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 :

PaysCodeIndicatifChiffres (national)Exemple (E.164)
BéninBJ+22910+2290195123456
BotswanaBW+2678+26771123456
CamerounCM+2379+237671234567
Côte d'IvoireCI+22510+2250123456789
GambieGM+2207+2203012345
GhanaGH+2339+233231234567
KenyaKE+2549+254712123456
LiberiaLR+2319+231770123456
NigeriaNG+23410+2348021234567
SénégalSN+2219+221701234567
Sierra LeoneSL+2328+23225123456
TanzanieTZ+2559+255621234567
OugandaUG+2569+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.