Versements
Un versement envoie de l’argent du solde de versement du marchand vers un portefeuille mobile ou un compte bancaire. Une clé prépare les versements et les lit ; seule une personne approuve ou rejette un versement, dans le portail marchand. Dans le bac à sable, tous les participants, toutes les destinations et tous les frais sont synthétiques, et aucun versement ne déplace d’argent réel.
Les portées
Section intitulée « Les portées »Trois portées de clé ouvrent les routes de versement /v1 : payouts:read lit les versements, les lots et leurs
fichiers, payouts:write fait les vérifications de nom, les versements et les lots et alimente le solde du bac à
sable, et balance:read lit le solde de versement. Une clé n’approuve ni ne rejette jamais un versement.
Seule la délégation d’un Propriétaire ou d’un Admin donne à une clé l’une de ces portées. La clé d’amorçage du bac à
sable et les clés émises par l’équipe de Futa n’en ont aucune, et keys:manage seule n’en donne jamais : POST /v1/keys
demandant une portée de versement avec une telle clé répond 403 SCOPE_ESCALATION.
Le solde de versement
Section intitulée « Le solde de versement »GET /v1/balance avec balance:read lit le solde dans les deux devises, jamais additionnées ni converties :
available et reserved en unités mineures entières, l’alert_threshold de solde bas de chaque devise et
below_alert, funded (faux tant qu’aucune alimentation ne l’a atteint) et l’unique funding_reference du
marchand, PF-…. Les encaissements et les règlements ne l’alimentent jamais. Un versement ou un lot réserve son
argent sur available quand il est soumis, et l’y rend quand il échoue, est rejeté ou expire.
{ "schema_version": 1, "merchant_id": "sandbox-merchant", "funding_reference": "PF-3XW8-K2NB", "balances": [ { "currency": "CDF", "available": 0, "reserved": 0, "alert_threshold": null, "alert_version": 0, "below_alert": false, "funded": false }, { "currency": "USD", "available": 100000, "reserved": 18000, "alert_threshold": 20000, "alert_version": 1, "below_alert": false, "funded": true } ], "as_of": "2026-10-28T09:15:00Z", "sandbox": true}Dans le bac à sable, POST /v1/sandbox/payout-top-ups avec payouts:write, une clé de test et une
Idempotency-Key alimente le solde en argent synthétique : le simulateur de la banque sponsor écrit un avis de
crédit portant la référence de financement du marchand, et le solde montre l’argent une fois cet avis comptabilisé,
jamais avant ; relisez donc GET /v1/balance. Aucun compte de financement réel, aucun virement ni aucun retrait
n’existe dans le bac à sable.
{ "amount": 100000, "currency": "USD"}{ "schema_version": 1, "synthetic": true, "notice_id": "SIM-SN-000000000001", "funding_reference": "PF-3XW8-K2NB", "amount": 100000, "currency": "USD", "credited_at": "2026-10-28T09:14:02.481233Z"}La même clé et le même corps n’alimentent qu’une fois. 503 TOP_UP_UNAVAILABLE laisse le résultat inconnu : renvoyez
la même requête avec la même clé. Un bac à sable qui ne donne à l’API marchande aucune clé de simulateur répond ainsi
à chaque alimentation, sans rien demander.
Le seuil d’alerte de solde bas se règle dans le portail marchand. Son alerte s’y affiche, et sa notification « Solde de versement bas » est enregistrée : rien n’est encore envoyé par email ni par SMS.
Un versement unique
Section intitulée « Un versement unique »Vérifier d’abord le nom
Section intitulée « Vérifier d’abord le nom »Chaque versement indique une vérification de nom faite pour lui dans les 24 heures. POST /v1/name-enquiries avec
payouts:write et une Idempotency-Key demande au participant qui détient la destination, avec le nom que vous
attendez, ou null :
{ "kind": "WALLET", "participant": "simulator", "destination": "+243810000001", "expected_name": "SYNTHETIC HOLDER"}La réponse donne status : FOUND, UNKNOWN_ACCOUNT, ou UNAVAILABLE avec le motif NOT_CONTRACTED. Un nom trouvé
n’est montré que masqué, son premier mot entier et chaque autre mot réduit à sa première lettre, avec un verdict
par rapport au nom attendu : MATCH, DIFFERENT, UNAVAILABLE, ou null quand aucun n’était attendu. Un nom qui
diffère de celui attendu, ou que le participant n’a pas pu donner, n’est jamais montré, même masqué. La destination
n’est jamais répétée. Une vérification est gardée 24 heures, jusqu’à expires_at, et une demande faite dans ce délai
répond par cette même vérification, avec cached vrai.
{ "schema_version": 1, "synthetic": true, "id": "00000000-0000-4000-8000-000000000011", "kind": "WALLET", "participant": "simulator", "destination_masked": "+243•••••0001", "status": "FOUND", "holder_name_masked": "SYNTHETIC H***", "reason": null, "expected_name_given": true, "verdict": "MATCH", "checked_at": "2026-10-28T09:20:11.204117Z", "expires_at": "2026-10-29T09:20:11.204117Z", "cached": false}Les vérifications de nom ont un budget, SYNTHETIC, sur une heure glissante : au plus 5 pour une destination, 60 par
personne ou par clé et 300 pour le marchand. Un renvoi exact sous sa clé ne compte qu’une fois, et les lignes d’un
lot, dont la validation du lot vérifie les noms, ne comptent dans aucun d’eux. Au-delà d’une limite,
429 NAME_ENQUIRY_RATE_LIMITED répond avec Retry-After, les secondes jusqu’à ce que chaque limite atteinte ait de
nouveau de la place.
L’envoyer
Section intitulée « L’envoyer »POST /v1/payouts avec payouts:write et une Idempotency-Key prépare le versement et le soumet :
{ "kind": "WALLET", "participant": "simulator", "destination": "+243810000001", "amount": 18000, "currency": "USD", "message": "Prime trimestrielle", "merchant_reference": "PAIE-2026-10-017", "name_enquiry_id": "00000000-0000-4000-8000-000000000011", "acknowledge_unavailable_name": false}- Les montants sont en unités mineures entières de leur devise, à partir de 1 : des cents en USD et des centimes
en CDF, où un montant est toujours en francs entiers, donc un multiple de 100.
18050vaut 180,50 dollars en USD ;1500000vaut 15 000 francs en CDF, et15000seulement 150 francs. - Les destinations sont un numéro de portefeuille,
+243et neuf chiffres, pour lekindWALLET, ou une référence de compte bancaire,SIM-BANK-et quatre chiffres dans le bac à sable, pour lekindBANK. messageest le motif du versement, au plus 100 caractères, ounull;merchant_referenceest la vôtre, de 1 à 64 caractères parmi les lettres, les chiffres,_,.,/et-, ounull.acknowledge_unavailable_namevauttrueseulement quand la vérification n’a pas pu donner le nom du titulaire : sans lui, un tel versement répond409 NAME_ACKNOWLEDGEMENT_REQUIRED, et avec lui après une vérification qui a donné un nom,400 INVALID_REQUEST.
Le montant et les frais du versement sont réservés sur le solde disponible quand il est enregistré, sinon il est
refusé avec 409 INSUFFICIENT_PAYOUT_BALANCE et le shortfall, en unités mineures de sa devise :
{ "error": { "code": "INSUFFICIENT_PAYOUT_BALANCE", "message_en": "The available payout balance does not cover this amount and its fees: the shortfall is stated. Nothing was reserved.", "message_fr": "Le solde de versement disponible ne couvre pas ce montant et ses frais : il manque le montant indiqué. Rien n'a été réservé.", "shortfall": { "amount": 4500, "currency": "USD" } }}Chaque refus propre à une route de versement est ce corps, shortfall à null sauf pour ce code ; les refus de clé, de
portée, de taille, de type de média et de quota sont le corps Error, et un 400 INVALID_REQUEST peut être l’un ou
l’autre : le corps Error quand la requête est refusée avant que la route de versement ne la lise, ce corps quand la
route ou le switch la refuse. Les autres refus d’un versement sont 404 NAME_ENQUIRY_NOT_FOUND,
409 NAME_ENQUIRY_STALE (la vérification date de plus de 24 heures), 409 PAYOUT_DESTINATION_UNKNOWN (le participant
ne connaît pas le compte), 409 PAYOUT_ROUTE_UNAVAILABLE (le participant ne reçoit pas de versements pour le moment),
409 MERCHANT_LIMIT_EXCEEDED (le plafond par versement, ou le plafond par jour, un jour allant de minuit à minuit à
l’heure de Kinshasa, Africa/Kinshasa ; tous deux SYNTHETIC dans le bac à sable), 409 PAYOUTS_HELD (Futa suspend les
versements du marchand) et 409 IDEMPOTENCY_CONFLICT. La page des erreurs les liste tous par statut.
Son approbation
Section intitulée « Son approbation »Au-delà du seuil d’approbation du marchand pour sa devise, un versement attend une deuxième personne,
PENDING_APPROVAL, son argent réservé, pendant sept jours au plus ; rien n’est envoyé avant son approbation. Le seuil
commence à zéro, un zéro que personne n’a fixé, si bien que chaque versement attend. Seul le Propriétaire fixe un
seuil : le baisser s’applique aussitôt, tandis que le relever ou le supprimer, y compris le premier seuil au-dessus de
zéro, est une proposition qu’un autre Propriétaire ou Admin approuve, sans exception pour un marchand qui n’a qu’un
Propriétaire, et la proposition expire au bout de sept jours (SYNTHETIC).
Approuver et rejeter sont le fait d’une personne, dans le portail marchand, jamais de l’API : un Propriétaire ou un
Admin qui n’a pas préparé le versement et n’est pas détenteur de la clé qui l’a préparé, avec une connexion récente et
un motif. Les détenteurs d’une clé sont les personnes qui l’ont créée ou renouvelée, et, pour une clé créée avec une
autre clé, les détenteurs de celle-ci à leur tour. Quand key_holders.known vaut false, un détenteur de cette clé
n’est pas une personne connue et personne ne peut approuver le versement ; un rejet ou l’expiration rend quand même
son argent. Une clé que plus de 50 personnes ont détenue ne prépare ni versement ni lot : ses commandes de préparation
sont refusées 400 INVALID_REQUEST.
C’est le principe des quatre yeux, pas une panne : un versement au-dessus du seuil demande toujours une deuxième personne, et un versement égal ou inférieur au seuil n’en demande aucune. Un marchand qui n’a qu’un Propriétaire et aucun Admin, dont le Propriétaire prépare les versements ou détient la clé qui les prépare, ne peut faire approuver aucun de ces versements, ni relever le seuil seul, et chacun expire au bout de sept jours, son argent rendu. Invitez un Admin : l’un des deux crée alors la clé ou prépare le versement et l’autre l’approuve, et l’Admin approuve la proposition de seuil du Propriétaire. Un membre Finance peut aussi préparer des versements dans le portail, que le Propriétaire approuve.
Un versement envoyé plus de 24 heures après sa vérification de nom, comme peut l’être un versement approuvé plus tard,
voit d’abord son nom vérifié de nouveau, comme une ligne de lot : un verdict différent l’arrête FAILED avec
NAME_CHANGED, rien n’étant envoyé et son argent rendu, et tant que le participant ne répond pas, ou ne peut plus
donner le nom, il reste QUEUED et la vérification est redemandée : à chaque étape, puis, après trois vérifications de
suite sans réponse, au plus une fois par minute. Quinze minutes après la première vérification sans réponse, le
versement est escaladé aux opérations, son argent toujours réservé jusqu’à ce qu’une réponse arrive ou que les
opérations agissent (ces trois nombres sont SYNTHETIC).
Ses états
Section intitulée « Ses états »201 répond par le versement tel qu’enregistré, QUEUED ou PENDING_APPROVAL. Lisez-le avec GET /v1/payouts/{id}
et payouts:read, qui fait foi ; GET /v1/payouts liste les versements du plus récent au plus ancien, 100 par page.
| État | Signification |
|---|---|
PENDING_APPROVAL |
Attend une deuxième personne dans le portail, son argent réservé. |
QUEUED |
Enregistré sans attendre d’approbation, ou approuvé : à envoyer. |
PROCESSING |
Envoyé au participant, pas encore définitif. |
PAID |
La destination est créditée ; paid_at dit quand. |
FAILED |
Il a échoué, avec son reason : le participant l’a refusé (UNKNOWN_ACCOUNT, ACCOUNT_SUSPENDED, CREDIT_REFUSED), ou la vérification de nom de Futa avant l’envoi a trouvé le nom changé (NAME_CHANGED) et rien n’a été envoyé. Son argent est de nouveau disponible. |
UNCERTAIN |
La réponse du participant n’est pas encore connue : son argent reste réservé, et il n’est jamais renvoyé sous une autre clé. |
REJECTED |
Rejeté par la personne chargée de l’approuver. Son argent est de nouveau disponible. |
EXPIRED |
Non approuvé dans les sept jours. Son argent est de nouveau disponible. |
{ "schema_version": 1, "id": "00000000-0000-4000-8000-000000000021", "reference": "PO-7G2K-9Q4M", "merchant_id": "sandbox-merchant", "kind": "WALLET", "participant": "simulator", "destination_masked": "+243•••••0001", "name": { "enquiry_id": "00000000-0000-4000-8000-000000000011", "status": "FOUND", "holder_name_masked": "SYNTHETIC H***", "verdict": "MATCH", "checked_at": "2026-10-28T09:20:11.204117Z", "acknowledged_unavailable": false }, "amount": 18000, "fee": 0, "total": 18000, "reserved": 18000, "currency": "USD", "fee_policy": "synthetic-zero-fee-v1", "message": "Prime trimestrielle", "merchant_reference": "PAIE-2026-10-017", "state": "PENDING_APPROVAL", "reason": null, "prepared_by": "key:00000000-0000-4000-8000-000000000004", "key_holders": { "known": true, "holders": ["user:00000000-0000-4000-8000-000000000003"] }, "threshold_version": 0, "approval": { "expires_at": "2026-11-04T09:21:30.112094Z", "approved_by": null, "rejected_by": null, "decided_at": null, "reason": null }, "escalated": false, "version": 1, "created_at": "2026-10-28T09:21:30.112094Z", "updated_at": "2026-10-28T09:21:30.112094Z", "paid_at": null, "sandbox": true}Les versements groupés par l’API
Section intitulée « Les versements groupés par l’API »Un lot paie de nombreuses destinations dans une seule devise. Par l’API, une clé le prépare ; une personne l’approuve dans le portail.
-
Le créer.
POST /v1/payout-batchesavecpayouts:writeet uneIdempotency-Keyindique sa devise et combien de lignes arriveront, au plus 100 000. Il répond par le lot,UPLOADED, avec sa référencePB-…. Un marchand garde au plus 20 lots ouverts, SYNTHETIC : au-delà,409 PAYOUT_BATCH_TOO_LARGE.GET /v1/payout-batches/{id}lit un lot, etGET /v1/payout-batchesles liste du plus récent au plus ancien, 100 par page.{"currency": "USD","expected_lines": 3} -
Envoyer ses lignes par tranches.
POST /v1/payout-batches/{id}/linesprend au plus 1 000 lignes et 512 Kio par appel, chaque ligne numérotée dans le lot, dans sa devise, son montant en unités mineures entières exactes. Le numéro de la tranche, de 1 au nombre de lignes annoncées, l’identifie : la même tranche renvoyée ne change rien, quelle que soit sa clé, et une tranche refusée peut être renvoyée avec des lignes corrigées sous son numéro. Voici les trois lignes de l’exemple de fichier plus bas :{"chunk": 1,"lines": [{"number": 1,"kind": "WALLET","participant": "simulator","destination": "+243810000001","expected_name": "SYNTHETIC HOLDER","amount": 18000,"currency": "USD","message": "Prime trimestrielle","merchant_reference": "LIGNE-1"},{"number": 2,"kind": "WALLET","participant": "simulator","destination": "+243810000002","expected_name": null,"amount": 25050,"currency": "USD","message": "Salaire, octobre","merchant_reference": "LIGNE-2"},{"number": 3,"kind": "WALLET","participant": "simulator","destination": "+243810000003","expected_name": null,"amount": 1200,"currency": "USD","message": null,"merchant_reference": null}]} -
Suivre sa validation. Une fois chaque ligne annoncée arrivée, le lot est
VALIDATING, puisVALIDATED: chaque ligne est vérifiée, son nom compris, et estVALID,REVIEW(à revoir) ouEXCLUDED. Une ligne dont tous les problèmes sont parmiNAME_DIFFERENT,NAME_UNAVAILABLE,DUPLICATE_PAYOUT,DUPLICATE_REFERENCEetROUTE_PAUSEDest à revoir, en attente de la décision du préparateur ; tout autre problème,INVALID_DESTINATION,ROUTE_UNAVAILABLE,INVALID_AMOUNT,MERCHANT_LIMIT_EXCEEDED,UNKNOWN_ACCOUNT,INVALID_EXPECTED_NAME,INVALID_MESSAGEouINVALID_REFERENCE, exclut la ligne jusqu’à ce qu’elle soit corrigée.GET /v1/payout-batches/{id}/validationdonne sa progression, ses décomptes, ce que ses lignes valides réserveraient et ses lignes à problème, etGET /v1/payout-batches/{id}/linesses lignes, masquées, 1 000 par page, éventuellement d’un seulstate.GET /v1/payout-batches/{id}/reportdonne le rapport de validation sous forme de fichier à partir deVALIDATED, ou une fois qu’un lot jamais validé estLAPSED. -
Décider des lignes à revoir. Tant que le lot est
VALIDATED,POST /v1/payout-batches/{id}/lines/{line}/decisionprendACCEPT,KEEP,REMOVEouCORRECTà laversiondu lot.ACCEPTetKEEPne s’appliquent qu’à une ligne à revoir, et sont traités de la même façon : l’un comme l’autre rend la ligneVALID, et la décision est enregistrée telle qu’envoyée. Une ligneROUTE_PAUSED, dont la route du participant était suspendue lors de sa vérification, n’accepte ni l’un ni l’autre (409 PAYOUT_ROUTE_UNAVAILABLE) : corrigez-la une fois la route rouverte, ou retirez-la. Une correction porte les nouvelles valeurs de la ligne sous son propre numéro et est vérifiée de nouveau.{"expected_version": 4,"decision": "REMOVE","correction": null} -
Le soumettre.
POST /v1/payout-batches/{id}/submità la version du lot l’envoie en approbation. Le total de ses lignes valides, montants et frais, est réservé sur le solde disponible, sinon il est refusé avec409 INSUFFICIENT_PAYOUT_BALANCEet le manque ; le lot est alorsPENDING_APPROVAL.{"expected_version": 5} -
Une personne l’approuve ou le rejette, dans le portail. Chaque lot attend une deuxième personne, quel que soit son total, pendant sept jours au plus, et rien n’est envoyé avant son approbation. Le principe des quatre yeux s’applique : un Propriétaire ou un Admin l’approuve ou le rejette, avec une connexion récente et un motif, et jamais quelqu’un qui l’a préparé (qui l’a créé, a envoyé une tranche, a décidé d’une ligne ou l’a soumis) ni le détenteur d’une clé parmi eux. Un lot garde au plus 100 préparateurs et 50 détenteurs de clé : une commande qui en ajouterait un de plus est refusée
409 PAYOUT_BATCH_INVALID. Approuvé, il estAPPROVED,PROCESSING, puisCOMPLETEDune fois chaque ligne définitive ; sinonREJECTEDouEXPIRED, son argent de nouveau disponible. Un lot jamais soumis qui reste sept jours inchangé estLAPSED, SYNTHETIC, et n’a jamais retenu d’argent. -
Lire son résultat.
GET /v1/payout-batches/{id}/resultdonne le fichier de résultat une fois le lotCOMPLETED,REJECTED,EXPIREDouLAPSED. Le rapport et le résultat sont écrits de façon asynchrone :202avecRetry-Afterpendant l’écriture du fichier, puis le fichier ;409 RESULT_NOT_READYavant que le lot atteigne l’état qu’il décrit. -
Relancer ses lignes échouées.
POST /v1/payout-batches/{id}/retryà la version du lot (« Relancer ») crée un nouveau lot,retry_ofle premier, des lignes que le participant a refusées définitivement, chacune liée à sa ligne d’origine et portant sa référence. La relance ne réserve rien : ses lignes sont vérifiées de nouveau, puis soumises, réservées et approuvées de nouveau comme une nouvelle soumission. Une ligne incertaine n’est jamais relancée ; sans ligne échouée à relancer,409 PAYOUT_LINE_NOT_RETRYABLE.
Le fichier futa-payout-batch-v1
Section intitulée « Le fichier futa-payout-batch-v1 »futa-payout-batch-v1 est le modèle CSV propre à Futa, intitulé « modèle Futa » partout où il est proposé : ce n’est
ni le format d’une banque, ni celui d’un régulateur, ni celui d’une norme. Une personne le téléverse dans le portail
marchand, qui propose aussi le modèle ; le téléversement est une action du portail, et une clé envoie les mêmes lignes
en JSON par POST /v1/payout-batches/{id}/lines. Le modèle est figé en version 1 : tout changement est un nouveau
modèle.
- Encodage. UTF-8, avec ou sans marque d’ordre des octets.
- Séparateur. Une virgule ; un fichier séparé par des points-virgules n’a pas d’en-tête valide.
- Enregistrements. Un par ligne, terminé par un saut de ligne ou par un retour chariot et un saut de ligne ; la dernière ligne peut se terminer sans.
- Guillemets. Une cellule peut être entourée de guillemets doubles, ce qui lui permet de contenir une virgule ; un guillemet double à l’intérieur s’écrit deux fois.
- En-tête. Exactement
type,participant,numero,nom_attendu,montant,devise,motif,reference, dans cet ordre et en minuscules. - Limites. Au moins 1 ligne et au plus 100 000 ; au plus 16 Mio (16 777 216 octets), marque d’ordre des octets comprise.
- Une seule devise. Chaque ligne est dans la devise du fichier.
- Cellules telles qu’écrites. Aucune cellule n’est rognée ni réinterprétée.
- Formules. Une cellule contenant un caractère de contrôle, ou dont le premier caractère autre qu’une espace est
=,+,-ou@, est refusée (UNSAFE_CELL) : un montant négatif en est une.
| Colonne | Contient | Devient dans la ligne |
|---|---|---|
type |
wallet ou bank, en minuscules |
kind : WALLET ou BANK |
participant |
Le code du participant : simulator ou synthetic-bank dans le bac à sable |
participant |
numero |
Un numéro de portefeuille en chiffres, sans le signe plus (243810000001) ; une référence de compte bancaire (SIM-BANK-0001) |
destination : +243810000001, ou la référence telle qu’écrite |
nom_attendu |
Facultatif : le nom du titulaire que vous attendez | expected_name, ou null si la cellule est vide |
montant |
Le montant dans l’unité de la devise avec un point décimal : au plus deux décimales en USD (180, 180.5, 180.50), aucune en CDF (15000) |
amount en unités mineures : 180.50 USD vaut 18050, 15000 CDF vaut 1500000 |
devise |
USD ou CDF, en majuscules |
currency |
motif |
Facultatif : le message du versement | message, ou null si la cellule est vide |
reference |
Facultatif : votre propre référence | merchant_reference, ou null si la cellule est vide |
Un tableur enregistre un long nombre comme 243810000001 sous la forme 2.4381E+11 si sa colonne n’est pas au format
texte : mettez numero au format texte avant d’enregistrer. Un numéro de portefeuille sous sa forme nationale,
0810000001, est signalé sur sa ligne (INVALID_DESTINATION).
type,participant,numero,nom_attendu,montant,devise,motif,referencewallet,simulator,243810000001,SYNTHETIC HOLDER,180.00,USD,Prime trimestrielle,LIGNE-1wallet,simulator,243810000002,,250.5,USD,"Salaire, octobre",LIGNE-2wallet,simulator,243810000003,,12,USD,,Un fichier qui enfreint le modèle est refusé en entier avant qu’aucune ligne soit envoyée : 422 PAYOUT_FILE_INVALID
avec ses 100 premiers problèmes, chacun avec sa ligne et sa colonne quand il les a, et more_problems quand il y en a
davantage. Un fichier de plus de 16 Mio est refusé 413 REQUEST_TOO_LARGE avant d’être lu en entier. Les valeurs que
le modèle lit, comme un participant inconnu, un montant nul ou un nom différent, sont jugées sur leur ligne par la
validation, sans jamais refuser le fichier entier. Tant que le lot qu’un fichier a créé est ouvert, le même fichier
renvoyé par la même personne répond par ce lot, quelle que soit sa clé. Une fois le lot terminé, le même fichier
renvoyé sous une nouvelle Idempotency-Key crée un nouveau lot, validé et approuvé de nouveau ; sous la clé de son
premier envoi, il répond encore par le lot qu’il a créé jusqu’à ce que les 720 heures de cette clé soient passées, puis
il est refusé 409 IDEMPOTENCY_CONFLICT.
Le rapport de validation et le résultat qu’écrit Futa sont des fichiers UTF-8 avec une marque d’ordre des octets, leurs lignes terminées par un retour chariot et un saut de ligne, chaque cellule entre guillemets, et une cellule qu’un tableur lirait comme une formule écrite après une apostrophe. Les destinations sont masquées et le nom d’un titulaire n’apparaît que masqué, quand il correspondait ou qu’aucun n’était attendu.
- Le rapport, un enregistrement par ligne que la validation a signalée ou dont le préparateur a décidé :
ligne,etat,problemes,decision,numero_masque,nom_masque,montant,devise. - Le résultat, un enregistrement par ligne une fois le lot terminé :
ligne,reference_futa,type,participant,numero_masque,montant,devise,motif,reference,etat,raison,ligne_relancee, oùreference_futaest lePB-…/NNNNNNde la ligne etligne_relanceedésigne la ligne d’origine de la ligne d’une relance. - Un lot caduc (
LAPSED) a lui aussi son rapport et son résultat écrits, chacun ne listant que les lignes vérifiées avant sa caducité.
L’idempotence
Section intitulée « L’idempotence »Chaque commande de versement prend une Idempotency-Key de 8 à 128 lettres, chiffres, _ et -, propre au marchand
et à la clé qui l’envoie :
- Pendant 720 heures, la même clé avec le même corps répond par le premier résultat de la commande, quoi qu’il se
soit passé depuis ; la même clé avec un autre corps répond
409 IDEMPOTENCY_CONFLICT. - Après 720 heures, une commande renvoyée sous sa clé est refusée
409 IDEMPOTENCY_CONFLICTet jamais envoyée : la clé est gardée dix ans, si bien qu’elle ne fait jamais un second versement. - Une réponse perdue, un délai dépassé,
503 SWITCH_UNAVAILABLEou503 TOP_UP_UNAVAILABLE, laisse le résultat inconnu : renvoyez la même requête sous la même clé avec le même corps. - Un résultat incertain n’est jamais renvoyé sous une nouvelle clé : lisez plutôt le versement ou le lot.
Une tranche de lignes est connue par son lot et son numéro plutôt que par sa clé, une alimentation n’alimente qu’une
fois sous sa clé, et les clés commençant par payout-batch- ou payout-file-, que Futa crée pour ses propres
commandes, sont refusées 400 INVALID_REQUEST. Une décision, une soumission et une relance couvrent chacune une ligne
ou un lot à la version envoyée : réutiliser leur clé pour une autre répond 409 IDEMPOTENCY_CONFLICT.
Les webhooks
Section intitulée « Les webhooks »Les versements et les lots envoient ces événements au point de réception du marchand, signés comme tout événement :
| Événement | Envoyé par défaut | Quand |
|---|---|---|
payout.pending_approval |
Non | Un versement attend une deuxième personne. |
payout.completed |
Oui | Un versement est payé (PAID). |
payout.failed |
Oui | Un versement a échoué, avec son motif. |
payout.rejected |
Non | Un versement a été rejeté par la personne chargée de l’approuver. |
payout.expired |
Non | Un versement n’a pas été approuvé dans les sept jours. |
payout_batch.validated |
Non | Les lignes d’un lot sont vérifiées, prêtes pour les décisions. |
payout_batch.completed |
Oui | Chaque ligne d’un lot est définitive. |
Un événement de versement suit payout-event.schema.json et un événement de lot
payout-batch-event.schema.json ;
payout-event.vectors.json et
payout-batch-event.vectors.json contiennent des événements enregistrés. QUEUED,
PROCESSING et UNCERTAIN n’envoient aucun événement, et les lignes d’un lot n’en envoient aucun qui leur soit
propre. Un événement ne porte que key_holders.known : qui détient quelle clé reste derrière les lectures
authentifiées. Dédupliquez sur l’id de l’événement, n’appliquez l’événement d’un versement que si son
data.version dépasse le dernier appliqué pour ce versement, et lisez GET /v1/payouts/{id} ou
GET /v1/payout-batches/{id} en cas de doute : un événement est un avis, pas l’enregistrement.
{ "id": "00000000-0000-4000-8000-000000000031", "type": "payout.completed", "schema_version": 1, "occurred_at": "2026-10-28T10:02:41.512903Z", "data": { "schema_version": 1, "id": "00000000-0000-4000-8000-000000000021", "reference": "PO-7G2K-9Q4M", "merchant_id": "sandbox-merchant", "kind": "WALLET", "participant": "simulator", "destination_masked": "+243•••••0001", "name": { "enquiry_id": "00000000-0000-4000-8000-000000000011", "status": "FOUND", "holder_name_masked": "SYNTHETIC H***", "verdict": "MATCH", "checked_at": "2026-10-28T09:20:11.204117Z", "acknowledged_unavailable": false }, "amount": 18000, "fee": 0, "total": 18000, "reserved": 0, "currency": "USD", "fee_policy": "synthetic-zero-fee-v1", "message": "Prime trimestrielle", "merchant_reference": "PAIE-2026-10-017", "state": "PAID", "reason": null, "prepared_by": "key:00000000-0000-4000-8000-000000000004", "key_holders": { "known": true }, "threshold_version": 0, "approval": { "expires_at": "2026-11-04T09:21:30.112094Z", "approved_by": "user:00000000-0000-4000-8000-000000000001", "rejected_by": null, "decided_at": "2026-10-28T10:01:12.004211Z", "reason": "Prime validée" }, "escalated": false, "version": 4, "created_at": "2026-10-28T09:21:30.112094Z", "updated_at": "2026-10-28T10:02:41.512903Z", "paid_at": "2026-10-28T10:02:41.512903Z", "sandbox": true }}Un nouveau point de réception reçoit par défaut payout.completed, payout.failed et payout_batch.completed, en
plus des événements de paiement et de remboursement que liste le
guide des webhooks ; choisissez les autres par sa configuration. Un point de
réception enregistré avant que les événements de versement soient livrés garde les événements qu’il avait :
ajoutez-lui les événements de versement pour les recevoir.
Le bac à sable
Section intitulée « Le bac à sable »Les versements du bac à sable n’atteignent que les participants synthétiques du simulateur : simulator, le
portefeuille de test (« Portefeuille simulé »), et synthetic-bank, la banque synthétique (« Banque simulée »). Six
numéros de portefeuille et six références bancaires choisissent comment le participant répond à un crédit et à une
vérification de nom ; la référence bancaire qui a le même dernier chiffre répond comme son numéro de portefeuille.
| Destination | Crédit | Vérification de nom |
|---|---|---|
+243810000001, SIM-BANK-0001 |
Crédité aussitôt | FOUND, SYNTHETIC H*** |
+243810000002, SIM-BANK-0002 |
Refusé : UNKNOWN_ACCOUNT |
UNKNOWN_ACCOUNT |
+243810000003, SIM-BANK-0003 |
Refusé : ACCOUNT_SUSPENDED |
FOUND, un autre nom : SYNTHETIC O*** H***, ou DIFFERENT par rapport à SYNTHETIC HOLDER |
+243810000004, SIM-BANK-0004 |
Aucune réponse tant que le contrôle de test du simulateur ne la règle pas, ce qu’aucune clé marchande ne peut faire : le versement reste UNCERTAIN |
FOUND, SYNTHETIC H*** |
+243810000005, SIM-BANK-0005 |
Accepté, puis crédité 2 secondes plus tard | FOUND, SYNTHETIC H*** |
+243810000006, SIM-BANK-0006 |
Indisponible au premier envoi, crédité quand il est renvoyé sous le même crédit | UNAVAILABLE, NOT_CONTRACTED : prenez-en acte |
Un crédit refusé fait échouer son versement avec ce motif. Toute autre destination bien formée est un compte que le
simulateur ne détient pas : sa vérification de nom répond UNKNOWN_ACCOUNT et son crédit est refusé de même. Un
versement unique n’est jamais préparé sur une vérification qui n’a trouvé aucun compte. Les deux noms sont
synthétiques et le disent.
Tous les frais sont le zéro synthétique, fee_policy synthetic-zero-fee-v1, et chaque plafond et chaque seuil est
SYNTHETIC : les plafonds par versement et par jour du marchand, le seuil d’approbation qui commence à zéro, les sept
jours d’une approbation et de la caducité d’un lot, les 20 lots ouverts et le budget de vérifications de nom. Aucun
n’est une valeur de production.
Pas encore disponible
Section intitulée « Pas encore disponible »- XLSX. Un classeur est refusé avec son motif (
XLSX_NOT_ACCEPTED), et il n’y a pas de « Modèle XLSX » : envoyez du CSV. - Modèles enregistrés. « Enregistrer comme modèle » et « Utiliser un modèle enregistré » ne sont pas construits.
- SMS au bénéficiaire. Le message d’un versement est enregistré et transmis au participant ; aucun SMS n’est envoyé.
- Alertes de solde bas par email ou SMS. L’alerte s’affiche dans le portail marchand et sa notification est enregistrée ; rien n’est envoyé.