Erreurs
Codes de statut, objet errors, et comment aiguiller votre code après un échec.
Wajub utilise les codes de statut HTTP conventionnels : 2xx signifie que la requête a réussi,
4xx que le problème vient de votre requête, 5xx qu'il vient de chez nous. Chaque erreur porte
la même enveloppe qu'un succès, plus un objet errors lorsqu'un champ est en cause.
Codes de statut
| Code | Signification |
|---|---|
200 | Succès. |
201 | Ressource créée. |
202 | Accepté. Le paiement ou le transfert est en cours chez le prestataire ; attendez le webhook. |
400 | La requête est bien formée mais la ressource n'est pas dans un état qui la permet (un paiement qui n'est plus pending, par exemple), ou version d'API non prise en charge. |
401 | Clé absente, inconnue, révoquée ou expirée. |
402 | POST /payments/{id} n'a pas pu débiter : le canal a refusé (error_code donne la raison), le compte ne peut pas encore encaisser en live (merchant_not_verified), ou un plafond de transaction est atteint (limit_exceeded). Le paiement repasse en pending. |
403 | Scope manquant, IP absente de la liste autorisée, clé privée utilisée depuis un navigateur, fonctionnalité réservée au live appelée avec une clé sandbox, restriction sur le compte, ou paiement bloqué par Shield. |
404 | Ressource introuvable. |
405 | Mauvaise méthode HTTP pour ce chemin. |
406 | Une clé privée ou restreinte est requise et une clé publique a été envoyée. |
409 | La requête entre en conflit avec une autre en cours ou avec l'état de la ressource : une Idempotency-Key encore en cours de traitement (le message est sous errors.idempotency_key), un débit déjà en cours, pour un débit ou une annulation (payment_in_progress), l'annulation d'un paiement dont des échéances sont payées (payment_partly_paid), un paiement préparé qui ne peut plus changer ni être activé, une connexion Sync dans un état qui refuse l'appel. L'unicité d'une reference n'est jamais contrôlée. |
422 | Échec de la validation, ou requête refusée telle quelle. errors est toujours présent ; voir error_code sur un payout refusé. |
429 | Limite de requêtes dépassée, ou événement renvoyé il y a moins de 5 minutes aux mêmes endpoints (event_resend_cooldown, pas une limitation). |
500 | Erreur interne de notre côté. |
501 | L'endpoint existe mais n'est pas disponible dans cet environnement, aujourd'hui uniquement POST /identity/resolve en live. |
502 | Le prestataire de paiement a refusé ou n'a pas pu être joint. |
Forme d'une erreur
status est toujours le libellé HTTP, jamais la chaîne littérale error. message est une phrase
destinée à vos logs, pas à un switch.
Le message du 404 nomme la ressource : Payment Not Found, Customer Not Found,
Transfer Not Found, Invoice Not Found, Link Not Found, Beneficiary Not Found,
Account Not Found, et Resource Not Found pour tout le reste.
Un chemin qui encode une barre oblique ou une barre oblique inverse, %2F ou %5C en majuscules
comme en minuscules, répond ce 404 quelle que soit la clé. Placez chaque identifiant tel quel dans
le chemin ; la chaîne de requête n'est pas concernée.
Erreurs de validation
Un 422 ajoute un objet errors qui associe chaque champ rejeté à sa liste de messages. Les champs
imbriqués utilisent la notation pointée, qui reprend le chemin dans le corps de votre requête. Chaque
422 le porte : un refus qui ne concerne aucun champ précis range son message sous errors.request.
errorsobjectfacultatifrequest pour un refus qui ne vise aucun champ. Présent sur chaque 422 et sur le 409 d'une Idempotency-Key encore en cours de traitement.messagestringfacultatif(and 1 more error) ou (and N more errors) quand plusieurs champs ont échoué. Lisez errors pour la liste complète.Deux échecs qui ne relèvent pas de la validation arrivent aussi en 422 sur le champ
idempotency_key : un en-tête Idempotency-Key mal formé, et une clé réutilisée avec un payload
différent. Voir Idempotence.
Erreurs de restriction
Wajub peut restreindre un compte, par exemple après un contrôle de risque. Un appel refusé par une
restriction répond 403 avec un error_code : aiguillez votre code sur error_code, et gardez
message pour vos logs.
error_code | Renvoyé par | Signification |
|---|---|---|
merchant_restricted | Tout appel, POST /payments/{id}, POST /transfers/batch | L'accès à l'API, les paiements ou les payouts groupés sont fermés pour le compte. Contactez le support. |
payment_method_restricted | POST /payments/{id} | Ce moyen de paiement, le Mobile Money ou les cartes internationales sont désactivés pour le compte. |
country_restricted | POST /payments/{id} | Le pays du payeur est refusé : paiements nationaux uniquement, zone CEMAC ou UEMOA uniquement, ou pays bloqué. |
currency_restricted | POST /payments/{id} | Seule la devise du compte est acceptée. |
feature_not_in_plan | /accounts, tout appel portant X-Sync, /tax | Sync ou Tax n'est pas inclus dans votre plan et aucune option n'est active. |
Un paiement refusé ainsi n'est pas débité et reste pending. Quand toute l'API est fermée, le
403 porte aussi un champ restriction avec le type de restriction. Un plafond de transaction
atteint sur POST /payments/{id} (unitaire, journalier ou mensuel) n'est pas une restriction : il
répond 402 avec error_code: limit_exceeded, comme merchant_not_verified pour un compte qui ne
peut pas encore encaisser en live.
Blocages Shield
Un paiement bloqué par Shield répond 403 avec error_code: shield_blocked, et
block_reference porte la référence à donner au support. Aucun prestataire n'a été contacté, rien
n'a été débité, et le paiement reste pending.
Refus de payout
Un payout refusé avant d'exister répond 422 avec un error_code à côté de message et
errors. Aiguillez votre code sur ce code. Les mêmes codes reviennent sur chaque ligne refusée de
POST /transfers/batch, dans results[].error_code, et sur POST /accounts/{id}/payouts.
error_code | Ce qui a refusé le payout | Que faire |
|---|---|---|
amount_out_of_range | Le montant sort du minimum et du maximum de cette devise. | Modifiez le montant. |
amount_above_channel_limit | Le montant dépasse le plafond par transfert de l'opérateur. | Répartissez-le sur plusieurs payouts. |
beneficiary_not_found | Aucun de vos bénéficiaires ne porte cet identifiant. Une ligne de lot le reçoit aussi quand elle désigne le bénéficiaire par téléphone ou par e-mail. | Vérifiez l'identifiant ben_. Ne réessayez pas tel quel. |
beneficiary_inactive | Le bénéficiaire est désactivé. | Réactivez-le, ou payez un autre bénéficiaire. |
beneficiary_not_allowed | Le bénéficiaire a été ajouté depuis le Dashboard et ne peut pas être payé par l'API. | Payez-le depuis le Dashboard. |
beneficiary_missing_payment_method | Le bénéficiaire n'a pas de moyen de payout, ou ce moyen n'a pas de canal. | Corrigez le bénéficiaire. |
beneficiary_missing_phone, beneficiary_missing_account_number, beneficiary_missing_wallet | Il manque au moyen le numéro, le compte ou l'adresse de portefeuille que son canal exige. | Corrigez le bénéficiaire. |
beneficiary_not_verified | La destination n'a pas encore été vérifiée. | Vérifiez-la, puis réessayez. |
beneficiary_cooling_off | La destination vient d'être ajoutée ou modifiée. | Réessayez une fois son payout_allowed_after passé. |
channel_not_supported | Le canal ne peut pas servir à un payout. | Utilisez le Mobile Money, un portefeuille Djamo, un compte Wajub ou un compte de payout. |
currency_not_supported | La devise est inconnue ou inactive. | Utilisez une devise prise en charge. |
idempotency_key_reused | L'Idempotency-Key a déjà servi pour un autre payout, ou sur une autre connexion. | Utilisez une nouvelle clé pour un nouveau payout. |
payouts_suspended | Les payouts sont suspendus sur toute la plateforme Wajub. | Réessayez plus tard. |
merchant_restricted | Les payouts sont restreints sur votre compte. | Contactez le support. |
transfers_disabled_for_source | Vos réglages de sécurité des transferts n'autorisent pas un payout d'ici : « Tableau de bord uniquement » refuse les payouts créés par l'API, « Arrêter tous les transferts » les refuse tous. Live uniquement. | Changez le réglage dans le Dashboard, ou envoyez le payout depuis l'endroit autorisé. |
negative_balance | L'un de vos soldes, dans n'importe quelle devise, est sous zéro. | Attendez que les encaissements le couvrent. |
reserve_held | Le montant entamerait la réserve retenue pour les remboursements. | Versez moins, ou attendez la libération de la réserve. |
daily_count_limit_exceeded, daily_amount_limit_exceeded | Votre propre plafond journalier, réglé dans les paramètres de sécurité des transferts. | Relevez votre plafond, ou attendez demain. |
single_payout_limit_exceeded, daily_payout_limit_exceeded, monthly_payout_limit_exceeded | Le plafond de la plateforme pour votre niveau de vérification. | Attendez la fin de la période, ou passez à un niveau supérieur. |
payouts_not_enabled | Les payouts ne sont pas encore disponibles sur votre compte. | Terminez la vérification. |
too_many_payouts, hourly_payout_amount_exceeded | Trop de payouts, ou un montant versé trop élevé, en peu de temps. | Réessayez plus tard. |
channel_unavailable | Les payouts vers ce canal sont temporairement indisponibles. | Réessayez plus tard, ou utilisez un autre canal. |
no_provider | Aucune route de payout n'existe pour ce canal et cette devise pour le moment. | Réessayez plus tard, ou utilisez un autre canal. |
insufficient_balance | available ne couvre pas le montant et les frais. | Approvisionnez le solde, puis réessayez. |
channel_not_available_in_sandbox, invalid_test_number, invalid_test_account, insufficient_funds | Sandbox uniquement : un virement envoyé à la main, ou un numéro ou compte de test qui choisit un refus. | Voir Scénarios de test. |
POST /transfers/batch ajoute les siens : idempotency_key_required (400, le lot n'a pas
d'Idempotency-Key), reference_reused_in_batch (une ligne réutilise une référence de ce lot pour
un autre payout), et processing_error ou payout_queue_busy sur une ligne qui n'a pas pu partir
cette fois : ces deux-là peuvent être réessayées telles quelles. Un POST /transfers seul répond dans ce dernier cas 429 avec
error_code: payout_queue_busy et Retry-After. POST /accounts/{id}/payouts ajoute account_not_lite,
account_not_active, account_not_connected et payout_destination_missing (422),
live_mode_only (400) et nothing_to_pay_out (409).
Une requête mal formée, par exemple un champ manquant, répond le 422 habituel sans error_code :
l'objet errors nomme le champ.
Chaque réponse a un identifiant de requête
X-Request-Id est défini sur chaque réponse, erreurs comprises. Journalisez-le à côté de votre
propre identifiant de corrélation, et indiquez-le quand vous ouvrez un ticket au support. Vous
pouvez aussi envoyer votre propre valeur, jusqu'à 64 caractères : elle vous est renvoyée au lieu
d'être générée.
curl https://api.wajub.com/payments/trx_CSUGajfv9xh0XQ5wu2lx \
-H "Authorization: $WAJUB_API_KEY" \
-H "X-Request-Id: order-4172-attempt-1"Aiguiller votre code après un échec
Aiguillez sur le code de statut, jamais sur message. Trois familles méritent un traitement
différent : un 422 est un bug dans votre payload et une nouvelle tentative n'y changera rien ; un
429 est un problème de rythme et la réponse vous indique combien de temps attendre ; un 5xx ou
un 502 est transitoire et justifie un nombre limité de nouvelles tentatives.
const res = await fetch(url, { method: 'POST', headers, body });
if (res.status === 422) {
const { errors } = await res.json();
return showFieldErrors(errors);
}
if (res.status === 429) {
const wait = Number(res.headers.get('retry-after') ?? 1);
return retryAfter(wait * 1000);
}
if (!res.ok) {
throw new Error(`Wajub ${res.status} ${res.headers.get('x-request-id')}`);
}Le SDK PHP transforme chaque code de statut en exception dédiée : un bloc catch suffit donc à
séparer les trois familles.
| Statut | Exception | Propriété supplémentaire |
|---|---|---|
401 | AuthenticationException | |
403 | PermissionException | |
404 | NotFoundException | |
400, 422 | InvalidRequestException | errors |
429 | RateLimitException | retryAfter |
| tout autre code | WajubError | httpStatus, raw |
| réseau | ApiConnectionException |
Chacune expose errorCode, httpStatus, errors et raw, le corps de la réponse décodé.
Les nouvelles tentatives sont déjà intégrées
Le SDK relance lui-même les échecs transitoires, en respectant Retry-After. Quand une exception vous parvient, l'appel a déjà eu ses chances.
Relisez la requête échouée
Toute requête, y compris celles qui ont échoué, reste visible dans Konsole → Journaux API : le corps que vous avez envoyé, la réponse exacte et le prestataire concerné.