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 :

  1. Crée un paiement (POST /payments) → tu reçois une payment_url.
  2. Redirige l'acheteur vers cette URL (page hébergée pay.mepaye.com).
  3. L'acheteur paie. Les fonds arrivent dans ton wallet (commission Mepaye 5 %).
  4. 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 :

http
Authorization: Bearer mp_live_xxxxxxxx

Deux 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
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"
    }
  }'
javascript
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

json
{
  "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).

http
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.

node.js
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
});
php
$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" } }.

HTTPcodeSignification
401unauthenticatedClé API manquante ou invalide.
403forbidden_scopeLa clé n'a pas le scope payments:write.
403kyc_requiredVérification d'identité (KYC) requise pour encaisser.
400idempotency_key_requiredHeader Idempotency-Key manquant.
400test_mode_not_available(historique) — les clés test sont désormais prises en charge.
422invalid_bodyCorps JSON invalide.
422amount_too_lowMontant inférieur au minimum (100 minor units).
422currency_mismatchLa devise doit être celle de ta boutique.
429rate_limit_exceededPlus de 100 requêtes/minute pour cette clé.

Prêt ? Crée ta clé dans le menu Développeur et commence en mode test.