API Paiements
Encaisse depuis ton propre site
Paiements Mobile Money (Wave, Orange Money, MTN, Moov), carte et crypto créés par API depuis ton tunnel. Le net encaissé est crédité sur ton wallet Business. Le mode test n'engage aucun argent réel.
Démarrage rapide
La clé mnz_sk_test_… se génère dans Paramètres → Développeur. Le paiement se crée avec l'opérateur et le numéro collectés dans ton tunnel ; la demande de validation part sur le téléphone du client.
curl -X POST https://api.monniz.shop/v1/payments \
-H "Authorization: Bearer mnz_sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"amount": "15000",
"payment_method": "moov-benin",
"customer": { "phone": "90165245" }
}'curl -X POST https://api.monniz.shop/v1/payments \
-H "Authorization: Bearer mnz_sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"amount": "15000",
"currency": "XOF",
"description": "Commande 42",
"payment_method": "moov-benin",
"customer": {
"name": "Aminata D.",
"phone": "90165245",
"email": "[email protected]"
},
"success_url": "https://ta-boutique.com/merci",
"cancel_url": "https://ta-boutique.com/panier"
}'Champs obligatoires et next_action
payment_method (tag opérateur, cf. GET /v1/config) est obligatoire ; customer.phone l'est dès qu'un numéro est à débiter. La demande arrive sur le téléphone du client et la réponse porte un next_action à afficher pendant la validation.Carte bancaire :
payment_method: "card" sans numéro — next_action renvoie l'URL du prestataire où ton client saisit sa carte (périmètre PCI SAQ A). Crypto : payment_method: "crypto" + crypto_asset — next_action renvoie l'adresse de dépôt à afficher.{
"success": true,
"data": { "payment": {
"id": "pay_01J8XKQ2M4N7P9R2S5T8V1W4X7",
"payment_id": "mzp_01J8XKQ2M4N7P9R2S5T8V1W4X6",
"status": "PENDING",
"livemode": false,
"amount": "15000",
"currency": "XOF",
"mode": "direct",
"checkout_url": null,
"expires_at": "2026-08-19T15:30:00+00:00",
"next_action": {
"type": "pin_prompt",
"message": "Ton client doit valider sur son téléphone.",
"instructions": "Composez *880# pour valider."
}
}},
"meta": { "request_id": "req_01J8XKQ2M4N7P9R2S5T8V1W4X7" }
}Endpoints
- POST
/v1/paymentsCréer un paiement → next_action - GET
/v1/payments/{id}Statut (id pay_… ou référence mzp_…) - GET
/v1/paymentsListe (curseur limit + starting_after) - GET
/v1/configPays, opérateurs, bornes et consignes payeur - GET
/v1/crypto/assetsActifs et réseaux crypto acceptés - POST
/v1/payments/{id}/crypto/confirmTon client déclare avoir envoyé les fonds (throttle 20/min) - POST
/v1/payment_linksCréer un lien de paiement - GET
/v1/payment_linksListe de tes liens de paiement - GET
/v1/payment_links/{id}Détail d'un lien - POST
/v1/payment_links/{id}/deactivateDésactiver un lien
Les liens de paiement fournissent une page d'encaissement hébergée par Monniz. Le webhook payment_link.paid signale le règlement d'un lien.
Les remboursements sont exécutés par Monniz ; les événements refund.succeeded / refund.failed et les statuts REFUNDED / PARTIALLY_REFUNDED en notifient le marchand.
Idempotence sur POST /v1/payments
POST /v1/payments accepte un en-tête Idempotency-Key (un UUID par tentative métier). Sans Idempotency-Key ni payment_id fourni, deux appels identiques créent deux paiements. Les codes IDEMPOTENCY_KEY_REUSE et IDEMPOTENCY_IN_PROGRESS s'y rapportent.Référence interactive complète (schémas, exemples, codes d'erreur) : api.monniz.shop/docs
Collection Postman
Deux dossiers, Paiements et Prépayés. La variable api_key de la collection porte ta clé.
S'importe dans Postman via Import → Link avec cette URL — https://api.monniz.shop/docs.postman
Cycle de vie d'un paiement
PENDING → PROCESSING → SUCCEEDED
→ FAILED (avec failure.code)
→ IN_RECONCILIATION → SUCCEEDED | FAILED
→ CANCELLED | EXPIREDUn paiement FAILED porte sa cause dans failure.code. La liste des codes peut s'allonger sans préavis.
INSUFFICIENT_BALANCE solde mobile money insuffisant PAYMENT_NOT_APPROVED le client n'a pas confirmé (PIN non saisi, abandon) PAYMENT_DECLINED refus opérateur, sans motif exploitable remonté PAYER_NOT_FOUND numéro inconnu chez l'opérateur OPERATOR_TIMEOUT l'opérateur n'a pas répondu à temps
Un paiement expire au bout de 30 minutes (expires_at) : resté PENDING, il passe EXPIRED. Cet état est final ; un encaissement ultérieur passe par un nouveau paiement, avec un nouveau payment_id le cas échéant.
Direct & hébergé
Direct — le mode par défaut, obtenu dès que payment_method est envoyé. L'opérateur et le numéro sont collectés dans ton tunnel ; l'encaissement part immédiatement. La carte et la crypto sont aussi du direct : sans numéro, leur next_action porte l'URL du prestataire ou l'adresse de dépôt.
Parcours crypto : GET /v1/crypto/assets liste les actifs et réseaux acceptés. Le paiement se crée avec payment_method: "crypto" et crypto_asset ; next_action porte l'adresse de dépôt à afficher. POST /v1/payments/{id}/crypto/confirm déclare l'envoi des fonds ; le règlement est signalé par le webhook payment.succeeded ou GET /v1/payments/{id}.
Page hébergée (mode: "hosted") — la page Monniz collecte le moyen de paiement et la réponse porte une checkout_url. Se demande explicitement ; destinée aux intégrations sans tunnel propre, comme le plugin WooCommerce.
curl -X POST https://api.monniz.shop/v1/payments \
-H "Authorization: Bearer mnz_sk_test_..." \
-H "Content-Type: application/json" \
-d '{
"amount": "15000",
"payment_method": "orange-money-ci",
"customer": { "name": "Aminata D.", "phone": "0000000" },
"otp_code": "123456"
}'En direct, pas de checkout_url : next_action indique la marche à suivre.
"next_action": {
"type": "pin_prompt",
"message": "Ton client doit valider sur son téléphone.",
"instructions": {
"ussd_code": "#144*82#",
"timing": "before",
"fr": "Composez #144*82# puis sélectionnez l'option 2…",
"en": "Dial #144*82# on your phone, then follow the instructions."
}
}
// ou, pour Wave et la carte :
"next_action": { "type": "redirect", "redirect_url": "https://pay.wave.com/c/cos-1p8xkq2m4n7p9r2s" }OTP requis avant la création
otp_required dans /v1/config (Orange Money CI et Burkina) exigent un otp_code à la création — le timing before l'indique. L'OTP est obtenu par le client via le code USSD avant l'appel de création.Configuration active
GET /v1/config décrit ce qui est encaissable maintenant. Chaque opérateur porte ses propres bornes et, le cas échéant, la consigne à afficher au payeur. Bornes et codes USSD peuvent changer sans préavis.
{
"tag": "orange-money-ci",
"name": "Orange Money CI",
"otp_required": true,
"amount": { "min": "200", "max": "300000" },
"instructions": {
"ussd_code": "#144*82#",
"timing": "before",
"fr": "Composez #144*82# puis sélectionnez l'option 2 pour obtenir le code OTP.",
"en": "Dial #144*82# on your phone, then follow the instructions."
}
}À l'encaissement, les bornes de l'opérateur choisi font foi, pas les bornes globales de la réponse.
Erreurs
Toute réponse en erreur porte un code machine dans errors.code. Le code est stable ; le message est en français et peut être reformulé sans préavis.
{
"success": false,
"message": "Paiement introuvable.",
"errors": { "code": "PAYMENT_NOT_FOUND" }
}PAYMENT_NOT_FOUND paiement inconnu de ton compte PAYMENT_NOT_PAYABLE déjà réglé, annulé ou expiré PAYMENT_METHOD_UNAVAILABLE opérateur indisponible ou inconnu CUSTOMER_PHONE_REQUIRED numéro manquant pour ce moyen de paiement PAYMENT_LINK_NOT_FOUND lien inconnu de ton compte IDEMPOTENCY_KEY_REUSE même clé, corps différent (409) IDEMPOTENCY_IN_PROGRESS requête identique encore en cours (409) RATE_LIMITED trop de requêtes sur ta clé (429) PROVIDER_UNAVAILABLE l'opérateur ne répond pas — réessaie (400 ou 502)
PROVIDER_UNAVAILABLE peut porter un statut HTTP 400 ou 502 ; le code d'erreur est la valeur stable.
Chaque réponse publie ton quota :
X-RateLimit-Limit requêtes autorisées par minute sur ta clé X-RateLimit-Remaining ce qu'il te reste sur la minute en cours X-RateLimit-Reset secondes avant remise à zéro Retry-After (sur 429 uniquement) attends ce nombre de secondes
Mode test
Avec une clé mnz_sk_test_…, aucun argent réel ne circule : la page de paiement affiche un simulateur (payer, solde insuffisant, PIN non saisi, timeout, indéterminé). Certains montants forcent un scénario :
1001 FCFA → FAILED / INSUFFICIENT_BALANCE 1002 FCFA → reste PROCESSING (timeout opérateur) 1003 FCFA → FAILED / PAYMENT_NOT_APPROVED 1004 FCFA → IN_RECONCILIATION autre → choix du scénario sur la page (simulateur)
IN_RECONCILIATION existe aussi en production
1004 (IN_RECONCILIATION) survient aussi en production.Frais et crédit du wallet
Ton client paie exactement amount. Les frais opérateur sont à ta charge : ton wallet Business est crédité du net (montant − frais). Le détail des opérateurs et de leurs frais est dans GET /v1/config.
Le net passe d'abord en solde en attente
Webhooks & support
Chaque réponse porte meta.request_id (req_…) : il identifie l'appel auprès du support.
Webhooks paiements
payment.succeeded, payment.failed et payment_link.paid (règlement d'un lien créé par l'API). Chaque envoi est signé t=…,v1=… ; la fonction de vérification est fournie dans chaque SDK.Source de vérité du statut
success_url ne confirme pas le paiement. Si ton endpoint est injoignable, l'événement est réessayé pendant ~2 jours puis abandonné. GET /v1/payments/{id} est la source de vérité du statut.Accès live
Aller plus loin
Prêt à encaisser depuis ton site ?
Crée ton compte Monniz Business, génère ta clé test et fais ton premier paiement simulé en cinq minutes.