PaymentBot · API partenairesBack-office

Documentation API

Création de paiements Mobile Money, suivi asynchrone et notifications de résultat.

Les exemples sont fictifs. Obtenez vos identifiants et les routes opérateurs autorisées auprès de votre administrateur avant les essais.

Environnements

EnvironnementURL de base
Recettehttps://paymentbot-rec.softandsolutions.com/api/public/v1
Productionhttps://paymentbot.softandsolutions.com/api/public/v1

Les clés et données sont propres à chaque environnement. Utilisez la recette pour qualifier votre intégration. Recette ne signifie pas simulation opérateur : un appareil relié à une SIM réelle peut effectuer un paiement réel.

Les exemples ci-dessous ciblent uniquement la recette. La page ne lance aucune requête de paiement.

Authentification

Chaque appel exige le header X-API-Key. Pour créer une transaction, ajoutez Content-Type: application/json et Idempotency-Key (120 caractères maximum).

Conservez la clé côté serveur, jamais dans un navigateur, une application cliente ou une URL. Les restrictions IP, les permissions sur les agents et la limite de requêtes par minute sont définies pour votre client API.

X-API-Key: <VOTRE_CLE_API_RECETTE>
Idempotency-Key: commande-exemple-001
Content-Type: application/json

Créer une transaction

POST /transactions
ChampObligationFormat
transactionIdOptionnelIdentifiant unique, 80 caractères maximum ; généré si absent.
amountRequisNombre JSON, minimum 0.01, soumis aux règles de l’opérateur. Pas de séparateur de milliers.
destNumberRequisNuméro bénéficiaire sous forme de chaîne, 40 caractères maximum.
countryRequisCode pays configuré, par exemple CMR ou CIV (8 caractères maximum).
operatorRequisCode opérateur configuré, par exemple MTNCMR (40 caractères maximum).
clientIdSelon affectationIdentifiant de l’agent autorisé, 80 caractères maximum. Facultatif si un agent unique ou par défaut est défini.
externalReferenceOptionnelVotre référence métier, 120 caractères maximum. Utilisez une référence unique par paiement.

Le nom technique clientId désigne l’agent qui porte les appareils, pas le client API. La devise est déterminée par le pays ; aucun champ currency n’est attendu dans la requête.

curl --request POST \
  'https://paymentbot-rec.softandsolutions.com/api/public/v1/transactions' \
  --header 'X-API-Key: <VOTRE_CLE_API_RECETTE>' \
  --header 'Idempotency-Key: commande-exemple-001' \
  --header 'Content-Type: application/json' \
  --data '{
    "transactionId": "EXEMPLE-TX-001",
    "amount": 500,
    "destNumber": "<NUMERO_BENEFICIAIRE_TEST>",
    "country": "CMR",
    "operator": "MTNCMR",
    "clientId": "<IDENTIFIANT_AGENT_RECETTE>",
    "externalReference": "commande-exemple-001"
  }'

201 Created signifie que la transaction est enregistrée, pas que le bénéficiaire a été crédité. Un rejeu idempotent retourne 200 OK.

{
  "transactionId": "EXEMPLE-TX-001",
  "clientId": "agent-exemple",
  "amount": 500,
  "destNumber": "<NUMERO_BENEFICIAIRE_TEST>",
  "country": "CMR",
  "operator": "MTNCMR",
  "status": "PENDING",
  "externalReference": "commande-exemple-001",
  "idempotentReplay": false,
  "createdDate": "2026-10-12T10:00:00Z"
}

Consulter le statut

GET /transactions/{transactionId}/status
GET /transactions/status?externalReference={reference}

Ces deux endpoints exigent X-API-Key. Ils ne retournent que les transactions appartenant au client API authentifié. Encodez les paramètres d’URL.

curl 'https://paymentbot-rec.softandsolutions.com/api/public/v1/transactions/EXEMPLE-TX-001/status' \
  --header 'X-API-Key: <VOTRE_CLE_API_RECETTE>'
{
  "transactionId": "EXEMPLE-TX-001",
  "externalReference": "commande-exemple-001",
  "amount": 500,
  "destNumber": "<NUMERO_BENEFICIAIRE_TEST>",
  "country": "CMR",
  "operator": "MTNCMR",
  "status": "PENDING",
  "deviceId": null,
  "resultMessage": null,
  "errorCode": null,
  "createdDate": "2026-10-12T10:00:00Z",
  "updatedDate": "2026-10-12T10:00:00Z",
  "operatorReferences": []
}

Idempotence et reprises

Associez une clé d’idempotence stable à chaque paiement métier. Après une coupure réseau ou une réponse incertaine, consultez le statut ou renvoyez la même clé avec le même payload. Une clé réutilisée avec un payload différent est refusée avec un 409.

Ne générez pas une nouvelle clé pour contourner un délai, une erreur concurrente ou un statut UNKNOWN : cela risquerait de créer un doublon. Un retry métier doit suivre la procédure de réconciliation de la plateforme.

Si le fractionnement est activé, suivez la transaction principale. Les sous-transactions ne produisent pas de callbacks partenaires individuels. Un paiement partiellement exécuté exige une réconciliation ; ce n’est pas une opération atomique avec annulation automatique.

Statuts

StatutSignification
PENDINGEn attente de prise en charge.
CLAIMEDRéclamée par un appareil.
PROCESSINGExécution en cours.
COMMITTINGÉtape de confirmation engagée ; ne pas recréer le paiement.
SUCCESSPaiement confirmé réussi.
FAILEDÉchec enregistré. Vérifier les détails et la réconciliation avant tout nouvel essai.
UNKNOWNRésultat incertain ; ni succès ni preuve d’absence de débit. Réconciliation nécessaire.
CANCELLEDTransaction annulée dans la plateforme.

Privilégiez les webhooks et espacez les consultations de statut. Une absence de callback ne prouve pas un échec.

Webhooks de résultat

Configurez une URL HTTPS de callback et un secret webhook dédié (32 caractères minimum). La plateforme envoie un POST JSON pour SUCCESS, FAILED et les annulations administratives CANCELLED. UNKNOWN ne déclenche pas de notification terminale.

Une annulation avant prise en charge émet transaction.cancelled, avec status: CANCELLED et l’identifiant de transaction d’origine. Un paiement fractionné émet uniquement le callback de son parent. Votre intégration doit accepter ce statut sans le convertir en échec d’exécution.

{
  "eventId": "00000000-0000-4000-8000-000000000001",
  "eventType": "transaction.success",
  "occurredAt": "2026-10-12T10:01:00Z",
  "transactionId": "EXEMPLE-TX-001",
  "externalReference": "commande-exemple-001",
  "clientId": "agent-exemple",
  "amount": 500,
  "destNumber": "<NUMERO_BENEFICIAIRE_TEST>",
  "country": "CMR",
  "operator": "MTNCMR",
  "status": "SUCCESS",
  "deviceId": "APPAREIL-EXEMPLE",
  "resultMessage": "Paiement confirmé",
  "errorCode": null,
  "operatorReferences": [
    {
      "reference": "EXEMPLE-OPERATEUR-001",
      "amount": 500,
      "currency": "XAF",
      "source": "SMS"
    }
  ]
}
X-PaymentBot-Event-Id: 00000000-0000-4000-8000-000000000001
X-PaymentBot-Signature: sha256=<HMAC_SHA256_HEXADECIMAL>

Vérifiez le HMAC-SHA256 du corps HTTP brut, avec le secret webhook, avant de traiter le JSON. Une re-sérialisation change la signature. Exemple Node.js :

import { createHmac, timingSafeEqual } from 'node:crypto';

function validSignature(rawBody, signature, secret) {
  if (typeof signature !== 'string' ||
      !/^sha256=[0-9a-f]{64}$/.test(signature)) return false;
  const expected = createHmac('sha256', secret).update(rawBody).digest();
  const received = Buffer.from(signature.slice(7), 'hex');
  return timingSafeEqual(expected, received);
}

Persistez l’événement et dédupliquez par eventId, puis répondez rapidement avec un code 2xx. Les livraisons peuvent être répétées ; chaque cycle comporte au maximum huit tentatives avec temporisation exponentielle. Un rejeu de notification conserve son eventId et ne relance pas le paiement.

operatorReferences contient les références des SMS rattachés sans ambiguïté à la tentative courante. Le tableau est vide si aucune preuve exploitable n’est disponible. Chaque entrée contient reference, amount, currency et source (SMS), plus partNumber pour un paiement fractionné. Il ne remplace pas externalReference. La présence d’une référence ne modifie pas le statut du paiement.

Sur activation par le SUPERADMIN pour votre Client API, un SMS rattaché après le callback peut déclencher transaction.reference_updated : même transactionId et statut, nouvel eventId, tableau actualisé. Traitez-le comme un enrichissement, sans comptabiliser un second paiement. Cette option est désactivée par défaut, sans rattrapage des SMS déjà rattachés. GET status expose les références même si l’option est désactivée. Les callbacks déjà créés restent immuables. Ignorez les champs inconnus pour accepter les évolutions additives.

Si plusieurs événements concernent la même transaction, ne supposez pas leur ordre de réception. Consultez son statut actuel en cas de divergence.

Agent virtuel de recette

À partir du backend 1.1.11, le SUPERADMIN peut activer un scénario sur votre Client API en recette. Utilisez vos identifiants de recette et les mêmes routes de création, GET status et callbacks. Aucun téléphone ni crédit Mobile Money n’est nécessaire.

Les créations simulées portent simulation: true dans GET status et les callbacks ; leurs références fictives portent source: SIMULATION. Aucun appel USSD, SMS réel ou mouvement d’argent n’est effectué. Les callbacks sont réellement envoyés : configurez une URL de test et ne créditez aucun compte réel.

Scénarios : succès, solde insuffisant, bénéficiaire sans compte, plafond opérateur, UNKNOWN à réconcilier, référence tardive, synchronisation retardée et fractionnement interrompu. Les contrôles d’accès API, quotas, antifraude et plafonds à la réception restent appliqués.

Le scénario est fixé lors de la création. Une désactivation ne convertit jamais une simulation en paiement réel. L’option « prochain paiement uniquement » repasse à OFF après une création acceptée ; les demandes suivantes reprennent le routage habituel. Utilisez un Client API dédié.

Un UNKNOWN ne produit pas de callback terminal avant réconciliation. Une référence tardive produit le second événement uniquement si cette notification est activée sur le Client API. Le fractionnement suit les plafonds configurés et ne notifie que le parent.

Ce simulateur valide le parcours serveur et l’intégration partenaire ; il ne teste pas l’accessibilité Android, les écrans opérateur ni la lecture et le rapprochement de vrais SMS. Le backend interdit sa configuration et son exécution hors recette.

Références opérateur et notifications complémentaires

Disponible à partir du backend 1.1.10. Le champ operatorReferences est ajouté aux deux GET status et aux nouveaux callbacks terminaux. Aucun nouvel APK n’est nécessaire si l’appareil remonte déjà les SMS structurés.

ChampTypeInterprétation
operatorReferencestableauVide si aucune référence exploitable ; ne remplace jamais le statut.
referencechaîneIdentifiant opaque fourni par l’opérateur : conserver les points, préfixes et zéros.
amountnombre JSONMontant de l’opération, ou de la tranche pour un paiement fractionné.
currencychaîneCode devise, par exemple XAF ou XOF.
sourcechaîneSMS pour une preuve opérateur ; SIMULATION pour une référence fictive en recette.
partNumberentier optionnelNuméro de tranche à partir de 1 ; absent pour un paiement simple.

externalReference reste votre référence métier et transactionId reste l’identifiant PaymentBot. Ne substituez aucun de ces champs par la référence opérateur.

Exemple : référence reçue après le résultat

{
  "eventId": "00000000-0000-4000-8000-000000000002",
  "eventType": "transaction.reference_updated",
  "occurredAt": "2026-11-05T10:01:10Z",
  "transactionId": "EXEMPLE-TX-001",
  "externalReference": "commande-exemple-001",
  "clientId": "agent-exemple",
  "amount": 500,
  "destNumber": "<NUMERO_BENEFICIAIRE_TEST>",
  "country": "CMR",
  "operator": "MTNCMR",
  "status": "SUCCESS",
  "deviceId": "APPAREIL-EXEMPLE",
  "resultMessage": "Paiement confirmé",
  "errorCode": null,
  "operatorReferences": [
    {
      "reference": "EXEMPLE-OPERATEUR-001",
      "amount": 500,
      "currency": "XAF",
      "source": "SMS"
    }
  ]
}

Le SUPERADMIN active cette option dans Clients API → Modifier → « Notifier les références opérateur reçues après le résultat ». Le réglage est OFF par défaut. Il n’active pas la validation automatique par SMS, qui est un paramètre distinct. Il ne relance aucun paiement.

SituationGET status / callbacks
SMS rattaché avant le callbackRéférence dans le callback de résultat ; aucun événement complémentaire nécessaire.
SMS rattaché après le callback, option OFFGET status enrichi ; aucun nouveau callback.
SMS rattaché après le callback, option ONÉvénement transaction.reference_updated avec le même transactionId, le statut courant et un nouvel eventId.
Doublon du même SMSPas de référence dupliquée ni d’événement supplémentaire pour le même contenu.
SMS candidat, ambigu ou ancienne tentativeRéférence exclue ; aucune modification du statut.
UNKNOWN avec référenceReste UNKNOWN ; aucun callback terminal émis par cet enrichissement.
Activation après des rattachements existantsAucun rattrapage des anciens rattachements.
DésactivationEmpêche de nouveaux événements complémentaires ; ceux déjà en file restent livrables.

Paiements fractionnés

{
  "transactionId": "EXEMPLE-PARENT",
  "status": "SUCCESS",
  "amount": 1400,
  "operatorReferences": [
    {
      "reference": "EXEMPLE-PART-1",
      "amount": 700,
      "currency": "XAF",
      "source": "SMS",
      "partNumber": 1
    },
    {
      "reference": "EXEMPLE-PART-2",
      "amount": 700,
      "currency": "XAF",
      "source": "SMS",
      "partNumber": 2
    }
  ]
}

Seul le parent émet les callbacks. Le tableau peut être incomplet si une tranche ne dispose pas d’un SMS exploitable ; ne déduisez pas le statut global du nombre de références.

Traitement côté partenaire

  1. Vérifier la signature HMAC du corps brut, puis dédupliquer par eventId.
  2. Sur transaction.reference_updated, enrichir le paiement existant sans créditer ni comptabiliser un second paiement.
  3. Ne pas supposer l’ordre de livraison : consulter GET status si les événements semblent contradictoires.
  4. Accepter les nouveaux champs JSON et distinguer ce type d’événement des notifications de résultat.

Les payloads déjà créés restent figés, y compris lors d’un retry de livraison. Seuls les SMS rattachés à la bonne tentative, au bon bénéficiaire, montant, appareil, agent, pays et opérateur sont retenus. Aucun SMS brut, nom, solde SIM ou PIN n’est ajouté à ces références. Une référence absente ne prouve pas un échec.

Erreurs et limites

HTTPInterprétation / action
400Payload ou header invalide, route opérateur indisponible, montant hors limites. Corriger la requête.
401Clé API absente ou invalide.
403Accès refusé : IP, état du client API ou permission sur l’agent.
404Transaction non trouvée pour ce client API.
409Conflit d’identifiant, d’idempotence ou de concurrence. Vérifier le statut avant de réessayer avec la même clé.
422SUBSCRIPTION_QUOTA_EXCEEDED : capacité mensuelle du forfait, tolérance comprise, dépassée.
429Limite de requêtes dépassée. Espacer les appels avec un délai progressif.
5xx / timeoutIndisponibilité ou résultat de requête incertain. Consulter le statut et conserver la clé d’idempotence.

Les seuils opérateurs, forfaits et permissions dépendent de votre configuration. N’utilisez pas le texte libre des messages comme unique règle de décision et n’assimilez pas une erreur HTTP à une preuve de non-débit.