Dépannage
Mon webhook n'arrive pas
Diagnostic
- Dashboard → Webhooks → Logs : la requête apparaît-elle ?
- Non → l'événement n'a pas été déclenché. Vérifiez que la transaction a atteint
success/failed/cancelled/expired. Forcez une vérification avecPOST/v1/payments/{id}/verify. - Oui, avec un
http_statusnon 2xx → votre serveur l'a refusée. Voir « Mon endpoint renvoie 4xx/5xx ». - Oui, avec
error_message: "timeout"→ votre serveur a mis plus de 15 secondes. Voir « Timeout ».
- Non → l'événement n'a pas été déclenché. Vérifiez que la transaction a atteint
Mon endpoint renvoie 4xx/5xx
| http_status | Cause fréquente |
|---|---|
| 400 | Parsing du corps : votre serveur lit le contenu avant de vérifier la signature |
| 401 / 403 | Un middleware d'authentification bloque les requêtes externes — exemptez /webhooks/soamtar |
| 404 | Mauvaise route — vérifiez l'URL exacte dans le dashboard |
| 422 | Validation du framework — acceptez le contenu tel quel |
| 500 | Erreur applicative — consultez vos logs |
| 502 / 503 | Reverse proxy en panne ou application non démarrée |
Correction : excluez votre route de webhook de la protection CSRF (ex. withoutMiddleware([VerifyCsrfToken::class]) sous Laravel) et de votre middleware d'authentification.
Timeout
Votre handler doit répondre 2xx en moins de 15 secondes. Pour un traitement long :
// Mauvais
public function webhook(Request $request) {
$this->processOrder($request); // 30 secondes
$this->sendEmail($request);
return response('ok');
}
// Bon
public function webhook(Request $request) {
ProcessSoamtarWebhookJob::dispatch($request->all());
return response('ok'); // immédiat
}Mon serveur reçoit le webhook 5 fois
C'est un réessai, comportement attendu. Causes :
- Vous répondez 2xx mais après plus de 15 s → SOAMTAR a déjà enregistré un timeout
- Vous répondez autre chose que 2xx
- Vous répondez 2xx mais SOAMTAR ne reçoit jamais la réponse (réseau)
Correction : dédupliquez de votre côté sur le couple (event, data.id).
CREATE UNIQUE INDEX uq_inbound_event ON your_webhook_log(event, transaction_id);Signature de webhook invalide
Symptôme
Votre vérification HMAC échoue alors que vous utilisez le bon secret.
Cause 1 : corps modifié avant vérification
Express, Symfony et Laravel parsent le JSON automatiquement. Vous devez vérifier la signature sur le corps brut, avant tout parsing.
// Mauvais
app.use(express.json()) // parse le JSON
app.post('/webhook', (req, res) => {
verify(req.body) // req.body est déjà un objet, pas le corps brut
})
// Bon
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
verify(req.body.toString()) // brut
const event = JSON.parse(req.body.toString())
})Cause 2 : encodage du HMAC
Comparez de l'hexadécimal en minuscules des deux côtés (hash_hmac en PHP renvoie des minuscules ; vérifiez en Node). Utilisez hash_equals / crypto.timingSafeEqual, pas ===.
Cause 3 : horodatage expiré
|now - t| > 300s est rejeté. Vérifiez l'horloge de votre serveur (NTP). N'élargissez pas la tolérance pour traiter d'anciennes livraisons : renvoyez-les depuis Dashboard → Webhook logs → Resend (chaque tentative est re-signée avec un horodatage récent) ou utilisez la vérification verify.
Conflit d'Idempotency-Key
Symptôme
409 idempotency_conflict quand vous renvoyez la requête.
Diagnostic
curl -X POST .../v1/payments \
-H "Idempotency-Key: abc-123" \
-d '{"merchant_ref":"ORDER-7","amount":"150.00","currency":"SLE"}'
# 201
curl -X POST .../v1/payments \
-H "Idempotency-Key: abc-123" \
-d '{"merchant_ref":"ORDER-7","amount":"150.00","currency":"SLE","description":"oops"}'
# 409 — corps différentCorrection :
- Pour réessayer : même clé, exactement le même corps.
- Pour une nouvelle requête : une nouvelle clé.
Bonne pratique
Générez la clé quand le client appuie sur « Payer » et conservez-la (stockage local / base de données) jusqu'au résultat final. Ne la régénérez pas à chaque réessai HTTP.
Transaction bloquée en pending
Diagnostic
curl -X POST https://app.so-amtar.com/v1/payments/{id}/verify \
-H "Authorization: Bearer sk_test_xxx"La vérification verify interroge Orange Money et synchronise le statut. Si Orange Money confirme, la transaction avance et un webhook vous est envoyé.
Si rien ne change
- Le client n'a pas confirmé la demande Orange Money avec son code PIN (la demande reste ouverte environ 5 minutes) : demandez-lui de vérifier son téléphone, ou lancez un nouveau paiement.
- Orange Money subit un incident : renseignez-vous auprès du support.
- La transaction expire automatiquement à
expires_at(2 heures par défaut). - Toujours bloquée : écrivez à
support@so-amtar.comavec la référence SOAMTAR (TXN-...).
Rembourser un paiement Orange Money
Utilisez POST/v1/refunds avec le transaction_id du paiement (voir Remboursements). Si l'appel échoue :
| Code d'erreur | Cause | Solution |
|---|---|---|
scope_insufficient (403) | La clé API n'a pas can_refund | Activez le scope sur la clé dans le dashboard |
refunds_disabled | Les remboursements ne sont pas activés sur votre compte | Contactez le support |
transaction_not_refundable | Le paiement n'est pas success (ou déjà remboursé) | Ne remboursez que des paiements réussis |
refund_environment_mismatch | Clé de test sur un paiement live, ou l'inverse | Utilisez la clé de l'environnement du paiement |
refund_payer_phone_missing | Le paiement n'a pas de téléphone payeur à créditer | Remboursez le client hors SOAMTAR |
refund_exceeds_refundable / transaction_already_fully_refunded | Montant supérieur au restant remboursable | Baissez amount ou arrêtez |
Un remboursement en processing attend la confirmation du crédit par Orange Money ; le statut final arrive par refund.success ou refund.failed (voir failure_reason). Vous pouvez aussi interroger GET/v1/refunds/{id}.
Le solde affiché ne correspond pas à mes reversements
| Composant | Inclus dans le solde affiché ? |
|---|---|
success pas encore reversé | Oui |
pending | Non |
success déjà reversé | Non |
| Remboursement ou paiement sortant émis | Oui (déduit) |
| Litige ouvert | Réservé sur un solde distinct |
Le solde « disponible » diffère du solde « comptable » ; le dashboard affiche les deux.
Une clé live ne trouve pas mon objet
Vous utilisez une clé live sur un objet créé en sandbox (ou l'inverse). Utilisez la clé du mode correspondant : les identifiants sont strictement cloisonnés par mode.
Problèmes de connexion à l'API (SSL, timeout…)
Erreur SSL : SSL certificate problem: unable to get local issuer certificate
Survient en général sur des serveurs Windows ou d'anciennes installations PHP/cURL sans bundle de certificats à jour.
Correction :
- Téléchargez le
cacert.pemofficiel depuis curl.se/ca/cacert.pem. - Pointez
php.inidessus :inicurl.cainfo = "C:/path/to/cacert.pem" openssl.cafile = "C:/path/to/cacert.pem" - Redémarrez votre serveur web.
Erreur : Connection Timeout
Si votre serveur ne joint pas app.so-amtar.com :
- Pare-feu : votre serveur doit être autorisé à sortir sur le port 443 (HTTPS).
- DNS : vérifiez que
app.so-amtar.comse résout depuis votre serveur. - Proxy : si votre serveur passe par un proxy, configurez le SDK en conséquence (ou les variables d'environnement
HTTP_PROXY).
Erreur : 401 Unauthorized (clé invalide)
- Utilisez la clé secrète (
sk_...) pour les appels backend, pas la clé publique (pk_...). - Vérifiez le mode : une clé
sk_test_ne fonctionne que pour les objets sandbox. - Assurez-vous qu'aucun espace ni retour à la ligne n'a été copié avec la clé.
Aller plus loin
- Support :
support@so-amtar.com