B-Cashby Bridge Docs
Documentation

Encaissez par Mobile Money, sans deviner ce qui se passe

Le paiement connecté, simple et fiable.

B-Cash expose une API REST pour encaisser et décaisser en francs CFA via les opérateurs Mobile Money camerounais, une console pour piloter votre activité, et un HUB qui exécute réellement les opérations. Cette documentation couvre chacun de ces nœuds.

API marchande

Ce que votre code appelle

Encaissements, décaissements, devis de frais, soldes, remboursements, webhooks. Authentification signée, montants en XAF entiers, idempotence obligatoire sur toute écriture d'argent.

Commencer par l'authentification →
Console marchand

Ce que votre équipe ouvre

Organisations, rôles, dossier d'activation, projets et clés, journal des livraisons de webhooks, règlements et audit.

Découvrir la console →
Orchestration

Ce qui relaie vers les opérateurs

La couche qui achemine chaque opération vers l'opérateur concerné, en suit l'issue et la reporte dans le registre. Vous observez des statuts, pas des mécanismes.

Ce que cela change pour vous →
Intégrations

Ce que vous branchez

Extension WooCommerce, applications mobiles via jeton éphémère, ou intégration directe depuis votre propre backend.

Choisir son intégration →

Hôtes

RôleHôteUsage
APIapi.gba-cm.onlineTous les appels /api/v1/* et /api/dashboard/*
Console marchanddashboard.gba-cm.onlineInterface marchand et back-office
Documentationdocs.gba-cm.onlineCette page
Hôtes à confirmer avant publication

Ces trois valeurs sont centralisées dans la constante HOSTS en bas du fichier : modifiez-les à un seul endroit, tous les exemples de la page se mettent à jour.

Conventions de lecture

Prise en main

Architecture du système

Cinq nœuds, un seul chemin pour l'argent. Comprendre qui parle à qui évite la moitié des questions de support.

Votre système appelle l'API B-Cash, qui achemine l'opération vers les opérateurs Mobile Money via sa couche d'orchestration, puis notifie votre endpoint par webhook. La console lit la même API. Votre système boutique · app · backend API B-Cash registre · frais · idempotence wallets · webhooks Orchestration acheminement et suivi d'issue Opérateurs MTN MoMo · Orange Money Console marchand équipe · dossier · clés Votre endpoint webhooks signés HTTPS signé événement signé

Qui fait quoi

NœudResponsabilitéQui s'y authentifie
API marchande
/api/v1/*
Enregistre l'intention de paiement, calcule les frais, tient le registre des soldes, garantit l'idempotence, émet les événements. Vos serveurs (clé HMAC) et vos clients mobiles (jeton éphémère)
Console
/api/dashboard/*
Lecture et pilotage humain : transactions, soldes, équipe, dossier d'activation, clés, audit. Vos utilisateurs (JWT de session)
Orchestration Relaie l'opération vers l'opérateur, en suit l'issue et la reporte dans le registre. Interne — aucune intégration marchande
Webhooks Pousse les changements d'état vers vos systèmes, avec signature et reprise automatique. B-Cash vers vous (signature HMAC à vérifier)
Opérateurs Débitent ou créditent réellement le compte Mobile Money du client. Hors périmètre B-Cash
Le point à retenir

Votre code ne parle jamais aux opérateurs. Il crée une transaction, puis il attend : soit en interrogeant la transaction, soit — de préférence — en recevant un webhook.

Prise en main

Démarrage en 5 minutes

Du compte vide au premier encaissement de test réussi.

1. Créer un compte et récupérer ses clés de test

Ouvrez un compte sur la console. Un projet sandbox et ses identifiants sont générés immédiatement. Le secret n'est affiché qu'une fois : copiez-le avant de fermer.

2. Vérifier que la clé fonctionne

GET /api/v1/me
# Sonde d'authentification : ne consomme rien, ne crée rien
curl https://api.gba-cm.online/api/v1/me \
  -H "X-BCash-Key: $BCASH_KEY" \
  -H "X-BCash-Timestamp: $(date +%s)" \
  -H "X-BCash-Signature: $SIGNATURE"

Le calcul de $SIGNATURE est décrit dans Authentification. Une réponse 200 confirme que clé, secret et horloge sont corrects.

3. Enregistrer un endpoint de notification

POST /api/v1/webhook-endpoints
{
  "url": "https://boutique.example.cm/webhooks/bcash",
  "events": ["transaction.succeeded", "transaction.failed"]
}

# → 201 { "reference": "whk_…", "secret": "whsec_…" }   ← le secret n'apparaît qu'ici

4. Créer un encaissement de test

POST /api/v1/collections
curl -X POST https://api.gba-cm.online/api/v1/collections \
  -H "X-BCash-Key: $BCASH_KEY" \
  -H "X-BCash-Timestamp: $(date +%s)" \
  -H "X-BCash-Signature: $SIGNATURE" \
  -H "Idempotency-Key: 8f1c2b4e-3a77-4c11-9f2d-6b0a5e91c7d3" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 25000,
    "operator": "MTN",
    "customer": { "phone": "+237650000000" },
    "merchantReference": "CMD-1042",
    "context": "API_DIRECT"
  }'

5. Forcer l'issue en sandbox

En test, personne ne va taper un code secret : provoquez vous-même le dénouement.

POST /api/v1/sandbox/transactions/{reference}/advance
{ "target": "SUCCEEDED" }

# La transaction suit le chemin nominal : écritures comptables,
# webhook transaction.succeeded livré et signé, trace d'audit.
Vous avez terminé

Votre endpoint vient de recevoir un événement signé. Il reste à en vérifier la signature (voir la marche à suivre) et à passer votre commande en « payée ».

Prise en main

Environnements & clés

Deux environnements, la même API, des clés distinctes qui ne se croisent jamais.

SandboxProduction
DisponibleDès l'inscriptionAprès approbation de votre dossier
ArgentAucun mouvement réelDébits et crédits réels
Issue d'une transactionVous la forcez (/sandbox/…/advance)Le client confirme sur son téléphone
WebhooksLivrés, signés, rejouablesIdentiques
Champ livemodefalsetrue

Anatomie d'un credential

Le secret ne se retrouve pas

Perdu, il ne peut pas être ré-affiché. Générez-en un nouveau : par défaut, l'ancien reste actif pendant votre déploiement, et vous le révoquez une fois la bascule terminée.

Rotation sans interruption

  1. Depuis la console, générez une nouvelle clé en mode sans interruption.
  2. Déployez le nouveau couple clé/secret sur vos serveurs.
  3. Vérifiez dans la console que la nouvelle clé enregistre des appels (colonne dernier usage).
  4. Révoquez l'ancienne.

La révocation immédiate existe aussi, pour un secret compromis : elle coupe l'ancienne clé à l'instant, et toute intégration encore en train de l'utiliser reçoit INVALID_SIGNATURE. La console vous demande de ressaisir le nom du projet avant de l'appliquer.

Fondamentaux

Authentification

Trois manières de s'authentifier, pour trois natures d'appelant. Choisir la bonne est la première décision d'architecture d'une intégration.

MéthodePour quiEn-têtes
Clé HMACVos serveurs, et eux seuls X-BCash-Key, X-BCash-Timestamp, X-BCash-Signature
Jeton éphémèreApplication mobile ou page web Authorization: Bearer bt_…
JWT de sessionUtilisateurs de la console Authorization: Bearer …
Une clé HMAC ne descend jamais dans un client

Une application mobile est décompilable et une page web est lisible. Toute clé qui y est embarquée est publique le jour de la première installation. Les clients utilisent le jeton éphémère, minté par votre backend.

Signer une requête HMAC

La chaîne à signer concatène quatre éléments séparés par des sauts de ligne :

Chaîne à signer
METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + sha256_hex(corps)

# METHOD    : verbe HTTP en majuscules, ex. POST
# PATH      : chemin seul, ex. /api/v1/collections (sans hôte, sans query)
# TIMESTAMP : secondes Unix, identique à l'en-tête X-BCash-Timestamp
# corps     : le corps JSON exact envoyé ; chaîne vide en GET
PHP
$body      = json_encode($payload, JSON_UNESCAPED_SLASHES);
$timestamp = (string) time();
$toSign    = "POST\n/api/v1/collections\n{$timestamp}\n" . hash('sha256', $body);
$signature = hash_hmac('sha256', $toSign, $secret);

// Envoyez exactement le même $body : re-sérialiser après signature invalide la requête.
Node.js
const body = JSON.stringify(payload);
const ts = Math.floor(Date.now() / 1000).toString();
const digest = crypto.createHash('sha256').update(body).digest('hex');
const signature = crypto
  .createHmac('sha256', secret)
  .update(`POST\n/api/v1/collections\n${ts}\n${digest}`)
  .digest('hex');
L'horloge compte

Un horodatage trop éloigné de l'heure du serveur fait échouer la signature. Synchronisez vos machines (NTP). Si vos appels échouent d'un coup sans changement de code, vérifiez l'heure système avant tout le reste.

Jeton éphémère pour les clients

Votre backend échange sa clé HMAC contre un jeton court, qu'il remet au client. Le jeton porte un sous-ensemble strict des scopes de la clé et vit au maximum 900 secondes.

POST/api/v1/auth/tokenHMAC uniquement
Requête et réponse
// Requête, signée en HMAC depuis VOTRE serveur
{ "scopes": ["collections:create", "transactions:read"], "ttl": 600 }

// Réponse
{ "token": "bt_9f2c…", "expiresIn": 600, "scopes": ["collections:create", "transactions:read"] }
Fondamentaux

Idempotence

Le réseau mobile coupe. La règle qui garantit qu'une coupure ne débite jamais deux fois votre client.

Toute route qui déplace de l'argent exige l'en-tête Idempotency-Key, un UUID que vous générez et que vous conservez le temps de l'opération.

SituationRéponse de l'API
Première requête avec cette cléL'opération est exécutée, la réponse est mémorisée
Même clé, même corpsLa réponse mémorisée est renvoyée — aucune seconde opération
Même clé, corps différent409 IDEMPOTENCY_CONFLICT
En-tête absent400 — la requête est refusée

Comment choisir sa clé

Rattachez la clé à l'intention métier, pas à la tentative technique. Une commande qu'on paie une fois mérite une clé stable ; si vous en générez une nouvelle à chaque clic, vous autorisez le double paiement que l'idempotence était censée empêcher.

Bon réflexe
// À la création de la commande, une fois pour toutes :
order.paymentIdempotencyKey ??= crypto.randomUUID();
// Toutes les tentatives de paiement de CETTE commande réutilisent cette clé.

Retrouver une transaction après une coupure

Si vous perdez la réponse, ne recréez rien : interrogez.

GET/api/v1/transactions/by-idempotency-key/{key}
GET/api/v1/transactions/by-merchant-reference/{reference}

Ces deux routes existent précisément pour cette situation. La seconde suppose que vous ayez renseigné merchantReference à la création — faites-le systématiquement.

Fondamentaux

Montants & devise

Le franc CFA n'a pas de sous-unité. Cette simplicité est un piège pour qui vient d'une API en centimes.

Fondamentaux

Erreurs & correlation ID

Toutes les erreurs partagent la même enveloppe, et chacune porte de quoi être retrouvée par le support.

Enveloppe d'erreur
{
  "error": {
    "code": "INSUFFICIENT_FUNDS",           // code machine stable
    "message": "Solde insuffisant.",        // texte lisible, susceptible d'évoluer
    "correlationId": "c7f3a91b4e2d",        // à citer au support
    "retryable": false,                     // rejouer peut-il aider ?
    "retryAfter": null,                     // délai conseillé, en secondes
    "details": {}                             // contexte selon le code
  }
}

Comment réagir

Codes HTTP

StatutSensCe qu'il faut faire
400Requête malforméeCorriger le corps ou les en-têtes
401Signature ou jeton invalideVérifier clé, secret, horloge
403Scope manquant, ou environnement interditVérifier les scopes du credential
404Ressource inexistante dans votre périmètreVérifier la référence
409Conflit d'état ou d'idempotenceLire code : l'état actuel interdit l'action
422Requête bien formée mais métier invalideLire details
429Trop de requêtesAttendre retryAfter
500Incident côté B-CashRejouer avec la même clé, puis signaler le correlationId
503Service temporairement indisponibleRejouer avec un délai croissant

Le catalogue exhaustif des codes machine est publié par l'API elle-même : GET /api/v1/metaerrors.catalog. Consommez-le plutôt que de recopier une liste qui vieillira.

Fondamentaux

Pagination & filtres

Les collections sont paginées côté serveur. Ne rapatriez jamais tout pour filtrer en mémoire.

GET/api/v1/transactions?page=1&itemsPerPage=25
ParamètreRôle
pageNuméro de page, à partir de 1
itemsPerPageTaille de page ; plafonnée côté serveur
statusFiltre sur un statut de la liste stable
merchantReferenceVotre propre référence

La réponse porte le total ; ne le recalculez pas à partir du nombre d'éléments reçus.

Encaisser & décaisser

Opérateurs

La liste est dynamique : elle porte l'état de service de chaque opérateur.

GET/api/v1/operatorspublic
ÉtatSensComportement attendu de votre interface
ACTIVEService nominalProposer l'opérateur
DEGRADEDLenteurs ou taux d'échec anormalProposer, en signalant un délai possible
DOWNIndisponibleMasquer ou griser, sans redéploiement
Pourquoi ne pas coder la liste en dur

Le champ operator est obligatoire à la création. Si vous figez « MTN et Orange » dans votre code, une panne opérateur devient une mise en production chez vous. En lisant cette route, elle devient un simple changement d'affichage.

Encaisser & décaisser

Frais & devis

Deux questions différentes se cachent derrière « combien ça coûte ». L'API vous demande laquelle vous posez.

POST/api/v1/fees/quoteaucun effet financier

Choisir sa base de calcul

amountBasisCe que représente amountQui absorbe les frais
GROSS (défaut)Le montant débité au client Vous. merchantNetAmount = amount − frais
ORDER_NETLe net que vous voulez encaisser Le client. customerPayableAmount = amount + frais, calculé exactement
Devis — le client paie les frais en sus
// Requête
{
  "type": "COLLECTION",
  "context": "WOOCOMMERCE",
  "amount": 25000,
  "amountBasis": "ORDER_NET",
  "operator": "MTN"
}

// Réponse
{
  "reference": "fq_7Q4K…",
  "orderAmount": 25000,
  "customerFeeAmount": 500,
  "customerPayableAmount": 25500,   // ← à utiliser comme "amount" à la création
  "merchantNetAmount": 25000,
  "feePayer": "CUSTOMER",
  "ruleVersion": "2026-07-01",
  "expiresAt": "2026-07-27T10:15:00Z"   // 900 s
}

Consommer un devis

Pour garantir au client le prix affiché, passez feeQuoteId à la création — et amount doit alors valoir exactement customerPayableAmount. Toute autre valeur est refusée : c'est la protection contre l'écran qui affiche un prix et l'API qui en débite un autre.

Encaisser & décaisser

Encaissements

La route à utiliser dans neuf cas sur dix : elle fixe le type et résout votre wallet destinataire toute seule.

POST/api/v1/collectionsscope collections:create

Corps de la requête

ChampRequisDescription
amountouiEntier XAF. Avec feeQuoteId, doit égaler customerPayableAmount.
operatorouiCode issu de GET /v1/operators.
customer.phoneouiFormat international, ex. +237650000000.
customer.displayNamenonNom affiché côté suivi.
merchantReferencenonVotre référence — mettez-la, elle vous sauvera un jour.
feeQuoteIdnonDevis à consommer.
contextnonOrigine de l'appel : WOOCOMMERCE, MOBILE_APP, API_DIRECT
metadatanonObjet plat, 32 clés max, 4 096 octets max. Préfixe bcash_ réservé.
callbackUrlDéprécié. Accepté, mais jamais utilisé pour livrer.
Les métadonnées sont visibles

Elles apparaissent dans la console et dans le corps des webhooks. N'y mettez ni pièce d'identité, ni mot de passe, ni donnée bancaire : c'est un champ de contexte, pas un coffre.

Réponse

201 Created
{
  "reference": "TRX7QK4M2",
  "type": "COLLECTION",
  "status": "PENDING_CONFIRMATION",
  "amount": 25500,
  "feeAmount": 500,
  "netAmount": 25000,
  "merchantReference": "CMD-1042",
  "nextAction": {
    "type": "AWAIT_MOBILE_CONFIRMATION",
    "displayMessage": "Confirmez le paiement sur votre téléphone.",
    "expiresAt": "2026-07-27T10:05:00Z"
  },
  "createdAt": "2026-07-27T10:00:00Z"
}

Utiliser nextAction

Ce bloc vous dit quoi afficher pendant l'attente, sans que vous ayez à inventer un texte par opérateur :

N'inventez pas votre propre délai d'expiration

expiresAt fait autorité. Un écran qui abandonne avant cette date affiche un échec pour une transaction qui va réussir — et votre client paiera deux fois.

Encaisser & décaisser

Décaissements

Envoyer de l'argent depuis votre solde vers un numéro Mobile Money.

POST/api/v1/payoutsscope payouts:create
Requête
{
  "amount": 50000,                       // ce que REÇOIT le bénéficiaire
  "operator": "ORANGE",
  "recipient": { "phone": "+237690000000", "name": "Fournisseur ALBA" },
  "merchantReference": "FACT-2026-118"
}
Votre wallet est débité de plus que amount

amount est le montant qui arrive chez le bénéficiaire ; les frais s'ajoutent au débit. Demandez un devis avant si vous devez afficher le coût total à un utilisateur.

Un décaissement échoue si le solde disponible est insuffisant : le montant nécessaire est réservé dès la création, puis libéré en cas d'échec.

Encaisser & décaisser

Cycle de vie d'une transaction

Douze statuts, quatre terminaux. Une transaction n'est jamais « peut-être payée ».

CREATEDFEE_QUOTEDHELDQUEUED PROCESSINGPENDING_CONFIRMATIONSUCCEEDED
StatutSensTerminal
CREATEDLa transaction est enregistrée, rien n'est engagé.non
FEE_QUOTEDLes frais sont fixés pour cette transaction.non
HELDLes fonds nécessaires sont réservés sur le wallet source.non
QUEUEDEn file d'exécution. Attend un worker ou un terminal.non
PROCESSINGExécution en cours chez l'opérateur. Plus annulable.non
PENDING_CONFIRMATIONLe client doit saisir son code secret.non
SUCCEEDEDL'argent a bougé. Livrez la commande.oui
FAILEDRefus, solde insuffisant, mauvais code… Aucun mouvement.oui
CANCELLEDAnnulée avant exécution. Fonds réservés libérés.oui
EXPIREDLe client n'a pas confirmé à temps.oui
REVERSEDIntégralement remboursée.oui, après SUCCEEDED
PENDING_RECONCILIATIONIssue indéterminée : ni succès, ni échec. En cours de rapprochement.non
Ne traitez jamais PENDING_RECONCILIATION comme un échec

Ce statut signifie exactement : « l'argent a peut-être bougé, nous vérifions ». Ni livraison, ni remboursement, ni nouvelle tentative tant que la transaction n'a pas basculé vers un statut terminal. Le traiter comme un échec, c'est risquer de livrer deux fois ou de rembourser un paiement qui n'a jamais eu lieu. La résolution est décrite dans Quand l'issue n'est pas confirmée.

Suivre une transaction

GET/api/v1/transactions/{reference}

Deux stratégies, et l'une est nettement meilleure :

Encaisser & décaisser

Annulation & remboursement

Annuler

POST/api/v1/transactions/{reference}/cancel

Possible uniquement avant exécution : CREATED, FEE_QUOTED, HELD, QUEUED. Une transaction en PROCESSING ne s'annule pas — l'argent est peut-être déjà en mouvement. L'appel libère les fonds réservés et reste idempotent : annuler une transaction déjà annulée renvoie 200.

Rembourser

POST/api/v1/transactions/{reference}/refundscope refunds:create
Remboursement partiel
POST /api/v1/transactions/TRX7QK4M2/refund
Idempotency-Key: 3d9a1c77-2f40-4b8e-9c15-77e0a2b4d611

{ "amount": 5000, "reason": "Article manquant" }
Encaisser & décaisser

Soldes & wallets

Un solde n'est pas un nombre unique : trois compartiments cohabitent.

GET/api/v1/wallets/{reference}
CompartimentCe qu'il contient
availableUtilisable immédiatement : décaissement, règlement.
reservedImmobilisé par des opérations en cours. Ni disponible, ni perdu.
pendingEncaissements dont l'issue n'est pas encore consolidée.

Le wallet marchand est créé automatiquement au premier encaissement. Un wallet peut être gelé par la conformité : la consultation reste possible, les mouvements sont bloqués. Le gel d'un wallet et la suspension d'une organisation sont deux mécanismes distincts.

Notifications

Webhooks : principe

Vous n'attendez pas l'API : c'est elle qui vous appelle quand l'état change.

Corps livré sur votre endpoint
{
  "id": "evt_9K2mQ7…",              // stable entre les tentatives → clé de déduplication
  "type": "transaction.succeeded",
  "apiVersion": "2026-07-20",
  "createdAt": "2026-07-27T10:02:13Z",
  "livemode": true,                  // false en sandbox
  "data": {
    "object": { /* la transaction complète */ },
    "previousAttributes": { "status": "PENDING_CONFIRMATION" }
  }
}

Les quatre règles à respecter

  1. Vérifiez la signature avant de lire le corps. Un endpoint non vérifié est une porte ouverte : n'importe qui peut poster « paiement réussi ».
  2. Répondez 2xx vite, sous quelques secondes. Accusez réception, puis traitez en tâche de fond. Un traitement lent déclenche des reprises inutiles.
  3. Dédupliquez sur id. Le même evt_… peut arriver deux fois ; votre traitement doit être sans effet la seconde fois.
  4. Ne supposez pas l'ordre. Un transaction.succeeded peut précéder un transaction.pending_confirmation. Fiez-vous au statut porté par l'objet, pas à l'ordre d'arrivée.
Notifications

Vérifier la signature

La seule étape non négociable de l'intégration.

En-têteContenu
X-BCash-TimestampSecondes Unix de l'émission
X-BCash-SignatureHMAC_SHA256(secret, timestamp + "\n" + corps_brut)
X-BCash-Signature-PreviousPrésent 24 h après une rotation : signature avec l'ancien secret
Node.js / Express
// Le corps BRUT est indispensable : un JSON re-sérialisé ne redonne pas les mêmes octets.
app.post('/webhooks/bcash', express.raw({ type: 'application/json' }), (req, res) => {
  const ts = req.get('X-BCash-Timestamp');
  const raw = req.body.toString('utf8');

  // Fenêtre anti-rejeu : on refuse un événement trop ancien.
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(400);

  const expected = crypto.createHmac('sha256', process.env.BCASH_WEBHOOK_SECRET)
    .update(`${ts}\n${raw}`).digest('hex');

  const candidates = [req.get('X-BCash-Signature'), req.get('X-BCash-Signature-Previous')].filter(Boolean);
  const valid = candidates.some(sig =>
    sig.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected)));   // comparaison à temps constant

  if (!valid) return res.sendStatus(401);

  res.sendStatus(200);              // on accuse réception AVANT de traiter
  queue.push(JSON.parse(raw));       // traitement asynchrone, dédupliqué sur event.id
});
PHP
$raw = file_get_contents('php://input');
$ts  = $_SERVER['HTTP_X_BCASH_TIMESTAMP'] ?? '';

if (abs(time() - (int) $ts) > 300) { http_response_code(400); exit; }

$expected = hash_hmac('sha256', "{$ts}\n{$raw}", $secret);
$ok = hash_equals($expected, $_SERVER['HTTP_X_BCASH_SIGNATURE'] ?? '')
   || hash_equals($expected, $_SERVER['HTTP_X_BCASH_SIGNATURE_PREVIOUS'] ?? '');

if (!$ok) { http_response_code(401); exit; }
Trois erreurs classiques

Utiliser le corps déjà parsé au lieu des octets bruts ; comparer les signatures avec == au lieu d'une comparaison à temps constant ; ignorer X-BCash-Signature-Previous et casser ses livraisons pendant les 24 heures d'une rotation.

Notifications

Gérer ses endpoints

GET/api/v1/webhook-endpoints
POST/api/v1/webhook-endpointsHTTPS obligatoire · scope webhooks:write
PATCH/api/v1/webhook-endpoints/{reference}
DELETE/api/v1/webhook-endpoints/{reference}
POST/api/v1/webhook-endpoints/{reference}/testémet un vrai webhook.test
POST/api/v1/webhook-endpoints/{reference}/rotate-secretchevauchement 24 h
POST/api/v1/webhook-endpoints/{reference}/enable
POST/api/v1/webhook-endpoints/{reference}/disable

La référence d'un endpoint a la forme whk_… et son secret whsec_…. Le secret n'est affiché qu'à la création et à la rotation ; l'objet ne renvoie ensuite que secretLast4.

Rotation du secret

Le nouveau secret signe immédiatement dans X-BCash-Signature, tandis que l'ancien co-signe dans X-BCash-Signature-Previous pendant 24 heures. Si votre vérification accepte les deux en-têtes, la rotation ne fait perdre aucune livraison. La date de fin de chevauchement est lisible dans previousSecretValidUntil.

Filtrer les événements

Le champ events vide signifie « tous ». Restreindre est plus sain : votre endpoint ne reçoit que ce qu'il sait traiter, et les événements ajoutés plus tard ne le surprennent pas.

Notifications

Livraison, reprise, doublons

SituationComportement
Vous répondez 2xxLivraison réussie, terminée.
Vous répondez autre chose, ou ne répondez pasNouvelles tentatives espacées, à intervalles croissants.
Échecs répétésL'endpoint bascule en FAILING ; la console vous alerte.
Endpoint durablement injoignablePeut être désactivé ; réactivez-le après correction.

Journal et relivraison

La console conserve chaque tentative : code HTTP, délai de réponse, corps renvoyé. Une livraison peut être rejouée manuellement.

GET/api/dashboard/webhook-deliveries
POST/api/dashboard/webhook-deliveries/{eventId}/replay
Rejouer un événement ne rejoue pas le paiement

La relivraison renvoie une notification, jamais un mouvement d'argent. En revanche, si votre traitement n'est pas dédupliqué sur id, vous pouvez expédier deux fois la même commande. La déduplication est votre responsabilité.

Politique de référence

Les paramètres exacts (nombre de tentatives, intervalles, fenêtre de tolérance) sont publiés par l'API : GET /api/v1/metawebhookDelivery. Lisez-les plutôt que de les supposer ; ils peuvent être ajustés sans changement de version majeure.

Tester

Sandbox

Le même code, les mêmes réponses, les mêmes webhooks — sans un franc en jeu.

POST/api/v1/sandbox/transactions/{reference}/advancecredentials sandbox uniquement

Cette route force l'issue d'une transaction non terminale, en empruntant le chemin nominal : écritures comptables, webhooks signés, trace d'audit. Ce n'est pas une simulation de façade — c'est la vraie mécanique, déclenchée à la main.

Forcer une issue
{ "target": "SUCCEEDED" }   // ou "FAILED", "EXPIRED"

Appelée avec un credential de production, elle répond 403. La liste à jour des scénarios est publiée dans GET /api/v1/metasandbox.

Tester

Scénarios de test

Ce qu'il faut avoir essayé avant de demander l'accès production. Le chemin heureux ne prouve presque rien.

ScénarioComment le provoquerCe que votre système doit faire
Paiement réussiadvance → SUCCEEDEDMarquer payé une seule fois, livrer
Paiement refuséadvance → FAILEDMessage clair, commande réouvrable
Client qui ne confirme pasadvance → EXPIREDLibérer le panier, proposer de réessayer
Webhook reçu deux foisRejouer la livraison depuis la consoleTraitement sans effet la seconde fois
Webhook hors ordreRejouer un événement ancienSe fier au statut, pas à l'ordre d'arrivée
Coupure après l'appelInterrompre la requête, puis rejouer la même cléUne seule transaction créée
Signature invalidePoster sur votre endpoint sans signatureRejet en 401
Rotation de secretFaire tourner le secret de l'endpointAucune livraison perdue pendant 24 h
Opérateur indisponibleLire un opérateur DOWNLe masquer dans le sélecteur
Remboursement partiel puis totalDeux appels /refundPassage à REVERSED géré
Tester

Passer en production

  1. Complétez votre dossier dans la console : profil légal, pièces justificatives, numéro Mobile Money de règlement, téléphone vérifié.
  2. Soumettez-le. Seul le propriétaire du compte peut le faire : c'est un acte qui engage l'organisation.
  3. Répondez aux demandes de l'équipe conformité, le cas échéant. Le motif exact est affiché dans la console.
  4. Après approbation, créez une clé de production depuis votre projet.
  5. Basculez vos variables d'environnement et vérifiez avec un petit montant réel.
Vos intégrations de test ne bougent pas

Les clés sandbox restent actives après le passage en production : votre environnement de recette continue de fonctionner exactement comme avant.

Aucune clé de production sans organisation approuvée

La règle est appliquée côté serveur, pas seulement masquée dans l'interface. Une tentative de création renvoie ORG_NOT_APPROVED. De même, une organisation suspendue ne peut plus créer de credential tant que la suspension court.

Console marchand

Vue d'ensemble

Une seule console pour les marchands, les développeurs, le support et l'administration : le menu et les droits diffèrent, pas l'application.

Activité

Transactions & soldes

Recherche par référence, statut, période ; détail complet avec le fil des événements et le correlation ID à citer au support.

Technique

Projets, clés, endpoints

Un projet par intégration, ses credentials par environnement, ses endpoints de notification et leur santé sur sept jours.

Organisation

Équipe & dossier

Invitations, rôles, progression d'activation, historique des décisions de conformité.

Argent

Règlements

Demande de versement vers le numéro Mobile Money validé dans votre dossier, avec suivi jusqu'à réception.

La console s'authentifie en JWT de session et consomme les routes /api/dashboard/*. Elle n'utilise jamais la signature HMAC : celle-ci reste réservée aux échanges de serveur à serveur.

Console marchand

Organisations & activation

Une organisation est l'entité qui encaisse. Son statut détermine ce qui est permis.

StatutSensSandboxProduction
BrouillonCréée, dossier non soumisouinon
Profil incompletInformations obligatoires manquantesouinon
Prêt à soumettreDossier complet côté marchandouinon
En vérificationExaminé par la conformité ; dossier figéouinon
Action requiseComplément demandé, motif affichéouinon
Production activeApprouvéeouioui
SuspendueOpérations de production bloquéeslecturenon
RefuséeDemande rejetée, motif consultableouinon
ClôturéeRelation terminéenonnon
Deux de ces statuts sont calculés

Profil incomplet et Prêt à soumettre ne sont pas stockés : ils sont déduits en temps réel de ce qui manque réellement. Un statut recalculable et stocké finit toujours par mentir sur l'état du dossier.

Suivre sa progression par l'API

GET/api/merchant-onboardingsource unique de la checklist
Réponse
{
  "organizationStatus": "READY_TO_SUBMIT",
  "completionPercent": 83,
  "steps": [
    { "code": "EMAIL_VERIFIED",       "status": "DONE" },
    { "code": "SANDBOX_PROJECT",      "status": "DONE" },
    { "code": "FIRST_SANDBOX_TX",     "status": "DONE" },
    { "code": "ORGANIZATION_PROFILE", "status": "DONE" },
    { "code": "PHONE_VERIFIED",       "status": "DONE" },
    { "code": "APPLICATION_FILE",     "status": "IN_PROGRESS" }
  ],
  "nextAction": "SUBMIT_APPLICATION_FILE",
  "statusReason": null
}

Pièces demandées

La liste dépend de votre forme juridique et s'affiche dans votre dossier. Le socle commun : pièce d'identité du représentant (recto et verso) et preuve de titularité du numéro Mobile Money de règlement. Les sociétés y ajoutent l'extrait RCCM et l'attestation NIU.

Console marchand

Rôles & équipe

Un utilisateur peut appartenir à plusieurs organisations, avec un rôle différent dans chacune.

RôlePeutNe peut pas
PropriétaireTout, y compris soumettre le dossier et transférer la propriété
AdministrateurGérer l'équipe, les projets, les clés de production, le profilSoumettre le dossier, transférer la propriété
DéveloppeurProjets, clés sandbox, endpoints, rotation, testsCréer la première clé de production, gérer l'équipe
FinanceTransactions, soldes, règlements, initiation d'opérationsToucher aux clés ou à l'équipe
SupportConsulter transactions et livraisonsToute écriture
Lecture seuleConsulterToute écriture

Changer d'organisation

POST/api/auth/switch-organization

La bascule émet un nouveau jeton dont le périmètre est l'organisation choisie. Un jeton porte un seul périmètre : il n'existe pas d'en-tête permettant de viser une autre organisation au fil des requêtes.

Console marchand

Projets & credentials

Un projet représente une intégration : une boutique, une application, un backend.

GET/api/dashboard/applications
POST/api/dashboard/applications/{reference}/credentialsgénère ou fait tourner
POST/api/dashboard/applications/{reference}/credentials/{publicKey}/revoke
Console marchand

API de la console

Les mêmes données que l'interface, si vous souhaitez construire vos propres tableaux de bord.

GET/api/auth/meidentité, organisation courante, adhésions
GET/api/dashboard/transactions
GET/api/dashboard/transactions/{reference}
GET/api/dashboard/wallets
POST/api/dashboard/wallets/{reference}/freezeconformité
POST/api/dashboard/operationsinitier une opération, idempotent
POST/api/dashboard/operations/quote
GET/api/dashboard/fee-rules
GET/api/dashboard/audit-logs
Une opération initiée n'est pas une opération exécutée

/api/dashboard/operations crée la transaction et la met en file (QUEUED) ; c'est un worker qui l'exécute et poste les écritures. Une transaction qui reste indéfiniment en QUEUED signale un worker à l'arrêt, pas un problème de contrat.

Ce que les journaux ne montrent jamais

Aucun secret, aucune signature, aucun numéro complet n'apparaît pour un rôle non autorisé. La consultation d'une pièce justificative passe par un lien signé de courte durée et laisse elle-même une trace d'audit.

Sous le capot

Exécution des opérations

Ce que devient une transaction entre votre appel d'API et le compte Mobile Money de votre client.

Une fois la transaction enregistrée, B-Cash la confie à sa couche d'orchestration. C'est elle qui relaie l'opération vers l'opérateur concerné, en suit l'issue et la reporte dans le registre. Votre intégration n'a rien à en connaître : elle observe des statuts, pas des mécanismes.

votre appelenregistrementorchestration opérateurstatut + webhook

Ce que cela change pour vous

Quand l'issue n'est pas confirmée

Il arrive qu'une opération ne puisse pas être conclue immédiatement : l'opérateur ne répond pas de manière exploitable, ou la confirmation n'arrive pas dans la fenêtre attendue. La transaction passe alors en PENDING_RECONCILIATION et les fonds concernés restent réservés, le temps que le rapprochement tranche.

Le dénouement est automatique : la transaction rejoint un statut terminal et l'événement correspondant vous est livré. Aucune action ne vous est demandée — et c'est précisément le point important.

Ne rien faire est la bonne action

Sur transaction.pending_reconciliation : n'expédiez pas, ne remboursez pas, ne relancez pas. Ce statut ne signifie pas « échec », il signifie « pas encore tranché ». Informez le client que son paiement est en cours de vérification et attendez l'événement terminal. Une nouvelle tentative pendant ce délai est le moyen le plus sûr de le débiter deux fois.

Combien de temps

Ces situations sont rares et se résolvent généralement en quelques minutes. Si une transaction reste dans cet état au-delà de ce qui vous paraît raisonnable, écrivez au support avec sa référence : le rapprochement peut être accéléré manuellement.

Intégrations

WooCommerce

Le chemin le plus court : votre boutique encaisse sans que vous écriviez de code.

  1. Créez un projet de type WooCommerce dans la console et notez sa clé publique et son secret sandbox.
  2. Installez l'extension B-Cash dans WooCommerce, puis renseignez la clé, le secret et l'environnement.
  3. Copiez l'URL de notification affichée par l'extension et enregistrez-la comme endpoint dans la console, rattachée à ce projet.
  4. Passez une commande de test et forcez l'issue depuis le sandbox : la commande doit passer en « payée » toute seule.
  5. Après approbation de votre dossier, remplacez les clés par celles de production.
Événement B-CashEffet attendu sur la commande
transaction.succeededCommande payée, traitement lancé
transaction.failedCommande en échec, panier réouvrable
transaction.expiredCommande annulée, stock libéré
transaction.pending_reconciliationCommande en attente — ne rien expédier
transaction.reversedCommande remboursée
Ne validez jamais une commande sur le retour navigateur

Le client peut fermer son téléphone avant la redirection, ou la rouvrir plus tard. Seul le webhook fait foi. La page de retour affiche un état d'attente ; c'est l'événement qui déclenche la livraison.

Le context transmis par l'extension vaut WOOCOMMERCE et le numéro de commande est envoyé dans merchantReference : vous retrouvez ainsi n'importe quelle transaction depuis votre back-office WooCommerce.

Intégrations

Applications mobiles

Le schéma est simple, à condition de ne jamais inverser les rôles : c'est votre backend qui détient le secret, jamais l'application.

Séquence
App mobile          Votre backend             API B-Cash
    │                     │                        │
    │  « je veux payer »  │                        │
    │────────────────────>│                        │
    │                     │  POST /v1/auth/token   │   (signé HMAC)
    │                     │───────────────────────>│
    │                     │<── bt_… (TTL 600 s) ───│
    │<── jeton éphémère ──│                        │
    │                                              │
    │  POST /v1/collections  (Bearer bt_…)         │
    │─────────────────────────────────────────────>│
    │<── nextAction : AWAIT_MOBILE_CONFIRMATION ───│
    │                                              │
    │  Écran d'attente jusqu'à expiresAt           │
    │                     │<── webhook signé ──────│
    │<── votre push /     │                        │
    │    votre polling ───│                        │

Règles côté application

Ce qu'il ne faut jamais faire

Embarquer la clé HMAC dans l'application, même « obfusquée ». Une application se décompile en quelques minutes, et une clé extraite permet d'encaisser en votre nom jusqu'à sa révocation.

Intégrations

Backend sur mesure

Intégration directe : vous contrôlez tout, vous portez donc tout.

Liste de contrôle avant production

PointVérification
SecretsHors du dépôt, en variables d'environnement ou coffre. Jamais dans un fichier versionné.
HorlogeNTP actif sur tous les serveurs appelants.
IdempotenceClé rattachée à la commande, persistée avec elle.
WebhooksSignature vérifiée en temps constant, corps brut, deux en-têtes acceptés.
DéduplicationTable des evt_… traités, contrainte d'unicité.
JournalisationcorrelationId et reference stockés avec la commande.
Filet de sécuritéTâche périodique qui interroge les transactions non terminales anciennes.
StatutsPENDING_RECONCILIATION traité comme une attente, pas comme un échec.
OpérateursListe lue depuis l'API, pas codée en dur.
RotationProcédure écrite et testée au moins une fois en sandbox.

Machine à états minimale côté commande

Traitement d'un événement
async function handleEvent(event) {
  // 1. Déduplication : la contrainte d'unicité fait le travail
  if (await events.exists(event.id)) return;
  await events.insert(event.id);

  const tx = event.data.object;
  const order = await orders.findByReference(tx.merchantReference);
  if (!order) return;                       // événement d'un autre système

  // 2. On se fie au STATUT porté par l'objet, jamais à l'ordre d'arrivée
  switch (tx.status) {
    case 'SUCCEEDED':               return order.markPaid(tx);
    case 'FAILED':
    case 'EXPIRED':                 return order.markUnpaid(tx);
    case 'REVERSED':                return order.markRefunded(tx);
    case 'PENDING_RECONCILIATION':  return order.markUnderReview(tx);   // on attend
    default:                        return;                            // état transitoire
  }
}
Références

Statuts & types

Statuts de transaction

CREATED, FEE_QUOTED, HELD, QUEUED, PROCESSING, PENDING_CONFIRMATION, SUCCEEDED, FAILED, CANCELLED, REVERSED, EXPIRED, PENDING_RECONCILIATION. Détail en Cycle de vie.

Types de transaction

TypeSens
COLLECTIONEncaissement Mobile Money vers votre wallet
MERCHANT_PAYOUTVersement de votre wallet vers un numéro
MERCHANT_PAYMENTPaiement réglé depuis un wallet client
WALLET_TOPUPAlimentation d'un wallet
CASH_OUTRetrait depuis un wallet
REFUNDRemboursement, lié par parentReference
Route générique ou routes dédiées

POST /v1/transactions accepte tous les types, mais préférez /v1/collections et /v1/payouts : elles fixent le type et résolvent le wallet à votre place, ce qui élimine une classe entière d'erreurs.

Références

Événements

ÉvénementÉmis quandAction typique
transaction.createdLa transaction est enregistréeJournaliser
transaction.pending_confirmationLe client doit confirmerAfficher l'attente
transaction.succeededL'argent a bougéLivrer
transaction.failedÉchec définitifRouvrir le panier
transaction.expiredPas de confirmation à tempsLibérer le stock
transaction.reversedRemboursement intégral atteintMarquer remboursé
transaction.pending_reconciliationIssue indéterminéeAttendre
webhook.testVous déclenchez un testVérifier la chaîne

La liste exhaustive et à jour est publiée dans GET /api/v1/meta. Un événement inconnu doit être ignoré sans erreur : de nouveaux types peuvent apparaître en 1.x.

Références

Scopes

ScopeAutorise
collections:createCréer un encaissement
payouts:createCréer un décaissement
refunds:createRembourser
transactions:readLire les transactions
wallets:readLire les soldes
fees:quoteDemander un devis
webhooks:writeGérer les endpoints

Un jeton éphémère ne peut porter qu'un sous-ensemble strict des scopes de la clé qui l'a émis. Accordez à chaque credential le minimum nécessaire : une clé de boutique n'a aucune raison de pouvoir décaisser.

Références

Versionnement

L'API est en version 1.3. En 1.x, ces garanties tiennent :

Conséquence pratique : votre désérialisation doit être tolérante. Un client qui plante sur un champ inconnu cassera à la prochaine évolution mineure.

Le champ apiVersion des webhooks (ex. 2026-07-20) date le format de l'enveloppe ; conservez-le dans vos journaux, il explique les différences entre deux événements anciens et récents.

Références

État & support

GET/api/v1/healthpublic
GET/api/v1/metapublic — contrat stable

/v1/meta mérite d'être lu au moins une fois : il publie les énumérations, la liste exhaustive des événements, la spécification de signature, les règles d'idempotence et de devis, ainsi que le catalogue des codes d'erreur. Tout ce que cette page décrit y est disponible sous forme exploitable par votre code.

Contacter le support efficacement

Trois éléments suffisent presque toujours à trancher un incident :

  1. Le correlationId de la réponse en erreur.
  2. La reference de la transaction, ou votre merchantReference.
  3. L'horodatage précis et l'environnement concerné.

Écrivez à support@gba-cm.online. N'envoyez jamais un secret ni une capture contenant une clé : si vous pensez qu'un secret a fuité, faites-le tourner d'abord, signalez ensuite.

Cette documentation est une première version

Elle couvre les nœuds existants à la version 1.3 de l'API. Les guides pas-à-pas de l'extension WooCommerce et du SDK mobile seront enrichis avec le contenu réel de chaque paquet.