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
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ête | Contenu |
|---|---|
| X-CRG-Key-Id | Votre identifiant public de clé |
| X-CRG-Timestamp | Secondes Unix. Fenêtre acceptée : ±300 s |
| X-CRG-Nonce | Valeur unique par requête, 8 à 128 caractères |
| X-CRG-Signature | HMAC-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
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éro | Issue imposée |
|---|---|
| 622000001 | succès |
| 622000002 | fonds insuffisants |
| 622000003 | client inconnu |
| 622000009 | panne 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.
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/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
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
Renvoie authorizationUrl : redirigez-y votre client. La demande expire au bout de 15 minutes.
À 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 :
| Borne | Effet |
|---|---|
| maxAmountCentimes | Plafond PAR prélèvement. Au-delà : 409. |
| periodicity | Intervalle minimal entre deux prélèvements : 20 h (daily), 6 j (weekly), 27 j (monthly). Avant : 409. |
| expiresAt | Fin de validité, 12 mois. Après : 409. |
| révocation | Le client peut révoquer seul, à tout moment, sans préavis. |
Traitez la révocation
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ête | Contenu |
|---|---|
| X-CRG-Event | Nom de l'événement |
| X-CRG-Delivery | Identifiant de la livraison |
| X-CRG-Timestamp | Secondes Unix |
| X-CRG-Signature | HMAC-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
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
| HTTP | Ce que cela veut dire |
|---|---|
| 400 | Requête mal formée, ou Idempotency-Key absent |
| 401 | Signature, clé, horodatage hors fenêtre, ou nonce déjà utilisé |
| 403 | Portée manquante, adresse source refusée, ou compte suspendu |
| 404 | Objet introuvable dans cet environnement |
| 409 | Référence déjà utilisée, ou état incompatible |
| 429 | Trop d'appels — ralentissez |
| 503 | Indisponibilité passagère : à retenter |
Les refus métier ne sont pas des erreurs HTTP. Ils arrivent avec status: "failed" et un code stable :
| failureCode | Cause |
|---|---|
| unknown_customer | Numéro inconnu, ou compte désactivé |
| insufficient_float | Votre float est insuffisant |
| insufficient_balance | Le solde du client est insuffisant |
| limit_exceeded | Plafond réglementaire du client dépassé |
Un 401 ne dit pas lequel
/ping.