Documentation

Production

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.

HMAC-SHA256 REST · JSON UEMOA / CEMAC Sandbox : 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_KEY publique — préfixée nk_test_ (sandbox) ou nk_live_ (prod)
  • API_SECRET — 64 caractères hexa, à garder secret
  • MERCHANT_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"
Sandbox
https://nekapaie.com/api/v1
Production
https://nekapaie.com/api/v1

2 Authentification HMAC

Chaque requête est signée. Trois en-têtes, un calcul de signature.

HeaderValeur
X-NekaPay-KeyVotre API_KEY
X-NekaPay-TimestampTimestamp Unix (sec) — fenêtre ±300 s
X-NekaPay-SignatureHMAC-SHA256 de timestamp + body, en hexa minuscule
Content-Typeapplication/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êtePOST /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éponse202 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.

PollingGET /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

CREATEDEnregistrée, pas encore envoyée à Orange.
INITIATEDEnvoyée à Orange, en attente.
PENDINGAcceptée par Orange, en attente du client.
SUCCESSConfirmée (état terminal).
FAILEDÉchec définitif (état terminal).
EXPIREDDélai client dépassé (~5 min).
REVERSEDRemboursement 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);
Retry : en cas d'échec, jusqu'à 7 tentatives sur ~31 h (1 min → 5 min → 15 min → 1 h → 6 h → 24 h). Répondez en 2xx ≤ 3 s.

6 Endpoints essentiels

L'API tient en six routes. Tout est documenté en détail dans la référence API.

POST/payments/cashin
Encaisser un paiement client → marchand.
POST/payments/cashout
Verser des fonds vers un wallet client.
GET/payments/{id}
Récupérer le statut d'une transaction.
POST/refunds
Rembourser tout ou partie d'un Cash-In réussi.
GET/balances
Lire le solde marchand par filiale.
GET/transactions/export
Export JSON/CSV pour la réconciliation.

7 Numéros de test (sandbox)

Forcez le comportement Orange en utilisant ces MSISDN dans votre Cash-In.

MSISDNComportementStatut final
+22376123456Paiement validé immédiatementSUCCESS
+22376111111Solde client insuffisantFAILED
+22376222222Client refuse (annulation USSD)FAILED
+22376333333Aucune réponse client (5 min)EXPIRED
+22376444444PENDING 30 s puis SUCCESSSUCCESS
+22376555555Erreur opérateur (503)FAILED

8 Erreurs courantes

Format : {"error": "...", "code": "ERROR_CODE"}.

HTTPCodeCause
400INVALID_JSONBody JSON invalide
401SIGNATURE_INVALIDSignature HMAC incorrecte
401TIMESTAMP_OUT_OF_RANGEDécalage horloge > 300 s — vérifier NTP
403MERCHANT_SUSPENDEDCompte marchand suspendu
404TRANSACTION_NOT_FOUNDID inconnu ou pas à vous
409Conflit d'idempotence — la transaction existe déjà, on renvoie l'originale
422INVALID_MSISDNFormat E.164 attendu (+22376...)
422INSUFFICIENT_BALANCESolde marchand insuffisant pour un Cash-Out
429RATE_LIMITEDLimite débit — voir Retry-After
503OPERATOR_UNAVAILABLEOrange 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_URL basculée vers https://nekapaie.com/api/v1
  • Webhook notify_url en HTTPS public, vérification HMAC active, idempotence côté handler
  • Horloge serveur synchronisée (NTP)
  • Plan de réconciliation quotidien via /transactions/export

Besoin d'aller plus loin ?

Référence API détaillée, exemples SDK complets, collection Postman, scénarios sandbox étendus.