MonCashAPIby FedtopupSe connecter

Exemples

Les cinq gestes d’une intégration complète, dans cinq langages. La clé est lue dans la variable d’environnement MONCASHAPI_SECRET_KEY, le secret de webhook dans MONCASHAPI_WEBHOOK_SECRET.

Créer un paiement

curl https://moncashapi.fedtopup.com/api/v1/payments \
  -H "Authorization: Bearer $MONCASHAPI_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: commande-1042" \
  -d '{
    "amount": 1000,
    "reference": "CMD-1042",
    "description": "Commande 1042",
    "return_url": "https://votre-site.com/merci"
  }'
// Côté serveur (Node 18+, Deno, Bun, fonctions « edge »). Jamais dans une page web.
const reponse = await fetch("https://moncashapi.fedtopup.com/api/v1/payments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MONCASHAPI_SECRET_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "commande-1042",
  },
  body: JSON.stringify({
    amount: 1000,
    reference: "CMD-1042",
    description: "Commande 1042",
    return_url: "https://votre-site.com/merci",
  }),
});
const paiement = await reponse.json();
if (!reponse.ok) {
  throw new Error(`${paiement.error.code} — ${paiement.error.request_id}`);
}
// Envoyez votre client vers paiement.payment_url
import express from "express";

const app = express();

app.post("/payer/:commande", async (req, res) => {
  const reponse = await fetch("https://moncashapi.fedtopup.com/api/v1/payments", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.MONCASHAPI_SECRET_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": `commande-${req.params.commande}`,
    },
    body: JSON.stringify({
      amount: 1000,
      reference: `CMD-${req.params.commande}`,
      return_url: "https://votre-site.com/merci",
    }),
  });
  const paiement = await reponse.json();
  if (!reponse.ok) return res.status(502).json({ erreur: paiement.error.code });
  res.redirect(303, paiement.payment_url);
});
<?php
$ch = curl_init("https://moncashapi.fedtopup.com/api/v1/payments");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 20,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("MONCASHAPI_SECRET_KEY"),
        "Content-Type: application/json",
        "Idempotency-Key: commande-1042",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "amount" => 1000,
        "reference" => "CMD-1042",
        "description" => "Commande 1042",
        "return_url" => "https://votre-site.com/merci",
    ]),
]);
$paiement = json_decode(curl_exec($ch), true);
$statut = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($statut >= 400) {
    throw new Exception($paiement["error"]["code"] . " — " . $paiement["error"]["request_id"]);
}
header("Location: " . $paiement["payment_url"], true, 303);
import os
import requests

reponse = requests.post(
    "https://moncashapi.fedtopup.com/api/v1/payments",
    headers={
        "Authorization": f"Bearer {os.environ['MONCASHAPI_SECRET_KEY']}",
        "Idempotency-Key": "commande-1042",
    },
    json={
        "amount": 1000,
        "reference": "CMD-1042",
        "description": "Commande 1042",
        "return_url": "https://votre-site.com/merci",
    },
    timeout=20,
)
paiement = reponse.json()
if not reponse.ok:
    raise RuntimeError(f"{paiement['error']['code']} — {paiement['error']['request_id']}")
print(paiement["payment_url"])  # envoyez votre client à cette adresse

Lire le statut d’un paiement

curl https://moncashapi.fedtopup.com/api/v1/payments/pay_3f9c1a7e52b04d6a81c0 \
  -H "Authorization: Bearer $MONCASHAPI_SECRET_KEY"
// Dans une page web : clé PUBLIABLE uniquement (pk_live_…).
// Elle ne sait lire que le statut d'un paiement de votre projet.
const reponse = await fetch(`https://moncashapi.fedtopup.com/api/v1/payments/${paiementId}`, {
  headers: { Authorization: "Bearer pk_live_VOTRE_CLE_PUBLIABLE" },
});
const paiement = await reponse.json();
if (paiement.status === "succeeded") {
  // Affichez « Paiement reçu ». La livraison, elle, se décide sur votre serveur.
}
const reponse = await fetch(`https://moncashapi.fedtopup.com/api/v1/payments/${paiementId}`, {
  headers: { Authorization: `Bearer ${process.env.MONCASHAPI_SECRET_KEY}` },
});
const paiement = await reponse.json();
if (reponse.ok && paiement.status === "succeeded" && paiement.amount === montantAttendu) {
  // Le paiement est réglé, du bon montant : livrez la commande paiement.reference
}
<?php
$ch = curl_init("https://moncashapi.fedtopup.com/api/v1/payments/" . rawurlencode($paiementId));
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 20,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("MONCASHAPI_SECRET_KEY")],
]);
$paiement = json_decode(curl_exec($ch), true);
curl_close($ch);

if (($paiement["status"] ?? "") === "succeeded" && $paiement["amount"] == $montantAttendu) {
    // Le paiement est réglé, du bon montant : livrez la commande $paiement["reference"]
}
import os
import requests

reponse = requests.get(
    f"https://moncashapi.fedtopup.com/api/v1/payments/{paiement_id}",
    headers={"Authorization": f"Bearer {os.environ['MONCASHAPI_SECRET_KEY']}"},
    timeout=20,
)
paiement = reponse.json()
if reponse.ok and paiement["status"] == "succeeded" and paiement["amount"] == montant_attendu:
    ...  # le paiement est réglé, du bon montant : livrez la commande paiement["reference"]

Recevoir et vérifier un webhook

# Recalculer une signature à la main, pour vérifier votre code :
printf '%s.%s.%s' "$HORODATAGE" "$ID_EVENEMENT" "$CORPS_BRUT" \
  | openssl dgst -sha256 -hmac "$MONCASHAPI_WEBHOOK_SECRET"
# Le résultat doit être égal à la valeur qui suit « v1= » dans X-MoncashAPI-Signature.
// Web Crypto : fonctionne dans les fonctions « edge », Deno, Bun et Node 18+.
export async function verifier(request, secret) {
  const corps = await request.text(); // le corps BRUT, avant tout JSON.parse
  const horodatage = request.headers.get("X-MoncashAPI-Timestamp") ?? "";
  const evenement = request.headers.get("X-MoncashAPI-Event-ID") ?? "";
  const recues = (request.headers.get("X-MoncashAPI-Signature") ?? "")
    .split(",").map((s) => s.trim().replace(/^v1=/, ""));

  const cle = await crypto.subtle.importKey(
    "raw", new TextEncoder().encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
  const octets = await crypto.subtle.sign(
    "HMAC", cle, new TextEncoder().encode(`${horodatage}.${evenement}.${corps}`));
  const attendue = [...new Uint8Array(octets)].map((o) => o.toString(16).padStart(2, "0")).join("");

  const recent = Math.abs(Date.now() / 1000 - Number(horodatage)) < 300;
  if (!recent || !recues.includes(attendue)) return null;
  return JSON.parse(corps);
}
import crypto from "node:crypto";
import express from "express";

const app = express();

// express.raw : la signature porte sur les octets reçus, pas sur un JSON re-sérialisé.
app.post("/webhooks/moncashapi", express.raw({ type: "application/json" }), (req, res) => {
  const corps = req.body.toString("utf8");
  const horodatage = req.get("X-MoncashAPI-Timestamp") ?? "";
  const evenement = req.get("X-MoncashAPI-Event-ID") ?? "";
  const recues = (req.get("X-MoncashAPI-Signature") ?? "")
    .split(",").map((s) => s.trim().replace(/^v1=/, ""));

  const attendue = crypto
    .createHmac("sha256", process.env.MONCASHAPI_WEBHOOK_SECRET)
    .update(`${horodatage}.${evenement}.${corps}`)
    .digest("hex");

  const valide = recues.some((s) =>
    s.length === attendue.length && crypto.timingSafeEqual(Buffer.from(s), Buffer.from(attendue)));
  const recent = Math.abs(Date.now() / 1000 - Number(horodatage)) < 300;
  if (!valide || !recent) return res.sendStatus(401);

  const message = JSON.parse(corps);
  // Un même événement peut arriver deux fois : ignorez un message.id déjà traité.
  if (message.type === "payment.succeeded") {
    const paiement = message.data.object;
    // paiement.reference est votre numéro de commande : marquez-la payée.
  }
  res.sendStatus(200);
});
<?php
$corps = file_get_contents("php://input"); // le corps BRUT
$horodatage = $_SERVER["HTTP_X_MONCASHAPI_TIMESTAMP"] ?? "";
$evenement = $_SERVER["HTTP_X_MONCASHAPI_EVENT_ID"] ?? "";
$recues = array_map(
    fn($s) => preg_replace('/^v1=/', '', trim($s)),
    explode(",", $_SERVER["HTTP_X_MONCASHAPI_SIGNATURE"] ?? "")
);

$attendue = hash_hmac("sha256", "$horodatage.$evenement.$corps", getenv("MONCASHAPI_WEBHOOK_SECRET"));
$valide = false;
foreach ($recues as $s) {
    if (hash_equals($attendue, $s)) { $valide = true; }
}
if (!$valide || abs(time() - (int) $horodatage) > 300) {
    http_response_code(401);
    exit;
}

$message = json_decode($corps, true);
// Un même événement peut arriver deux fois : ignorez un $message["id"] déjà traité.
if ($message["type"] === "payment.succeeded") {
    $paiement = $message["data"]["object"];
    // $paiement["reference"] est votre numéro de commande : marquez-la payée.
}
http_response_code(200);
import hashlib
import hmac
import os
import time

from flask import Flask, request

app = Flask(__name__)


@app.post("/webhooks/moncashapi")
def webhook():
    corps = request.get_data()  # le corps BRUT
    horodatage = request.headers.get("X-MoncashAPI-Timestamp", "")
    evenement = request.headers.get("X-MoncashAPI-Event-ID", "")
    recues = [s.strip().removeprefix("v1=")
              for s in request.headers.get("X-MoncashAPI-Signature", "").split(",")]

    attendue = hmac.new(
        os.environ["MONCASHAPI_WEBHOOK_SECRET"].encode(),
        f"{horodatage}.{evenement}.".encode() + corps,
        hashlib.sha256,
    ).hexdigest()

    valide = any(hmac.compare_digest(attendue, s) for s in recues)
    recent = horodatage.isdigit() and abs(time.time() - int(horodatage)) < 300
    if not (valide and recent):
        return "", 401

    message = request.get_json()
    # Un même événement peut arriver deux fois : ignorez un message["id"] déjà traité.
    if message["type"] == "payment.succeeded":
        paiement = message["data"]["object"]
        # paiement["reference"] est votre numéro de commande : marquez-la payée.
    return "", 200

Lire le solde

curl https://moncashapi.fedtopup.com/api/v1/balance \
  -H "Authorization: Bearer $MONCASHAPI_SECRET_KEY"
const reponse = await fetch("https://moncashapi.fedtopup.com/api/v1/balance", {
  headers: { Authorization: `Bearer ${process.env.MONCASHAPI_SECRET_KEY}` },
});
const solde = await reponse.json(); // { available, reserved, currency }
const reponse = await fetch("https://moncashapi.fedtopup.com/api/v1/balance", {
  headers: { Authorization: `Bearer ${process.env.MONCASHAPI_SECRET_KEY}` },
});
const { available, reserved } = await reponse.json();
<?php
$ch = curl_init("https://moncashapi.fedtopup.com/api/v1/balance");
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("MONCASHAPI_SECRET_KEY")],
]);
$solde = json_decode(curl_exec($ch), true);
curl_close($ch);
import os
import requests

solde = requests.get(
    "https://moncashapi.fedtopup.com/api/v1/balance",
    headers={"Authorization": f"Bearer {os.environ['MONCASHAPI_SECRET_KEY']}"},
    timeout=20,
).json()

Demander un retrait

curl https://moncashapi.fedtopup.com/api/v1/withdrawals \
  -H "Authorization: Bearer $MONCASHAPI_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: retrait-2026-09-30-001" \
  -d '{
    "amount": 10000,
    "wallet": "moncash",
    "phone": "37123456",
    "name": "Jean Pierre"
  }'
const reponse = await fetch("https://moncashapi.fedtopup.com/api/v1/withdrawals", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MONCASHAPI_SECRET_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "retrait-2026-09-30-001", // obligatoire
  },
  body: JSON.stringify({ amount: 10000, wallet: "moncash", phone: "37123456", name: "Jean Pierre" }),
});
const retrait = await reponse.json();
// retrait.status === "requested" : la demande attend notre équipe
import { randomUUID } from "node:crypto";

// Gardez la clé d'idempotence avec votre demande : en cas de coupure, renvoyez la MÊME.
const cle = randomUUID();
const reponse = await fetch("https://moncashapi.fedtopup.com/api/v1/withdrawals", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.MONCASHAPI_SECRET_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": cle,
  },
  body: JSON.stringify({ amount: 10000, wallet: "moncash", phone: "37123456", name: "Jean Pierre" }),
});
const retrait = await reponse.json();
if (!reponse.ok) throw new Error(retrait.error.code);
<?php
$ch = curl_init("https://moncashapi.fedtopup.com/api/v1/withdrawals");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 20,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("MONCASHAPI_SECRET_KEY"),
        "Content-Type: application/json",
        "Idempotency-Key: retrait-2026-09-30-001",
    ],
    CURLOPT_POSTFIELDS => json_encode([
        "amount" => 10000,
        "wallet" => "moncash",
        "phone" => "37123456",
        "name" => "Jean Pierre",
    ]),
]);
$retrait = json_decode(curl_exec($ch), true);
curl_close($ch);
import os
import requests

reponse = requests.post(
    "https://moncashapi.fedtopup.com/api/v1/withdrawals",
    headers={
        "Authorization": f"Bearer {os.environ['MONCASHAPI_SECRET_KEY']}",
        "Idempotency-Key": "retrait-2026-09-30-001",
    },
    json={"amount": 10000, "wallet": "moncash", "phone": "37123456", "name": "Jean Pierre"},
    timeout=20,
)
retrait = reponse.json()
Une question sur l’intégration ?