Aller au contenu
Développeurs

API Prépayés
Vends du prépayé depuis ton site

Recharges télécom, eSIM et cartes cadeaux, achetées par API au prix coûtant débité du wallet Monniz Business. La livraison est effectuée par Monniz.

Mode test

La clé test simule tout le parcours : aucun débit, aucun appel réel, mêmes réponses.

Tarification

Chaque offre expose cost_price_xof, le prix coûtant débité de ton wallet à l’achat.

Webhooks signés

Chaque livraison ou échec est poussé sur ton serveur, signé HMAC, avec retries.

Démarrage rapide

  1. 1

    Génère ta clé test

    Dans Paramètres → Développeur, génère la clé mnz_sk_test_…. Elle n'est affichée qu'une seule fois.

  2. 2

    Explore le catalogue

    bash
    curl "https://api.monniz.shop/v1/prepaid/catalog/topups?country=BJ" \
      -H "Authorization: Bearer mnz_sk_test_..."
  3. 3

    Fais un premier achat simulé

    La clé test ne débite rien et n'appelle aucun fournisseur :

    bash
    curl -X POST "https://api.monniz.shop/v1/prepaid/purchase/topup" \
      -H "Authorization: Bearer mnz_sk_test_..." \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: 8f4e2a10-77b3-4c1e-9b1a-2d6c58c01a42" \
      -d '{
        "offer_id": "TOPUP-MTN-BJ-1000",
        "recipient_phone": "+2290167819813"
      }'

    La réponse contient une transaction COMPLETED avec une livraison factice.

  4. 4

    Passe en réel

    Une fois ton business vérifié (KYB), la clé mnz_sk_live_… se génère au même endroit — même API, même contrat.

Le détail de chaque endpoint est dans la référence interactive

Paramètres, réponses, console d'essai et exemples multi-langages : api.monniz.shop/docs. Collection Postman (dossier Prépayés) — dans Postman : ImportLink, puis colle https://api.monniz.shop/docs.postman.

Authentification & modes

Toutes les requêtes portent ta clé en en-tête Authorization: Bearer <clé>. Le préfixe de la clé détermine le mode :

mnz_sk_test_…Test

Achats simulés : aucun débit, aucune livraison réelle, réponses au même format (livemode: false). Disponible dès l'inscription.

mnz_sk_live_…Live

Achats réels débités de ton wallet Business. Nécessite un KYB approuvé.

Les données test et live sont étanches : une clé test ne voit que les transactions test, et inversement. Tu peux restreindre les adresses IP autorisées (exactes ou CIDR) et révoquer tes clés à tout moment — effet immédiat.

La clé est un secret côté serveur

La clé s'utilise uniquement côté serveur — jamais dans un front, une app mobile ou un dépôt git. Une clé compromise se révoque et se régénère depuis le dashboard.

Catalogue & prix

  • GET/v1/prepaid/catalog/countriesPays disponibles par type
  • GET/v1/prepaid/catalog/brandsMarques (logo, couleur)
  • GET/v1/prepaid/catalog/categoriesSous-types valides du filtre sub_type
  • GET/v1/prepaid/catalog/{topups|esims|vouchers}Offres — filtres country, brand, sub_type, search, pagination
  • GET/v1/prepaid/catalog/offers/{offer_id}Détail d'une offre

Chaque offre porte un bloc pricing : cost_price_xof est le prix coûtant, débité du wallet à l'achat.

json
{
  "offer_id": "TOPUP-MTN-BJ-1000",
  "name": "Recharge MTN 1 000 FCFA",
  "pricing": {
    "price_type": "FIXED",
    "cost_price_xof": 1039
  }
}

Les offres price_type: "RANGE" (montant libre) exposent des bornes value_min/value_max et un pricing à la borne minimale (at_min_value) ; le montant choisi se passe dans value à l'achat.

Achat

  • POST/v1/prepaid/purchase/previewDevis sans engagement (prix au moment T)
  • POST/v1/prepaid/purchase/{topup|esim|voucher}Achat — Idempotency-Key OBLIGATOIRE

Idempotency-Key obligatoire

L'en-tête Idempotency-Key (un UUID unique par intention d'achat) est obligatoire : sans lui, la requête est refusée (428). Rejouer une requête avec la même clé renvoie la réponse d'origine, sans second débit.

Corps de la requête selon le type :

ChampTypeDétail
offer_idrequisIdentifiant de l'offre du catalogue
recipient_phonerequis (topup)Format international strict, ex. +2290167819813 — Bénin et Côte d'Ivoire gardent le 0 après l'indicatif
recipient_emailoptionnelEmail du bénéficiaire (vouchers, eSIM)
iccidoptionnel (eSIM)Recharge d'une eSIM existante
fieldsselon l'offre (voucher)Objet {nom: valeur} — champs requis listés dans required_fields de l'offre
valuerequis (RANGE)Montant choisi, dans la devise de remise
locked_pricerecommandéLe cost_price_xof montré à ton client — achat refusé si le prix réel a dérivé de plus de 5 %

Réponse (achat accepté) :

json
{
  "success": true,
  "data": {
    "transaction": {
      "reference": "MZB_PRE_20260818_8QCF6OCA",
      "livemode": true,
      "status": "PROCESSING",
      "amount": 1039,
      "delivery_details": null,
      "created_at": "2026-08-18T11:42:05+00:00"
    }
  }
}

status: "PROCESSING" signifie que la livraison est en cours, amount est le montant débité de ton wallet, et delivery_details reste null jusqu'à la livraison.

Cycle de vie & suivi

PROCESSINGCOMPLETEDouFAILEDREFUNDED

PROCESSING : payé, livraison en cours (quelques secondes à quelques minutes). COMPLETED : livré — delivery_details contient la confirmation (n° de confirmation, ePIN, activation eSIM…). FAILED / CANCELLED : échec de livraison → remboursement automatique de ton wallet, état final REFUNDED.

  • GET/v1/prepaid/transactionsHistorique — filtres product_type, status, date_from, date_to
  • GET/v1/prepaid/transactions/{reference}Détail + statut d'une transaction
  • GET/v1/prepaid/transactions/{reference}/qrcodeQR code eSIM (PNG) — une fois COMPLETED
  • GET/v1/prepaid/balanceSolde de ton wallet Business

La livraison n'est confirmée que par COMPLETED

La livraison n'est confirmée que par le statut COMPLETED (webhook ou GET /transactions/{reference}) ; la réponse de l'achat ne la confirme pas.

Webhooks

L'URL HTTPS et les événements se configurent dans l'onglet Développeur. Chaque transition est POSTée sur ton serveur : transaction.completed, transaction.failed, transaction.refunded — 8 tentatives avec backoff (10 s → 24 h, ~2 jours) tant que la réponse n'est pas 2xx. GET /transactions/{reference} rend le même statut.

L'en-tête Monniz-Signature porte le timestamp et la signature v1 = HMAC-SHA256 de "{t}.{corps brut}" avec ton secret whsec_… :

requête reçue
POST https://ton-site.com/webhooks/monniz
X-Monniz-Event: transaction.completed
Monniz-Signature: t=1787053351,v1=3f5a1c9b2e847d0a6c1f4b8e5d2a7c90e1b6f3a8d4c7092e5b1f8a3d6c40e7b2

{
  "id": "evt_01J8XKQ2M4N7P9R2S5T8V1W4X8",
  "type": "transaction.completed",
  "livemode": true,
  "created": "2026-08-18T11:42:31+00:00",
  "data": {
    "reference": "MZB_PRE_20260818_8QCF6OCA",
    "status": "COMPLETED",
    "product_type": "topup",
    "amount": 1039,
    "delivery_details": { "confirmation": { "confirmationNumber": "4058712396" } }
  }
}

Signature

Chaque envoi est signé ; la signature se vérifie avec le secret whsec_…, affiché une seule fois à la configuration du webhook.
php
// Monniz-Signature: t={timestamp},v1={hmac}
[$tPart, $vPart] = explode(',', $_SERVER['HTTP_MONNIZ_SIGNATURE'] ?? ',');
$t = substr($tPart, 2);
$given = substr($vPart, 3);
$expected = hash_hmac('sha256', $t . '.' . $rawBody, $webhookSecret);
// Timestamp signé = anti-rejeu : refuse un événement trop vieux.
if (abs(time() - (int) $t) > 300 || ! hash_equals($expected, $given)) {
    http_response_code(401); exit;
}
node.js
// Express — utiliser le corps BRUT, pas le JSON parsé
const crypto = require('crypto')
const header = req.get('Monniz-Signature') || ''            // t=...,v1=...
const t = (header.match(/t=(\d+)/) || [])[1] || '0'
const given = (header.match(/v1=([a-f0-9]{64})/) || [])[1] || ''
const expected = crypto.createHmac('sha256', WEBHOOK_SECRET).update(t + '.' + rawBody).digest('hex')
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300  // anti-rejeu
if (!fresh || !crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(given.padEnd(expected.length)))) {
  return res.status(401).end()
}

Une réponse 2xx marque l'événement comme livré. Un même événement peut être livré plusieurs fois.

Erreurs

Toutes les erreurs suivent le même format :

json
{ "success": false, "message": "...", "errors": { "code": "..." } }
HTTPCodeQuand
401API_KEY_MISSING / API_KEY_INVALIDClé absente, inconnue ou révoquée
403PREPAID_DISABLEDService momentanément indisponible (maintenance)
403KYB_REQUIRED / ACCOUNT_SUSPENDEDMode live sans KYB approuvé, ou compte suspendu
403IP_NOT_ALLOWEDIP hors de ta whitelist
429RATE_LIMIT_EXCEEDEDLimite par minute atteinte
428IDEMPOTENCY_KEY_REQUIREDEn-tête Idempotency-Key manquant sur l'achat
422UNPROCESSABLEValidation : numéro mal formé, champ voucher manquant, prix dérivé (> 5 %), solde insuffisant…
404NOT_FOUNDOffre ou transaction introuvable (ou pas à toi)

Limites & sécurité

Rate limit

60 requêtes/minute par défaut — la limite exacte figure dans ton onglet Développeur.

Solde

Les achats live sont débités de ton wallet Business. Un solde insuffisant refuse l'achat (422).

Remboursements

Tout échec de livraison recrédite le wallet automatiquement, à l'identique du débit et sans frais.

Pagination

page / per_page (max 100) sur les listes, avec meta.total dans la réponse.

Aller plus loin

Prêt à vendre du prépayé ?

Crée ton compte Monniz Business, génère ta clé test et fais ton premier achat simulé en cinq minutes.

API Prépayés — Documentation développeurs | Monniz Business