Aller au contenu

Structure des réponses

L'enveloppe JSON, le format des listes, la forme des erreurs et les identifiants communs à tous les endpoints.

Chaque endpoint répond avec la même enveloppe JSON, en cas de succès comme d'échec. Trois clés sont toujours présentes : un seul handler de réponses couvre donc toute l'API, et vous ne distinguez que les parties qui changent.

Les trois clés toujours présentes

code répète le statut HTTP dans le corps, pour les clients qui lisent difficilement les en-têtes. status est le libellé HTTP de ce code (OK, Created, Not Found, Unprocessable Content), et jamais la chaîne littérale error. message est une courte phrase qui décrit ce qui s'est passé, destinée à vos logs plutôt qu'à vos clients.

Tout le reste du corps dépend de ce que vous avez demandé.

Une ressource

Quand vous récupérez ou créez un seul objet, il arrive sous une clé qui porte le nom de son type. Un paiement revient sous transaction, un client sous customer, un transfert sous transfer. Il n'y a pas d'enveloppe générique data à déballer.

GET /payments/{id} · 200 OK
{
"code": 200,
"status": "OK",
"message": "Payment retrieved",
"transaction": {
"id": "trx_test_CSUGajfv9xh0XQ5wu2lx",
"reference": "order-4172",
"status": "succeeded",
"amount": 5000,
"currency": "XAF",
"created_at": "2026-05-24T10:21:08+00:00"
}
}

Un endpoint peut ajouter ses propres clés de premier niveau à côté de la ressource quand elles n'en sont pas des propriétés. La création d'un paiement est le premier cas que vous rencontrerez : l'URL de la page hébergée décrit la session que vous venez d'ouvrir, pas la ligne de transaction. Elle se place donc à côté de la ressource plutôt qu'à l'intérieur.

POST /payments · 201 Created
{
"code": 201,
"status": "Created",
"message": "Payment initiated",
"authorization_url": "https://pay.wajub.com/tok_7Yh2Mp4kQ9",
"authorization_token": "tok_7Yh2Mp4kQ9",
"transaction": {
"id": "trx_test_CSUGajfv9xh0XQ5wu2lx",
"status": "pending",
"amount": 25000,
"currency": "XAF"
}
}

Plusieurs ressources

Les endpoints de liste dérogent volontairement à la règle de nommage. Le tableau s'appelle toujours items, quel que soit son contenu, et un objet meta porte l'état de la pagination. Écrire une seule fonction de liste qui marche pour les paiements, les clients, les transferts et les événements vaut donc la peine.

GET /payments · 200 OK
{
"code": 200,
"status": "OK",
"message": "Payments retrieved",
"items": [
{
"id": "trx_test_CSUGajfv9xh0XQ5wu2lx",
"status": "succeeded",
"amount": 5000
},
{
"id": "trx_test_9Wm4Kd2pR7vB5nL6hY1c",
"status": "pending",
"amount": 12000
}
],
"meta": {
"current_page": 1,
"last_page": 4,
"per_page": 25,
"total": 87
}
}

meta existe sous deux formes, et celle que vous recevez dépend de votre requête. Par défaut, vous paginez par numéro de page et meta vous indique où vous en êtes par rapport au total. Envoyez plutôt un paramètre cursor et l'API passe en pagination par curseur : c'est ce qu'il vous faut pour un gros export ou une reprise de données, car elle reste correcte pendant que de nouvelles lignes arrivent.

meta, mode curseur
{
"per_page": 25,
"next_cursor": "eyJpZCI6IjAxOTIzYWJjIiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ",
"prev_cursor": null,
"has_more": true
}

La taille de page vaut 25 par défaut et plafonne à 100. Pagination explique comment parcourir une collection du début à la fin.

Erreurs

Une erreur garde les trois mêmes clés. Le code HTTP est dans la plage 4xx ou 5xx, status est le libellé de ce code, et message dit ce qui n'a pas marché. Comme l'enveloppe ne change pas, un appel en échec se lit avec le même code qu'un appel réussi.

422 Unprocessable Content
{
"code": 422,
"status": "Unprocessable Content",
"message": "The given data was invalid.",
"errors": {
"amount": [
"The amount field is required."
],
"currency": [
"The selected currency is invalid."
]
}
}

Deux classes d'erreurs ajoutent un champ à traiter explicitement. Un échec de validation contient errors, un objet indexé par nom de champ dont les valeurs sont des tableaux de messages : un champ peut donc échouer sur plusieurs règles à la fois. Une réponse bloquée par la limite de requêtes contient retry_after, le nombre de secondes à attendre, ce qui vous évite de deviner un délai.

Un 409 Conflict contient aussi errors, et il ne signifie qu'une chose : vous avez réutilisé une reference qui existe déjà sur un autre paiement. Traitez-le comme une soumission en double plutôt que comme une mauvaise requête.

Un 404 vous indique quelle ressource manquait, Payment Not Found plutôt qu'un message générique : le champ message suffit donc pour aiguiller l'erreur sans examiner l'URL appelée. Le catalogue complet des codes se trouve dans la référence API.

Statuts de paiement

Le status d'une transaction prend l'une de neuf valeurs. Les transitions entre elles, et celles qui sont finales, sont décrites dans Concepts clés.

StatutSignification
pendingCréé, en attente d'une action du client.
processingConfirmé auprès du prestataire, règlement en cours.
succeededFonds reçus. Vous pouvez livrer la commande.
failedRefusé par le prestataire ou par le portefeuille.
cancelledAnnulé par le client ou par vous.
expiredLa fenêtre de paiement s'est fermée avant la fin.
partialUne partie du montant a été capturée, sur un paiement fractionné.
refundedEntièrement remboursé après un succès.
partially_refundedPartiellement remboursé après un succès.

Identifiants

Chaque objet porte un id construit de la même façon : un préfixe qui nomme le type, puis test_ quand l'objet vit dans la sandbox, puis une chaîne aléatoire. Le préfixe vous dit ce que vous avez entre les mains sans rien consulter, et le segment test_ empêche de confondre un identifiant sandbox avec un identifiant live dans un log ou un ticket de support.

PréfixeObjetPartie aléatoire
trx_Paiement20 caractères
po_Transfert sortant20 caractères
rfd_Remboursement20 caractères
dsp_Litige20 caractères
ben_Bénéficiaire20 caractères
acc_Sous-compte Sync20 caractères
cus_Client24 caractères
evt_Événement webhook24 caractères

Ainsi, un paiement sandbox s'écrit trx_test_CSUGajfv9xh0XQ5wu2lx et son jumeau en production trx_CSUGajfv9xh0XQ5wu2lx. Stockez les identifiants en texte de longueur illimitée, jamais dans une colonne fixe de 24 ou 32 caractères. Les factures et les liens de paiement suivent leurs propres règles, listées dans Concepts clés.

Dates

Chaque horodatage est au format ISO 8601 en UTC, avec un décalage explicite plutôt qu'un suffixe Z.

Format des horodatages
"created_at": "2026-05-24T10:21:08+00:00"

Les deux écritures désignent le même instant et tout parseur ISO 8601 les gère. Mais si vous comparez des horodatages sous forme de chaînes, ce que vous ne devriez pas faire, c'est ce décalage que vous recevrez réellement.

Montants

Les montants sont exprimés dans l'unité principale de la devise. 5000 dans une réponse signifie 5 000 XAF, et non 50 XAF, soit l'inverse de ce que font la plupart des API de paiement. Le franc CFA n'a pas de subdivision utilisée au quotidien : une convention en unité mineure inventerait une coupure qui n'existe pas.

Les valeurs reviennent sous forme de nombres arrondis à deux décimales, et le minimum que vous pouvez débiter est 0.01 dans l'unité de la devise. Lisez le champ currency avant tout calcul, car un même chiffre signifie des choses très différentes en XAF et en USD.

Que pensez-vous de ce contenu ?