Intégrer Orange Money en quelques minutes
Tout ce qu'il faut pour passer votre premier paiement, recevoir un webhook et passer en production — sur une seule page.
nk_test_*
1 Démarrer
Inscrivez-vous, récupérez vos clés sandbox, configurez vos variables d'environnement.
1. Demandez vos clés à votre administrateur Neka Paie. Vous recevez par email :
API_KEYpublique — préfixéenk_test_(sandbox) ounk_live_(prod)API_SECRET— 64 caractères hexa, à garder secretMERCHANT_ID— votre identifiant marchand (UUID)
2. Stockez les clés en variables d'environnement (jamais en clair dans le code) :
BASHexport NEKAPAY_API_KEY="nk_test_xxxxxxxxxxxxxxxx"
export NEKAPAY_API_SECRET="0b2cc4dc6e9c250dc4ac2367b6947d98..."
export NEKAPAY_BASE_URL="https://nekapaie.com/api/v1"
https://nekapaie.com/api/v1
https://nekapaie.com/api/v1
2 Authentification HMAC
Chaque requête est signée. Trois en-têtes, un calcul de signature.
| Header | Valeur |
|---|---|
X-NekaPay-Key | Votre API_KEY |
X-NekaPay-Timestamp | Timestamp Unix (sec) — fenêtre ±300 s |
X-NekaPay-Signature | HMAC-SHA256 de timestamp + body, en hexa minuscule |
Content-Type | application/json |
Formule : signature = HMAC_SHA256(api_secret, timestamp + body_brut)
PHP$body = json_encode($payload, JSON_UNESCAPED_SLASHES);
$ts = (string) time();
$sig = hash_hmac('sha256', $ts . $body, getenv('NEKAPAY_API_SECRET'));
$headers = [
'X-NekaPay-Key: ' . getenv('NEKAPAY_API_KEY'),
'X-NekaPay-Timestamp: ' . $ts,
'X-NekaPay-Signature: ' . $sig,
'Content-Type: application/json',
];
JSconst crypto = require('crypto');
const body = JSON.stringify(payload);
const ts = Math.floor(Date.now() / 1000).toString();
const sig = crypto.createHmac('sha256', process.env.NEKAPAY_API_SECRET)
.update(ts + body).digest('hex');
const headers = {
'X-NekaPay-Key': process.env.NEKAPAY_API_KEY,
'X-NekaPay-Timestamp': ts,
'X-NekaPay-Signature': sig,
'Content-Type': 'application/json',
};
PYTHONimport hmac, hashlib, json, os, time
body = json.dumps(payload, separators=(',', ':'))
ts = str(int(time.time()))
sig = hmac.new(
os.environ['NEKAPAY_API_SECRET'].encode(),
(ts + body).encode(),
hashlib.sha256,
).hexdigest()
headers = {
'X-NekaPay-Key': os.environ['NEKAPAY_API_KEY'],
'X-NekaPay-Timestamp': ts,
'X-NekaPay-Signature': sig,
'Content-Type': 'application/json',
}
BASHBODY='{"merchant_order_id":"order_001","amount":50000,"customer_msisdn":"+22376123456","country_code":"ML"}'
TS=$(date +%s)
SIG=$(printf '%s' "${TS}${BODY}" | openssl dgst -sha256 -hmac "$NEKAPAY_API_SECRET" | cut -d' ' -f2)
curl -X POST "$NEKAPAY_BASE_URL/payments/cashin" \
-H "Content-Type: application/json" \
-H "X-NekaPay-Key: $NEKAPAY_API_KEY" \
-H "X-NekaPay-Timestamp: $TS" \
-H "X-NekaPay-Signature: $SIG" \
-d "$BODY"
3 Votre premier paiement (Cash-In)
Initier un paiement, redirigez le client vers Orange, c'est tout.
Requête — POST /payments/cashin
JSON{
"merchant_order_id": "order_001",
"amount": 50000,
"currency": "XOF",
"customer_msisdn": "+22376123456",
"country_code": "ML",
"return_url": "https://nekapaie.com/payment/success",
"cancel_url": "https://nekapaie.com/payment/cancel"
}
Réponse — 202 Accepted
JSON{
"data": {
"transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"merchant_order_id": "order_001",
"type": "CASHIN",
"status": "PENDING",
"amount": 50000,
"currency": "XOF",
"payment_url": "https://webpayment.orange.com/pay/abc123",
"created_at": "2026-04-29T14:30:00Z"
}
}
Redirigez votre utilisateur vers data.payment_url pour valider le paiement.
4 Suivre l'état d'une transaction
Trois moyens : webhook (recommandé), polling, ou dashboard.
Polling — GET /payments/{transaction_id}
BASHTS=$(date +%s)
SIG=$(printf '%s' "${TS}" | openssl dgst -sha256 -hmac "$NEKAPAY_API_SECRET" | cut -d' ' -f2)
curl "$NEKAPAY_BASE_URL/payments/$TX_ID" \
-H "X-NekaPay-Key: $NEKAPAY_API_KEY" \
-H "X-NekaPay-Timestamp: $TS" \
-H "X-NekaPay-Signature: $SIG"
Statuts possibles
CREATED | Enregistrée, pas encore envoyée à Orange. |
INITIATED | Envoyée à Orange, en attente. |
PENDING | Acceptée par Orange, en attente du client. |
SUCCESS | Confirmée (état terminal). |
FAILED | Échec définitif (état terminal). |
EXPIRED | Délai client dépassé (~5 min). |
REVERSED | Remboursement effectué. |
5 Webhooks
Recevez une notification signée à chaque état terminal (SUCCESS, FAILED, EXPIRED, REVERSED).
Payload reçu sur votre notify_url
JSON{
"event": "transaction.status_changed",
"transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"merchant_order_id": "order_001",
"type": "CASHIN",
"status": "SUCCESS",
"amount": 50000,
"currency": "XOF",
"timestamp": "2026-04-29T14:32:18+00:00"
}
Vérifier la signature côté serveur :
PHP$ts = $_SERVER['HTTP_X_NEKAPAY_TIMESTAMP'];
$sig = $_SERVER['HTTP_X_NEKAPAY_SIGNATURE'];
$body = file_get_contents('php://input');
// Anti-replay : refuser si timestamp > 5 min
if (abs(time() - (int) $ts) > 300) { http_response_code(401); exit; }
$expected = hash_hmac('sha256', $ts . $body, $apiSecret);
if (!hash_equals($expected, $sig)) { http_response_code(401); exit; }
// OK — répondre vite et traiter en asynchrone
http_response_code(200);
2xx ≤ 3 s.
6 Endpoints essentiels
L'API tient en six routes. Tout est documenté en détail dans la référence API.
/payments/cashin
/payments/cashout
/payments/{id}
/refunds
/balances
/transactions/export
7 Numéros de test (sandbox)
Forcez le comportement Orange en utilisant ces MSISDN dans votre Cash-In.
| MSISDN | Comportement | Statut final |
|---|---|---|
+22376123456 | Paiement validé immédiatement | SUCCESS |
+22376111111 | Solde client insuffisant | FAILED |
+22376222222 | Client refuse (annulation USSD) | FAILED |
+22376333333 | Aucune réponse client (5 min) | EXPIRED |
+22376444444 | PENDING 30 s puis SUCCESS | SUCCESS |
+22376555555 | Erreur opérateur (503) | FAILED |
8 Erreurs courantes
Format : {"error": "...", "code": "ERROR_CODE"}.
| HTTP | Code | Cause |
|---|---|---|
| 400 | INVALID_JSON | Body JSON invalide |
| 401 | SIGNATURE_INVALID | Signature HMAC incorrecte |
| 401 | TIMESTAMP_OUT_OF_RANGE | Décalage horloge > 300 s — vérifier NTP |
| 403 | MERCHANT_SUSPENDED | Compte marchand suspendu |
| 404 | TRANSACTION_NOT_FOUND | ID inconnu ou pas à vous |
| 409 | — | Conflit d'idempotence — la transaction existe déjà, on renvoie l'originale |
| 422 | INVALID_MSISDN | Format E.164 attendu (+22376...) |
| 422 | INSUFFICIENT_BALANCE | Solde marchand insuffisant pour un Cash-Out |
| 429 | RATE_LIMITED | Limite débit — voir Retry-After |
| 503 | OPERATOR_UNAVAILABLE | Orange indisponible — la tx reste PENDING |
Liste complète : codes d'erreur.
9 Passer en production
Checklist rapide avant la bascule.
- Documents KYC soumis et validés par Neka Paie (~48 h)
- Clés
nk_live_*reçues et stockées dans un coffre (Vault / AWS Secrets Manager) NEKAPAY_BASE_URLbasculée vershttps://nekapaie.com/api/v1- Webhook
notify_urlen HTTPS public, vérification HMAC active, idempotence côté handler - Horloge serveur synchronisée (NTP)
- Plan de réconciliation quotidien via
/transactions/export