NTPay Documentation de l'API Espace marchand

Encaisser par mobile money avec NTPay

Une seule API pour Orange Money, Vodacom M-Pesa et Afrimoney (et les opérateurs ajoutés ensuite) : vous demandez un paiement, le client le confirme sur son téléphone, NTPay vous notifie le résultat. Les montants encaissés, frais déduits, vont sur votre solde NTPay et vous sont reversés.

Démarrer

Chaque intégration (votre site, votre application) a deux clés, visibles dans l'espace marchand, rubrique Intégrations : une clé de production et une clé de test. Envoyez-la dans l'en-tête de chaque appel, depuis votre serveur uniquement :

Authorization: Bearer VOTRE_CLE
Content-Type: application/json
Accept: application/json

Adresse de l'API : https://ntpay.cloud/api (l'ancienne adresse https://gateway.ntoprog.org/api reste valable). En production, les appels doivent venir du serveur de votre intégration (son adresse est enregistrée au premier appel).

Réessayer sans risque : Idempotency-Key

Sur POST /api/c2b, /api/payout, /api/b2c, /api/new-transaction, /api/refunds et /api/invoices, ajoutez un en-tête Idempotency-Key unique par opération (par exemple un UUID). Si la connexion coupe, renvoyez la même requête avec la même clé : dans les 24 heures, vous recevez la première réponse (en-tête Idempotent-Replayed: true) au lieu d'un second paiement. La même clé avec une autre requête répond 422 ; si la première est encore en cours, 409. Une requête refusée avant tout traitement (clé d'API ou champ invalide) n'est pas retenue : corrigez-la et renvoyez-la avec la même clé.

Idempotency-Key: 7f3c2a10-5b8e-4d1f-9a6e-2c4b8d0e1f37

Mode test

Avec la clé de test, tous les appels sont simulés par NTPay : aucun opérateur, aucun argent réel, depuis n'importe quel poste. Les réponses et les notifications ont exactement le format réel. Activez la clé de test depuis la page de votre intégration ; vos opérations apparaissent dans Mode test.

Le résultat dépend des 4 derniers chiffres du numéro (préfixe d'un opérateur actif, ex. 0850000002) :

Fin du numéroRésultat
…0002Refusé : solde insuffisant
…0003Refusé : code secret incorrect
…0004Jamais confirmé : annulé après 5 min, sans notification
…0005Refusé tout de suite : opérateur indisponible
tout autrePayé après 5 secondes

Même règle pour le compte crédité d'un reversement. Les reversements de test puisent dans un solde de test : encaissements de test payés, moins les frais habituels.

Encaisser

POST /api/c2b envoie une demande de paiement au téléphone du client. L'opérateur est déduit du préfixe du numéro.

ChampDescription
phone_numberNuméro mobile money du client : 0812345678 ou 243812345678
amountMontant
currencyCDF ou USD
trans_codeVotre référence, unique (ex. le numéro de commande) : elle revient dans la notification (order_id)
callback_urlVotre URL de notification
languageFacultatif : FR ou EN
curl -X POST https://ntpay.cloud/api/c2b \
  -H "Authorization: Bearer $NTPAY_KEY" -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{"phone_number":"0851234567","amount":5000,"currency":"CDF","trans_code":"CMD-1042","callback_url":"https://votre-site.cd/ntpay"}'

Réponse 201 : la demande est partie, le client doit la confirmer. Le contenu est la réponse de l'opérateur, plus ntpay_reference, la référence NTPay pour suivre le statut.

// Orange Money
{"content": {"result_code": "0", "result_description": "Paiement En Cours de traitement !", "transaction_code": "2610257K3QX9MZ2DAH4W", "ntpay_reference": "2610257K3QX9MZ2DAH4W"}}
// Vodacom M-Pesa
{"content": {"result_code": "3", "result_description": "Processed", "transaction_id": "…", "ntpay_reference": "261025R4M8C2XKQ7WZ5H"}}
// Afrimoney
{"content": {"result_code": "TI", "result_description": "…", "status_value": "PAUSED", "transaction_code": "…", "ntpay_reference": "26102790K4QX7M2ZTR8H"}}

Le résultat arrive par notification, en général en moins d'une minute.

Point d'accès historique

POST /api/new-transaction reste disponible pour les intégrations existantes : mêmes champs, plus operation (c2b), output et return_url. Il répond {"result": "success", "code": 200, "message": "Transaction successfully sent!"} ou {"result": "declined", …}.

Notifications

À chaque résultat, NTPay envoie un POST JSON à votre callback_url :

{"code": 4, "message": "Paiement de 5000.00 CDF … effectue.", "result": "success", "order_id": "CMD-1042"}

result vaut success ou failed ; code est le statut. Chaque notification porte trois en-têtes :

X-NTPay-TimestampHeure d'envoi (secondes Unix)
X-NTPay-Signaturev1= + HMAC-SHA256 (hexadécimal) de horodatage + "." + corps avec la clé de signature de votre intégration
X-NTPay-Notification-IdLe même à chaque nouvel essai d'une notification

Vérifier une notification

Refusez une notification dont la signature ne correspond pas ou dont l'horodatage a plus de 5 minutes. Après un changement de clé, deux valeurs v1= sont envoyées pendant 24 h : au moins une doit correspondre.

// PHP
$body = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_NTPAY_TIMESTAMP'] ?? '';
$expected = 'v1=' . hash_hmac('sha256', $timestamp . '.' . $body, getenv('NTPAY_CALLBACK_SECRET'));
$signed = false;
foreach (explode(',', $_SERVER['HTTP_X_NTPAY_SIGNATURE'] ?? '') as $part) {
    $signed = $signed || hash_equals($expected, trim($part));
}
if (!$signed || abs(time() - (int)$timestamp) > 300) { http_response_code(401); exit; }
$notification = json_decode($body, true);
# Python (Flask)
import hashlib, hmac, os, time
from flask import request, abort

body = request.get_data()
timestamp = request.headers.get("X-NTPay-Timestamp", "")
expected = "v1=" + hmac.new(os.environ["NTPAY_CALLBACK_SECRET"].encode(), f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest()
signed = any(hmac.compare_digest(expected, part.strip()) for part in request.headers.get("X-NTPay-Signature", "").split(","))
if not signed or abs(time.time() - int(timestamp or 0)) > 300:
    abort(401)

Répondez vite avec un code 2xx. Sinon NTPay réessaie après 1 min, 5 min, 30 min, 2 h et 6 h : traitez chaque X-NTPay-Notification-Id (ou order_id) une seule fois. Le guide complet et votre clé de signature sont sur la page de votre intégration, avec un bouton pour envoyer une notification de test.

Paiements sortants

Chaque reversement, remboursement et ligne de paiement de masse notifie aussi son résultat final, avec les mêmes en-têtes et les mêmes nouveaux essais : au callback_url donné avec le reversement, sinon à l'URL de notification des paiements sortants de votre intégration (page de l'intégration).

{"type": "payout", "kind": "payout", "code": 4, "result": "success",
 "message": "Reversement de 4850.00 CDF au 0812345678 effectue",
 "payout_reference": "2610267M2XKQ9ZTR4HAP", "collection_reference": "2610257K3QX9MZ2DAH4W",
 "order_id": "CMD-1042", "amount": "4850.00", "currency": "CDF", "account_number": "0812345678"}

kind vaut payout, refund ou mass ; pour une ligne de paiement de masse, order_id est la référence de la ligne.

Suivre un statut

GET /api/status/{ntpay_reference}, avec votre clé : NTPay interroge l'opérateur et répond avec son résultat. Quel que soit l'opérateur, content porte aussi status (ci-dessous), status_text, ntpay_reference et trans_code.

StatutSignification
0Créée
1En cours : en attente de la confirmation du client
2Refusée
3Annulée : pas de confirmation à temps
4Réussie

Reverser

POST /api/payout crée un reversement d'un encaissement réussi vers un compte mobile money, pris sur votre solde : même devise, au plus le montant encaissé, une fois par encaissement. Il est envoyé dans la minute.

Si vous avez choisi une devise des reversements (page Solde), currency peut aussi être celle-ci : amount est alors le montant reversé, et l'encaissement est converti au cours du moment, dans les plafonds des conversions. Les reversements automatiques sont faits dans cette devise quand la conversion est possible, sinon dans celle de l'encaissement.

curl -X POST https://ntpay.cloud/api/payout \
  -H "Authorization: Bearer $NTPAY_KEY" -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{"trans_code":"CMD-1042","account_number":"0812345678","amount":4850,"currency":"CDF","callback_url":"https://votre-site.cd/ntpay/reversements"}'

callback_url est facultatif : le résultat du reversement y est notifié (voir Notifications, paiements sortants).

Réponse 201 {"result": "success", "message": "Payout created !"} ; sinon 404, 409 (déjà reversé) ou 422 (non réussi, devise, montant, solde insuffisant, conversion impossible) avec un message.

Reversements soumis à approbation

Si account_number n'est pas l'un des comptes de reversement de votre intégration (page de l'intégration), ou si le reversement dépasse le plafond journalier de vos reversements par l'API (fixé dans l'espace NTPay, Circuits d'approbation), il est créé et son montant réservé, mais il attend une approbation dans l'espace NTPay : par votre circuit d'approbation, puis par NTPay. La réponse 201 porte alors "approval": "pending". Approuvé, il part dans la minute ; rejeté, son résultat vous est notifié comme un reversement refusé ("result": "failed") et le montant revient au solde. D'ici là, POST /api/b2c répond 409. Le mode test n'applique pas cette approbation.

POST /api/b2c avec {"transaction_code": "référence NTPay de l'encaissement"} envoie tout de suite son reversement en attente.

Rembourser

POST /api/refunds rembourse un encaissement réussi de votre intégration au numéro qui a payé, pris sur votre solde : en une ou plusieurs fois, jusqu'au montant encaissé. Les frais NTPay de l'encaissement ne sont pas rendus : votre solde doit couvrir le remboursement. Il est envoyé dans la minute.

curl -X POST https://ntpay.cloud/api/refunds \
  -H "Authorization: Bearer $NTPAY_KEY" -H "Content-Type: application/json" -H "Accept: application/json" \
  -H "Idempotency-Key: 3b9e7c1a-remboursement-1042" \
  -d '{"trans_code":"CMD-1042","amount":4000,"reason":"Article retourné","callback_url":"https://votre-site.cd/ntpay/remboursements"}'
  • L'encaissement : trans_code (votre référence) ou ntpay_reference.
  • amount : facultatif, tout ce qui reste à rembourser par défaut. reason : facultatif.
  • callback_url : facultatif ; le résultat y est notifié comme un reversement, avec "kind": "refund" (voir Notifications, paiements sortants), sinon à l'URL de notification des paiements sortants de l'intégration.

Réponse 201 : refund (reference, collection_reference, amount, currency, account_number, status, approval) et refundable, ce qui reste à rembourser. Au-delà du seuil de remboursement de votre compte (Circuits d'approbation), il attend une approbation : "approval": "pending". Sinon 404 (encaissement introuvable pour votre intégration) ou 422 (non réussi, déjà remboursé, montant trop élevé, solde insuffisant, carte bancaire, opérateur indisponible).

GET /api/refunds/{reference} : un remboursement ; avec la ntpay_reference d'un encaissement, tous ses remboursements et refundable. Avec la clé de test, les remboursements portent sur vos encaissements de test.

Factures

POST /api/invoices crée une facture et l'envoie au client (WhatsApp ou SMS, et e-mail avec le PDF), avec un lien pour la payer par mobile money. Si vous avez connecté votre e-MCF (espace marchand, Factures, Facture normalisée), elle est d'abord normalisée par la DGI : NTPay vérifie que les montants calculés par la DGI sont les siens au centime près, puis la réponse porte le code DEF/DGI, les compteurs et le QR code.

curl -X POST https://ntpay.cloud/api/invoices \
  -H "Authorization: Bearer $NTPAY_KEY" -H "Content-Type: application/json" -H "Accept: application/json" \
  -H "Idempotency-Key: 7d1f0c2e-facture-1042" \
  -d '{"reference":"INV-1042","currency":"CDF","price_mode":"ht","due_date":"2026-11-10",
       "notify_url":"https://votre-site.cd/ntpay/factures",
       "customer":{"name":"Client SARL","type":"PM","nif":"A1234567B","phone":"0812345678","email":"[email protected]"},
       "lines":[{"name":"Abonnement","type":"SER","quantity":2,"unit_price":10000,"tax_group":"B"},
                {"name":"Terminal","type":"BIE","quantity":1,"unit_price":5000,"tax_group":"A"}]}'
  • tax_group : le groupe de taxation de la DGI, de A à S (B : TVA 16 %, A : exonéré…) ; type : BIE (bien), SER (service) ou TAX.
  • customer.type : PP personne physique (par défaut), PM personne morale (NIF obligatoire), PC, PL, AO. Un client déjà connu (même NIF, téléphone ou e-mail) est repris.
  • Facultatifs par ligne : code, taxe spécifique (specific_value « 100 » ou « 10% », specific_amount pour la ligne, prix HT seulement), remise (original_price, price_modification).
  • En USD, une facture normalisée est certifiée en CDF au taux de la DGI, avec le total en USD ; le client paie en USD.
  • "send": false crée un brouillon, envoyé plus tard par POST /api/invoices/{id}/send.

Réponse 201 avec invoice : id, number (FAC-2026-0001), status (draft, certifying, sent, paid, cancelled), totals, certification, payment_url et pdf_url. Si la DGI ne répond pas, la facture est certifying : elle sera normalisée puis envoyée automatiquement. Refusée par la DGI : 422 avec son message, la facture reste un brouillon. 409 si reference a déjà servi.

GET /api/invoices/{id} la relit. Payée, elle est notifiée à notify_url, signée comme les autres notifications :

{"type": "invoice", "event": "invoice.paid", "invoice_id": 128, "number": "FAC-2026-0001", "reference": "INV-1042",
 "amount": "31960.00", "currency": "CDF", "ntpay_reference": "2610277K3QX9MZ2DAH4W", "account_number": "0812345678",
 "paid_at": "2026-10-28T09:14:02+00:00", "def_code": "UJKI-…"}

POST /api/invoices/{id}/cancel l'annule. Une facture normalisée ne se supprime pas : NTPay émet une facture d'avoir normalisée, motif reason de la DGI (RAN annulation, COR correction, RAM reprise, RRR remise). Juste après la normalisation, la DGI peut mettre une minute à accepter l'avoir : il est alors normalisé automatiquement. Une facture payée garde son statut, l'avoir est émis : remboursez le client depuis la transaction.

Avec la clé de test, POST /api/invoices répond "result": "preview" avec les totaux par groupe : rien n'est enregistré ni envoyé.

Liens de paiement

Sans développement : créez un lien dans l'espace marchand et partagez-le. Depuis votre site, un simple lien vers la page de paiement suffit (le code du bouton est sur la page du lien). L'adresse peut pré-remplir la page : amount (montant libre), name, email, f1 à f3 (informations demandées).

https://pay.ntpay.cloud/k3w7sabmwf?amount=15000&f1=E-2041

WooCommerce et PrestaShop

Votre boutique accepte le mobile money sans développement : le client choisit « Mobile money », indique son numéro et confirme sur son téléphone ; la page de la commande suit le paiement et s'actualise seule. La commande est payée par la notification signée de NTPay, ou par une vérification du statut si elle tarde ; refusé ou non confirmé, le client peut réessayer. Les remboursements se font depuis la commande (bouton Rembourser de WooCommerce, bloc NTPay de la commande dans PrestaShop) : au numéro qui a payé, pris sur votre solde NTPay, en une ou plusieurs fois. Devises : CDF et USD.

  • NTPay pour WooCommerce 1.1.0 : WordPress 6.2+, WooCommerce 8+ (validation de commande classique ou en blocs), PHP 7.4+. Extensions, Ajouter, Téléverser l'extension, puis WooCommerce, Réglages, Paiements, NTPay.
  • NTPay pour PrestaShop 1.1.0 : PrestaShop 1.7.8 et 8. Modules, Gestionnaire de modules, Téléverser un module, puis Configurer.

Dans les réglages, collez la clé de production, la clé de test et la clé de signature des notifications (page de votre intégration). Essayez en mode test : un numéro qui finit par 0002 est refusé, par 0004 jamais confirmé, les autres réussissent. L'ancienne extension WordPress (/api/new-transaction) continue de fonctionner.

Erreurs

Code HTTPCause
401Clé absente, inconnue ou désactivée (sur /api/c2b : 500 avec le message)
404Transaction introuvable pour votre intégration
409Référence déjà utilisée, reversement déjà fait
422Champ manquant ou invalide (errors détaille chaque champ), règle de reversement non respectée
500Refus de l'opérateur ou indisponibilité : le message l'indique, réessayez plus tard

Tous vos appels des 30 derniers jours, avec ce qui a été envoyé et répondu, sont dans l'espace marchand : Journal des appels API.

L'état de chaque opérateur, mesuré en continu, et les maintenances annoncées sont sur la page de statut, aussi en JSON pour votre supervision : https://ntpay.cloud/statut.json.

Téléchargements

Une question : [email protected].