Guide développeur

Intégrer X Money pas à pas

Encaissez Orange Money et Wave avec une seule API, sans gérer les spécificités de chaque provider. Ce guide explique le comment avec des exemples concrets ; la référence exhaustive des champs reste le Swagger. URL de base : https://api.xmoney.xamle.io · JSON · montants en francs CFA entiers (devise XOF) · authentification Authorization: Bearer <clé API>.

1. Les concepts en 1 minute

ConceptRôle
ProjetEspace qui regroupe des transactions (une boutique, une app…). Tout paiement appartient à un projet.
Clé APISecret xm_live_… qui authentifie votre serveur. Peut être limitée à un projet et à des scopes.
PaiementUne demande d'argent : le payeur la règle via une page de paiement hébergée par X Money.
PayoutUn envoi d'argent vers un numéro Orange Money / Wave.
WebhookNotification HTTP signée envoyée à votre serveur quand un événement survient. Optionnel.

Cycle de vie d'un paiement : pending → succeeded | failed | expired (→ refunded après remboursement). Un paiement expire 5 minutes après sa création.

2. Démarrer

Obtenir une clé API

Dashboard → Développeurs → + Créer une clé :

  • Projet : « Tous les projets », ou un projet précis — la clé ne pourra alors créer et lire que les transactions de ce projet (les autres répondent 404) ;
  • Scopes : read (lecture), payments (créer/gérer des encaissements), payouts (créer/gérer des décaissements).
⚠ Le secret n'est affiché qu'une seule fois. Stockez-le côté serveur (variable d'environnement), jamais dans du code front-end ni dans un dépôt git.

Premier appel

bash
curl https://api.xmoney.xamle.io/v1/projects \
  -H "Authorization: Bearer xm_live_..."

Toutes les erreurs ont le même format :

json
{ "error": { "code": "invalid-api-key", "message": "Clé API inconnue ou révoquée.", "doc_url": "…" } }

3. Encaisser un paiement

3.1 Le flux standard (checkout hébergé)

  1. 1Votre serveur crée un paiement → l'API renvoie une checkout_url ;
  2. 2Vous redirigez le client (ou lui envoyez le lien) ;
  3. 3Le client choisit Orange Money ou Wave et confirme dans son application ;
  4. 4X Money vous notifie (webhook) et/ou vous vérifiez le statut par l'API ;
  5. 5Le client est redirigé vers votre success_url / error_url si fournies.
bash — créer un paiement
curl -X POST https://api.xmoney.xamle.io/v1/payments \
  -H "Authorization: Bearer xm_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: commande-4521" \
  -d '{
    "provider": "auto",
    "amount": 25000,
    "currency": "XOF",
    "project_id": "prj_...",
    "merchant_reference": "CMD-4521",
    "customer": { "name": "Awa Diop", "phone": "+221771234567" },
    "success_url": "https://votre-site.sn/merci",
    "error_url": "https://votre-site.sn/echec",
    "metadata": { "commande": 4521 }
  }'
json — réponse (extrait)
{
  "id": "pay_01M2...",
  "status": "pending",
  "checkout_url": "https://api.xmoney.xamle.io/p/pay_01M2...",
  "amount": 25000,
  "expires_at": "2026-09-16T10:05:00.000Z"
}
ChampDescription
provider"auto" (recommandé : le payeur choisit sur la page), "orange_money" ou "wave" (imposé, initiation immédiate)
amountEntier ≥ 100. Omettez-le pour un montant libre : le payeur saisit lui-même le montant sur la page
customerFacultatif. Si absent, la page de paiement demande au payeur son nom et son numéro avant de payer
merchant_referenceVotre référence interne (facture, commande) — visible dans le dashboard et les webhooks
success_url / error_urlRedirection du payeur après verdict (?ref=<id> ou ?reason=<code>&ref=<id> ajoutés)
metadataObjet JSON libre, restitué tel quel
Idempotence : envoyez un header Idempotency-Key unique par opération métier. En cas de retry réseau, la même clé renvoie le paiement déjà créé au lieu d'en créer un second.

3.2 Suivre le statut

bash
# Lecture simple
curl https://api.xmoney.xamle.io/v1/payments/pay_01M2... \
  -H "Authorization: Bearer xm_live_..."

# Re-vérification active auprès du provider (si un payeur dit avoir payé)
curl -X POST https://api.xmoney.xamle.io/v1/payments/pay_01M2.../sync \
  -H "Authorization: Bearer xm_live_..."
✅ Règle d'or : ne livrez jamais un produit sur la foi d'une redirection navigateur. Vérifiez status === "succeeded" côté serveur (webhook ou GET) — une redirection peut être interrompue ou forgée.

3.3 Autres opérations

EndpointEffet
GET /v1/payments?status=…&project_id=…Liste paginée (starting_after pour la page suivante)
POST /v1/payments/:id/expireExpirer manuellement un paiement en attente
POST /v1/payments/:id/refundRembourser un paiement Wave réussi

4. Webhooks — être notifié

Optionnel mais recommandé en production. Sans webhook, vous pouvez interroger GET /v1/payments/:id — la plateforme suit de toute façon chaque paiement. Le webhook évite simplement le polling : X Money pousse l'événement vers votre serveur dès qu'il survient.

4.1 Configurer

Dashboard → Développeurs → Webhooks → ajoutez l'URL HTTPS de votre serveur. Le secret de signature est affiché une seule fois : stockez-le comme la clé API. Événements : payment.succeeded, payment.failed, payment.expired, refund.succeeded, payout.succeeded, payout.failed, payout.reversed, payout_batch.completed, settlement.created, settlement.completed.

4.2 Ce que vous recevez

Un POST sur votre URL, avec les headers X-Money-Event et X-Money-Signature: t=<timestamp>,v1=<hmac> :

json — corps de l'événement
{
  "id": "evt_01M2...",
  "type": "payment.succeeded",
  "created_at": "2026-09-16T10:03:12.000Z",
  "data": {
    "payment": {
      "id": "pay_01M2...",
      "project_id": "prj_...",
      "provider": "wave",
      "amount": 25000,
      "currency": "XOF",
      "status": "succeeded",
      "merchant_reference": "CMD-4521",
      "metadata": { "commande": 4521 },
      "settled_at": "2026-09-16T10:03:11.000Z"
    }
  }
}

4.3 Vérifier la signature (obligatoire)

La signature est un HMAC-SHA256 du corps brut préfixé du timestamp : v1 = hexa( HMAC_SHA256( secret, timestamp + corpsBrut ) ). Rejetez si le timestamp a plus de 5 minutes (anti-rejeu).

js — Node/Express (corps BRUT, pas l'objet re-sérialisé)
const crypto = require('crypto');

app.post('/webhooks/xmoney', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.header('X-Money-Signature') ?? '';
  const t = (header.match(/t=(\d+)/) ?? [])[1];
  const v1s = [...header.matchAll(/v1=([a-f0-9]+)/g)].map((m) => m[1]);

  if (!t || Math.abs(Date.now() / 1000 - Number(t)) > 300) return res.sendStatus(400);
  const expected = crypto.createHmac('sha256', process.env.XMONEY_WEBHOOK_SECRET)
    .update(`${t}${req.body}`).digest('hex');
  if (!v1s.some((v) => v.length === expected.length &&
        crypto.timingSafeEqual(Buffer.from(v), Buffer.from(expected)))) {
    return res.sendStatus(400);
  }

  const event = JSON.parse(req.body);
  // Traitez l'événement de façon IDEMPOTENTE (dédoublonnez sur event.id) :
  // les retries peuvent livrer deux fois le même événement.
  if (event.type === 'payment.succeeded') {
    // ex. marquer la commande event.data.payment.merchant_reference comme payée
  }
  res.sendStatus(200); // répondez 2xx en < 10 s ; traitez le lourd en asynchrone
});

4.4 Fiabilité

  • Réponse non-2xx ou timeout (10 s) → retries automatiques : 1 min, 5 min, 30 min, 2 h, 12 h, 24 h (6 tentatives) ;
  • Le dashboard (Développeurs → Logs) montre chaque livraison, permet de renvoyer un événement et d'envoyer un événement de test ;
  • Traitez les événements de façon idempotente (clé de dédoublonnage : id de l'événement).

5. Décaisser (payouts)

Nécessite le scope payouts. L'argent part de votre solde encaissé.

bash — créer un payout
curl -X POST https://api.xmoney.xamle.io/v1/payouts \
  -H "Authorization: Bearer xm_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: salaire-2026-09-fatou" \
  -d '{
    "provider": "wave",
    "amount": 150000,
    "currency": "XOF",
    "project_id": "prj_...",
    "recipient": { "mobile": "+221771234567", "name": "Fatou Ndiaye" },
    "payment_reason": "Salaire septembre",
    "merchant_reference": "SAL-2026-09-018"
  }'
  • recipient.mobile au format E.164 (+221…) ;
  • payment_reason : 40 caractères max (affiché sur le reçu Wave) ;
  • Statuts : pending_approval → processing → succeeded | failed (| reversed après annulation) ;
  • Validation à 4 yeux : au-delà du seuil configuré, le payout est créé en pending_approval et doit être approuvé dans le dashboard par un second utilisateur — votre intégration doit traiter ce statut comme normal ;
  • Plafonds : un dépassement du plafond par transaction ou journalier est refusé avec un code d'erreur explicite ;
  • Le solde est réservé dès la création ; en cas d'échec il est automatiquement libéré.

Envoi en masse : POST /v1/payout-batches avec jusqu'à 500 payouts ; suivez l'ensemble via GET /v1/payout-batches/:id et l'événement payout_batch.completed. Annulation : POST /v1/payouts/:id/reverse (Wave uniquement, fenêtre de 3 jours).

6. SDK mobile (Flutter, Android, iOS)

Pour intégrer le checkout dans une application mobile, utilisez le SDK officiel plutôt qu'une WebView maison : il présente la page de paiement, ouvre les applications Wave / Orange Money / Max It via deeplink, suit le statut en temps réel et renvoie un résultat typé. Repo privé : github.com/Xamle-tech/xmoney-mobile-sdk (accès sur demande) — un seul repo, trois façons de consommer.

Votre appSDKInstallation
Flutter (Android + iOS)x_money_flutterdépendance pub par git, chemin flutter/x_money_flutter
Android natif (Kotlin/Java)android/xmoney-sdkmodule Gradle à inclure dans votre projet
iOS natif (Swift)XMoneySDKSwift Package Manager — ajoutez l'URL du repo dans Xcode

6.1 Le principe : la clé API reste sur votre serveur

Il n'existe pas de clé « publishable » : une app mobile distribuée au public ne doit jamais embarquer de clé xm_live_…. Le flux est donc :

  1. 1l'app appelle votre backend (ex. POST /api/commander) ;
  2. 2votre backend crée le paiement (POST /v1/payments, section 3) et renvoie checkout_url à l'app ;
  3. 3le SDK présente le checkout et rend un résultat : succeeded, failed (avec le code reason), cancelled ou dismissed (écran fermé) ;
  4. 4votre backend reste la source de vérité via le webhook payment.succeeded.

6.2 Configuration : les URLs de retour du SDK

Quand le paiement est destiné au mobile, passez ces deux URLs sentinelles lors de la création (elles n'ont pas besoin d'exister : le SDK intercepte la navigation avant tout chargement) :

json — dans le body de POST /v1/payments
{
  "success_url": "https://xmoney-sdk/return/success",
  "error_url":   "https://xmoney-sdk/return/error"
}

Côté app, déclarez les schemes des providers pour que les deeplinks s'ouvrent — Android 11+ (manifest) et iOS (Info.plist) :

xml — AndroidManifest.xml (app)
<queries>
  <intent><action android:name="android.intent.action.VIEW" />
    <data android:scheme="wave" /></intent>
  <intent><action android:name="android.intent.action.VIEW" />
    <data android:scheme="maxit" /></intent>
</queries>
xml — Info.plist (iOS)
<key>LSApplicationQueriesSchemes</key>
<array>
  <string>wave</string>
  <string>maxit</string>
</array>

6.3 Présenter le checkout

dart — Flutter
import 'package:x_money_flutter/x_money_flutter.dart';

// checkoutUrl = renvoyé par votre backend
final result = await XMoneyCheckout.start(checkoutUrl: checkoutUrl);
switch (result.outcome) {
  case XMoneyCheckoutOutcome.succeeded:
    // UX de succès — la livraison réelle attend le webhook backend
  case XMoneyCheckoutOutcome.failed:
    // result.reason : 'expired', 'insufficient-funds', 'payer-cancelled'…
  case XMoneyCheckoutOutcome.cancelled:
    // annulé par le payeur
  case XMoneyCheckoutOutcome.dismissed:
    // écran fermé — le paiement peut encore aboutir (webhook)
}

En natif, même contrat : XMoneyCheckoutContract (Android, registerForActivityResult) et XMoneyCheckout.present(from:…) (iOS) — exemples complets dans les README du repo. Un paiement expire au bout de 5 minutes (reason = "expired").

✅ La règle d'or de la section 3 s'applique aussi au mobile : le résultat du SDK sert à l'expérience utilisateur, la livraison dépend du webhook (ou d'un GET /v1/payments/:id) côté serveur.

6.4 Checkout 100 % natif (sans page hébergée)

Créez le paiement avec un provider explicite ("wave" ou "orange_money") : la réponse contient wave_launch_url, deeplinks et qr_code. Construisez votre propre UI, ouvrez le deeplink, puis attendez le verdict avec le client de statut public du SDK (XMoneyStatusClient.pollUntilTerminal(checkoutUrl) — aucun secret requis, cadence 2,5 s).

6.5 Payouts depuis une app interne (avancé)

Le SDK inclut aussi un client de l'API authentifiée (XMoneyApi en Dart, XMoneyApiClient en Kotlin/Swift) : payouts, soldes des deux poches, paiements.

⚠ Réservé aux applications internes de confiance (app terrain, back-office mobile) — jamais une app publiée en store grand public : une clé embarquée peut être extraite du binaire. Utilisez une clé dédiée au scope minimal, restreinte à un projet, avec plafonds et validation à 4 yeux activés.
dart — payout depuis une app interne
const api = XMoneyApi(baseUrl: 'https://api.xmoney.xamle.io', apiKey: cleInterne);

final payout = await api.createPayout(
  provider: 'wave',
  amount: 50000,
  recipient: const XMoneyPayoutRecipient(mobile: '+221771234567', name: 'Fatou Ndiaye'),
  projectId: 'prj_...',
  idempotencyKey: 'sal-2026-09-001', // OBLIGATOIRE — rejouez LA MÊME clé après un timeout
);
// payout.status : succeeded, pendingApproval (4 yeux → dashboard),
// processing (re-vérifier via api.syncPayout) ou failed (payout.errorCode)

Les règles de la section 5 s'appliquent telles quelles : pending_approval se débloque uniquement dans le dashboard, et les refus 422 (solde, plafonds) ne doivent pas être retentés.

7. Projets, soldes et rapports

EndpointDescription
GET /v1/projectsVos projets (une clé limitée à un projet ne voit que lui)
GET /v1/balances?project_id=…Soldes par provider (encaissé − commissions − décaissé)
GET /v1/settlementsHistorique des reversements vers votre compte
GET /v1/reports/summary?from=…&to=…Agrégats (volumes, taux de réussite)

8. Bonnes pratiques & sécurité

  • La clé API ne quitte jamais votre serveur — jamais dans une app mobile distribuée au public ni du JS navigateur. Le front appelle votre backend, qui appelle X Money (seule exception encadrée : une app interne de confiance, voir section 6.5) ;
  • Une clé par usage : par environnement (test/prod), par projet si pertinent, scopes minimaux (une clé de site e-commerce n'a pas besoin de payouts) ;
  • Allowlist IP : vous pouvez restreindre chaque clé aux IP de vos serveurs ;
  • Révocation : en cas de doute, révoquez la clé dans le dashboard (effet immédiat) et créez-en une nouvelle ;
  • Idempotency-Key partout sur les POST qui créent de l'argent ;
  • Montant côté serveur : recalculez toujours le montant vous-même, ne faites jamais confiance à un montant envoyé par le navigateur.

9. Checklist avant la mise en production

  1. 1Intégration validée avec un premier paiement réel de faible montant (ex. 100 F), remboursé ensuite si besoin ;
  2. 2Webhook configuré + signature vérifiée + traitement idempotent (si intégration serveur) ;
  3. 3La livraison du produit dépend du statut succeeded vérifié côté serveur ;
  4. 4Clés stockées en variables d'environnement, scopes minimaux, allowlist IP ;
  5. 5Un canal d'alerte interne si un webhook échoue (les retries couvrent 24 h).

Guide côté back-office : guide utilisateur.