Documentation de l'API
Estimez le prix d'une course, publiez-la, suivez le livreur et recevez chaque statut dans votre application.
URL de base https://api.nabanam.educaly.net/api/v1
Démarrer
- Demandez un accès partenaire. Naɓanam crée votre compte et définit avec vous votre mode d'encaissement.
- Connectez-vous à l'espace partenaire et créez une clé d'API.
- Déclarez l'adresse de votre webhook et abonnez-la à
delivery.status_changed. - Estimez un prix, puis publiez votre première course.
Authentification
Envoyez votre clé dans l'en-tête x-api-key de chaque requête. Les corps de requête et de réponse sont
en JSON.
Exemple
curl https://api.nabanam.educaly.net/api/v1/deliveries/calculate-price \
-H "x-api-key: nbn_live_…" \
-H "Content-Type: application/json" \
-d '{ "pickupPosition": { "lat": 14.6937, "lng": -17.4441 }, "dropoffPosition": { "lat": 14.7167, "lng": -17.4677 } }' Appelez l'API depuis votre serveur uniquement. Une clé placée dans une application mobile ou un site web peut être lue par n'importe qui. En cas de doute, révoquez-la dans l'espace partenaire et créez-en une autre.
Estimer le prix
POST/deliveries/calculate-price
Renvoie le prix d'une course entre deux points, sans la publier. Affichez-le à votre client avant le paiement.
Corps de la requête
pickupPositionobjet { lat, lng }obligatoire- Coordonnées GPS du point de retrait, en nombres décimaux.
dropoffPositionobjet { lat, lng }obligatoire- Coordonnées GPS du point de livraison.
Réponse 201
distanceMnombre- Distance estimée du trajet, en mètres. Elle est corrigée pour approcher le trajet par la route.
priceentier- Prix de la course en FCFA, à afficher à votre client.
Requête
POST /api/v1/deliveries/calculate-price
{
"pickupPosition": { "lat": 14.6937, "lng": -17.4441 },
"dropoffPosition": { "lat": 14.7167, "lng": -17.4677 }
} Réponse
{
"distanceM": <distance en mètres>,
"price": <prix en FCFA>
} Le prix dépend de la distance, selon une grille par tranches. Le même calcul s'applique à la publication.
Publier une course
POST/deliveries
Publiez la course quand votre client a validé sa commande. Elle est ensuite proposée aux livreurs proches du point de retrait.
Corps de la requête
-
externalReftexte obligatoire - Votre référence de commande. Elle est unique pour votre compte : une référence déjà utilisée est refusée.
-
pickupAddresstexte obligatoire - Adresse du point de retrait, telle que le livreur la lira.
-
pickupPositionobjet { lat, lng } obligatoire - Coordonnées GPS du point de retrait.
-
dropoffAddresstexte obligatoire - Adresse de livraison.
-
dropoffPositionobjet { lat, lng } obligatoire - Coordonnées GPS du point de livraison.
-
senderNametexte obligatoire - Nom du vendeur ou du point de retrait.
-
senderPhonetexte obligatoire - Téléphone du vendeur. Le livreur peut l'appeler depuis l'application.
-
recipientNametexte obligatoire - Nom du destinataire.
-
recipientPhonetexte obligatoire - Téléphone du destinataire. Format international conseillé, par exemple
+221…. -
packageDescriptiontexte facultatif - Description du colis, visible par le livreur.
-
successUrlURL obligatoire en encaissement Wave - Page où votre client revient après un paiement réussi.
-
errorUrlURL obligatoire en encaissement Wave - Page où votre client revient si le paiement échoue.
Requête
POST /api/v1/deliveries
{
"externalRef": "CMD-1042",
"pickupAddress": "Rue 10, Point E, Dakar",
"pickupPosition": { "lat": 14.6937, "lng": -17.4441 },
"dropoffAddress": "Cité Keur Gorgui, Dakar",
"dropoffPosition": { "lat": 14.7167, "lng": -17.4677 },
"senderName": "Boutique Point E",
"senderPhone": "+221 77 000 00 01",
"recipientName": "Awa Ndiaye",
"recipientPhone": "+221 77 000 00 02",
"packageDescription": "Deux plats, un sac",
"successUrl": "https://votre-app.sn/commandes/CMD-1042/paiement-reussi",
"errorUrl": "https://votre-app.sn/commandes/CMD-1042/paiement-echoue"
} Réponse 201, encaissement par vous
La course est publiée immédiatement. Conservez son id pour le suivi.
{
"id": "<identifiant de la course>",
"externalRef": "CMD-1042",
"status": "PENDING",
"paymentStatus": "CONFIRMED",
"price": <prix en FCFA>,
"createdAt": "…"
} Réponse 201, encaissement par Wave
Redirigez votre client vers waveLaunchUrl. Il paie le prix de la course plus 1 % de frais Wave,
arrondi aux 5 FCFA supérieurs. Après un paiement réussi, il revient sur successUrl et la course est
publiée. Sinon, il revient sur errorUrl et la course n'est pas proposée aux livreurs.
{
"waveLaunchUrl": "<lien de paiement Wave>",
"clientReference": "<référence du paiement>"
} Cette réponse ne contient pas l'identifiant de la course : suivez-la avec votre externalRef.
Suivre une course
GET/deliveries/:id/tracking
GET/deliveries/by-ref/:externalRef/tracking
Renvoie le statut de la course, le livreur assigné, sa dernière position et son heure d'arrivée estimée. Utilisez l'identifiant de la course ou votre référence.
Réponse 200
statustexte- Statut de la course. Voir Statuts d'une course.
delivererobjet ou null- Livreur assigné :
fullName,phone,imageUrl,vehicleType(BIKE,SCOOTERouTRICYCLE),vehiclePlateNumber. Vautnulltant qu'aucun livreur n'a accepté la course. positionobjet ou null- Dernière position connue du livreur :
lat,lng,updatedAt. etaobjet ou null- Arrivée estimée :
targetvautPICKUPavant le retrait etDROPOFFensuite, avecdistanceM,durationSecetestimatedArrivalAt. Vautnullsans position du livreur ou hors trajet.
GET /api/v1/deliveries/by-ref/CMD-1042/tracking
{
"deliveryId": "<identifiant de la course>",
"externalRef": "CMD-1042",
"status": "ACCEPTED",
"deliverer": {
"fullName": "…",
"phone": "…",
"imageUrl": "…",
"vehicleType": "SCOOTER",
"vehiclePlateNumber": "…"
},
"position": { "lat": 14.7012, "lng": -17.4498, "updatedAt": "…" },
"eta": {
"target": "PICKUP",
"distanceM": <distance restante en mètres>,
"durationSec": <durée estimée en secondes>,
"estimatedArrivalAt": "…"
}
}
L'acceptation d'une course n'est pas encore envoyée par webhook. Pour savoir qu'un livreur a pris la course,
consultez cet endpoint : deliverer n'est plus null et status vaut
ACCEPTED.
Webhooks
Naɓanam envoie une requête POST à votre adresse à chaque changement de statut d'une course. Déclarez
l'adresse dans l'espace partenaire : elle doit être en HTTPS et publique. Les adresses locales ou privées sont
refusées.
Requête reçue par votre serveur
POST https://votre-app.sn/webhooks/nabanam
Content-Type: application/json
X-Nabanam-Signature: sha256=<signature>
{
"event": "delivery.status_changed",
"payload": {
"externalRef": "CMD-1042",
"deliveryId": "<identifiant de la course>",
"previousStatus": "ACCEPTED",
"newStatus": "PENDING",
"occurredAt": "…",
"cancellation": { "reason": "BREAKDOWN", "label": "Panne" }
}
} Quand un événement est envoyé
- Le livreur récupère le colis (
PICKED_UP), part vers le destinataire (IN_PROGRESS) ou livre (DELIVERED). -
La course revient à
PENDING: le livreur l'a rendue, etcancellationdonne le motif, ou il ne l'a pas récupérée à temps, sanscancellation. Elle est de nouveau proposée aux livreurs. - L'équipe Naɓanam modifie un statut, par exemple pour annuler une course. Le motif, s'il est saisi, est dans
cancellation.
Motifs dans cancellation.reason
BREAKDOWN- Panne
UNFORESEEN- Imprévu
PRICE_TOO_LOW- Prix trop bas
AREA_INACCESSIBLE- Zone inaccessible
MERCHANT_UNREACHABLE- Vendeur injoignable
OTHER- Autre
ADMIN- Motif saisi par l'équipe Naɓanam
Le champ label contient le motif en clair, prêt à afficher.
Vérifier la signature
L'en-tête X-Nabanam-Signature vaut sha256= suivi du HMAC-SHA256 du corps brut, calculé
avec le secret de votre webhook. Le secret est visible dans l'espace partenaire. Refusez toute requête dont la
signature ne correspond pas.
Node.js et Express
import crypto from "node:crypto";
import express from "express";
const app = express();
// La signature porte sur le corps brut : lisez-le avant de le parser.
app.post("/webhooks/nabanam", express.raw({ type: "application/json" }), (req, res) => {
const expected = "sha256=" + crypto
.createHmac("sha256", process.env.NABANAM_WEBHOOK_SECRET)
.update(req.body)
.digest("hex");
const received = req.get("X-Nabanam-Signature") ?? "";
const valid =
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!valid) return res.sendStatus(401);
const { event, payload } = JSON.parse(req.body);
// Mettez à jour la commande payload.externalRef avec payload.newStatus
res.sendStatus(200);
}); Réponses et nouvelles tentatives
- Répondez par un code
2xxrapidement, puis traitez l'événement. - Sans réponse
2xx, l'envoi est retenté, jusqu'à 5 tentatives au total, avec un délai qui double à partir de 2 secondes. - Un même événement peut donc arriver plus d'une fois, et dans le désordre. Utilisez
occurredAtpour ignorer un statut plus ancien que celui que vous avez déjà.
Tester votre adresse
Dans l'espace partenaire, le bouton de test envoie deux requêtes avec l'événement ping et des données
fictives : la première avec une signature invalide, à laquelle votre serveur doit répondre 401, la
seconde avec une signature valide, qui doit recevoir un 2xx. Le journal des envois montre le code
renvoyé par votre serveur.
Statuts d'une course
PENDING- En attente d'un livreur. La course est proposée aux livreurs proches. En encaissement Wave, elle ne l'est qu'après confirmation du paiement.
ACCEPTED- Un livreur a accepté la course et se rend au point de retrait.
PICKED_UP- Colis récupéré. Le livreur l'a confirmé à moins de 100 mètres du point de retrait.
IN_PROGRESS- En route vers le destinataire. Étape facultative : une course peut passer directement de
PICKED_UPàDELIVERED. DELIVERED- Livré. Le livreur l'a confirmé à moins de 100 mètres du point de livraison.
CANCELLED- Course annulée, par l'équipe Naɓanam ou après un paiement Wave qui n'a pas abouti.
Paiement
Le mode d'encaissement est défini à l'ouverture de votre compte. Dans les deux cas, Naɓanam paie le livreur par Wave après la livraison.
Encaissement par Wave
Naɓanam crée un lien de paiement Wave à la publication. Votre client paie, puis la course part vers les livreurs.
successUrl et errorUrl sont obligatoires.
Encaissement par vous
Vous collectez les frais de livraison dans votre propre parcours. La course est publiée dès l'appel à l'API.
Erreurs
Une erreur renvoie un code HTTP et un corps JSON. message est un texte, ou une liste pour les erreurs de validation.
{
"statusCode": 400,
"message": ["recipientPhone should not be empty"],
"error": "Bad Request"
} 400Bad Request- Champ manquant ou mal formé (
messageliste les champs concernés), référence déjà utilisée, ousuccessUrleterrorUrlabsents en encaissement Wave. 401Unauthorized- Clé d'API absente, invalide ou révoquée.
404Not Found- Course introuvable pour votre compte, ou aucune tranche tarifaire ne couvre cette distance.
5xxErreur serveur- Erreur de notre côté, par exemple un lien de paiement Wave qui n'a pas pu être créé. La référence a pu être enregistrée : réessayez avec une nouvelle
externalRef.
Une question sur l'intégration ? Écrivez-nous.