Référence API
Annexe technique : endpoints publics, headers conventions, codes erreur, catalogue events webhook. Pour les guides narratifs, voir Connecter un marchand et Encaisser pour un marchand.
Base URL
| Environnement | Base URL |
|---|---|
| Production | https://prod.paywithnex.com/api |
| Test (staging) | https://dev.paywithnex.com/api |
Tous les endpoints partner publics sont préfixés /partners-public/.
Authentification
Toutes les requêtes doivent porter le header :
Authorization: Bearer nex_sk_live_xxxxxxxxxxxxxxxxxxxxxxxxSans ce header, ou avec une clé invalide / révoquée, vous recevez 401.
Endpoints
Connexions
| Méthode | Path | Scope requis | Description |
|---|---|---|---|
POST | /partners-public/connections/request | aucun | Demande une connexion à un marchand par téléphone E.164. Anti-énumération : renvoie 200 même si marchand inconnu. |
GET | /partners-public/connections/:id | aucun | Lit le statut courant d'une connexion. Renvoie 404 si elle ne vous appartient pas. |
POST | /partners-public/connections/:id/revoke | aucun | Révoque une connexion active de votre côté (status → revoked, revoked_by=integration). |
Commandes
| Méthode | Path | Scope requis | Description |
|---|---|---|---|
POST | /partners-public/orders | orders.write | Crée un QR de paiement temporaire au nom d'un marchand connecté. |
Schémas de payload
POST /connections/request
// Request
{
"merchantPhone": "+242069887766", // E.164 obligatoire
"scopes": ["orders.write"], // au moins 1, valeurs autorisées ci-dessous
"requestedReason": "Terminal MaishaPay déployé chez la boulangerie" // optionnel
}
// Response 200
{
"connectionId": "0f165ba2-ec79-4b1a-b5e3-072e6a44a229",
"status": "pending",
"expiresAt": "2026-05-30T16:44:07.768Z",
"alreadyExisted": false
}GET /connections/:id
// Response 200 (ou 404 si la connexion n'est pas à vous)
{
"id": "0f165ba2-...",
"integrationId": "4dd5e991-...",
"merchantId": "0d85d7a4-...",
"status": "active", // pending | active | rejected | revoked | expired
"scopes": ["orders.write"],
"requestedReason": "...",
"pendingExpiresAt": "2026-05-30T16:44:07.768Z",
"activatedAt": "2026-05-24T08:15:23.000Z", // null si pas active
"revokedAt": null,
"revokedBy": null, // merchant | integration | admin | system | null
"createdAt": "...",
"updatedAt": "..."
}POST /orders
// Request
{
"merchantId": "0d85d7a4-...", // UUID obligatoire (issu de la connexion active)
"amount": 2500, // int > 0, plus petite unité de la devise
"currencyCode": "XAF", // ISO 4217, optionnel (défaut XAF)
"ttlSeconds": 300, // optionnel, 60..900, défaut 300
"description": "Baguettes × 3", // optionnel, 0-280 chars
"externalReference": "MAISHA-ORDER-42" // optionnel, 0-128 chars, renvoyé dans le webhook
}
// Response 201
{
"orderId": "cdefc6f6-...",
"qrToken": "T_bfB1miJXZISq2_21ixG2D",
"status": "pending",
"amount": 2500,
"currencyCode": "XAF",
"expiresAt": "2026-05-24T12:05:00.000Z",
"merchantId": "0d85d7a4-...",
"integrationId": "4dd5e991-..."
}Catalogue scopes
| Scope | Permet |
|---|---|
orders.write | Créer des commandes (QR de paiement) au nom du marchand via POST /orders. |
orders.read | Lire le statut des commandes créées (endpoint à venir V2). |
Au moment de demander une connexion, vous précisez les scopes nécessaires. Le marchand voit la liste en clair lors de l'arbitrage.
Codes erreur
Authentification (401)
code | Cause | Action |
|---|---|---|
INVALID_INTEGRATION_CREDENTIALS | Bearer manquant, mal formé, ou clé révoquée. | Vérifier le header Authorization, recharger la clé depuis le secret manager. |
INTEGRATION_REVOKED | Votre intégration entière a été révoquée par Nex. | Contacter partners@paywithnex.com. |
Autorisation (403)
code | Cause | Action |
|---|---|---|
NO_ACTIVE_CONNECTION | Pas de connexion active pour ce merchantId avec votre intégration. | Vérifier que le marchand a accepté ; refaire GET /connections/:id. |
MISSING_SCOPE | La connexion existe mais ne porte pas le scope requis (ex: orders.write absent). | Re-demander une connexion avec les bons scopes. |
Validation (400)
code | Cause | Action |
|---|---|---|
VALIDATION_ERROR | Body mal formé : amount ≤ 0, ttlSeconds < 60 ou > 900, merchantId pas UUID, scope inconnu… | Lire errors[] pour le détail champ par champ. |
INVALID_PHONE_FORMAT | merchantPhone n'est pas E.164 valide. | Format obligatoire +<countryCode><number>, sans espaces. |
INVALID_AMOUNT | amount non entier ou ≤ 0. | Convertir en plus petite unité (XAF/XOF = unités). |
INVALID_TTL | ttlSeconds < 60. | Minimum 60 secondes. |
TTL_TOO_LONG | ttlSeconds > 900. | Max 15 min. |
Conflit (409)
code | Cause | Action |
|---|---|---|
CONFLICT | État incohérent : ex. essayer d'accepter une connexion déjà active, révoquer une connexion expired. | Lire le message pour le détail. |
Not found (404)
code | Cause |
|---|---|
CONNECTION_NOT_FOUND | connectionId inconnu, ou n'appartient pas à votre intégration (anti-leak). |
Headers réponse standard
Toutes les réponses Nex portent :
Content-Type: application/json; charset=utf-8
X-Request-Id: <uuid> // ID unique de la requête — à inclure dans tout report bug
Server-Timing: total;dur=247 // temps de traitement Nex en ms (utile pour debug latency)Catalogue events webhook
order.paid
Émis quand un QR créé via POST /orders est payé par un client final (transaction completed côté ledger).
Payload data :
{
"orderId": "cdefc6f6-...", // = QR id
"transactionId": "tx-uuid-...", // référence interne Nex (pour support)
"amount": 2500,
"currencyCode": "XAF",
"paidAt": "2026-05-24T12:02:34.000Z", // commit ledger
"externalReference": "MAISHA-ORDER-42", // votre référence (echo), absent si non fourni
"description": "Baguettes × 3" // echo, absent si non fourni
}Quand : entre 100 ms et quelques secondes après le commit ledger (latence enqueue BullMQ + worker pickup).
Garanties : at-least-once (jusqu'à 7 retries × 30s × 2^n = ~1h cumulé). Voir Recevoir les webhooks.
Format Nex-Signature (HMAC SHA256)
Nex-Signature: t=1716553354,v1=5e7c1a3f4b8d2c9e6f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3| Composant | Description |
|---|---|
t | Unix timestamp en secondes au moment de la signature (côté Nex). |
v1 | hex_lower(HMAC_SHA256(webhook_secret, "${t}.${rawBody}")) |
Code de vérification : voir Guide implémentation receiver.
Convention de versioning
- Préfixe public :
/api/...(le versioningv1est géré côté serveur, transparent pour vous). Une montée de version majeure ne serait introduite qu'en cas de breaking change (sur un horizon de 12+ mois) et annoncée à l'avance. - Additions de champs dans les payloads existants : non-breaking, déployées sans préavis. Votre receiver doit tolérer des champs inconnus (ignorer plutôt que crasher).
- Retraits de champs : passent par un cycle de deprecation 6 mois avec annonce email aux contacts techniques.
- Nouveaux events webhook : votre receiver doit retourner
200quand il reçoit un event qu'il ne sait pas traiter (ne pas crasher).
Voir aussi
- Vue d'ensemble — concepts
- Cycle de vie — opérations longue durée
- Guide implémentation receiver — code receiver complet