Documentation développeur
API Paiement
Encaisse des paiements depuis ton site ou ton app via pay.mepaye.com. Tu crées un paiement, tu rediriges l'acheteur vers notre page hébergée, et ton serveur reçoit un webhook signé quand c'est payé.
Introduction
L'API est REST/JSON. Base URL : https://mepaye.com/api/v1. Le modèle d'intégration :
- Crée un paiement (
POST /payments) → tu reçois unepayment_url. - Redirige l'acheteur vers cette URL (page hébergée pay.mepaye.com).
- L'acheteur paie. Les fonds arrivent dans ton wallet (commission Mepaye 5 %).
- Ton serveur reçoit un webhook signé
payment.succeeded→ tu livres.
L'acheteur ne voit que pay.mepaye.com — aucun agrégateur n'est exposé.
Authentification
Crée une clé dans le menu Développeur (la vérification d'identité KYC est obligatoire). Authentifie chaque requête avec l'en-tête :
Authorization: Bearer mp_live_xxxxxxxxDeux environnements, distingués par le préfixe : mp_live_ (encaissement réel) et mp_test_ (simulation, voir Mode test). La clé porte le scope payments:write. Ne l'expose jamais côté client — uniquement depuis ton serveur. Limite : 100 requêtes/minute par clé.
Créer un paiement
POST /api/v1/payments
En-têtes : Authorization, Content-Type: application/json, et Idempotency-Key (obligatoire).
Corps
amount— entier, en minor units (5000 = 5000 XOF). Minimum 100, pas de plafond.currency— doit être la devise de ta boutique.externalReference— ta référence de commande (renvoyée dans le webhook).buyer(optionnel) —name,email,phone,country(ISO-2). Préremplis & modifiables sur la page.
curl -X POST https://mepaye.com/api/v1/payments \
-H "Authorization: Bearer mp_live_xxx" \
-H "Idempotency-Key: cmd_8f2a1c" \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"currency": "XOF",
"externalReference": "CMD-2026-0142",
"buyer": {
"name": "Awa Diop",
"email": "awa@exemple.com",
"phone": "+221770000000",
"country": "SN"
}
}'const res = await fetch("https://mepaye.com/api/v1/payments", {
method: "POST",
headers: {
"Authorization": "Bearer " + process.env.MEPAYE_API_KEY,
"Idempotency-Key": order.id, // ta référence unique
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 5000, // minor units (5000 XOF)
currency: "XOF", // = devise de ta boutique
externalReference: order.id,
buyer: { name: "Awa Diop", email: "awa@exemple.com", country: "SN" },
}),
});
const payment = await res.json();
// Redirige l'acheteur :
redirect(payment.payment_url);Réponse
{
"id": "cmqz19fk40008jr1hxlyy6065",
"status": "pending",
"test": false,
"payment_url": "https://pay.mepaye.com/checkout/cmqz19fk40008jr1hxlyy6065",
"externalReference": "CMD-2026-0142",
"idempotent_replay": false
}Redirige l'acheteur vers payment_url. Idempotence : renvoie le même Idempotency-Key(ex. l'id de ta commande) et tu obtiens le même paiement (idempotent_replay: true) — jamais de double encaissement, même si tu rejoues la requête.
Webhooks
Configure ton URL, ton secret et tes events dans le menu Développeur → Webhooks. Events disponibles : payment.succeeded et payment.failed (testet live — le flag test du payload distingue).
POST https://ton-site.com/webhooks/mepaye
X-Mepaye-Event: payment.succeeded
X-Mepaye-Signature: sha256=2f1a…c9
X-Mepaye-Delivery: <id>
{
"event": "payment.succeeded",
"data": {
"id": "cmqz19fk40008jr1hxlyy6065",
"externalReference": "CMD-2026-0142",
"status": "PAID",
"amount": 5000,
"currency": "XOF",
"test": false,
"created_at": "2026-06-29T09:45:30.000Z"
},
"deliveredAt": "2026-06-29T09:45:31.200Z"
}On réessaie jusqu'à 5 fois (backoff exponentiel) tant que ton endpoint ne répond pas un 2xx ; après 20 échecs consécutifs, le webhook est désactivé. Bonnes pratiques : réponds 200 rapidement, et sois idempotent côté réception (un même X-Mepaye-Deliverypeut arriver plus d'une fois).
Vérifier la signature
Chaque webhook porte X-Mepaye-Signature: sha256=<hex>. C'est un HMAC-SHA256 du corps brut de la requête, avec le secret de ton webhook. Vérifie-le avantde traiter — sinon n'importe qui pourrait forger un événement.
const crypto = require("crypto");
app.post("/webhooks/mepaye", express.raw({ type: "*/*" }), (req, res) => {
const raw = req.body; // BUFFER brut (non parsé !)
const received = req.header("X-Mepaye-Signature"); // "sha256=..."
const expected =
"sha256=" + crypto.createHmac("sha256", process.env.MEPAYE_WEBHOOK_SECRET)
.update(raw).digest("hex");
const ok =
received &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!ok) return res.status(401).end();
const { event, data } = JSON.parse(raw);
// event = "payment.succeeded" | "payment.failed" ; data.test = mode test
// → livre le produit / débloque le service pour data.externalReference
res.status(200).end(); // réponds 2xx vite
});$raw = file_get_contents("php://input");
$received = $_SERVER["HTTP_X_MEPAYE_SIGNATURE"] ?? "";
$expected = "sha256=" . hash_hmac("sha256", $raw, getenv("MEPAYE_WEBHOOK_SECRET"));
if (!hash_equals($expected, $received)) {
http_response_code(401);
exit;
}
$payload = json_decode($raw, true);
// $payload["event"], $payload["data"]["externalReference"], $payload["data"]["test"]
http_response_code(200);⚠️ Calcule le HMAC sur le corps brut reçu (non re-sérialisé) et compare en timing-safe (timingSafeEqual / hash_equals).
Mode test
Une clé mp_test_ crée un paiement de test : la payment_url ouvre une page « Mode test » avec deux boutons — Simuler un paiement réussi / Simuler un échec. Aucun argent réel, aucun agrégateur, aucune pollution de tes ventes.
Le webhook correspondant (payment.succeeded ou payment.failed) est émis avec test: true. Tu testes ton intégration de bout en bout avant de passer en mp_live_.
Codes d'erreur
Format : { "error": { "code", "message" } }.
| HTTP | code | Signification |
|---|---|---|
| 401 | unauthenticated | Clé API manquante ou invalide. |
| 403 | forbidden_scope | La clé n'a pas le scope payments:write. |
| 403 | kyc_required | Vérification d'identité (KYC) requise pour encaisser. |
| 400 | idempotency_key_required | Header Idempotency-Key manquant. |
| 400 | test_mode_not_available | (historique) — les clés test sont désormais prises en charge. |
| 422 | invalid_body | Corps JSON invalide. |
| 422 | amount_too_low | Montant inférieur au minimum (100 minor units). |
| 422 | currency_mismatch | La devise doit être celle de ta boutique. |
| 429 | rate_limit_exceeded | Plus de 100 requêtes/minute pour cette clé. |
Prêt ? Crée ta clé dans le menu Développeur et commence en mode test.