CRGCRG Super AppAPI partenairesMon espace

Crédit Rural de Guinée

API partenaires

Envoyer de l'argent vers un portefeuille CRG Super App, et encaisser depuis un portefeuille avec l'accord de son titulaire.

Principes

Deux adresses de base. Un seul numéro de version, celui de la plateforme.

production   https://superapp.creditruralgn.com/api/v1/partner
sandbox      https://staging.superapp.creditruralgn.com/api/v1/partner

L'environnement est déterminé par votre clé, jamais par un paramètre : une clé de sandbox ne peut pas demander à être traitée en production.

Les montants sont en centimes

Tous les montants sont des entiers, en centimes de franc guinéen. 50 000 GNF s'écrivent 5000000. C'est l'erreur la plus coûteuse d'une intégration : un facteur cent sur un versement.

Chaque opération d'écriture exige un en-tête Idempotency-Key. Rejouer la même clé rend le même résultat — succès comme échec — au lieu de créer une seconde opération. C'est votre protection quand le réseau coupe après l'envoi : sans elle, vous ne pouvez pas distinguer « rien n'est parti » de « c'est passé mais je ne l'ai pas su ».

Votre reference doit être unique chez vous : c'est votre clé de rapprochement, et la réutiliser renvoie 409.

Signer un appel

Nous ne délivrons pas de jeton porteur. Un jeton prouve l'identité, pas l'intégrité : il laisse modifier le montant en vol et rejouer la requête. Chaque appel est donc signé.

En-têteContenu
X-CRG-Key-IdVotre identifiant public de clé
X-CRG-TimestampSecondes Unix. Fenêtre acceptée : ±300 s
X-CRG-NonceValeur unique par requête, 8 à 128 caractères
X-CRG-SignatureHMAC-SHA256 hexadécimal (voir ci-dessous)

La chaîne à signer, en cinq parties séparées par un point :

timestamp . nonce . MÉTHODE . chemin . sha256(corps)

signature = hex( HMAC-SHA256( secret, chaîne ) )

Le chemin est celui de la requête, paramètres compris. Le sha256 porte sur le corps brut, octet pour octet : ne le re-sérialisez pas avant de signer, l'ordre des clés JSON changerait la signature. Un corps vide se signe comme la chaîne vide.

// Node.js
const crypto = require("crypto");

function signer({ secret, method, path, body }) {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const nonce = crypto.randomBytes(16).toString("hex");
  const empreinte = crypto.createHash("sha256").update(body ?? "").digest("hex");
  const chaine = [timestamp, nonce, method.toUpperCase(), path, empreinte].join(".");
  return {
    "X-CRG-Key-Id": process.env.CRG_KEY_ID,
    "X-CRG-Timestamp": timestamp,
    "X-CRG-Nonce": nonce,
    "X-CRG-Signature": crypto.createHmac("sha256", secret).update(chaine).digest("hex"),
  };
}

Vérifier que tout est en place

Appelez GET /api/v1/partner/ping en premier. Elle ne déplace rien et répond à la question qui fait perdre le plus de temps : « ma signature est-elle correcte ? ». Elle renvoie aussi l'environnement déduit de votre clé.

Un nonce ne sert qu'une fois

Rejouer un nonce dans les dix minutes renvoie 401. Tirez-le au hasard, ne le dérivez pas de la référence : deux tentatives légitimes sur la même opération doivent porter deux nonces, tout en gardant la même Idempotency-Key.

Sandbox

Votre clé de sandbox commence par pk_sandbox_. Rien n'y bouge : aucun portefeuille réel, aucun float, aucune écriture comptable. Le fichier client n'y est jamais lu — seuls les numéros ci-dessous existent.

NuméroIssue imposée
622000001succès
622000002fonds insuffisants
622000003client inconnu
622000009panne amont — 503, à retenter

Servez-vous en pour coder les chemins d'échec. C'est leur raison d'être : une intégration qui ne connaît que le chemin heureux découvre les autres en production.

Autoriser un prélèvement en sandbox

Aucun client réel ne porte ces numéros, donc personne ne peut autoriser un prélèvement d'essai. Utilisez POST /api/v1/partner/charges/{reference}/simulate avec {"decision": "authorize"} ou "decline". Cette route refuse en production : là, seul le client décide.

Le solde de float en sandbox est constant et généreux : le refus pour fonds insuffisants se provoque par le numéro 622000002, pas en épuisant un compteur.

Verser vers un portefeuille

Vous versez depuis un float préfinancé chez CRG. Chaque versement le débite ; un float insuffisant refuse le versement, il ne le met pas en attente. Surveillez votre solde.

GET/api/v1/partner/balancebalance:read
POST/api/v1/partner/name-enquiryname_enquiry:read

Renvoie le nom du titulaire. À appeler systématiquement avant un versement : c'est ce qui permet à votre client de voir qu'il s'est trompé d'un chiffre avant que l'argent ne parte. Plafonné à 60 appels par minute et par clé.

POST/api/v1/partner/payoutspayouts:write
GET/api/v1/partner/payouts/{reference}payouts:read
POST /api/v1/partner/payouts
Idempotency-Key: 7f3a9c12-...

{
  "reference": "ACME-2026-000418",
  "phone": "622451230",
  "amountCentimes": 5000000,
  "narration": "Transfert depuis Acme Pay"
}

→ 201
{
  "id": "…", "reference": "ACME-2026-000418",
  "phone": "622451230", "amountCentimes": 5000000,
  "status": "succeeded", "failureCode": null,
  "environment": "live", "createdAt": "2026-10-16T12:00:00.000Z"
}

Un refus n'est pas une erreur HTTP

Un client inconnu ou un float insuffisant renvoient 201 avec status: "failed" et un failureCode. Le versement existe : il se consulte et se rapproche. Testez status, pas seulement le code HTTP.

Prélever ponctuellement

Pour un achat en ligne ou une facture. Vous créez une demande ; elle n'engage rien. Seul le client la transforme en paiement, sur une page servie par CRG ou dans son application, avec son code PIN.

Le PIN ne transite jamais chez vous

Ne demandez jamais son code PIN à un client, et ne construisez pas d'écran qui le réclame. Il est saisi sur notre surface uniquement. Un PIN saisi ailleurs est un PIN compromis, et nous refuserons toute intégration qui le collecte.
POST/api/v1/partner/chargescharges:write

Renvoie authorizationUrl : redirigez-y votre client. La demande expire au bout de 15 minutes.

GET/api/v1/partner/charges/{reference}charges:read
POST/api/v1/partner/charges/{reference}/cancelcharges:write

À appeler si votre client abandonne : mieux vaut retirer la demande que la laisser s'afficher chez lui jusqu'à péremption.

POST /api/v1/partner/charges
Idempotency-Key: 2b84e0f1-...

{
  "reference": "SHOP-4471",
  "phone": "622451230",
  "amountCentimes": 2500000,
  "narration": "Commande 4471 — 2 articles"
}

→ 201
{
  "status": "pending",
  "authorizationUrl": "https://superapp.creditruralgn.com/pay/…",
  "expiresAt": "2026-10-16T12:15:00.000Z"
}

Les états possibles : pending, succeeded, declined si le client refuse, expired au bout des 15 minutes, canceled si vous annulez, failed avec un code si le débit n'a pas abouti.

Le narration est obligatoire : un client à qui l'on demande d'autoriser un débit doit savoir pour quoi.

Prélever par mandat

Pour un abonnement ou une facture récurrente. Le client consent une fois ; vous prélevez ensuite sans lui redemander son PIN.

Un mandat n'est pas un blanc-seing. Trois bornes vous sont opposables, et une quatrième appartient au client :

BorneEffet
maxAmountCentimesPlafond PAR prélèvement. Au-delà : 409.
periodicityIntervalle minimal entre deux prélèvements : 20 h (daily), 6 j (weekly), 27 j (monthly). Avant : 409.
expiresAtFin de validité, 12 mois. Après : 409.
révocationLe client peut révoquer seul, à tout moment, sans préavis.
POST/api/v1/partner/mandatesmandates:write
GET/api/v1/partner/mandates/{reference}mandates:read
POST/api/v1/partner/mandates/{reference}/debitsmandates:write

Traitez la révocation

Souscrivez à mandate.revoked. Sans cela vous continuerez de tenter des débits et accumulerez des refus que vous ne saurez pas expliquer — et votre client, lui, croira avoir résilié.

Recevoir nos rappels

Donnez-nous une adresse HTTPS et nous vous notifions. Vous recevez un secret de signature, distinct de celui de vos appels : les deux voyagent dans des sens opposés et se révoquent indépendamment.

En-têteContenu
X-CRG-EventNom de l'événement
X-CRG-DeliveryIdentifiant de la livraison
X-CRG-TimestampSecondes Unix
X-CRG-SignatureHMAC-SHA256 de « timestamp.corps »
// Vérification — Node.js / Express avec le corps BRUT
const attendue = crypto
  .createHmac("sha256", process.env.CRG_WEBHOOK_SECRET)
  .update(`${req.header("X-CRG-Timestamp")}.${rawBody}`)
  .digest("hex");

const fournie = req.header("X-CRG-Signature");
const ok =
  attendue.length === fournie.length &&
  crypto.timingSafeEqual(Buffer.from(attendue), Buffer.from(fournie));

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

Vérifiez aussi que l'horodatage est récent : sans cela, un rappel capté serait rejouable indéfiniment contre votre serveur.

Les événements que nous émettons :

payout.succeeded          payout.failed
charge.succeeded          charge.failed
charge.declined           charge.expired
mandate.activated         mandate.revoked
mandate.debit.succeeded   mandate.debit.failed

Répondez vite, et traitez en asynchrone

Répondez 2xx dès réception, puis traitez. Nous coupons à 10 secondes. Un 4xx est pris pour un refus définitif et le rappel est abandonné ; un 5xx, un 408 ou un 429 sont retentés — huit fois, en espaçant jusqu'à une heure.

Soyez idempotents de votre côté aussi

Un rappel peut arriver plus d'une fois : nous garantissons au moins une livraison, pas exactement une. Dédupliquez sur X-CRG-Delivery, ou sur le couple événement + référence.

Les rappels partent aussi en sandbox : éprouvez-les là avant la production.

Erreurs et codes

HTTPCe que cela veut dire
400Requête mal formée, ou Idempotency-Key absent
401Signature, clé, horodatage hors fenêtre, ou nonce déjà utilisé
403Portée manquante, adresse source refusée, ou compte suspendu
404Objet introuvable dans cet environnement
409Référence déjà utilisée, ou état incompatible
429Trop d'appels — ralentissez
503Indisponibilité passagère : à retenter

Les refus métier ne sont pas des erreurs HTTP. Ils arrivent avec status: "failed" et un code stable :

failureCodeCause
unknown_customerNuméro inconnu, ou compte désactivé
insufficient_floatVotre float est insuffisant
insufficient_balanceLe solde du client est insuffisant
limit_exceededPlafond réglementaire du client dépassé

Un 401 ne dit pas lequel

Une clé inconnue et une signature fausse renvoient le même message. Nous ne dirons pas lequel des deux est en cause : cela renseignerait un attaquant sur les clés qui existent. Vérifiez votre signature avec /ping.