Transferts
L'objet transfert, les trois appels et les conditions qui déterminent son exécution.
Un transfert retire des fonds de votre solde pour les envoyer vers le portefeuille Mobile Money d'une autre personne. Vous donnez à Wajub un montant et un destinataire. Wajub réserve les fonds, choisit une route, puis remet le payout à un opérateur. Transferts et payouts présente toute la section, le coût d'un payout et les conditions nécessaires à son exécution. Cette page sert de référence pour l'objet lui-même.
Les payouts nécessitent une clé secrète. Une clé publique renvoie 406 Private Key Required avant
toute validation. Un navigateur est refusé encore plus tôt. Une clé restreinte fonctionne aussi si
ses autorisations couvrent les transferts.
Les trois appels
Il n'en existe aucun autre. L'API ne permet ni de modifier, ni d'annuler, ni de supprimer un transfert. Il n'existe aucun endpoint de lot.
| Appel | Fonction |
|---|---|
GET /transfers | Lister et filtrer vos payouts |
POST /transfers | Créer un payout et le démarrer immédiatement |
GET /transfers/{id} | Récupérer un payout |
{id} correspond à la chaîne po_ renvoyée par l'API. Dans la sandbox, elle commence par
po_test_, ce qui permet de distinguer les deux environnements dans un log.
L'objet transfert
Les trois appels renvoient cet objet. beneficiary, payment_method et provider sont chargés à
chaque fois et sont donc toujours présents.
Certains champs ne fonctionnent pas comme leur nom le suggère. L'un d'eux constitue un piège.
idstringfacultatifpo_ en live et po_test_ dans la sandbox.referencenullfacultatifnull. Lisez l’avertissement ci-dessous avant de vous appuyer sur ce champ.provider_referencestringfacultatifamountnumberfacultatif10000 représente dix mille francs, pas cent.statusenumfacultatifpending, processing, review, succeeded, failed ou cancelled.completebooleanfacultatifsucceeded. Un payout échoué est terminé mais renvoie toujours false. Ce champ indique donc le résultat, pas la fin du payout.sourceenumfacultatifapi pour toute création par votre intégration, dashboard ou recurring pour un payout lancé depuis la Konsole.metadataobjectfacultatiffailure_reasonstringfacultatifstatus vaut failed, avec failure_message. Ce champ est absent pour tous les autres statuts, et non défini à null.ledger_balance_currencystringfacultatifmetadata.settlement.reference vaut toujours null, quelle que soit la valeur envoyée
POST /transfers accepte et valide un champ reference dans le corps, puis l'ignore. Aucune
colonne correspondante n'existe sur un transfert. Le sérialiseur demande ce champ dans chaque
réponse, où il vaut toujours null. Placez votre identifiant dans metadata ou description,
puis utilisez id lorsque le payout revient. La recherche par référence fonctionne en mode live,
mais elle porte sur l'id po_, pas sur une valeur que vous avez envoyée.
Wajub écrit aussi dans metadata. Un payout live sur le circuit Wajub reçoit un objet reservation
qui indique les fonds retenus et l'heure de la réservation. Il reçoit aussi un objet settlement
si les fonds viennent d'un portefeuille dans une autre devise. Lisez vos propres clés par leur nom
au lieu de supposer que tout l'objet vous appartient.
Créer un transfert
https://api.wajub.com/transfersbeneficiarystring | objectobligatoireamountnumberobligatoire0.01 et dans les plafonds définis pour la devise.currencystringfacultatifdéfaut : XAFXAF, pas la devise de votre compte.descriptionstringfacultatifreasonstringfacultatifreferencestringfacultatifmetadataobjectfacultatifTransmettre le destinataire inline crée le bénéficiaire pendant l'appel. Cette forme convient à une personne que vous ne paierez qu'une fois. Les champs appartiennent au bénéficiaire et sont tous décrits dans Bénéficiaires.
beneficiary.namestringobligatoirebeneficiary.channelstringobligatoirecm.mtn. La forme générique cm.mobile détermine l'opérateur à partir du numéro.beneficiary.phonestringfacultatifaccount_number.beneficiary.account_numberstringfacultatifbeneficiary.emailstringfacultatifbeneficiary.country_codestringfacultatifbeneficiary.notestringfacultatifL'ensemble permet de créer en un seul appel un payout qui en aurait autrement nécessité deux.
curl https://api.wajub.com/transfers -H "Authorization: sk.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…" -H "Content-Type: application/json" -H "Idempotency-Key: payout-892" -d '{
"amount": 10000,
"currency": "XAF",
"description": "Supplier payment #892",
"metadata": { "vendor_id": "v_892" },
"beneficiary": {
"name": "Aminata Diallo",
"channel": "cm.mtn",
"phone": "+237670000000"
}
}'La réponse est 201 Created. Un bénéficiaire créé de cette manière est normal : il possède un id,
apparaît dans GET /beneficiaries et peut être payé plus tard sans répéter son numéro.
Pour payer un bénéficiaire enregistré, utilisez uniquement son id
La forme sous forme de chaîne recherche l'id ben_. Son numéro de téléphone et son adresse
e-mail ne fonctionnent pas, même s'ils identifient ce bénéficiaire ailleurs dans l'API. Les deux
renvoient 422 The selected beneficiary is invalid.. Un bénéficiaire ajouté depuis la Konsole est
également refusé avec This beneficiary was added from the Konsole dashboard and cannot be paid out via the API.. Une clé divulguée ne doit pas pouvoir payer une destination que votre
intégration n'a jamais créée.
Les montants utilisent l'unité principale
Le champ amount d'un transfert utilise l'unité principale de la devise et accepte les décimales.
10000 envoie dix mille francs. Une facture utilise au contraire des lignes en
unités entières de la devise. Ne réutilisez donc pas une conversion de l'un vers l'autre.
Chaque devise possède un minimum et un maximum, contrôlés avant toute autre opération. Dépasser
l'un de ces plafonds renvoie une erreur 422 qui indique les deux valeurs.
| Devise | Minimum | Maximum |
|---|---|---|
XAF XOF | 25 | 2 000 000 |
NGN | 100 | 999 999,99 |
RWF | 500 | 999 999 999 |
UGX | 1 000 | 999 999 999 |
GHS KES USD EUR | 0,50 | 999 999,99 |
Ces valeurs constituent les plafonds de la plateforme pour un appel. Les autres plafonds vous sont propres : le plafond quotidien défini par vos soins, la limite liée à votre palier KYC et le plafond par transfert de l'opérateur. Transferts et payouts présente toute la séquence.
L'idempotence est facultative et constitue votre seule protection
Envoyez un en-tête Idempotency-Key. Une nouvelle tentative du même appel renverra alors le
transfert d'origine au lieu d'envoyer les fonds deux fois. Si vous l'omettez, rien n'empêche un
doublon. Il n'existe aucune fenêtre, aucune déduplication selon le montant et aucune annulation de
repli.
La clé accepte au maximum 128 caractères parmi les lettres, les chiffres et . _ : -. Toute autre
valeur renvoie une erreur 422 sur le seul format. Elle est conservée pendant 24 heures, par compte
et par environnement.
Définition d'un même appel
Réutiliser une clé pendant cette période avec un amount, une currency, un beneficiary, une
description ou une reason différente renvoie 422 This Idempotency-Key was already used with a different request payload.. La réutiliser avec seulement d'autres metadata ne produit pas
cette erreur. Vous récupérez le transfert d'origine et les nouvelles métadonnées sont ignorées
sans avertissement. Dérivez la clé de l'élément payé, comme une ligne de commande ou un traitement
de paie. Une même clé ne pourra ainsi jamais décrire deux payouts différents.
La création de payouts possède sa propre limite de requêtes, qui s'ajoute à celle de votre plan :
20 appels par minute et par compte, quel que soit le plan. Elle s'applique aussi à POST /beneficiaries. Une boucle qui crée un bénéficiaire puis le paie compte donc deux fois.
Réponse reçue et statut initial
Un payout live est créé avec le statut processing et est déjà en cours d'acheminement. pending
n'appartient qu'à la sandbox. Même dans celle-ci, ce statut ne dure que l'instant entre l'écriture
de la ligne et sa prise en charge par le prestataire simulé. La réponse reçue indique donc déjà
processing. Vous ne pouvez l'observer que dans un webhook sandbox transfer.created.
transfer.created est émis à la création de chaque payout, y compris pour un payout retenu pour
examen. Statuts et webhooks détaille les quatre
événements et le rôle de chaque handler.
cancelled ne provient jamais de l'API. Ce statut apparaît lorsqu'un payout est refusé dans la file
de confirmation de la Konsole. C'est le seul statut que votre intégration ne peut pas produire.
Raisons de la retenue d'un payout live
Un payout live qui déclenche l'un des contrôles suivants est créé avec le statut review au lieu de
processing. Ses fonds sont réservés, aucun opérateur ne l'a reçu et il attend que Wajub le libère
ou le refuse.
| Motif de retenue | Signification |
|---|---|
| Il s'agit de votre premier payout | Votre compte ne possède encore aucun payout réussi dans cette devise |
| Le montant dépasse le seuil de la plateforme | 1 000 000 par défaut, comparé au montant quelle que soit la devise |
| Le montant dépasse votre propre seuil | Le montant d'approbation défini dans Transfer security settings |
| Vous avez demandé la confirmation des payouts | Le réglage de confirmation, sauf pour le montant exempté par votre approbation automatique |
| Le montant est inhabituel pour vous | Au moins cinq fois la moyenne de vos cinquante derniers payouts réussis, dès que vous en avez au moins trois |
Un payout retenu quitte finalement review dans l'une de deux directions. S'il est libéré, il passe
à processing et continue normalement. S'il est refusé, il passe à failed avec failure_reason: admin_rejected, puis la réservation revient dans votre solde disponible.
Vous ne pouvez ni lister ni débloquer les payouts en cours d'examen
GET /transfers?status=review renvoie une erreur 422. Le filtre accepte les cinq autres statuts,
mais pas celui-ci. Pour voir un payout retenu, récupérez-le directement ou parcourez une liste non
filtrée. Aucun endpoint ne permet de l'approuver, de l'annuler ou de le relancer. Le webhook
transfer.created vous informe de la retenue. transfer.processing ou transfer.failed vous
informe de sa résolution. Attendez ces événements au lieu d'utiliser le polling.
Les payouts sandbox ne sont jamais retenus. Vous ne pouvez pas reproduire ce comportement avant le passage en live. Votre premier payout live mérite donc une surveillance humaine plutôt qu'une tâche cron.
En cas d'échec
Un payout échoué contient failure_reason, un code court, et failure_message, une phrase. Ces deux
champs sont absents pour tous les autres statuts. Ne les lisez qu'après avoir vérifié le statut.
Wajub écrit lui-même le code lorsque le payout n'a jamais atteint un opérateur. Après cette étape,
le code correspond à la valeur renvoyée par l'opérateur, ou à la valeur littérale failed si sa
réponse est inutilisable. Considérez ce champ comme un libellé à consigner dans vos logs et à
présenter à une personne, pas comme une enum sur laquelle baser vos branches.
failure_reason | Problème rencontré |
|---|---|
invalid_recipient | La destination est introuvable ou le bénéficiaire a perdu son canal |
no_provider | Aucune route de payout n'était disponible pour ce canal et cette devise |
provider_rejected | L'opérateur a refusé le payout et en indique la raison dans failure_message |
team_restricted | Une restriction de payout a été ajoutée à votre compte entre la création et l'exécution |
admin_rejected | Un payout retenu a été refusé pendant l'examen |
failed, ou le propre code d'un opérateur | Le payout a atteint un opérateur et a échoué |
La sandbox fait exception. Son vocabulaire se limite à failure, network_error et
invalid_recipient, chacun associé à un numéro de test.
Pour un payout sur le circuit Wajub, un échec libère la réservation. Le principal et l'estimation
des frais reviennent dans available, sans aucun prélèvement. Vous pouvez recréer le payout avec
une nouvelle clé d'idempotence.
Tester le payout
Les payouts sandbox puisent dans votre solde sandbox et se terminent automatiquement environ deux secondes plus tard. Le résultat n'est pas aléatoire. Il dépend du numéro du destinataire, qui doit être un numéro de test reconnu. Sinon, l'appel est immédiatement refusé.
| Numéro | Résultat |
|---|---|
+237670000000 | succeeded |
+237670000001 | Refus à la création avec 422, le transfert n'est jamais créé |
+237670000002 | failed, motif failure |
+237670000003 | failed, motif network_error |
+237670000004 | failed, motif invalid_recipient |
Le suffixe compte, pas le préfixe. Ces cinq terminaisons fonctionnent donc après tous les préfixes
d'opérateurs connus de la sandbox. +2250700000002 échoue comme +237670000002. Des zéros initiaux
sont tolérés entre le préfixe et le suffixe.
Des fonds insuffisants ne produisent pas un payout échoué
…0001 représente un destinataire dont le portefeuille ne peut pas recevoir les fonds. La
sandbox renvoie une erreur 422 avant toute création. Il n'existe aucun transfert à récupérer ni
aucun webhook. Pour tester un handler d'échec, utilisez plutôt …0002.
Les payouts sandbox entraînent 2 % de frais de plateforme ajoutés au montant. Ces frais ne sont pas rendus en cas d'échec. Ce taux propre à la sandbox ne correspond pas à la tarification live.
Lister les transferts
https://api.wajub.com/transfersstatusenumfacultatifpending, processing, succeeded, failed ou cancelled. Pas review.searchstringfacultatifreason, ainsi que le nom et le numéro du bénéficiaire.per_pageintegerfacultatifdéfaut : 25cursorstringfacultatifnext_cursor d'une page précédente. L'envoyer fait passer toute la réponse en mode curseur.Il n'existe aucun filtre par date ou par montant. Une tâche de rapprochement parcourt donc la liste
et applique son propre filtre sur created_at.
curl -G https://api.wajub.com/transfers -H "Authorization: sk.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…" -d status=failed -d per_page=50La liste est triée par date de création, de la plus récente à la plus ancienne. Vous ne pouvez pas
modifier cet ordre. Par défaut, elle utilise une pagination par numéro de page. meta contient alors
current_page, last_page, per_page et total. Si vous transmettez un cursor, meta change
entièrement et contient per_page, next_cursor, prev_cursor et has_more. Choisissez un mode et
écrivez votre boucle en conséquence. Un handler qui lit last_page casse dès qu'un curseur est
ajouté.
Récupérer un transfert
https://api.wajub.com/transfers/{id}Seul l'id po_ permet de retrouver un transfert. Un transfert appartenant à un autre compte ou à
l'autre environnement renvoie 404 Transfer Not Found, sans révéler son existence ailleurs.
curl https://api.wajub.com/transfers/po_CSUGajfv9xh0XQ5wu2lx -H "Authorization: sk.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…"La récupération sert au rapprochement et au support, pas au suivi d'un payout. Attendez plutôt le webhook. Un payout qui atteint un opérateur peut y rester plusieurs minutes. Le polling ne vous apprend rien que l'événement ne vous aurait pas indiqué plus tôt.
Pages associées
- Démarrage rapideVotre premier payout de test, de bout en bout.
- Prestataires et canauxTous les slugs de canaux, classés par pays et par opérateur.
- Statuts et webhooksLes quatre événements et le rôle de chaque handler.
- BénéficiairesEnregistrez une destination une fois, puis payez-la par son id.
- Solde et règlementsLes fonds disponibles et ceux déjà réservés.