Documentation de l'API SysPay
SysPay expose une API REST JSON unique pour encaisser (mobile money, cartes), verser, rembourser et gérer une trésorerie multi-pays. Toutes les URL sont relatives à https://votre-domaine/api/v1.
checkout_url, puis livrez la commande à réception du webhook payment.succeeded.Authentification & modes
Chaque requête porte une clé secrète dans l'en-tête Authorization. Le préfixe de la clé détermine le mode :
| sk_test_… | Sandbox : routée exclusivement vers le simulateur. Il est structurellement impossible d'atteindre un réseau réel. |
| sk_live_… | Production : disponible après validation KYC du compte. |
Les clés sont hachées côté SysPay : elles ne sont affichées qu'une fois à la création. Ne les placez jamais dans une application mobile ou un navigateur.
Conventions
- Montants : toujours des entiers en unités mineures (
amount_minor). XOF, XAF, GNF : 0 décimale (5000 = 5 000 FCFA). USD, EUR : centimes. - Téléphones : format E.164 (
+221771234567). Un numéro local est complété par l'indicatif du pays. - Dates : ISO 8601 UTC (
2026-11-07T16:27:12Z). - Enveloppe :
{"success": true, "data": …}ou{"success": false, "error": {code, message, fields}}. - Listes :
?page=1&limit=20(max 100) →{object:"list", data:[…], total, has_more}.
Erreurs
| Code | HTTP | Signification |
|---|---|---|
| unauthorized | 401 | Clé absente, invalide ou révoquée |
| validation_error | 422 | Paramètre manquant ou invalide (voir fields) |
| otp_required | 422 | Le réseau exige un code OTP pré-paiement |
| idempotency_conflict | 409 | Même Idempotency-Key, corps différent |
| insufficient_balance | 402 | Solde disponible insuffisant pour le versement |
| live_not_enabled / payout_not_enabled | 403 | Compte non activé pour le live ou les versements |
| limit_exceeded | 422 | Plafond KYC par transaction ou mensuel dépassé |
| no_route | 503 | Aucun fournisseur actif pour ce réseau et ce pays |
Idempotence
Ajoutez Idempotency-Key (≤ 120 caractères, par exemple votre numéro de commande) sur toute création. Rejouer la même clé avec le même corps renvoie l'objet existant (idempotent_replay: true) ; avec un corps différent, SysPay répond 409 idempotency_conflict.
Sessions de checkout
| country * | Code pays ISO (SN, CI, CM…) |
| amount_minor * | Montant en unités mineures |
| currency | Facultatif, doit correspondre au pays |
| network | Imposer un réseau (sinon le client choisit) |
| description, merchant_reference | Affichés au client / renvoyés dans les webhooks |
| customer {name, email, phone} | Préremplissage de la page de paiement |
| return_url, cancel_url | Retour après paiement (succès et échec) |
| expires_in_minutes | Durée de validité (5 à 10080, défaut 60) |
| metadata | Objet libre renvoyé tel quel |
Après paiement, SysPay revérifie le statut auprès du fournisseur, puis redirige vers return_url?syspay_status=success|failed&syspay_transaction_id=…&syspay_reference=…&merchant_reference=…. Ces paramètres sont indicatifs : la source de vérité reste le webhook ou GET /checkout/sessions/{id}.
États : OPEN → COMPLETED | EXPIRED | CANCELLED. Un client peut retenter sur une session ouverte après un échec.
Liens de paiement
| title * | Titre affiché |
| amount_type | FIXED (montant requis) ou OPEN (montant libre) |
| amount_minor | Requis si FIXED |
| min_amount_minor | Minimum pour un lien OPEN |
| country, network, redirect_url, max_uses | Options |
Réponse : url à partager. POST /payment-links/{id}/disable ou /enable.
Paiements directs (push)
Pour débiter le client depuis votre propre interface : SysPay envoie la demande à l'opérateur, le client valide sur son téléphone.
Certains fournisseurs renvoient un checkout_url (page opérateur) : redirigez le client si présent.
| Endpoint | Rôle |
|---|---|
| GET /payments/{id} | Statut (revérifie si en attente) |
| POST /payments/{id}/verify | Force une vérification auprès du fournisseur |
| POST /payments/{id}/retry | Rejoue un échec : même id, nouvelle référence fournisseur, jamais de doublon |
| POST /payments/{id}/cancel | Annule un paiement PENDING (jamais UNKNOWN) |
| POST /payments/{id}/confirm-otp | Code OTP post-push |
| GET /payments/{id}/attempts | Historique détaillé des appels |
| GET /payments | Liste (filtres status, merchant_reference) |
OTP
Les règles sont déclarées par réseau × pays et exposées par GET /reference/networks (otp_mode, otp_instructions) :
- prepayment (ex. Orange Money SN, CI, BF) : le client génère un code via USSD ; envoyez-le dans
otpà la création, sinon422 otp_required. - postpush (ex. Wizall SN) : un SMS est envoyé après la demande ; transmettez le code avec
POST /payments/{id}/confirm-otp.
États et cycle de vie
| PENDING | Demande envoyée, en attente de validation client |
| UNKNOWN | Le fournisseur n'a pas répondu à temps. SysPay vérifie automatiquement chaque minute : ne rejouez pas. |
| SUCCESS | Confirmé ; fonds crédités au portefeuille (net de frais) |
| FAILED / CANCELLED / EXPIRED | Terminal sans mouvement de fonds (EXPIRED après 24 h sans réponse) |
| PENDING_APPROVAL | Versement en attente de validation SysPay (fonds gelés) |
| REJECTED | Versement refusé, fonds restitués |
Versements
Le montant et les frais sont gelés avant l'appel : en cas de solde insuffisant, rien ne part (402). En cas d'échec, les fonds sont restitués par écriture inverse. GET /payouts/{id}, GET /payouts.
Remboursements
{"amount_minor": 2000, "reason": "Article indisponible"} — total si amount_minor est omis. Versé sur le numéro d'origine, sans frais, débité du solde disponible. GET /refunds.
Soldes & transferts
Un portefeuille par pays. Conversion entre portefeuilles : GET /wallet-transfers/quote?from_country=SN&to_country=CM&amount_minor=100000 puis POST /wallet-transfers (validation SysPay en live, un seul transfert en attente à la fois).
Webhooks
Configurez vos endpoints dans la console. Événements : payment.created, payment.succeeded, payment.failed, payment.unknown, payment.cancelled, payment.expired, checkout.completed, payout.created, payout.succeeded, payout.failed, payout.rejected, refund.succeeded, refund.failed, transfer.approved, transfer.rejected.
Vérifier la signature
v1 = HMAC_SHA256(secret, t + "." + corps_brut). Refusez les messages de plus de 5 minutes et répondez 2xx rapidement. Les échecs sont rejoués : 10 s, 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h.
Traitez les webhooks de manière idempotente (même data.id = même commande) : un événement peut être reçu plus d'une fois.
Référentiel
Réseaux réellement disponibles pour votre mode (encaissement et versement), limites, format de numéro et règles OTP.
Sandbox
| Numéro se terminant par | Comportement |
|---|---|
| 1111 | Refus opérateur (insufficient_funds) |
| 0000 | Délai dépassé → UNKNOWN, puis SUCCESS à la vérification |
| 2222 | Fournisseur injoignable (teste la bascule de route) |
| autre | PENDING ; boutons Valider / Refuser sur la page de paiement ; SUCCESS automatique après 8 s à la vérification |
| OTP 000000 | Code post-push refusé ; tout autre code de 4 à 6 chiffres est accepté |
Les versements sandbox réussissent immédiatement (sauf 1111, 0000, 2222) ; les transferts entre portefeuilles sont approuvés automatiquement.
Mise en production
- Soumettez vos pièces KYC depuis Console → Activation.
- Après validation, générez une clé
sk_live_. - Configurez un webhook HTTPS en mode live.
- Remplacez la clé test par la clé live côté serveur. Aucune autre modification n'est nécessaire.