Chargement de vos clés API…

Webhooks

Recevoir les événements Mobupay en temps réel.

Format d'un événement

Mobupay envoie une requête POST à votre endpoint avec un body JSON :

{
  "id": "evt_2NjK8mLk0093xY",
  "type": "payment.succeeded",
  "createdAt": "2026-05-14T12:34:56Z",
  "data": {
    "paymentId": "pay_xxxxxxxx",
    "amount": 1000,
    "currency": "XPF",
    "status": "transit",
    "reference": "SPR-xxxxxxxx",
    "customerId": "cus_xxxxxxxx",
    "customerCreated": true,
    "paymentMethodId": "pm_xxxxxxxx",
    "cardBrand": "CB",
    "cardLast4": "0007"
  }
}

Quand un client est créé pendant le paiement (paramètre saveCustomer), le champ customerId contient l'identifiant créé et customerCreated vaut true. Si une carte est enregistrée au passage, paymentMethodId est fourni.

Répondez 2xx dans les 10 secondes pour acquitter la réception. Tout autre code (4xx/5xx) ou timeout déclenchera un retry.

Liste des événements

ÉvénementDescription
payment.createdUn paiement vient d'être initié.
payment.authorizedLa carte du client a été autorisée (avant capture si MANUAL).
payment.capturedLe paiement est encaissé (les fonds sont transférés).
payment.failedLe paiement a échoué (refus carte).
payment.expiredLa session / le lien de paiement a expiré sans paiement.
payment.refundedUn remboursement total ou partiel a été effectué.
payment.partially_refundedUn remboursement partiel a été effectué.
payment.cancelledUn paiement autorisé non capturé a été annulé.
customer.createdUn nouveau client a été créé via l'API.
customer.updatedLes informations d'un client ont été modifiées.
payment_method.addedUne nouvelle carte a été tokenisée pour un client.
payment_method.removedUne carte tokenisée a été supprimée.
payout.createdUn virement de fonds vers votre compte bancaire a été initié.
payout.completedLe virement a été exécuté.
payout.failedLe virement a échoué.
dispute.createdUn litige (contestation) a été ouvert sur un paiement.
dispute.updatedLe statut d'un litige a évolué.

Événements plateforme

Si vous êtes une plateforme (marketplace), abonnez-vous à ces événements pour suivre le cycle de vie de vos sous-marchands affiliés : invitation, onboarding KYB, ouverture du compte de paiement et rattachement.

ÉvénementDescription
merchant.session.createdUne session d'invitation a été créée pour un sous-marchand.
merchant.session.expiredLe lien d'invitation d'un sous-marchand a expiré.
merchant.session.cancelledL'invitation d'un sous-marchand a été annulée.
merchant.createdLe sous-marchand a démarré son onboarding et a été créé (rattaché à votre plateforme).
merchant.openedLe compte de paiement du sous-marchand a été ouvert (KYB validé, prêt à encaisser).
merchant.rejectedL'onboarding du sous-marchand a été refusé.
merchant.updatedLes informations du sous-marchand ont été modifiées.
merchant.linkedUn marchand existant a été rattaché à votre plateforme.
merchant.unlinkedUn sous-marchand a été détaché de votre plateforme.

Vérification de signature

Chaque requête webhook contient un header X-Mobupay-Signature avec une signature HMAC-SHA256 du body. Vérifiez-la avant de traiter l'événement pour vous protéger des requêtes forgées.

Exemple Node.js :

import crypto from "node:crypto";

const SECRET = process.env.MOBUPAY_WEBHOOK_SECRET;

function verifySignature(rawBody, signature) {
  const expected = crypto
    .createHmac("sha256", SECRET)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

app.post("/webhook", (req, res) => {
  const signature = req.headers["x-mobupay-signature"];
  if (!verifySignature(req.rawBody, signature)) {
    return res.status(401).send("Invalid signature");
  }
  const event = JSON.parse(req.rawBody);
  // ... traiter l'event
  res.sendStatus(200);
});

Important : utilisez le body brut (non-parsé) pour calculer la signature. Un body re-serialisé par JSON.stringify peut produire un hash différent.

Signature V2 (anti-rejeu, recommandée)

En plus de X-Mobupay-Signature (V1, HMAC du corps), chaque livraison porte X-Mobupay-Timestamp (epoch secondes) et X-Mobupay-Signature-V2 = HMAC-SHA256 de {timestamp}.{corps}. Le timestamp étant signé, une requête interceptée ne peut pas être rejouée plus tard : rejetez-la si l'horodatage dépasse votre fenêtre de tolérance (5 min recommandé).

import crypto from "node:crypto";

function verifyV2(rawBody, headers, secret, toleranceSec = 300) {
  const ts = headers["x-mobupay-timestamp"];
  const sig = headers["x-mobupay-signature-v2"];
  if (!ts || !sig) return false;
  if (Math.abs(Date.now() / 1000 - Number(ts)) > toleranceSec) return false; // anti-rejeu
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${ts}.${rawBody}`)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}

V1 reste émise pour compatibilité. Les nouveaux intégrateurs (connecteurs e-commerce) doivent vérifier V2 + la fraîcheur du timestamp.

Politique de retry

Si votre endpoint ne répond pas 2xx dans les 10 secondes, Mobupay retry :

  • 1ère retry : après 1 min
  • 2ème : 5 min
  • 3ème : 30 min
  • 4ème : 2 h
  • 5ème : 12 h
  • Après 5 échecs : abandon (votre endpoint est considéré comme cassé). Vous pouvez voir l'historique dans le BO.

Idempotence côté receiver

À cause des retry, un même événement peut être livré plusieurs fois. Utilisez le champ id (préfixe evt_) pour dédupliquer côté serveur : stockez les IDs déjà traités et ignorez les doublons.