Référence de l’API
Reversements
Un reversement envoie des fonds de votre solde vers un numéro M-Pesa. Le solde est contrôlé au moment du traitement.
L’objet Reversement
idUUIDrequis- Identifiant du reversement.
environmentchaîne- Environnement de la clé d'origine :
sandbox(test) oulive(argent réel). Une clé ne lit et n'agit que dans son environnement.défaut : live statuschaînerequis- État : CREATED, PENDING, SUCCEEDED, FAILED ou UNKNOWN (non confirmé, à réconcilier). SUCCEEDED et FAILED sont définitifs.
amount_minorentierrequis- Montant brut demandé, débité de votre solde (unités mineures).
currencychaînerequis- Devise : CDF ou USD.
railchaînerequis- Canal de versement.valeurs : mpesa
fee_minorentier ou null- Frais de versement prélevés (unités mineures). null jusqu'au règlement.
net_minorentier ou null- Net versé au bénéficiaire = brut - frais (unités mineures). null jusqu'au règlement.
fee_percent_bpsentier ou null- Pourcentage appliqué, en points de base (1 % = 100).
fee_fixed_minorentier ou null- Frais fixe appliqué (unités mineures).
fee_min_minorentier ou null- Plancher de frais appliqué.
fee_max_minorentier ou null- Plafond de frais appliqué.
fee_sourcechaîne ou null- Provenance du barème :
catalog(grille publique) ounegotiated. created_atdate-heurerequis- Date de création (UTC, ISO 8601).
Objet Reversement
{
"id": "961f2e65-9fc0-4b67-bf45-61295079e2a1",
"environment": "sandbox",
"status": "CREATED",
"amount_minor": 100000,
"currency": "CDF",
"rail": "mpesa",
"fee_minor": null,
"net_minor": null,
"fee_percent_bps": null,
"fee_fixed_minor": null,
"fee_min_minor": null,
"fee_max_minor": null,
"fee_source": null,
"created_at": "2026-09-24T13:05:22.165527Z"
}Créer un reversement
POST/v1/payouts
Reverse des fonds de votre solde vers un numéro M-Pesa. Le solde est contrôlé au traitement, pas à la réception de la requête : un solde insuffisant fait passer le reversement à FAILED. Exige un en-tête Idempotency-Key.
En-tête Idempotency-Key requis, et inclus dans la chaîne signée.
Corps de la requête
amount_minorentierrequis- Montant à reverser, en unités mineures (entier), débité de votre solde.> 0
currencychaînerequis- Devise du reversement.valeurs : CDF, USD
beneficiary_refchaînerequis- Numéro M-Pesa du bénéficiaire, au format international (+243…).64 caractères max.
webhook_urlURL ou null- Adresse HTTPS prévenue de l'issue finale, en plus des adresses déclarées dans votre espace marchand.
Réponse
Code 201, avec l’objet Reversement.
Erreurs
400En-tête Idempotency-Key manquant.401Authentification manquante ou invalide.409Idempotency-Key réutilisée avec un corps différent.422Validation échouée, ou versement refusé par les contrôles anti-fraude.429Limite de débit dépassée (voir Retry-After).
Forme commune et conduite à tenir : les erreurs.
POST /v1/payouts
KEY="ak_sandbox"
SECRET="sk_sandbox_demo"
P="/v1/payouts"
BODY='{"amount_minor":100000,"beneficiary_ref":"+243820000000","currency":"CDF"}'
IDEM=$(uuidgen)
TS=$(date +%s)
SIG=$(printf '%s' "$TS.POST.$P.$IDEM.$BODY" \
| openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.*= //')
curl "https://sandbox.inopas.net$P" \
-H "X-Inopas-Key: $KEY" \
-H "X-Inopas-Timestamp: $TS" \
-H "X-Inopas-Signature: sha256=$SIG" \
-H "Idempotency-Key: $IDEM" \
-H "Content-Type: application/json" \
-d "$BODY"import { createHmac, randomUUID } from "node:crypto";
const KEY = "ak_sandbox";
const SECRET = "sk_sandbox_demo";
const path = "/v1/payouts"; // signé sans la query string
const body = JSON.stringify({
"amount_minor": 100000,
"beneficiary_ref": "+243820000000",
"currency": "CDF"
});
const idem = randomUUID();
const ts = String(Math.floor(Date.now() / 1000));
const sig = createHmac("sha256", SECRET)
.update(`${ts}.POST.${path}.${idem}.${body}`)
.digest("hex");
const res = await fetch(`https://sandbox.inopas.net${path}`, {
method: "POST",
headers: {
"X-Inopas-Key": KEY,
"X-Inopas-Timestamp": ts,
"X-Inopas-Signature": `sha256=${sig}`,
"Idempotency-Key": idem,
"Content-Type": "application/json",
},
body,
});
console.log(res.status, await res.json());import hashlib, hmac, json, time, urllib.error, urllib.request, uuid
KEY = "ak_sandbox"
SECRET = b"sk_sandbox_demo"
path = "/v1/payouts" # signé sans la query string
body = json.dumps({
"amount_minor": 100000,
"beneficiary_ref": "+243820000000",
"currency": "CDF",
}, separators=(",", ":")).encode()
idem = str(uuid.uuid4())
ts = str(int(time.time()))
sig = hmac.new(SECRET, f"{ts}.POST.{path}.{idem}.".encode() + body, hashlib.sha256).hexdigest()
entetes = {"X-Inopas-Key": KEY, "X-Inopas-Timestamp": ts, "X-Inopas-Signature": "sha256=" + sig}
entetes |= {"Idempotency-Key": idem, "Content-Type": "application/json"}
req = urllib.request.Request("https://sandbox.inopas.net" + path, headers=entetes, data=body, method="POST")
try:
with urllib.request.urlopen(req) as resp:
print(resp.status, json.load(resp))
except urllib.error.HTTPError as e:
print(e.code, json.load(e))Réponse 201
HTTP/2 201
{
"id": "961f2e65-9fc0-4b67-bf45-61295079e2a1",
"environment": "sandbox",
"status": "CREATED",
"amount_minor": 100000,
"currency": "CDF",
"rail": "mpesa",
"fee_minor": null,
"net_minor": null,
"fee_percent_bps": null,
"fee_fixed_minor": null,
"fee_min_minor": null,
"fee_max_minor": null,
"fee_source": null,
"created_at": "2026-09-24T13:05:22.165527Z"
}Lire un reversement
GET/v1/payouts/{payout_id}
Lit l'état courant du reversement et, une fois réglé, ses frais et le net versé.
Paramètres
payout_idUUID, dans le cheminrequis- Identifiant renvoyé à la création.
Réponse
Code 200, avec l’objet Reversement.
Erreurs
401Authentification manquante ou invalide.404Versement introuvable.422Paramètre invalide (validation_error).
Forme commune et conduite à tenir : les erreurs.
GET /v1/payouts/{payout_id}
KEY="ak_sandbox"
SECRET="sk_sandbox_demo"
P="/v1/payouts/961f2e65-9fc0-4b67-bf45-61295079e2a1"
TS=$(date +%s)
SIG=$(printf '%s' "$TS.GET.$P.." \
| openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.*= //')
curl "https://sandbox.inopas.net$P" \
-H "X-Inopas-Key: $KEY" \
-H "X-Inopas-Timestamp: $TS" \
-H "X-Inopas-Signature: sha256=$SIG"import { createHmac } from "node:crypto";
const KEY = "ak_sandbox";
const SECRET = "sk_sandbox_demo";
const path = "/v1/payouts/961f2e65-9fc0-4b67-bf45-61295079e2a1"; // signé sans la query string
const ts = String(Math.floor(Date.now() / 1000));
const sig = createHmac("sha256", SECRET)
.update(`${ts}.GET.${path}..`)
.digest("hex");
const res = await fetch(`https://sandbox.inopas.net${path}`, {
headers: {
"X-Inopas-Key": KEY,
"X-Inopas-Timestamp": ts,
"X-Inopas-Signature": `sha256=${sig}`,
},
});
console.log(res.status, await res.json());import hashlib, hmac, json, time, urllib.error, urllib.request
KEY = "ak_sandbox"
SECRET = b"sk_sandbox_demo"
path = "/v1/payouts/961f2e65-9fc0-4b67-bf45-61295079e2a1" # signé sans la query string
body = b""
idem = ""
ts = str(int(time.time()))
sig = hmac.new(SECRET, f"{ts}.GET.{path}.{idem}.".encode() + body, hashlib.sha256).hexdigest()
entetes = {"X-Inopas-Key": KEY, "X-Inopas-Timestamp": ts, "X-Inopas-Signature": "sha256=" + sig}
req = urllib.request.Request("https://sandbox.inopas.net" + path, headers=entetes)
try:
with urllib.request.urlopen(req) as resp:
print(resp.status, json.load(resp))
except urllib.error.HTTPError as e:
print(e.code, json.load(e))Réponse 200
HTTP/2 200
{
"id": "961f2e65-9fc0-4b67-bf45-61295079e2a1",
"environment": "sandbox",
"status": "CREATED",
"amount_minor": 100000,
"currency": "CDF",
"rail": "mpesa",
"fee_minor": null,
"net_minor": null,
"fee_percent_bps": null,
"fee_fixed_minor": null,
"fee_min_minor": null,
"fee_max_minor": null,
"fee_source": null,
"created_at": "2026-09-24T13:05:22.165527Z"
}