Retour
Documentation XPay
API v1 — Production

Intégrez les paiements XPay en quelques minutes

L'API XPay permet à votre application d'accepter des paiements en USD et CDF via Mobile Money et carte, avec un checkout hébergé sécurisé et des webhooks en temps réel. En V1, la devise de règlement marchand est toujours le USD.

Introduction

XPay est une plateforme de paiement pour les marchands d'Afrique centrale. Elle expose une API REST simple qui vous permet de :

  • Créer des demandes de paiement en USD ou CDF côté client
  • Rediriger vos clients vers une page de checkout sécurisée hébergée par XPay
  • Recevoir une notification (webhook) HTTP dès qu'un paiement est confirmé
  • Poller le statut d'une transaction à tout moment via un endpoint public
  • Consulter les soldes et l'historique des transactions dans votre tableau de bord
Base URL de production : https://x-pay.dev
Format : toutes les requêtes et réponses sont en JSON UTF-8. Les montants sont exprimés en centimes — 1 USD = 100, 1 CDF = 100.
Devises supportées : USD, CDF.
Règlement marchand : toujours USD. Un paiement CDF est converti au taux du jour et crédite uniquement le wallet USD.
Fuseau horaire : tous les timestamps sont en UTC au format ISO 8601.

Règle d'or des wallets

Seul le wallet USD est crédité, quelle que soit la devise payée par le client.

Votre client peut être facturé en USD ou en CDF. Dans les deux cas, XPay règle le paiement en USD uniquement, en convertissant le montant au taux du jour lorsque c'est nécessaire.

  • Wallet USD : crédité par tous les paiements confirmés, en dollars. C'est votre solde réel de règlement.
  • Wallet CDF : jamais crédité par un paiement provenant d'une intégration API ou d'un checkout. Il ne sert que pour les conversions et retraits manuels gérés directement par XPay.
  • Conversion automatique : un paiement de 5 000 CDF est collecté en CDF côté client, puis converti au taux du jour et crédité en USD sur votre wallet.

Dans le webhook de confirmation, les champs amount et currency indiquent ce qui a été facturé au client. Les champs settled_amount et settled_currency représentent ce qui a été réellement crédité sur votre wallet USD — c'est ce que vous devez utiliser pour votre comptabilité et la livraison de votre service.

Politique tarifaire XPay

  • Dépôts : 0 % de frais. Le montant réglé est crédité intégralement sur votre wallet USD. Minimum 1 USD (ou équivalent CDF).
  • Retraits : 15 % de frais. Minimum 80 USD ou 5 500 FC par demande.
  • Pour un dépôt, le webhook renvoie settled_amount — c'est exactement ce qui est crédité (aucun fee_amount n'est déduit en v1).
  • La politique tarifaire est gérée depuis l'administration XPay ; les frais de dépôt sont verrouillés à 0 %.

Démarrage rapide

  1. 1

    Créez votre compte XPay

    Inscrivez-vous sur x-pay.dev et confirmez votre email.

  2. 2

    Générez une clé API

    Rendez-vous dans Plus → Intégrations API et cliquez sur Générer une clé. Copiez-la immédiatement, elle ne sera plus jamais affichée.

  3. 3

    Configurez votre URL webhook

    Ajoutez l'URL publique qui recevra les notifications de paiement (HTTPS obligatoire).

  4. 4

    Créez une demande de paiement

    Appelez POST /api/public/payments avec le montant et la devise (voir ci-dessous).

  5. 5

    Redirigez votre client

    Ouvrez la payment_url retournée dans un nouvel onglet ou une iframe.

  6. 6

    Traitez le webhook

    À la confirmation, XPay envoie un POST à votre URL avec le statut et le customer_reference.

Authentification

Toutes les requêtes API doivent inclure votre clé secrète dans l'en-tête HTTP X-API-Key (ou Authorization: Bearer <clé>).

Format de clé : xpk_live_... (32 caractères après le préfixe).

bash
curl https://x-pay.dev/api/public/payments \
  -H "X-API-Key: xpk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 500, "currency": "USD" }'
Ne jamais exposer votre clé dans un client (mobile, navigateur). Les appels doivent toujours partir de votre backend.

Créer un paiement

POSThttps://x-pay.dev/api/public/payments

Paramètres

ChampTypeRequisDescription
amountintegerrequisMontant en centimes (min : 1)
currencystringrequisUSD ou CDF
descriptionstringoptionnelLibellé affiché au client (max 200 car.)
customer_referencestringoptionnelVotre identifiant interne (commande, user id…). Renvoyé dans le webhook.
customer_emailstringoptionnelEmail du client (pré-remplissage)
webhook_urlstringoptionnelSurcharge de l'URL webhook globale, pour ce paiement uniquement.
metadataobjectoptionnelObjet JSON libre renvoyé tel quel dans le webhook.

Réponse (201)

json
{
  "order_id": "xpay_lz3k9x_a1b2c3d4",
  "transaction_id": "9f0e...",
  "status": "pending",
  "payment_url": "https://x-pay.dev/checkout/xpay_lz3k9x_a1b2c3d4",
  "amount": 500,
  "currency": "USD"
}

Exemple

bash
curl -X POST https://x-pay.dev/api/public/payments \
  -H "X-API-Key: xpk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 1500,
    "currency": "USD",
    "description": "Commande #1042",
    "customer_reference": "ORDER-1042",
    "customer_email": "client@example.com",
    "metadata": { "user_id": "u_123", "plan": "pro" }
  }'

Page de paiement

Ouvrez la payment_url reçue. Trois intégrations sont supportées :

Nouvel onglet

window.open(payment_url, '_blank'). L'onglet tente de se fermer automatiquement après succès (bloqué par certains navigateurs — prévoir un fallback).

Redirection

window.location.href = payment_url. Le client revient sur votre site via document.referrer ou return_url après paiement.

Iframe

Embarquez la page dans une iframe. XPay émet un postMessage à la page parente à la confirmation.

Détection côté client (postMessage)

Depuis l'iframe ou l'onglet ouvert, XPay envoie ce message dès qu'un paiement est confirmé :

js
window.addEventListener("message", (event) => {
  if (event.origin !== "https://x-pay.dev") return;
  const data = event.data;
  if (data && data.type === "xpay:payment" && data.status === "success") {
    // data.order_id contient l'identifiant de la transaction
    // Rafraîchissez votre UI / redirigez le client
  }
});

Vérifier le statut d'une transaction (polling)

Endpoint public non authentifié (ne renvoie aucune donnée sensible). Idéal en complément du webhook pour un affichage temps réel :

GEThttps://x-pay.dev/api/public/checkout-session/:order_id
json
{
  "order_id": "xpay_lz3k9x_a1b2c3d4",
  "status": "pending",           // pending | success | failed
  "amount": 1500,
  "currency": "USD",
  "checkout_url": "https://...", // null si status != pending
  "created_at": "2026-07-10T12:30:00.000Z"
}
Cet endpoint est sans authentification : n'y stockez jamais d'information sensible côté transaction. Il est conçu pour être appelé depuis un client web ou mobile.

Webhooks marchand

Dès qu'un paiement est confirmé, XPay envoie une requête POST JSON à l'URL webhook configurée. Votre endpoint doit répondre HTTP 2xx en moins de 8 s (timeout serveur XPay). Toute réponse hors 2xx ou hors délai est marquée failed.

En-têtes envoyés

http
POST /votre/endpoint HTTP/1.1
Host: votre-domaine.com
Content-Type: application/json
X-XPay-Event: payment.success

Payload (body)

json
{
  "event": "payment.success",
  "status": "success",
  "order_id": "xpay_lz3k9x_a1b2c3d4",
  "transaction_id": "9f0e2a10-7c8f-4b2e-9c1d-3a0f5e6d7b12",
  "customer_reference": "ORDER-1042",
  "amount": 50000,
  "currency": "CDF",
  "settled_amount": 22,
  "settled_currency": "USD",
  "exchange_rate": 2283.29,
  "metadata": { "user_id": "u_123" },
  "confirmed_at": "2026-07-10T12:34:56.000Z"
}
Règle métier XPay : tous les paiements sont réglés en USD. Quand vous facturez en CDF, XPay collecte le montant en CDF côté client, puis convertit au taux du jour et crédite uniquement votre wallet USD. Le wallet CDF n'est jamais crédité par un paiement d'intégration. Les champs settled_amount et settled_currency représentent ce qui a réellement été crédité — c'est ce que vous devez rapprocher côté comptabilité.

Champs du payload

ChampTypeRequisDescription
eventstringToujours payment.success en v1 (payment.failed prévu)
statusstringsuccess (v1 n'envoie pas encore failed)
order_idstringIdentifiant XPay unique — clé d'idempotence
transaction_idstring (uuid)UUID interne XPay de la transaction
customer_referencestring | nullVotre référence passée à la création
amountintegerMontant facturé au client, en centimes de la devise d'origine
currencystringDevise facturée (USD ou CDF)
settled_amountintegerMontant réellement crédité au wallet, en centimes USD
settled_currencystringToujours USD en v1 (devise de règlement XPay)
exchange_ratenumber | nullTaux USD→CDF appliqué si currency=CDF ; null si currency=USD
metadataobject | nullObjet JSON libre transmis à la création
confirmed_atstringTimestamp ISO 8601 UTC de la confirmation

Réponse attendue de votre serveur

Répondez avec un code HTTP 2xx (200, 201 ou 204). Le body est facultatif et ignoré par XPay. Toute réponse ≥ 300, une déconnexion ou un dépassement de délai marque la livraison comme failed — vous pouvez ensuite la rejouer manuellement depuis votre dashboard XPay.

http
HTTP/1.1 200 OK
Content-Type: application/json

{ "received": true }
Idempotence obligatoire. XPay peut rejouer un webhook (retry manuel, incident réseau). Utilisez order_id comme clé unique dans votre base et ignorez silencieusement (200 OK) tout événement déjà traité.

Retry & livraisons échouées

  • v1 : une tentative automatique à la confirmation. Si elle échoue, la livraison est stockée en base avec le code HTTP et la réponse renvoyés par votre serveur.
  • Rejeu manuel disponible depuis le dashboard XPay (Transactions → détail).
  • v2 (à venir) : retry automatique exponentiel (1 min, 5 min, 30 min, 2 h, 12 h) jusqu'à 24 h.

Exemple de handler (Node.js / Express)

js
app.post("/webhooks/xpay", express.json(), async (req, res) => {
  const {
    event,
    status,
    order_id,
    customer_reference,
    amount,
    currency,
    metadata,
  } = req.body;

  // 1. Filtrer l'événement
  if (event !== "payment.success" || status !== "success") {
    return res.status(200).end();
  }

  // 2. Idempotence : ignorer si déjà traité
  const already = await db.orders.findOne({ xpay_order_id: order_id });
  if (already?.paid) return res.status(200).end();

  // 3. Vérifier le montant attendu
  const order = await db.orders.findOne({ reference: customer_reference });
  if (!order || order.amount !== amount || order.currency !== currency) {
    return res.status(200).end(); // 200 pour ne pas déclencher un retry inutile
  }

  // 4. Livrer le service
  await db.orders.updateOne(
    { _id: order._id },
    { $set: { paid: true, xpay_order_id: order_id, paid_at: new Date() } },
  );

  // 5. Accuser réception
  return res.status(200).json({ received: true });
});

Sécurité

  • HTTPS obligatoire — l'URL webhook doit être servie en TLS.
  • Clés secrètes côté serveur uniquement — ne jamais intégrer xpk_live_* dans une app mobile ou dans du JS client.
  • Idempotence — implémentez la déduplication par order_id.
  • Vérifiez le montant — comparez toujours amount et currency reçus avec ce que votre client devait payer avant de livrer.
  • Rotation — en cas de compromission, régénérez une nouvelle clé depuis le dashboard ; l'ancienne est révoquée immédiatement.

Codes d'erreur

Toutes les erreurs suivent ce format :

json
{ "error": { "code": "invalid_api_key", "message": "API key invalide ou révoquée" } }
CodeHTTPDescription
missing_api_key401En-tête X-API-Key absent
invalid_api_key401Clé inconnue ou révoquée
invalid_json400Body JSON malformé
invalid_payload400Paramètre requis manquant ou invalide
invalid_amount400Le montant doit être un entier positif
invalid_currency400Seuls USD et CDF sont supportés
checkout_url_failed500Impossible d'initialiser le paiement
internal_error500Erreur interne — réessayer

Exemples de code

node
import fetch from "node-fetch";

const res = await fetch("https://x-pay.dev/api/public/payments", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.XPAY_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    amount: 1500,
    currency: "USD",
    description: "Commande #1042",
    customer_reference: "ORDER-1042",
  }),
});
const { payment_url } = await res.json();
return { redirect: payment_url };

Tests & Go-Live

  1. Effectuez un premier paiement d'un petit montant (ex. 100 CDF) depuis votre app.
  2. Vérifiez dans votre tableau de bord XPay que la transaction apparaît en statut Réussi.
  3. Contrôlez la réception du webhook côté serveur (logs) — la livraison doit être en 200 OK.
  4. Confirmez que votre logique métier (activation de compte, réduction, livraison…) s'est bien exécutée.
  5. En cas d'échec de webhook, XPay le rejoue automatiquement. Vous pouvez aussi rejouer manuellement depuis le dashboard.
Besoin d'aide ? Écrivez à notre assistant WhatsApp : +243 997 532 529
Documentation XPay v1 — mise à jour le 10 juillet 2026.
Toute évolution rétrocompatible est ajoutée sans changement de version.