Codes d'erreur de l'API de paiement : corriger, rejouer ou attendre, code par code
Codes d'erreur de l'API de paiement, du 400 au 502 : l'enveloppe, le correlationId, la cause, la correction et quand rejouer avec votre clé d'idempotence.
Les codes d'erreur de l'API de paiement de ChariPay suivent une règle simple : le statut HTTP dit qui doit agir, le code dit quoi faire. Une erreur 4xx se corrige de votre côté avant tout nouvel essai — sauf 429, où il suffit d'attendre la durée indiquée par Retry-After ; une erreur 5xx vient de notre côté et se rejoue avec la même clé d'idempotence. Ce guide détaille l'enveloppe d'erreur et son correlationId, chaque code publié avec sa cause, sa correction et la conduite à tenir, puis une règle de décision et une journalisation qui n'inonde pas vos alertes.
L'enveloppe d'erreur et le correlationId
ChariPay est une plateforme de paiement marocaine, un PSP opéré par Chari Money, établissement de paiement agréé par Bank Al-Maghrib. Toutes les erreurs de son API marchande — les chemins /v1 — partagent la même enveloppe :
{
"error": {
"code": "VALIDATION_ERROR",
"message": "amount: must be greater than 0"
},
"correlationId": "b0c1e2d3-4f56-7890-abcd-ef0123456789"
}Trois champs, trois usages. Le code est stable : il fait partie du contrat, et c'est lui que votre programme teste. Le message s'adresse à un humain : il aide à comprendre, pas à décider. Testez le code, jamais le message. Le correlationId identifie la requête dans nos journaux ; il revient aussi sur les réponses en succès, et si vous envoyez votre propre en-tête X-Request-Id, il est répété sur la réponse et devient le correlationId. Générez donc un identifiant par requête de votre côté, envoyez-le en X-Request-Id, et une ligne de vos journaux se reliera sans effort à une ligne des nôtres.
Une exception à connaître : les endpoints d'inscription et du portail ont leur propre format d'erreur, avec des codes ERR-XXXX. L'enveloppe ci-dessus vaut pour les chemins /v1 que votre clé appelle, comme pour les appels /checkout/* du checkout direct, et c'est d'elle que traite la suite.
400 et 401 : la requête ou la clé
| Code | Statut | Cause | Correction |
|---|---|---|---|
VALIDATION_ERROR | 400 | un champ ne passe pas la validation | lire le message, corriger le champ |
MISSING_PARAMETER | 400 | un paramètre requis manque | l'ajouter |
INVALID_IDEMPOTENCY_KEY | 400 | Idempotency-Key absente ou trop longue là où elle est exigée, par exemple sur POST /checkout/submit d'une session réutilisable | fournir une clé valide |
UNAUTHORIZED | 401 | clé d'API absente ou invalide | vérifier l'en-tête X-CHARI-PAY-API-KEY et la clé chargée par votre configuration |
INVALID_TOKEN | 401 | checkout direct : clé de vérification vk invalide ou déjà consommée, ou session non vérifiée au préalable | respecter l'ordre des appels (verify avant submit) et ne jamais réutiliser une vk |
Un 400 signale une requête que l'API refuse telle quelle. Avant de relire votre logique, vérifiez que la requête respecte les conventions de l'API :
- des champs en camelCase :
orderId, pasorder_id; - un montant en dirhams, en unités majeures, avec deux décimales au plus :
149.90; - toute URL déclarée — retour, notification, logo — en
https://publique, sous peine de400; - un
expiresAtdans le futur, au format ISO-8601 UTC :2026-07-31T10:15:00Z; - une
metadataplate — chaînes, nombres, booléens —, sans objet imbriqué ni tableau, de 4 Ko au plus.
Omettez aussi un champ optionnel plutôt que de l'envoyer à null : l'API raisonne en champs absents. Un 400 ne se rejoue jamais tel quel, quel que soit son code, puisque la même requête produira la même erreur. Corrigez, puis renvoyez la requête corrigée. Si un 401 UNAUTHORIZED surgit juste après un déploiement, vérifiez d'abord la variable d'environnement qui porte la clé : elle est peut-être vide, mal nommée ou tronquée.
Pour câbler votre gestion d'erreurs, provoquez ces erreurs sur une clé de test — un champ omis, une URL en http://, une clé volontairement erronée — et lisez le statut et le code obtenus. Commencer en mode test : trois champs — nom, e-mail professionnel, entreprise —, un lien d'activation reçu par e-mail pour choisir votre mot de passe, puis le portail, où vous créez vous-même votre clé de test après une ré-authentification. C'est gratuit, sans limite de durée, et la vérification de votre entreprise ne conditionne que la production.
403 PRODUCTION_ACCESS_NOT_ENABLED et FORBIDDEN
403 PRODUCTION_ACCESS_NOT_ENABLED dit exactement ce qu'il signifie : vous appelez l'API avec une clé chari_sk_live_… alors que la production n'est pas encore activée sur votre compte. Ce n'est pas un bug d'intégration, c'est une étape administrative. Deux corrections : revenir à votre clé chari_sk_test_… tant que vous développez, ou terminer le passage en production — vérification de l'entreprise, étude du dossier, mise en service de 6 000 MAD TTC réglée une seule fois —, dont la liste de bascule est dans passer de la sandbox à la production. Votre programme doit tester ce code exact, PRODUCTION_ACCESS_NOT_ENABLED, et l'afficher clairement à l'équipe plutôt que de le noyer parmi les erreurs génériques.
403 FORBIDDEN est d'une autre nature : la clé est authentifiée, mais elle n'a pas la permission requise pour cet appel. Chaque clé porte des permissions par module ; l'exemple publié est l'export CSV des transactions, qui exige la permission transaction:export. La correction consiste à vérifier, dans le portail, les permissions de la clé que votre intégration utilise, et à appeler avec une clé qui porte celle que l'appel exige. N'élargissez pas les permissions par réflexe : accordez à une clé ce que votre intégration appelle réellement, et rien de plus.
404, 409, 410 et 422 : ressource, conflit, expiration, état
| Statut | Code | Ce qu'il signifie | Que faire |
|---|---|---|---|
| 404 | OPERATION_NOT_FOUND | aucune opération ne correspond à l'identifiant fourni | vérifier l'identifiant et l'environnement de la clé |
| 404 | ORDER_NOT_FOUND | aucun paiement ne correspond à la référence | vérifier la référence envoyée |
| 404 | SESSION_NOT_FOUND | session inconnue | vérifier le sessionId envoyé |
| 409 | IDEMPOTENCY_CONFLICT | même Idempotency-Key, corps différent | corriger la génération des clés ; ne pas rejouer à l'identique |
| 409 | SESSION_ALREADY_CONSUMED | la session a déjà été payée | ne plus la soumettre ; pour un nouveau paiement, créer une nouvelle session, et pour rendre l'argent, rembourser |
| 409 | SESSION_NOT_ACTIVE | la session a été annulée | en créer une nouvelle si la commande doit encore être payée |
| 410 | SESSION_EXPIRED | la session a dépassé son expiresAt | créer une nouvelle session |
| 422 | WALLET_NOT_ACTIVE | requête valide, mais le wallet concerné n'est pas actif | ne pas rejouer en boucle ; contacter l'équipe, correlationId à l'appui |
Sur une transaction ou un remboursement, OPERATION_NOT_FOUND peut aussi venir d'un operationId créé dans l'autre environnement : c'est la clé qui choisit l'environnement, et la sandbox et la production sont isolées.
409 IDEMPOTENCY_CONFLICT signale un bug chez vous, pas un incident : la même clé a servi pour deux corps différents. Une clé d'idempotence se dérive de l'intention — la commande et l'opération —, jamais d'un tirage aléatoire ; le mécanisme complet est dans l'idempotence des paiements.
Les codes de session, eux, ne sont pas des incidents : ils décrivent la vie normale d'une session, consommée une seule fois et valable jusqu'à son expiration ou son annulation. Ils répondent notamment aux appels qui ouvrent et paient la session — ceux de la page hébergée ou, en checkout direct, ceux de votre serveur. Le cycle de vie qui les produit est expliqué dans expiration et retour d'une session de paiement.
429 RATE_LIMITED : lire Retry-After
Les endpoints exposés au public annoncent leur budget dans trois en-têtes de réponse. Au-delà, l'API répond 429 avec le code RATE_LIMITED et un en-tête Retry-After.
| En-tête | Ce qu'il vous dit |
|---|---|
X-RateLimit-Limit | la limite qui s'applique à l'appel |
X-RateLimit-Remaining | ce qu'il vous reste avant de l'atteindre |
X-RateLimit-Reset | le moment où le compteur repart |
Retry-After | sur un 429, combien de temps attendre avant de rejouer |
La conduite à tenir tient en deux temps : attendre la durée annoncée par Retry-After, puis rejouer la même requête, avec la même clé d'idempotence. Pour les traitements par lot — synchronisation d'un catalogue, relecture de statuts, export —, étalez les appels et surveillez X-RateLimit-Remaining plutôt que d'attendre le 429. Des rafales de 429 sur un endpoint de lecture peuvent trahir un sondage trop agressif : le résultat d'un paiement s'apprend par webhook, et le sondage n'est qu'un repli. Aucun seuil chiffré n'est publié : ne codez pas de limite en dur, lisez les en-têtes.
5xx et 502 BAAS_CHARI_ERROR : rejouer avec la même clé
Les 5xx sont de notre côté : rejouez avec votre clé d'idempotence plutôt que de créer une nouvelle ressource. C'est exactement le cas pour lequel les trois outils d'idempotence existent :
- `Idempotency-Key` sur les créations : rejouer la même valeur renvoie le premier résultat si la première tentative est passée, sans doublon.
- `externalId` sur les liens, les sessions, les abonnements et les produits : une création avec un
externalIdexistant renvoie la ressource existante en200au lieu de201. - `refundReference` sur les remboursements :
202à la création,200au rejeu, jamais un second remboursement.
Rejouez avec un délai croissant entre les tentatives, et arrêtez-vous au bout de quelques essais pour alerter un humain plutôt que de boucler. Une création sans clé d'idempotence, rejouée après un 5xx, peut créer un doublon : c'est la raison pour laquelle toutes vos créations doivent en porter une, dès la sandbox.
Un cas ressemble à une panne et n'en est pas une : en sandbox, 502 BAAS_CHARI_ERROR répond à une carte autre que la carte de test. Rejouer ne sert à rien : changez de carte. La seule carte acceptée en sandbox est la 4918 9141 0719 5005, avec le CVV 123, une date d'expiration future et le code 3-D Secure 555 ; le reste du parcours de recette est dans tester un paiement en mode test.
Codes d'erreur de l'API de paiement : la règle de décision
| Statut | Ce que vous faites | Rejouer ? |
|---|---|---|
| 400 | corriger le champ | après correction |
| 401 | corriger l'en-tête ou la clé | après correction |
| 403 | utiliser la bonne clé ou les bonnes permissions, ou activer la production | après correction |
| 404 | vérifier l'identifiant envoyé et, pour une opération, l'environnement de la clé | après correction |
| 409 | conflit d'idempotence : corriger la génération des clés ; session payée ou annulée : une nouvelle session si un paiement reste dû | jamais à l'identique |
| 410 | créer une nouvelle session | non |
| 422 | lever l'état qui bloque | pas avant |
| 429 | attendre Retry-After | oui, à l'identique |
| 5xx | attendre, avec un délai croissant | oui, avec la même clé d'idempotence |
502 BAAS_CHARI_ERROR en sandbox | changer de carte | non |
Une erreur d'API n'est pas un refus de la banque. Un paiement refusé par la banque de l'acheteur n'apparaît pas comme une erreur de votre appel : la création de la session a réussi, c'est le paiement qui n'a pas abouti, et cela se lit dans son statut. Les causes de ces échecs — abandon pendant l'authentification, refus de la banque émettrice, données erronées, soupçon de fraude — sont traitées dans 3-D Secure et taux de réussite.
Journaliser et alerter sans bruit
Pour chaque appel en erreur, journalisez la méthode et le chemin appelés, le statut HTTP, le code et le correlationId — qui reprend votre X-Request-Id si vous en envoyez un. Jamais la clé d'API : une clé est un secret, et un journal circule. Puis réglez vos alertes selon ce que chaque signal annonce :
| Signal | Ce qu'il annonce | Réaction |
|---|---|---|
401 ou 403 en production | clé absente ou invalide, permission manquante, production non activée | alerte immédiate : vos appels n'aboutissent plus |
rafales de 429 | sondage trop agressif ou lot mal étalé | alerte, puis lisser les appels |
5xx répétés | incident de notre côté | alerte après plusieurs échecs consécutifs, rejeu avec les clés d'idempotence |
409 IDEMPOTENCY_CONFLICT | bug de génération des clés | ticket prioritaire |
400 isolés | données d'une commande particulière | ticket, sans réveiller personne |
Quand vous écrivez à l'équipe — contacter l'équipe —, joignez le correlationId, l'heure en UTC et le chemin appelé : c'est ce qui permet de retrouver votre requête dans nos journaux. Le guide API de paiement au Maroc replace la gestion d'erreurs dans l'intégration complète, et la documentation de l'API liste les réponses possibles de chaque endpoint.
Questions fréquentes
Que signifie l'erreur 403 PRODUCTION_ACCESS_NOT_ENABLED ?
Elle signifie que vous appelez l'API avec une clé chari_sk_live_… alors que la production n'est pas encore activée sur votre compte. Ce n'est pas un bug d'intégration : continuez avec votre clé de test, ou terminez le passage en production — vérification de l'entreprise, étude du dossier, mise en service de 6 000 MAD TTC réglée une seule fois.
À quoi sert le correlationId d'une réponse d'erreur ?
Il identifie votre requête dans nos journaux. Il revient sur toutes les réponses, succès compris, et reprend votre en-tête X-Request-Id si vous en envoyez un. Journalisez-le systématiquement et joignez-le à toute demande d'assistance, avec l'heure en UTC et le chemin appelé : c'est ce qui permet de retrouver votre requête dans nos journaux.
Faut-il rejouer une requête après une erreur 500 ?
Oui. Les 5xx sont de notre côté : rejouez avec votre clé d'idempotence plutôt que de créer une nouvelle ressource. La même Idempotency-Key, le même externalId ou la même refundReference garantissent qu'aucun doublon ne se crée si la première tentative était passée. Espacez les essais avec un délai croissant et alertez un humain au bout de quelques échecs.
Que faire après une erreur 429 RATE_LIMITED ?
Attendez la durée indiquée par l'en-tête Retry-After, puis rejouez la même requête, avec la même clé d'idempotence. Pour éviter la suivante, surveillez X-RateLimit-Remaining, étalez vos traitements par lot et remplacez le sondage par les webhooks : le résultat d'un paiement s'apprend par événement signé, pas en interrogeant l'API en boucle.
Pourquoi la sandbox renvoie-t-elle 502 BAAS_CHARI_ERROR ?
Parce que la carte saisie n'est pas la carte de test. En sandbox, seule la carte 4918 9141 0719 5005 est acceptée, avec le CVV 123, une date d'expiration future et le code 3-D Secure 555. Toute autre carte reçoit 502 BAAS_CHARI_ERROR : rejouer ne change rien, changez de carte.
Que veut dire l'erreur 409 IDEMPOTENCY_CONFLICT ?
Que la même Idempotency-Key a été envoyée avec un corps différent de la première requête. C'est un bug de votre côté, pas un incident réseau : une clé d'idempotence se dérive de l'intention — la commande et l'opération —, et une requête différente appelle une clé différente. Corrigez la génération des clés au lieu de rejouer à l'identique.
Quelle est la limite de requêtes de l'API ?
Aucun seuil chiffré n'est publié. Les endpoints exposés au public annoncent leur budget dans les en-têtes X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset, et répondent 429 RATE_LIMITED avec Retry-After au-delà. Lisez ces en-têtes plutôt que de coder une limite en dur : votre intégration s'adaptera d'elle-même.
Une erreur d'API signifie-t-elle un refus de la banque ?
Non. Une erreur d'API signifie que votre requête n'a pas abouti. Un paiement refusé par la banque de l'acheteur est autre chose : la session a bien été créée, c'est le paiement qui n'a pas abouti, et cela se lit dans son statut. Les causes de ces refus relèvent de l'authentification et de la banque émettrice, pas de votre code.
Prochaine étape
Provoquez ces erreurs dans la sandbox — un champ manquant, une URL en http://, une clé erronée, une session expirée, la même clé d'idempotence avec un autre corps, externalId compris — et vérifiez que votre code réagit comme le prévoit la règle de décision : Commencer en mode test. Câblez ensuite la journalisation du correlationId, puis préparez la bascule avec passer de la sandbox à la production.
Écrit par Équipe ChariPay.