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
https://x-pay.devFormat : 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é (aucunfee_amountn'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
Créez votre compte XPay
Inscrivez-vous sur x-pay.dev et confirmez votre email.
- 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
Configurez votre URL webhook
Ajoutez l'URL publique qui recevra les notifications de paiement (HTTPS obligatoire).
- 4
Créez une demande de paiement
Appelez POST /api/public/payments avec le montant et la devise (voir ci-dessous).
- 5
Redirigez votre client
Ouvrez la payment_url retournée dans un nouvel onglet ou une iframe.
- 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).
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" }'Créer un paiement
Paramètres
| Champ | Type | Requis | Description |
|---|---|---|---|
| amount | integer | requis | Montant en centimes (min : 1) |
| currency | string | requis | USD ou CDF |
| description | string | optionnel | Libellé affiché au client (max 200 car.) |
| customer_reference | string | optionnel | Votre identifiant interne (commande, user id…). Renvoyé dans le webhook. |
| customer_email | string | optionnel | Email du client (pré-remplissage) |
| webhook_url | string | optionnel | Surcharge de l'URL webhook globale, pour ce paiement uniquement. |
| metadata | object | optionnel | Objet JSON libre renvoyé tel quel dans le webhook. |
Réponse (201)
{
"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
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é :
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 :
{
"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"
}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
POST /votre/endpoint HTTP/1.1
Host: votre-domaine.com
Content-Type: application/json
X-XPay-Event: payment.successPayload (body)
{
"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"
}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
| Champ | Type | Requis | Description |
|---|---|---|---|
| event | string | Toujours payment.success en v1 (payment.failed prévu) | |
| status | string | success (v1 n'envoie pas encore failed) | |
| order_id | string | Identifiant XPay unique — clé d'idempotence | |
| transaction_id | string (uuid) | UUID interne XPay de la transaction | |
| customer_reference | string | null | Votre référence passée à la création | |
| amount | integer | Montant facturé au client, en centimes de la devise d'origine | |
| currency | string | Devise facturée (USD ou CDF) | |
| settled_amount | integer | Montant réellement crédité au wallet, en centimes USD | |
| settled_currency | string | Toujours USD en v1 (devise de règlement XPay) | |
| exchange_rate | number | null | Taux USD→CDF appliqué si currency=CDF ; null si currency=USD | |
| metadata | object | null | Objet JSON libre transmis à la création | |
| confirmed_at | string | Timestamp 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/1.1 200 OK
Content-Type: application/json
{ "received": true }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)
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
amountetcurrencyreç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 :
{ "error": { "code": "invalid_api_key", "message": "API key invalide ou révoquée" } }| Code | HTTP | Description |
|---|---|---|
| missing_api_key | 401 | En-tête X-API-Key absent |
| invalid_api_key | 401 | Clé inconnue ou révoquée |
| invalid_json | 400 | Body JSON malformé |
| invalid_payload | 400 | Paramètre requis manquant ou invalide |
| invalid_amount | 400 | Le montant doit être un entier positif |
| invalid_currency | 400 | Seuls USD et CDF sont supportés |
| checkout_url_failed | 500 | Impossible d'initialiser le paiement |
| internal_error | 500 | Erreur interne — réessayer |
Exemples de code
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
- Effectuez un premier paiement d'un petit montant (ex. 100 CDF) depuis votre app.
- Vérifiez dans votre tableau de bord XPay que la transaction apparaît en statut Réussi.
- Contrôlez la réception du webhook côté serveur (logs) — la livraison doit être en 200 OK.
- Confirmez que votre logique métier (activation de compte, réduction, livraison…) s'est bien exécutée.
- En cas d'échec de webhook, XPay le rejoue automatiquement. Vous pouvez aussi rejouer manuellement depuis le dashboard.
Toute évolution rétrocompatible est ajoutée sans changement de version.