Troubleshooting
Symptômes courants rencontrés lors de l'intégration des APIs Intram, leurs causes probables et les solutions à appliquer.
Cette page regroupe les problèmes les plus fréquents — sur le compte, sur l'API publique de paiement et sur la Merchant API. Cherchez votre symptôme dans la table, puis suivez le lien vers la section détaillée.
Index par symptôme
401 invalid token / 401 No token provided
401 missing_api_key / 401 invalid_api_key
401 signature_invalid
403 ip_allowlist_empty
403 ip_not_allowed
403 merchant_inactive
400 missing_idempotency_key
409 idempotency_conflict
400 validation_error
404 transaction_not_found
Webhook jamais reçu
Signature webhook invalide chez moi
Opération bloquée en queued
insufficient_balance au runtime
provider_rejected au runtime
Le client paie mais je vois PENDING
Refund refusé not_refundable
Sandbox ne fonctionne pas
502 Bad Gateway / 504 Gateway Timeout
401 : token invalide ou absent
Symptôme
ou
Quand Sur les endpoints dashboard (qui exigent un JWT utilisateur).
Causes
Le header
Authorizationn'est pas envoyé.Le token JWT est expiré (durée de vie 24 h par défaut).
Le token a été signé avec un autre
APP_SECRETque celui du serveur.
Fix
Vérifiez que votre client envoie bien
Authorization: <token>(sans préfixeBearercôté admin Intram).Re-loggez-vous depuis le dashboard pour obtenir un nouveau token.
Si vous appelez depuis votre code, refresh-loggez avant chaque appel sensible ou implémentez un mécanisme de refresh.
401 : clés API Merchant manquantes ou invalides
Symptôme
ou
Quand Sur tout endpoint /api/v1/merchant/*.
Causes
Vous n'envoyez pas le header
X-Api-Key.La clé est faussée (espace ajouté en fin de chaîne, copier-coller incomplet).
Vous utilisez une clé sandbox sur une URL live, ou inversement (vérifiez l'env de la clé : préfixe
pk_sandbox_…vspk_live_…).La clé a été régénérée depuis le dashboard et l'ancienne ne fonctionne plus.
Fix
Récupérez la clé depuis Développeurs → API dans le dashboard.
Vérifiez le préfixe (
pk_sandbox_oupk_live_).Mettez à jour vos variables d'environnement et redéployez.
401 : signature HMAC invalide
Symptôme
Où <reason> ∈ missing_signature, missing_timestamp, invalid_timestamp_format, timestamp_out_of_window, signature_mismatch.
Causes par reason
reason
Cause
missing_signature
Header X-Signature absent
missing_timestamp
Header X-Timestamp absent
invalid_timestamp_format
Timestamp n'est pas en ISO 8601
timestamp_out_of_window
Plus de 5 minutes d'écart entre votre horloge et celle du serveur
signature_mismatch
Le HMAC calculé ne correspond pas
Fix pour signature_mismatch — checklist
Le path utilisé pour signer est le path interne
/api/v1/merchant/<endpoint>, pas l'URL publiquehttps://api.intram.org/v1/<endpoint>.Le body utilisé est le raw body envoyé, octet pour octet. Pas une re-sérialisation post-modification.
La query string est triée alphabétiquement par clé, jointe par
&.Le séparateur entre les 5 champs est un
\n(line feed), pas\r\n.Le secret utilisé est bien votre clé secrète (pas la clé privée, ni la clé publique).
Vous comparez la même casse : l'en-tête doit commencer par
sha256=minuscule, suivi du hex en minuscule.
Fix pour timestamp_out_of_window
Synchronisez l'horloge serveur via NTP :
timedatectl statusdoit afficherSystem clock synchronized: yes.Vérifiez que vous n'envoyez pas un timestamp pré-calculé qui dort dans un queue.
Voir le détail complet de la signature.
403 : ip_allowlist_empty
ip_allowlist_emptySymptôme
Cause Vous utilisez une clé pk_live_… mais aucune IP n'est whitelistée pour votre compte. C'est volontaire : une liste vide rejette tout (fail-closed).
Fix
Récupérez l'IP publique de votre serveur backend :
curl -4 ifconfig.medepuis le serveur.Dashboard → Développeurs → IPs autorisées → Ajouter un serveur.
Collez l'IP, ajoutez un libellé, sauvegardez.
Réessayez votre appel — c'est immédiat (pas de cache).
⚠️ N'ajoutez pas l'IP de votre navigateur par erreur ; c'est l'IP du serveur qui doit être autorisée.
Voir Sandbox vs Live + IP allowlist.
403 : ip_not_allowed
ip_not_allowedSymptôme
Causes
Votre serveur a une nouvelle IP publique (changement de fournisseur, NAT modifié, scale-out vers une autre région).
Votre allowlist contient
203.0.113.4mais la requête vient de203.0.113.5(différence d'un octet).Vous appelez depuis une machine de dev non whitelistée (alors qu'il faudrait utiliser une clé sandbox).
Fix
Le champ
client_ipdans la réponse vous dit exactement quelle IP a été vue.Si c'est légitime : ajoutez cette IP à l'allowlist (ou un CIDR englobant).
Si vous êtes en dev local : utilisez une clé sandbox (pas de restriction).
403 : merchant_inactive
merchant_inactiveSymptôme
Cause Votre compte business n'est pas encore validé par l'équipe Intram.
Fix
Suivez la procédure Activation du compte marchand.
Téléversez les pièces justificatives requises.
Attendez la validation (1 à 3 jours ouvrés).
En attendant, vous pouvez intégralement développer en
SANDBOX.
400 : missing_idempotency_key
missing_idempotency_keySymptôme
Cause Vous faites un POST/PUT/PATCH sans header Idempotency-Key.
Fix Générez une clé unique par opération métier (crypto.randomUUID() par exemple) et passez-la en header :
Format : 8 à 128 caractères, [A-Za-z0-9_-]. Voir Idempotency.
409 : idempotency_conflict
idempotency_conflictSymptôme
Cause Vous réutilisez une clé d'idempotence avec un body différent. Le serveur refuse car il garantit qu'une clé = une opération.
Fix
Si c'est un retry de la même opération : assurez-vous d'envoyer exactement le même body.
Si c'est une nouvelle opération (montant différent, destinataire différent…) : utilisez une nouvelle clé.
Voir Idempotency — quand changer de clé.
400 : validation_error
validation_errorSymptôme
Cause Le body ne respecte pas le schéma de l'endpoint.
Fix Lisez details[] pour voir précisément quel champ pose problème. Référez-vous à la doc de l'endpoint concerné dans la Reference Merchant API.
404 : transaction_not_found
transaction_not_foundSymptôme
Causes
La référence n'existe pas (faute de frappe).
La transaction existe mais appartient à un autre marchand. Vous ne pouvez accéder qu'aux vôtres.
Vous êtes en sandbox et vous cherchez une transaction live (ou inverse) — les wallets sont isolés.
Fix
Listez vos transactions via
GET /merchant/transactions?limit=10pour vérifier le format des références.Vérifiez l'environnement de la clé que vous utilisez.
Aucun webhook ne parvient
Symptôme Vous avez configuré un webhook mais aucun appel n'arrive sur votre endpoint.
Causes possibles & vérifications
URL en HTTP (pas HTTPS)
Le serveur refuse de créer un webhook non-HTTPS. La création aurait dû échouer en 400 — recréez en HTTPS.
URL inaccessible publiquement
Votre URL doit être joignable depuis Internet (pas localhost, pas IP privée). Utilisez ngrok / Cloudflare Tunnel pour les tests locaux.
Firewall bloque le trafic Intram
Vérifiez vos règles entrantes côté serveur applicatif. Vous pouvez whitelister les IPs sortantes Intram (demandez au support la liste actuelle).
Souscription mal configurée (event qui ne match pas)
GET /merchant/webhooks pour lister vos souscriptions ; vérifiez le pattern event.
Aucun event émis (l'opération n'a peut-être pas eu lieu)
GET /merchant/operations/:id pour voir le statut de l'opération concernée.
Test rapide
Si ce test passe et qu'aucun event réel n'arrive ensuite : c'est que les events ne sont pas émis (cf. dernière ligne du tableau).
Voir Webhooks signés.
Vérification signature webhook échoue
Symptôme Vous recevez bien les webhooks mais votre vérification HMAC échoue.
Causes courantes
Vous utilisez le body parsé (
req.bodyaprèsexpress.json()) au lieu du raw body brut. La signature porte sur les octets bruts — re-sérialiser produit un body légèrement différent.Mauvais secret — assurez-vous d'utiliser le secret affiché une seule fois à la création du webhook. Si vous l'avez perdu, supprimez et recréez la souscription.
Timestamp trop vieux — vérifiez l'écart entre
X-Intram-Timestampet votre horloge serveur.Encodage du body — si vous lisez le body en UTF-8 décodé, vous pouvez perdre des octets multi-byte. Lisez en
Bufferbrut.
Pattern correct (Node + Express)
Voir Recevoir les webhooks.
Opération asynchrone ne progresse pas
Symptôme GET /merchant/operations/:id renvoie status: "queued" ou "processing" depuis plusieurs minutes sans bouger.
Causes
Le worker (
merchant-api-worker) est arrêté côté infrastructure. Côté ops :pm2 status.Backlog de la queue (peu probable en charge normale).
Pour les payment requests : la transaction attend le webhook provider qui n'arrive pas.
Fix côté intégrateur
Patientez quelques secondes — la grande majorité des opérations se terminent en moins de 30 s.
Si > 5 minutes : escaladez au support en fournissant l'
operation_id.Mettez en place des webhooks pour ne plus avoir à poller (préféré).
insufficient_balance
insufficient_balanceSymptôme (au niveau opération, dans op.error)
Causes
Votre wallet est effectivement vide ou en dessous du montant + frais demandés.
Le
pendingest conséquent mais pas encore enavailable.
Fix
Vérifiez votre solde réel :
GET /merchant/balanceet regardezavailablevspending.Attendez le settlement du
pending(cycle variable selon les providers).Faites des payouts plus petits ou groupez-les différemment.
provider_rejected
provider_rejectedSymptôme (au niveau opération)
Cause Le provider externe (MTN, Moov, SBIN, Stripe) a refusé l'opération.
Fix
Lisez
details.responsemsgpour comprendre la raison (compte fermé, MSISDN invalide, plafond atteint…).Pour Mobile Money : vérifiez le format du
msisdn(international sans+, ex :22961234567).Pour les bank wires : vérifiez le SWIFT et le numéro de compte.
Le wallet a été automatiquement recrédité — vous pouvez retenter avec une nouvelle clé d'idempotence après correction.
Transaction reste pending après paiement
pending après paiementSymptôme Le client dit avoir payé via Mobile Money mais GET /merchant/transactions/:reference montre status: PENDING.
Causes
Le webhook provider n'est pas encore arrivé chez Intram (latence typique : 5–30 s, max 6 minutes).
Le client a abandonné après l'OTP sans confirmer.
Erreur réseau côté provider.
Fix
Patientez jusqu'à 6 minutes (timeout côté provider).
Si toujours
pendingaprès : la transaction est officiellement timeout — informez le client. Si l'argent a quand même été débité chez lui, un rapprochement manuel est nécessaire (escaladez au support).Pour éviter le polling : abonnez-vous aux webhooks
payment_request.paid/payment_request.failed.
not_refundable
not_refundableSymptôme
Cause La transaction n'est pas dans un statut qui permet le refund. Statuts refundables : SUCCESS (refund complet ou partiel) et PENDING (annulation côté Stripe). Pas refundables : ERROR, REFUNDED.
Fix
Si la transaction est
ERROR: pas de remboursement nécessaire, l'argent n'a pas été débité.Si la transaction est déjà
REFUNDED: un refund a déjà été appliqué, voirrefunds[]dansGET /merchant/transactions/:reference.
Confusion sandbox / live
Symptômes possibles
"Mon paiement sandbox n'apparaît pas dans le dashboard live."
"Mon payout sandbox ne crédite pas le destinataire."
"Le client a payé mais je ne vois rien dans mon wallet."
Cause Vous mélangez les deux environnements. Les wallets sandbox et live sont complètement isolés.
Fix
Vérifiez le préfixe de votre clé :
pk_sandbox_…vspk_live_….Dans le dashboard, le toggle SANDBOX/LIVE en haut de page filtre les transactions affichées.
En sandbox, aucun argent réel ne bouge — c'est normal que le destinataire ne reçoive rien sur son téléphone.
Voir Sandbox vs Live.
502 / 504 côté infrastructure
Symptômes
502 Bad Gateway504 Gateway TimeoutRéponse vide / connexion fermée
Causes
Le service Intram est temporairement indisponible (maintenance, incident).
Votre requête dépasse le timeout du proxy (rare avec le Merchant API qui est async ; possible avec l'ancienne API
/payments/updatequi attend jusqu'à 6 min).
Fix
Vérifiez le statut Intram (notifications dans le dashboard).
Retentez avec backoff exponentiel.
Pour la Merchant API : utilisez votre
Idempotency-Keyoriginale lors des retries — c'est sûr.Si le problème persiste plus de quelques minutes, contactez le support avec l'horodatage UTC et l'
operation_idsi applicable.
Vous ne trouvez pas votre symptôme ?
Consultez le glossaire pour clarifier les termes.
Vérifiez les pages dédiées : Merchant API errors, Webhooks, Authentification.
Contactez le support depuis le dashboard avec :
L'horodatage UTC précis de votre requête
L'
operation_id(si Merchant API) ou lareference(si API publique)Le code d'erreur reçu
Un extrait des headers que vous envoyez (en masquant les secrets)
Mis à jour