Appearance
Codes d'erreur
Toute erreur API renvoie une enveloppe uniforme :
json
{
"error": {
"code": "amount_below_merchant_minimum",
"message": "Le montant est inférieur au minimum autorisé (100.00 XOF).",
"context": { "minimum": "100.00", "currency": "XOF" },
"documentation_url": "https://docs.haybtech.sn/errors#amount_below_merchant_minimum"
}
}Le champ context est optionnel — présent uniquement quand l'erreur porte des données structurées exploitables côté SDK (montant min, currency, provider, etc.).
| Code HTTP | Sens |
|---|---|
| 400 | Requête malformée (header manquant, JSON invalide) |
| 401 | Authentification (clé absente, invalide, révoquée) |
| 403 | Autorisation (scope insuffisant, livemode mismatch) |
| 404 | Ressource introuvable pour ce marchand |
| 409 | Conflit (idempotency mismatch, in-flight) |
| 422 | Validation métier (limite dépassée, état incohérent, KYC manquant) |
| 429 | Rate limit |
| 500 | Erreur interne (à signaler au support) |
| 502 | Provider en aval indisponible |
| 503 | Provider en aval temporairement injoignable |
Authentification & autorisation
missing_credentials
HTTP 401. Header Authorization: Bearer <key> (ou X-API-Key) absent.
invalid_api_key
HTTP 401. Clé inconnue, révoquée, expirée, ou IP hors whitelist.
scope_insufficient
HTTP 403. Clé sans le scope requis pour cette route (ex: can_payout requis pour POST /v1/payouts).
livemode_mismatch
HTTP 403. Clé test sur un objet live ou inversement. Vérifier l'environnement avant de retry.
unauthorized
HTTP 401. Authentification générique requise.
forbidden
HTTP 403. Action refusée par les policies.
ip_not_allowed
HTTP 401. IP source absente de la liste autorisée (clé ou compte marchand).
test_key_in_production
HTTP 401. Une clé sk_test_* a été utilisée sur l'API live. Utiliser une clé sk_live_* ou continuer l'intégration en environnement non-production avec vos clés de test.
Idempotency
idempotency_key_required
HTTP 400. Header Idempotency-Key absent sur une requête mutante.
idempotency_key_too_long
HTTP 400. Clé > 255 caractères.
idempotency_conflict
HTTP 409. Même clé, body différent — OU requête déjà in-flight pour cette clé.
Validation de paiement / payout
Toutes ces erreurs sont HTTP 422 et sont réparables côté client sans contacter le support.
validation_error
Validation au niveau du FormRequest (champ manquant, type incorrect, format invalide). Le payload contient un sous-objet errors avec les détails par champ :
json
{ "error": { "code": "validation_error", "errors": { "amount": ["The amount field is required."] } } }merchant_inactive
Le compte marchand est suspendu / pending / closed. Contacter le support pour réactivation. Context: { status: "suspended" }
merchant_kyc_required
Validation KYC manquante. Les paiements en mode live sont restreints jusqu'à approbation.
payouts_disabled
Le service de payout n'est pas activé sur ce compte. Contacter le support.
provider_unavailable
Provider inconnu, désactivé ou non configuré. Context: { provider: "free_money" }
provider_country_unsupported
Le provider ne sert pas le pays cible (ex: Wave hors UEMOA). Context: { provider: "wave", country: "TG" }
provider_payout_unsupported
Le provider supporte les paiements mais pas les payouts. Context: { provider: "free_money" }
merchant_provider_not_authorized
Le marchand a une whitelist allowed_providers qui exclut ce provider. Context: { provider: "wave" }
amount_must_be_positive
Montant ≤ 0. Aucun contexte.
amount_below_merchant_minimum
Montant inférieur au plancher configuré pour le marchand. Context: { minimum: "100.00", currency: "XOF" }
amount_below_provider_minimum
Montant inférieur au plancher du provider (ex: Orange Money min 10 XOF). Context: { minimum: "10.00", provider: "orange_money", currency: "XOF" }
amount_above_merchant_maximum
Montant > plafond par transaction du marchand. Context: { maximum: "5000000.00", currency: "XOF" }
daily_limit_exceeded
Plafond quotidien de transferts atteint pour ce marchand. Context: { limit: "10000000.00", currency: "XOF" }
merchant_limit_exceeded
Une limite quotidienne ou mensuelle configurée sur le compte marchand a été dépassée. Le champ message détaille la cause précise (volume, montant cumulé, etc.).
bceao_limit_exceeded
Plafond BCEAO du payeur (calculé sur 30 jours glissants par MSISDN). Context: { tier: 1, ceiling: 200000 }
invalid_phone_number
Numéro de téléphone non-parsable au format E.164 pour le pays cible. Context: { reason: "..." }
insufficient_balance
Solde marchand insuffisant pour couvrir montant + frais (payout uniquement). Context: { currency: "XOF", available: "1234.50", required: "10148.50" }
fees_unavailable
Aucune grille de frais configurée pour cette combinaison (provider, pays). À signaler au support.
Remboursements
transaction_not_found
HTTP 404. Aucune transaction avec cet id/internal_ref pour ce marchand. Context: { transaction_id: "..." }
transaction_not_refundable
La transaction n'est pas en status success. Seuls les paiements aboutis sont remboursables. Context: { status: "pending" }
non_payment_transaction_refund
Tentative de remboursement d'un payout / refund / autre. Context: { type: "payout" }
transaction_already_fully_refunded
Tous les fonds ont déjà été remboursés.
refund_exceeds_refundable
Le montant demandé dépasse le solde remboursable restant. Context: { requested: "5000", refundable: "3000.0000", already_refunded: "7000.00" }
Splits (marketplace)
split_items_required
La liste items est vide.
non_payment_transaction_split
Tentative de split sur un type ≠ payment.
transaction_not_splittable
Transaction en status terminal (refunded, cancelled, expired, disputed). Context: { status: "refunded" }
split_total_exceeds_transaction
Somme des splits > montant de la transaction parent. Context: { total_split: "11000.00", transaction_amount: "10000.00" }
splits_exceed_transaction_amount
Variant TOCTOU détecté inside-transaction : la somme des splits existants + nouveaux dépasse le montant. Refaire la requête après reload côté client.
beneficiary_not_active
Marchand bénéficiaire suspendu / fermé. Context: { beneficiary_merchant_id: "..." }
Souscriptions
subscription_plan_not_available
Le plan n'existe pas, est inactif, ou n'appartient pas au marchand authentifié. Context: { plan_id: "..." }
subscription_customer_mismatch
Le customer_id n'appartient pas au marchand authentifié (cross-tenant). Context: { customer_id: "..." }
subscription_already_in_status
Tentative de cancel/cancel-at-period-end sur un abonnement déjà en cancelled ou expired. Context: { status: "cancelled" }
Providers & infrastructure
gateway_unavailable
HTTP 503. Provider en aval temporairement injoignable. Voir header Retry-After.
gateway_error
HTTP 502. Le provider a refusé l'init avec une erreur métier.
not_found
HTTP 404. Ressource générique introuvable.
method_not_allowed
HTTP 405. Mauvais verbe HTTP sur l'endpoint.
too_many_requests
HTTP 429. Rate limit atteint. Respecter Retry-After.
api_error
HTTP 500. Erreur serveur non classifiée. À signaler au support avec le request_id de la réponse.
Webhooks providers entrants (côté HayBTech)
provider_unavailable_webhook
HTTP 404. Provider désactivé sur ce slug/pays.
invalid_signature
HTTP 401. Signature ou timestamp rejeté.
duplicate
HTTP 200, no-op. Événement déjà traité (idempotent côté HayBTech).
Que faire selon le code
| Code | Action recommandée |
|---|---|
| 4xx (validation) | Corriger la requête, utiliser une nouvelle Idempotency-Key |
| 401 | Vérifier la clé. Ne PAS retry. |
| 409 | Vérifier votre logique d'idempotency |
| 422 | Lire error.code + error.context ; corriger le champ identifié |
| 429 | Attendre le Retry-After, puis retry |
| 5xx | Retry avec la même Idempotency-Key, backoff exponentiel |
| 502/503 | Provider en aval, retry après quelques secondes |
Reporter un bug
support@haybtech.com avec :
- Le code d'erreur (
error.code) - L'
error.contextcomplet (s'il existe) - Le
documentation_url - L'horodatage exact
- Votre
merchant_refou l'idHayBTech - Le header
X-Request-Idde la réponse (toujours présent)
