Docs › Quickstarts › Node.js
BackendIntégrer NaabigaPay en Node.js
Votre serveur Express détient la clé secrète : il crée les liens de paiement et vérifie les webhooks.
Deux choses à faire côté serveur : créer un lien de paiement quand un client veut payer, puis vérifier la signature du webhook que NaabigaPay envoie une fois le paiement réussi. La clé secrète (sk_live_…) reste sur ce serveur et n'est jamais exposée au navigateur.
1.Créer le lien
Votre front (web ou mobile) appelle cette route ; c'est elle qui appelle POST/v1/payment-links avec la clé secrète, puis renvoie l'url de paiement.
// npm i express node-fetch (Node 18+ : fetch est déjà global)
import express from 'express';
import fetch from 'node-fetch';
const app = express();
app.use(express.json());
const NPAY = 'https://api.pay.naabiga.com/api/v1';
const SECRET = process.env.NPAY_SECRET; // sk_live_... : ne quitte jamais le serveur
// Votre application appelle CETTE route, jamais NaabigaPay directement.
app.post('/api/pay', async (req, res) => {
const { commande, amount } = req.body;
const r = await fetch(`${NPAY}/payment-links`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${SECRET}`,
'Content-Type': 'application/json',
'Idempotency-Key': `cmd-${commande}`, // évite un double lien en cas de retry
},
body: JSON.stringify({
amount, // entier, en FCFA
reference: commande, // votre numéro de commande
description: `Commande ${commande}`,
return_url: 'https://monsite.com/merci',
cancel_url: 'https://monsite.com/panier',
single_use: true,
}),
});
if (!r.ok) return res.status(502).json({ error: await r.text() });
const link = await r.json();
res.json({ url: link.url }); // le front ouvre cette URL
});La réponse contient url (ex. https://pay.naabiga.com/link/9f3c…) : la page où le client paie. Pour un montant libre, remplacez amount par amount_min, amount_max et une description.
2.Vérifier le webhook
NaabigaPay envoie POST à l'URL enregistrée dans le portail marchand, avec les en-têtes X-NPay-Event et X-NPay-Signature (HMAC-SHA256 du corps brut). Utilisez express.raw pour disposer du corps brut, recalculez le HMAC, comparez en temps constant, et ne créditez qu'ensuite.
import crypto from 'crypto';
const WEBHOOK_SECRET = process.env.NPAY_WEBHOOK_SECRET;
// express.raw (et non express.json) : la signature porte sur le corps BRUT.
app.post('/webhooks/naabiga', express.raw({ type: '*/*' }), (req, res) => {
const signature = req.get('X-NPay-Signature') || '';
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(req.body) // req.body est un Buffer (corps brut, non modifié)
.digest('hex');
const a = Buffer.from(signature);
const b = Buffer.from(expected);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(400).send('signature invalide');
}
const payload = JSON.parse(req.body.toString('utf8'));
if (req.get('X-NPay-Event') === 'payment.succeeded') {
// Créditez selon le montant de VOTRE commande (payload.reference), pas celui du corps.
marquerCommandePayee(payload.reference);
}
res.sendStatus(200);
});Astuce. Déclarez la route webhook avant tout express.json() global, sinon le corps est déjà transformé et la signature ne correspond plus.
Référence rapide
| Méthode | Endpoint | Rôle |
|---|---|---|
| POST | /v1/payment-links | Créer un lien de paiement |
| GET | /v1/payments/{reference} | Statut d'un paiement |
Base : https://api.pay.naabiga.com/api/v1 · Auth : Authorization: Bearer sk_…. Voir la documentation complète et le format des webhooks.