Documentation développeurs

Intégrez la caisse C-Pay à votre site ou votre application

Une API REST, des webhooks signés, des SDK et une extension WooCommerce pour que vos ventes en ligne tombent dans la même caisse que le comptoir. Base : https://api.c-pay.global/api/v1.

Démarrage rapide Référence Swagger

Démarrage rapide

Périmètre de lancement : les six pays de la CEMAC et la RD Congo. Moyens prévus : cartes bancaires, opérateurs de monnaie mobile et GIMAC. Les fonctions sont disponibles selon le pays et le service activés ; la connexion GIMAC est en cours d’intégration. La détection d’un numéro ne garantit pas la disponibilité d’un paiement.

Trois étapes séparent votre compte de votre premier lien de paiement. Un compte de niveau 0 suffit pour intégrer ; la vérification KYB n'est nécessaire que pour relever vos limites.

  1. Créez un compte marchand

    Sur merchant.c-pay.global/register. Le portail marchand est aussi l'endroit où vous suivez les paiements reçus par l'API.

  2. Créez une clé API dans le portail

    Portail marchand → Clés API → nouvelle clé avec les permissions voulues (pay_links, transactions). La clé et son secret ne sont affichés qu'une fois : stockez-les côté serveur, jamais dans un navigateur ou une application mobile.

  3. Créez votre premier lien de paiement

    Trois appels cURL ci-dessous : vérifier la clé, créer le lien, retrouver le paiement dans vos transactions.

Votre premier lien de paiement en trois appels

# 1. Vérifier la clé : la liste (vide ou non) de vos transactions répond 200
curl https://api.c-pay.global/api/v1/merchant/public/transactions?limit=1 \
  -H "X-API-Key: $CPAY_API_KEY" \
  -H "X-API-Secret: $CPAY_API_SECRET"

# 2. Créer un lien de paiement de 25 000 XAF valable 48 h
curl -X POST https://api.c-pay.global/api/v1/merchant/public/pay-links \
  -H "X-API-Key: $CPAY_API_KEY" \
  -H "X-API-Secret: $CPAY_API_SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 25000, "currency": "XAF", "description": "Facture INV-001", "expiresIn": 48 }'
# → 201 { "short_url": "https://pay.c-pay.global/AB12CD", "expires_at": "…", … }
#   Envoyez short_url à votre client (SMS, WhatsApp, e-mail) ou affichez-le en QR.

# 3. Retrouver le paiement une fois le client passé par la page
curl "https://api.c-pay.global/api/v1/merchant/public/transactions?status=authorized&limit=5" \
  -H "X-API-Key: $CPAY_API_KEY" \
  -H "X-API-Secret: $CPAY_API_SECRET"

Pour être prévenu sans interroger l'API, configurez un webhook : C-Pay appelle votre serveur à chaque paiement confirmé ou échoué.

Authentification et conventions

Les endpoints /merchant/public/* s'authentifient par clé API. Les endpoints de l'API Caisse utilisent la session du marchand (jeton Bearer). Toutes les requêtes et réponses sont en JSON.

En-têtes HTTP attendus
En-têteObligatoireRôle
X-API-Keyoui (routes publiques)Clé créée dans le portail marchand. Absente ou inconnue : 401 API_KEY_REQUIRED / 401 INVALID_API_KEY.
X-API-Secretoui pour les clés récentesSecret remis avec la clé. Les clés d'ancienne génération n'en ont pas ; les nouvelles le vérifient (401 INVALID_API_SECRET).
Idempotency-Keyrecommandé sur tout POST, obligatoire sur les opérations de caisse8 à 128 caractères, par exemple un UUID v4. Rejouer la même clé avec le même corps renvoie la réponse initiale ; la même clé avec un corps différent renvoie 409 IDEMPOTENCY_CONFLICT ; une requête identique encore en cours renvoie 409 IDEMPOTENCY_IN_PROGRESS. Longueur invalide : 400 INVALID_IDEMPOTENCY_KEY. Les clés sont conservées 24 h.
Content-Typeoui sur POSTapplication/json.
AuthorizationAPI Caisse uniquementBearer <access>, jeton de 15 minutes obtenu par POST /merchant/auth/login et renouvelé par /merchant/auth/refresh.
Base URL
https://api.c-pay.global/api/v1
Montants
Les exemples du lancement utilisent des montants entiers en XAF ou en CDF. Aucune conversion de change : la devise du client doit être celle du marchand.
Erreurs
Corps { "error": "message lisible", "code": "CODE_STABLE", "details?": … }. Codez sur code, pas sur error. Liste dans Codes d'erreur.
Permissions
Chaque clé porte ses permissions (pay_links, transactions, collect, disburse). Permission manquante : 403.
Limites de débit
Les points d'entrée publics sont limités en débit ; 429 avec les en-têtes RateLimit-* standard. POST /detect/phone : 30 requêtes par minute et par adresse.
Sécurité
N'appelez l'API que depuis votre serveur. Une clé exposée dans un navigateur ou une application mobile doit être révoquée immédiatement dans le portail.

Référence des endpoints publics

Seuls les endpoints réellement en production figurent ici. La référence Swagger fait foi en cas d'écart.

GET /merchant/public/transactions

Historique paginé de vos transactions, les plus récentes d'abord. Permission transactions.

Paramètres de requête
ParamètreTypeDescription
statuschaîneFiltre sur le statut : pending, authorized, failed, expired.
pageentierPage à partir de 1. Défaut 1.
limitentierTaille de page, de 1 à 100. Défaut 20.
curl "https://api.c-pay.global/api/v1/merchant/public/transactions?status=authorized&page=1&limit=20" \
  -H "X-API-Key: $CPAY_API_KEY" \
  -H "X-API-Secret: $CPAY_API_SECRET"

Réponse 200 OK

{
  "data": [
    {
      "transaction_id": "550e8400-e29b-41d4-a716-446655440000",
      "type": "collect",
      "amount": 25000,
      "currency": "XAF",
      "status": "authorized",
      "payment_method": "mobile_money",
      "auth_code": "FT123456",
      "reference": "INV-001",
      "initiated_at": "2026-10-16T09:12:33.120Z"
    }
  ],
  "total": 1,
  "page": 1,
  "limit": 20
}

POST /detect/phone public, sans clé

Déduit le pays, la devise et l'opérateur Mobile Money d'un numéro au format international. Utile pour pré-afficher l'opérateur dans votre interface. Limité à 30 requêtes par minute et par adresse ; aucune transaction n'est créée.

curl -X POST https://api.c-pay.global/api/v1/detect/phone \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+237670123456" }'

Réponse 200 OK

{
  "success": true,
  "phone": "+237670123456",
  "country": "CM",
  "countryName": "Cameroun",
  "currency": "XAF",
  "region": "CEMAC",
  "operator": "mtn_momo",
  "brand": "MTN Mobile Money",
  "alternatives": [ { "operator": "express_union", "brand": "Express Union" } ]
}

Numéro non reconnu : 200 avec success: false et un code (PHONE_INVALID, COUNTRY_UNSUPPORTED, PHONE_LENGTH, OPERATOR_UNKNOWN, OPERATOR_NO_WALLET). Corps sans phone : 400 PHONE_REQUIRED. alternatives liste les services que le client choisit lui-même (par exemple Express Union dans le périmètre actuel).

POST /merchant/public/collect bientôt

Encaissement Mobile Money direct par clé API (demande de paiement poussée sur le téléphone du client). Corps prévu : amount, customerPhone, currency, description, reference. Cet endpoint répond aujourd'hui 410 FEATURE_DISABLED : il sera ouvert pays par pays au fil de l'activation des opérateurs. En attendant, les liens de paiement couvrent le même besoin, et l'API Caisse offre l'encaissement direct en session marchand.

API Caisse session marchand

Ces endpoints portent les opérations de caisse du portail marchand et de l'application Android : encaisser, retirer, transférer, suivre. Ils sont authentifiés par la session du marchand (Authorization: Bearer <access> obtenu via POST /merchant/auth/login, 15 minutes, renouvelé par /merchant/auth/refresh), pas par clé API. Ils conviennent à vos propres outils de caisse ; pour une intégration serveur, préférez les endpoints publics ci-dessus. Toute requête qui crée un mouvement exige Idempotency-Key.

Endpoints de l'API Caisse
EndpointRôle
GET /merchant/cash/summarySolde (available, pending, reserved), totaux du jour, limites et profil (niveau KYB, wallet, comptes bancaires).
POST /merchant/cash/collectEncaisser. amount, method (mobile_money ou card), customerPhone (requis en Mobile Money, opérateur détecté ; operator seulement pour sélectionner un service alternatif activé, par exemple Express Union), currency, description, reference. Mobile Money : 201 avec txnId, status: "pending", operator, fee, net, statusUrl. Carte : 201 avec payLink { shortId, shortUrl, expiresAt } si une passerelle carte est configurée, sinon 410 FEATURE_DISABLED.
GET /merchant/cash/collect/:txnIdStatut d'un encaissement : pending, authorized, failed, expired, avec amount, fee, net, receiptUrl si disponible. Interrogez toutes les 3 secondes pendant 90 secondes, puis affichez « en attente de confirmation ».
GET / PUT /merchant/cash/walletWallet Mobile Money de destination des retraits (operator, phone, verifiedAt). L'enregistrement exige confirm.
POST /merchant/cash/withdrawRetirer vers le wallet enregistré. amount, description, confirm. 201 : reference PAY-XXXXXX, status, fee, net. Erreurs : 422 INSUFFICIENT_BALANCE, 422 WALLET_MISSING, 422 DAILY_LIMIT_EXCEEDED, 428 STEP_UP_REQUIRED, 410 FEATURE_DISABLED.
POST /merchant/cash/transfersTransférer. direction wallet_to_bank (compte bancaire vérifié, confirm requis, référence TRF-XXXXXX, statut processing ou pending_manual) ou bank_to_wallet (ordre d'approvisionnement FND-XXXXXX, statut awaiting_funds, instructions de virement ; 409 FUNDING_UNAVAILABLE sans compte de collecte). Compléments : GET /merchant/cash/transfers, GET …/transfers/:id, POST …/transfers/:id/cancel.
GET /merchant/cash/historyJournal unifié (from, to, type ∈ collect, withdraw, transfer_out, transfer_in, limit, offset) avec totals { in, out }. Export : GET /merchant/cash/history/export.csv?from&to.

Confirmation forte : l'objet confirm

Les retraits, les transferts wallet_to_bank et l'enregistrement du wallet exigent un objet confirm dans le corps. Son contenu dépend de la configuration du compte :

Contenu de l'objet confirm
CompteCorps attendu
Double authentification (TOTP) activée"confirm": { "code": "123456" } — code à 6 chiffres de l'application d'authentification
Sans double authentification"confirm": { "password": "…" } — mot de passe du compte

Objet absent ou invalide : 428 STEP_UP_REQUIRED { "methods": ["totp"] } ou ["password"], qui indique la méthode à présenter. Cinq échecs en 15 minutes : 429. Un ordre bank_to_wallet n'exige pas de confirmation : aucun fonds ne quitte le compte.

# Retrait de 50 000 vers le wallet enregistré (compte avec 2FA)
curl -X POST https://api.c-pay.global/api/v1/merchant/cash/withdraw \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Idempotency-Key: 2c1f6a1e-5e0b-4f7c-9a3d-8b2e4c6d1f0a" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 50000, "description": "Caisse du soir", "confirm": { "code": "123456" } }'

# Transfert wallet -> banque (compte sans 2FA)
curl -X POST https://api.c-pay.global/api/v1/merchant/cash/transfers \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Idempotency-Key: 9d7e5c3a-1b2f-4e6d-8a0c-3f5b7d9e1a2c" \
  -H "Content-Type: application/json" \
  -d '{ "direction": "wallet_to_bank", "accountId": "<account_id>", "amount": 200000, "confirm": { "password": "…" } }'

Webhooks sortants

Déclarez une URL https:// dans le portail marchand (rubrique Webhooks) ; le secret de signature est affiché une seule fois à la création. À chaque changement de statut, C-Pay envoie un POST JSON signé en HMAC-SHA256. Les adresses privées ou non HTTPS sont refusées, les redirections ne sont pas suivies, le délai maximal est de 10 secondes et une réponse 5xx ou une erreur réseau déclenche jusqu'à 3 nouvelles tentatives (5 s puis 10 s).

En-têtes envoyés

Content-Type:      application/json
User-Agent:        CPay-Webhooks/1.0
X-CPay-Event:      payment.completed
X-CPay-Signature:  sha256=3f1a…c9        ← à vérifier
X-CPay-Timestamp:  2026-10-16T09:12:33.120Z   (informatif, non signé)
X-CPlus-Event:     payment.completed       (transition, sera retiré)
X-CPlus-Signature: sha256=3f1a…c9        (transition, sera retiré)

Les en-têtes X-CPlus-* sont conservés provisoirement pour les consommateurs existants et seront retirés au plus tôt 90 jours après annonce sur cette page. Nouvelles intégrations : n'utilisez que X-CPay-Signature.

Événements et corps

Événements du webhook marchand : payment.completed, payment.failed, webhook.test (même forme, txnId préfixé test-, envoyé par le bouton « Tester » du portail).

{
  "event": "payment.completed",
  "txnId": "550e8400-e29b-41d4-a716-446655440000",
  "reference": "INV-001",
  "amount": 25000,
  "currency": "XAF",
  "status": "authorized",
  "method": "mobile_money",
  "authCode": "FT123456",
  "timestamp": "2026-10-16T09:12:33.120Z"
}

Vérifier la signature

signature = "sha256=" + hex(HMAC-SHA256(secret, corps brut)). Calculez sur le corps exactement tel qu'il est reçu (octets, pas une re-sérialisation JSON), comparez en temps constant, répondez 2xx rapidement puis traitez en asynchrone. Un même événement peut être livré plusieurs fois : dédupliquez sur txnId.

const crypto = require('crypto');
app.post('/cpay/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const given = String(req.get('X-CPay-Signature') || '').replace(/^sha256=/, '');
  const expected = crypto.createHmac('sha256', process.env.CPAY_WEBHOOK_SECRET).update(req.body).digest('hex');
  if (given.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
    return res.status(401).end();
  }
  const event = JSON.parse(req.body);   // { event, txnId, reference, amount, currency, status, method, authCode, timestamp }
  res.status(200).end();                // puis traitement asynchrone, idempotent sur event.txnId
});

SDK & plugin

Les SDK encapsulent l'authentification, l'idempotence et la vérification des webhooks. Ils sont facultatifs : l'API s'utilise avec n'importe quel client HTTP.

SDK JavaScript / Node.js @c-pay/sdk 1.1.0

Pour Node.js 18+, côté serveur uniquement. Fournit CPayClient (liens de paiement, transactions) et CPayClient.verifyWebhook(), qui choisit le bon en-tête de signature pendant la transition.

npm install @c-pay/sdk

const { CPayClient } = require('@c-pay/sdk');
const cpay = new CPayClient({ apiKey: process.env.CPAY_API_KEY, currency: 'XAF' });
const link = await cpay.collection.payLink({ amount: 25000, description: 'Facture INV-001' });
console.log(link.short_url);

SDK PHP calculus-plus/cpay-sdk 1.1.0

PHP 7.4+, extensions cURL et JSON. Classe CPay\CPayClient (createPayLink, listTransactions, getTransaction) et exceptions CPayException portant le code de l'API.

composer require calculus-plus/cpay-sdk

use CPay\CPayClient;
$cpay = new CPayClient(getenv('CPAY_API_KEY'), ['currency' => 'XAF']);
$link = $cpay->createPayLink(25000, ['description' => 'Facture INV-001']);
echo $link['short_url'];

Les SDK 1.1.0 envoient X-API-Key mais pas encore X-API-Secret : utilisez-les avec une clé d'ancienne génération (sans secret) ou appelez l'API directement comme ci-dessus. Les sources sont remises par developers@c-pay.global si votre gestionnaire de paquets ne trouve pas encore le paquet.

Archives à télécharger

Version 2.7.0. Vérifiez l'empreinte SHA-256 après téléchargement (sha256sum fichier.zip).

Extension WooCommerce 2.7.0

Ajoute C-Pay comme moyen de paiement dans WooCommerce 6.0+ (PHP 7.4+). Encaissement Mobile Money et lien de paiement ; configuration avec votre clé API et votre secret ; webhook signé vérifié par l'extension.

SHA-256 : b71c8c66bdbc834dbb90fb56a60c0533e48448b07bc7f613d12abac272f5b89e

Télécharger (83 Ko)
1. WordPress → Extensions → Ajouter → Téléverser l'archive
2. Activer, puis WooCommerce → Réglages → Paiements → C-Pay
3. Renseigner la clé API et le secret (portail marchand → Clés API)
4. Déclarer l'URL de webhook affichée par l'extension dans le portail marchand (rubrique Webhooks)

SDK Android 2.7.0

Bibliothèque Kotlin/Java (AAR, Android 5.0+ / API 21) pour encaisser depuis une application marchande. Les identifiants doivent venir de votre backend, jamais être embarqués dans l'APK.

SHA-256 : 52bbf97dc8d7f73ec7ea3f0c4d449dd690897abf2878877a0ad046d9a62a8c77

Télécharger (13 Ko)
// Initialisation — identifiants fournis par votre serveur
CPay.init(context, CPayConfig(apiKey = cleFournieParVotreServeur, env = CPayEnv.PRODUCTION))

// Encaissement : montant + numéro, l'opérateur est détecté
CPay.collect(activity, amount = 5000.0, customerPhone = "+237670123456", reference = "CMD-001",
    onSuccess = { txn -> confirmerCommande(txn.txnId) },
    onError   = { err -> afficherErreur(err.message) })

Sandbox

Un environnement de test est disponible sur demande : il n'est pas encore en libre-service. Écrivez à developers@c-pay.global en précisant votre cas d'usage, le pays visé et le type d'intégration (API, WooCommerce, Android). Nous revenons vers vous avec une clé de test, l'URL de base du bac à sable et les numéros de téléphone de test qui simulent un paiement confirmé, refusé ou expiré.

En production, le bouton Tester de la rubrique Webhooks du portail envoie un événement webhook.test signé à votre URL.

Codes d'erreur

Toute erreur renvoie { "error", "code", "details?" }. Les codes ci-dessous sont stables ; le message error peut changer.

Codes d'erreur de l'API
HTTPCodeSignification
400PHONE_REQUIREDCorps de /detect/phone sans phone.
400INVALID_IDEMPOTENCY_KEYIdempotency-Key hors de 8 à 128 caractères.
401API_KEY_REQUIREDEn-tête X-API-Key absent.
401INVALID_API_KEYClé inconnue, désactivée ou expirée.
401INVALID_API_SECRETX-API-Secret absent ou incorrect pour une clé qui en possède un.
403MERCHANT_INACTIVECompte marchand suspendu ou non activé.
403—Permission manquante sur la clé (pay_links, transactions…).
409IDEMPOTENCY_CONFLICTMême Idempotency-Key avec un corps différent.
409IDEMPOTENCY_IN_PROGRESSRequête identique encore en cours de traitement.
409FUNDING_UNAVAILABLEAucun compte de collecte C-Pay pour la devise (ordre d'approvisionnement).
409TRANSFER_NOT_CANCELLABLELe transfert n'est plus dans un statut annulable.
410FEATURE_DISABLEDFonction non activée : encaissement direct par clé API, carte sans passerelle, Tap-to-Phone, rail sortant absent.
422VALIDATION_ERRORParamètres invalides (details précise le champ).
422INVALID_EXPIRES_INexpiresIn hors de 1 à 720 heures.
422CURRENCY_MISMATCHLa devise du numéro du client n'est pas celle du marchand.
422INSUFFICIENT_BALANCESolde disponible insuffisant (details.available).
422WALLET_MISSINGAucun wallet enregistré pour le retrait.
422DAILY_LIMIT_EXCEEDEDLimite journalière de retrait du niveau KYB atteinte.
428STEP_UP_REQUIREDObjet confirm absent ou invalide ; methods indique totp ou password.
429—Limite de débit atteinte (en-têtes RateLimit-*), ou cinq confirmations erronées en 15 minutes.
503IDEMPOTENCY_UNAVAILABLEContrôle d'idempotence momentanément indisponible ; réessayez avec la même clé.