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_urlimport 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 adresseLire 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 "", 200Lire 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 équipeimport { 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()