Votre premier encaissement
Vous allez demander un paiement M-Pesa depuis votre terminal, puis le suivre jusqu’à son issue. Il suffit de curl et openssl (présents sur macOS et Linux), ou de Node 18, ou de Python 3 : aucun compte, aucune bibliothèque à installer.
1. Envoyer la demande
Copiez l’exemple dans le langage de votre choix et lancez-le tel quel : il porte déjà les identifiants publics du Sandbox et calcule sa propre signature.
En production, le payeur reçoit alors sur son téléphone une demande de confirmation M-Pesa et la valide avec son code secret. Dans le Sandbox, un simulateur répond à sa place.
| Champ | Description |
|---|---|
amount_minorentierrequis | Montant en unités mineures : 2500000 vaut 25 000,00 CDF. |
currencychaînerequis | CDF ou USD. |
customer_refchaînerequis | Numéro M-Pesa du payeur, au format international (+243…). |
descriptionchaîne | 255 caractères au plus. Reprise dans votre espace marchand. |
webhook_urlURL | Adresse HTTPS prévenue de l’issue finale, en plus de celles déclarées dans votre espace. |
railchaîne | Canal de paiement. mpesa par défaut. |
# Identifiants PUBLICS du Sandbox : aucune inscription.
KEY="ak_sandbox"
SECRET="sk_sandbox_demo"
BODY='{"amount_minor":2500000,"currency":"CDF","customer_ref":"+243810000000","description":"Commande 1042"}'
TS=$(date +%s)
IDEM=$(uuidgen)
SIG=$(printf '%s' "$TS.POST./v1/charges.$IDEM.$BODY" \
| openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.*= //')
curl https://sandbox.inopas.net/v1/charges \
-H "Content-Type: application/json" \
-H "X-Inopas-Key: $KEY" \
-H "X-Inopas-Timestamp: $TS" \
-H "X-Inopas-Signature: sha256=$SIG" \
-H "Idempotency-Key: $IDEM" \
-d "$BODY"// charge.mjs : node charge.mjs (Node 18 ou plus, aucune dépendance)
import { createHmac, randomUUID } from "node:crypto";
const KEY = "ak_sandbox";
const SECRET = "sk_sandbox_demo";
const HOST = "https://sandbox.inopas.net";
const path = "/v1/charges";
const body = JSON.stringify({
amount_minor: 2500000, // 25 000,00 CDF
currency: "CDF",
customer_ref: "+243810000000",
description: "Commande 1042",
});
const ts = String(Math.floor(Date.now() / 1000));
const idem = randomUUID();
const sig = createHmac("sha256", SECRET)
.update(`${ts}.POST.${path}.${idem}.${body}`)
.digest("hex");
const res = await fetch(HOST + path, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Inopas-Key": KEY,
"X-Inopas-Timestamp": ts,
"X-Inopas-Signature": `sha256=${sig}`,
"Idempotency-Key": idem,
},
body, // la chaîne signée, octet pour octet
});
console.log(res.status, await res.json());# charge.py : python3 charge.py (bibliothèque standard seulement)
import hashlib, hmac, json, time, uuid, urllib.request
KEY = "ak_sandbox"
SECRET = b"sk_sandbox_demo"
HOST = "https://sandbox.inopas.net"
path = "/v1/charges"
body = json.dumps({
"amount_minor": 2500000, # 25 000,00 CDF
"currency": "CDF",
"customer_ref": "+243810000000",
"description": "Commande 1042",
}, separators=(",", ":")).encode()
ts = str(int(time.time()))
idem = str(uuid.uuid4())
sig = hmac.new(SECRET, f"{ts}.POST.{path}.{idem}.".encode() + body, hashlib.sha256).hexdigest()
req = urllib.request.Request(HOST + path, data=body, method="POST", headers={
"Content-Type": "application/json",
"X-Inopas-Key": KEY,
"X-Inopas-Timestamp": ts,
"X-Inopas-Signature": "sha256=" + sig,
"Idempotency-Key": idem,
})
with urllib.request.urlopen(req) as resp:
print(resp.status, json.load(resp))2. Lire la réponse
L’API répond 201 sans attendre le payeur : l’encaissement est enregistré à l’état CREATED, puis transmis au canal en arrière-plan. Votre serveur n’est jamais bloqué par la lenteur d’un opérateur.
Gardez l’id : c’est lui qui permet de suivre le paiement. Les frais (fee_minor, net_minor) restent à null jusqu’à la confirmation, puis donnent la ventilation exacte : brut, frais, net.
HTTP/2 201
content-type: application/json
x-correlation-id: d7f5abaa11de4f5ea00df73ee47b8b8f
ratelimit-limit: 120
ratelimit-remaining: 108
ratelimit-reset: 24
{
"id": "8089246d-8876-4e7b-b156-4431ae6bc527",
"status": "CREATED",
"amount_minor": 2500000,
"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,
"redirect_url": null,
"description": "Commande 1042",
"created_at": "2026-09-24T12:45:46.919251Z"
}3. Suivre l’état
Relisez l’encaissement avec son id. Dans le Sandbox il passe de CREATED à PENDING, puis à SUCCEEDED en quelques secondes.
SUCCEEDED et FAILED sont définitifs : arrêtez d’interroger dès que vous les lisez. Si vous interrogez en boucle, espacez les appels et reculez à chaque tentative ; mieux, déclarez une adresse de webhook dans votre espace marchand et laissez l’API vous prévenir de l’issue finale.
CHARGE_ID="colle-ici-l-id-renvoye"
KEY="ak_sandbox"
SECRET="sk_sandbox_demo"
TS=$(date +%s)
P="/v1/charges/$CHARGE_ID"
# Lecture : pas de clé d'idempotence ni de corps, la chaîne finit par « .. »
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"// statut.mjs : node statut.mjs <id>
import { createHmac } from "node:crypto";
const KEY = "ak_sandbox";
const SECRET = "sk_sandbox_demo";
const path = `/v1/charges/${process.argv[2]}`;
const ts = String(Math.floor(Date.now() / 1000));
// Lecture : Idempotency-Key et corps vides, la chaîne finit par « .. »
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}`,
},
});
const charge = await res.json();
console.log(charge.status); // CREATED, PENDING, SUCCEEDED, FAILED ou UNKNOWN# statut.py : python3 statut.py <id>
import hashlib, hmac, json, sys, time, urllib.request
KEY = "ak_sandbox"
SECRET = b"sk_sandbox_demo"
path = f"/v1/charges/{sys.argv[1]}"
ts = str(int(time.time()))
# Lecture : Idempotency-Key et corps vides, la chaîne finit par « .. »
sig = hmac.new(SECRET, f"{ts}.GET.{path}..".encode(), hashlib.sha256).hexdigest()
req = urllib.request.Request("https://sandbox.inopas.net" + path, headers={
"X-Inopas-Key": KEY,
"X-Inopas-Timestamp": ts,
"X-Inopas-Signature": "sha256=" + sig,
})
with urllib.request.urlopen(req) as resp:
print(json.load(resp)["status"])Et ensuite
- Provoquez un refus et un paiement non confirmé : ce sont eux qui testent vraiment votre intégration.
- Branchez la signature dans votre code, avec un vecteur de test pour la vérifier.
- Traitez les erreurs et relancez sans jamais débiter deux fois.