Qu'est-ce qu'OPTGateway ?
OPTGateway est une passerelle de paiement. Elle se place entre votre site et les opérateurs de paiement : M-Pesa, Orange Money, Airtel Money, Afrimoney, illicocash et les cartes Visa et Mastercard.
Sans passerelle, il faudrait signer un contrat et écrire une intégration différente pour chaque opérateur. Avec OPTGateway, vous écrivez une seule intégration, et vos clients choisissent leur moyen de paiement.
Les mots à connaître
Vous les croiserez partout dans ce guide. Pas besoin de tout retenir maintenant : revenez ici au besoin.
- Clé API
- Un mot de passe réservé à votre serveur, qui prouve à OPTGateway que la demande vient bien de vous. Elle commence par
sk_test_(essais) ousk_live_(argent réel). Ne la mettez jamais dans une page web ou une application mobile. - Mode test
- Un bac à sable : tout fonctionne comme en vrai, mais aucun argent ne circule. Idéal pour apprendre.
- Mode live
- Le mode réel : les clients sont vraiment débités. Il s'active après la vérification de votre établissement par notre équipe.
- Référence
- Votre numéro à vous pour ce paiement, par exemple un numéro de facture. Il doit être unique : deux paiements ne peuvent pas avoir la même référence.
- Session de paiement
- Une demande de paiement prête à être payée. Elle possède une adresse (
url) vers laquelle vous envoyez votre client. - Webhook
- Un message qu'OPTGateway envoie automatiquement à votre serveur pour dire « ce paiement est réussi ». C'est la preuve fiable qu'il faut attendre avant de livrer.
- OTP
- Un code à usage unique envoyé par SMS au client, par exemple par illicocash, pour confirmer qu'il est bien le titulaire du compte.
Comment se passe un paiement
- Votre site crée une demande : le montant, la devise et votre référence.
- Le client paie sur la page OPTGateway, avec son téléphone ou sa carte.
- OPTGateway vérifie auprès de l'opérateur que l'argent est bien reçu, avec le bon montant.
- Votre serveur est prévenu par un webhook, et le client revient sur votre site.
Moyens de paiement acceptés
Vos clients choisissent sur la page de paiement. Selon le moyen choisi, ils valident de façons différentes : OPTGateway gère chacune pour vous.
| Moyen | Valeur de method | Comment le client valide | Montants |
|---|---|---|---|
illicocash | Il reçoit un code par SMS et le saisit sur la page de paiement. | à partir de 1 000 CDF · de 1 USD à 5 000 USD | |
mobile_money | Une demande s'affiche sur son téléphone ; il la valide avec son code secret. | de 1 000 CDF à 1 000 000 CDF · de 1 USD à 5 000 USD | |
card | Il est redirigé vers la page sécurisée de sa banque, puis revient. | de 1 000 CDF à 1 000 000 CDF · de 1 USD à 5 000 USD | |
pepele | Il est redirigé vers la page Pepele Mobile (Trust Merchant Bank), saisit son numéro et son code PIN, puis revient. | aucune limite propre à OPTGateway |
Chaque paiement renvoyé par l'API, et chaque webhook, indique son moyen dans method. Pour le mobile money, le champ operator précise l'opérateur : mpesa, orange, airtel ou afrimoney (déduit du numéro). Vous distinguez ainsi sans ambiguïté un paiement illicocash d'un paiement M-Pesa.
Le moyen de paiement est toujours précisé, il est obligatoire :
- Page de paiement : passez
methodsà la création de la session, avec les moyens à proposer au client. Par exemple"methods": ["illicocash", "mobile_money", "card", "pepele"]pour tous, ou"methods": ["illicocash"]pour illicocash seul. Le client choisit ensuite parmi ces moyens, s'ils sont actifs sur votre compte. - API directe : passez
methodà la création du paiement, avec le moyen choisi par le client, par exemple"method": "mobile_money".
Sans ce champ, la demande est refusée avec l'erreur invalid_request. GET /v1/payment-methods liste les moyens actifs sur votre compte.
Plusieurs wallets : choisir où encaisser
Un établissement peut avoir plusieurs wallets pour un même moyen de paiement, par exemple une université avec un wallet illicocash par faculté. Chaque wallet a un code que vous choisissez à son enregistrement, par exemple DROIT, et un numéro chez le fournisseur (le merchant ID illicocash, ou le code marchand FlexPay). Passez l'un ou l'autre dans le champ wallet pour dire où encaisser :
"methods": ["illicocash", "mobile_money"],
"wallet": "DROIT"- Si plusieurs wallets encaissent le moyen demandé,
walletest obligatoire. Sans lui, la demande est refusée avec l'erreurwallet_required, qui liste les codes possibles. - Avec un seul wallet, il est facultatif : il sert alors à classer vos paiements, par exemple par faculté.
Chaque paiement et chaque webhook indiquent leur wallet dans wallet, toujours par son code, même si vous avez passé son numéro. GET /v1/wallets liste vos codes et numéros, et GET /v1/payments?wallet=DROIT filtre les paiements d'un wallet.
Vos wallets s'enregistrent dans l'espace marchand, page Wallets : vous saisissez vous-même les identifiants remis par Rawbank ou FlexPay. Ils sont chiffrés, jamais réaffichés, et notre équipe les vérifie avant d'activer le wallet.
Devises : francs congolais (CDF) et dollars (USD). Les moyens actifs dépendent des comptes d'encaissement configurés pour vous par notre équipe ; votre tableau de bord les affiche. Un montant hors limites est refusé avant tout envoi au client, avec le code amount_below_minimum.
Créer votre compte et votre clé de test
- Créez votre compte sur l'espace marchand. Vous êtes tout de suite en mode test.
- Dans le menu, ouvrez Clés API, choisissez « Test », puis cliquez sur Créer.
- Copiez la clé tout de suite : pour votre sécurité, elle ne sera plus jamais affichée. En cas de perte, créez-en une nouvelle et révoquez l'ancienne.
- Rangez-la sur votre serveur dans une variable d'environnement nommée
OPTGATEWAY_KEY, par exemple dans le fichier.envde votre projet.
Astuce : dans l'espace marchand, la page Documentation contient des boutons « Essayer » qui envoient de vraies requêtes de test. Pratique pour voir les réponses sans écrire une ligne.
Méthode 1 : le lien de paiement, sans code
Vous n'avez pas de site, ou vous voulez encaisser un montant ponctuel ? Le lien de paiement suffit.
- Dans l'espace marchand, cliquez sur Créer un lien de paiement, dès la page d'accueil.
- Indiquez le montant, la devise et le motif, par exemple « Frais d'inscription 2026-2027 ». La référence et la date d'expiration sont facultatives.
- Cliquez sur Créer le lien, puis sur Partager sur WhatsApp (le message est prérempli avec le montant et le lien) ou Copier le lien. Le QR code peut être imprimé sur une facture.
- Le client ouvre le lien et paie. La page Liens de paiement affiche chaque lien avec son statut : payé, en attente ou expiré.
Sans date choisie, un lien reste valable 24 heures ; vous pouvez le prolonger jusqu'à 30 jours. Le client y choisit parmi tous les moyens actifs sur votre compte, sauf si vous en cochez certains dans les options avancées.
Méthode 2 : la page de paiement (recommandée)
Votre site envoie le client vers la page de paiement OPTGateway, puis le récupère une fois le paiement terminé. C'est la méthode la plus simple à coder : la page gère le numéro de téléphone, le code SMS, la carte bancaire et les erreurs.
Étape 1 : installer le SDK
Le SDK est une petite bibliothèque qui vous évite d'écrire les appels HTTP à la main. Téléchargez celui de votre langage, puis installez-le dans votre projet.
- PHP avec Composer : ajoutez le bloc ci-dessous à votre
composer.json(dans la sectionrepositoriessi elle existe déjà), puis lancezcomposer require enywork/optgateway:1.2.0. - PHP sans Composer : décompressez l'archive dans votre projet et chargez
autoload.php. - Node.js : une seule commande, qui télécharge et installe le paquet.
{
"repositories": [
{
"type": "package",
"package": {
"name": "enywork/optgateway",
"version": "1.2.0",
"dist": { "url": "https://pay.optgateway.com/sdk/optgateway-php-1.2.0.zip", "type": "zip" },
"require": { "php": ">=7.4", "ext-curl": "*", "ext-json": "*" },
"autoload": { "psr-4": { "Enywork\\OPTGateway\\": "src/" } }
}
}
]
}// 1. Décompressez optgateway-php-1.2.0.zip dans votre projet
// 2. Chargez le SDK en haut de vos scripts :
require __DIR__ . '/optgateway-php/autoload.php';npm install https://pay.optgateway.com/sdk/enywork-optgateway-1.2.0.tgzAvec Node.js, le code reste identique : import OPTGateway from '@enywork/optgateway'. Les paquets seront aussi publiés sur Packagist et npm ; les commandes composer require et npm install @enywork/optgateway fonctionneront alors directement, avec le même code.
Étape 2 : créer la demande de paiement
Ce code s'exécute sur votre serveur, au moment où le client clique sur « Payer » sur votre site.
<?php
require 'vendor/autoload.php'; // sans Composer : require 'optgateway-php/autoload.php';
use Enywork\OPTGateway\Client;
// La clé est lue dans une variable d'environnement, jamais écrite dans le code
$optg = new Client(getenv('OPTGATEWAY_KEY'), ['base_url' => 'https://pay.optgateway.com']);
$session = $optg->checkout->createSession([
'amount' => '150000', // montant en francs congolais
'currency' => 'CDF', // ou 'USD'
'reference' => 'FACT-2026-000123', // votre numéro de facture, unique
'description' => 'Frais académiques - 1er semestre',
'methods' => ['illicocash', 'mobile_money', 'card'], // obligatoire : moyens proposés au client
'return_url' => 'https://votre-site.cd/paiement/retour',
]);
// Étape suivante : envoyer le client vers la page de paiement
header('Location: ' . $session['url'], true, 303);
exit;import OPTGateway from '@enywork/optgateway';
// La clé est lue dans une variable d'environnement, jamais écrite dans le code
const optg = new OPTGateway(process.env.OPTGATEWAY_KEY, { baseUrl: 'https://pay.optgateway.com' });
app.post('/payer', async (req, res) => {
const session = await optg.checkout.sessions.create({
amount: '150000', // montant en francs congolais
currency: 'CDF', // ou 'USD'
reference: 'FACT-2026-000123', // votre numéro de facture, unique
description: 'Frais académiques - 1er semestre',
methods: ['illicocash', 'mobile_money', 'card'], // obligatoire : moyens proposés au client
return_url: 'https://votre-site.cd/paiement/retour',
});
// Étape suivante : envoyer le client vers la page de paiement
res.redirect(303, session.url);
});curl https://pay.optgateway.com/v1/checkout/sessions \
-H "Authorization: Bearer sk_test_VOTRE_CLE" \
-H "Content-Type: application/json" \
-d '{
"amount": "150000",
"currency": "CDF",
"reference": "FACT-2026-000123",
"description": "Frais académiques - 1er semestre",
"methods": ["illicocash", "mobile_money", "card"],
"return_url": "https://votre-site.cd/paiement/retour"
}'OPTGateway répond avec la session créée. Le champ important est url :
{
"id": "cs_test_Gq3…",
"url": "https://pay.optgateway.com/pay/cs_test_Gq3…",
"status": "open",
"amount": "150000.00",
"currency": "CDF",
"reference": "FACT-2026-000123"
}Et si le client clique deux fois ? Aucun risque : avec la même reference, OPTGateway renvoie la même session. Le client ne peut pas payer deux fois la même facture.
Étape 3 : envoyer le client vers la page
C'est la dernière ligne de l'exemple ci-dessus : une redirection vers session.url. Le client voit la page de paiement, choisit son moyen de paiement et paie.
Étape 4 : recevoir la confirmation (webhook)
Quand le paiement est confirmé, OPTGateway appelle une adresse de votre serveur. Pour la configurer :
- Dans l'espace marchand, ouvrez Webhooks et saisissez l'adresse, par exemple
https://votre-site.cd/webhooks/optgateway. - Cliquez sur Générer un nouveau secret et rangez-le dans la variable
OPTGATEWAY_WEBHOOK_SECRET. - Ajoutez ce code sur votre serveur, puis cliquez sur Envoyer un test pour vérifier qu'il répond.
<?php
// Fichier appelé par OPTGateway : https://votre-site.cd/webhooks/optgateway
require 'vendor/autoload.php';
use Enywork\OPTGateway\Webhook;
use Enywork\OPTGateway\Exception\SignatureVerificationException;
try {
$event = Webhook::constructEvent(
file_get_contents('php://input'), // le corps brut de la requête
$_SERVER['HTTP_OPTGATEWAY_SIGNATURE'] ?? '',
getenv('OPTGATEWAY_WEBHOOK_SECRET') // le secret whsec_... de votre tableau de bord
);
} catch (SignatureVerificationException $e) {
http_response_code(400); // pas envoyé par OPTGateway : on ignore
exit;
}
if ($event['type'] === 'checkout.session.completed') {
// Le paiement est confirmé : marquez la facture comme payée
marquerFacturePayee($event['data']['reference']);
}
http_response_code(200); // répondre vite : "bien reçu"import express from 'express';
import OPTGateway from '@enywork/optgateway';
// express.raw : on a besoin du corps brut pour vérifier la signature
app.post('/webhooks/optgateway', express.raw({ type: 'application/json' }), (req, res) => {
let event;
try {
event = OPTGateway.webhooks.constructEvent(
req.body,
req.get('OPTGateway-Signature'),
process.env.OPTGATEWAY_WEBHOOK_SECRET // le secret whsec_... de votre tableau de bord
);
} catch (err) {
return res.sendStatus(400); // pas envoyé par OPTGateway : on ignore
}
if (event.type === 'checkout.session.completed') {
// Le paiement est confirmé : marquez la facture comme payée
marquerFacturePayee(event.data.reference);
}
res.sendStatus(200); // répondre vite : "bien reçu"
});Important : marquez une facture comme payée uniquement dans ce code-là. Le webhook est la seule preuve fiable. OPTGateway le renvoie automatiquement pendant environ 22 heures si votre serveur ne répond pas.
Étape 5 : la page de retour
Après le paiement, le client revient sur votre return_url. L'adresse contient status=succeeded, mais un client malin pourrait la taper lui-même. Pour afficher le bon message, demandez l'état réel à OPTGateway :
<?php
// https://votre-site.cd/paiement/retour?session_id=...&status=succeeded
// On ne se fie pas à l'adresse : on demande l'état réel à OPTGateway
$session = $optg->checkout->retrieveSession($_GET['session_id'] ?? '');
if ($session['status'] === 'completed') {
echo 'Merci, votre paiement est bien reçu.';
} else {
echo 'Le paiement n\'est pas encore confirmé.';
}// https://votre-site.cd/paiement/retour?session_id=...&status=succeeded
// On ne se fie pas à l'adresse : on demande l'état réel à OPTGateway
app.get('/paiement/retour', async (req, res) => {
const session = await optg.checkout.sessions.retrieve(String(req.query.session_id || ''));
res.send(session.status === 'completed'
? 'Merci, votre paiement est bien reçu.'
: 'Le paiement n\'est pas encore confirmé.');
});Étape 6 : tester
Faites un paiement complet avec votre clé de test et les numéros de test. Vérifiez trois choses : la page de paiement s'ouvre, le webhook arrive sur votre serveur, et la facture passe à « payée ».
Méthode 3 : l'API directe (avancé)
Pour construire votre propre écran de paiement, par exemple dans une application mobile. Votre écran propose les moyens de paiement, le client en choisit un, puis vous créez le paiement avec ce moyen dans method (obligatoire). Vous suivez ensuite le champ next_action, qui indique ce que le client doit faire.
// 1. Créer le paiement, avec le moyen choisi par le client (obligatoire)
let p = await optg.payments.create({
method: 'illicocash', // ou 'mobile_money', 'card', 'pepele'
amount: '5000', currency: 'CDF', reference: 'CMD-42',
customer: { phone: '+243850000005' },
});
// 2. Suivre l'action demandée
if (p.next_action?.type === 'otp') {
// illicocash : le client a reçu un code par SMS, vous le lui demandez
p = await optg.payments.confirm(p.id, codeSaisiParLeClient);
} else if (p.next_action?.type === 'redirect') {
// carte bancaire : envoyer le client vers p.next_action.url
} else if (p.next_action?.type === 'wait_for_customer') {
// M-Pesa, Orange, Airtel, Afrimoney : le client valide sur son téléphone
// attendez le webhook payment.succeeded
}// 1. Créer le paiement, avec le moyen choisi par le client (obligatoire)
$p = $optg->payments->create([
'method' => 'illicocash', // ou 'mobile_money', 'card', 'pepele'
'amount' => '5000', 'currency' => 'CDF', 'reference' => 'CMD-42',
'customer' => ['phone' => '+243850000005'],
]);
// 2. Suivre l'action demandée
$action = $p['next_action']['type'] ?? null;
if ($action === 'otp') {
// illicocash : le client a reçu un code par SMS, vous le lui demandez
$p = $optg->payments->confirm($p['id'], $_POST['code']);
} elseif ($action === 'redirect') {
header('Location: ' . $p['next_action']['url']); // carte bancaire
}
// wait_for_customer : le client valide sur son téléphone, attendez le webhooknext_action.type | Ce qui se passe | Ce que vous faites |
|---|---|---|
otp | illicocash envoie un code par SMS au client | Demandez le code au client, puis appelez confirm |
wait_for_customer | Le client reçoit une demande sur son téléphone (M-Pesa, Orange, Airtel, Afrimoney) | Affichez « Validez sur votre téléphone » et attendez le webhook |
redirect | Paiement par carte bancaire ou par Pepele Mobile | Envoyez le client vers next_action.url |
Tester sans argent réel
Avec une clé sk_test_, les paiements sont simulés, avec le parcours réel de chaque moyen :
| Moyen | Scénario | Résultat |
|---|---|---|
illicocash | N'importe quel numéro | Code SMS demandé : 123456 réussit, tout autre code est refusé |
mobile_money | Par exemple +243 97 000 00 01 | Validation sur le téléphone, puis paiement réussi après environ 5 secondes |
mobile_money | Numéro qui finit par 2, par exemple +243 97 000 00 02 | Validation sur le téléphone, puis échec pour solde insuffisant |
card | Aucun | Page bancaire simulée : le paiement réussit quand le client revient sur la page de paiement |
pepele | Aucun | Page Pepele Mobile simulée : le paiement réussit quand le client revient sur la page de paiement |
Si votre compte n'a qu'illicocash en live, seul illicocash est proposé en test aussi : votre intégration se comporte comme en production.
Sécurité : les bons réflexes
Cinq règles suffisent à éviter l'essentiel des problèmes. Relisez-les avant la mise en ligne.
- La clé API reste sur votre serveur. Jamais dans une page web, une application mobile ou un dépôt Git. Rangez-la dans une variable d'environnement. Si elle a fuité, révoquez-la dans Clés API et créez-en une autre.
- Livrez uniquement sur webhook. La page de retour peut être ouverte par n'importe qui ; seul le webhook signé prouve le paiement.
- Vérifiez la signature de chaque webhook, avec le SDK ou à la main, et refusez les messages de plus de 5 minutes.
- Contrôlez le montant et la référence reçus dans le webhook avant de marquer la facture comme payée.
- Gardez une référence unique par facture. Si vous renvoyez la même demande, OPTGateway renvoie le même paiement au lieu d'en créer un second : pas de double débit en cas de clic répété ou de coupure réseau.
Passer en production
- Dans le tableau de bord, onglet Vérification, commencez par la vérification allégée : nom de l'établissement, nom et téléphone du représentant légal, photo de sa pièce d'identité (recto et verso). Notre équipe l'examine, en général sous 48 heures ouvrées, puis ouvre le mode live avec un plafond mensuel. Complétez ensuite le dossier complet (RCCM, ou arrêté / agrément pour une école ou une université, et adresse) pour lever le plafond. Au-delà du plafond, un paiement est refusé avec l'erreur
monthly_volume_exceeded. - Vous n'avez aucun compte de fournisseur à configurer : vos clients paient sur les comptes d'ENYWORK, qui vous reverse les fonds, commission déduite. Si votre établissement a son propre contrat (wallet illicocash, compte FlexPay, AvadaPay ou Pepele Mobile), déclarez-le dans la page Wallets, rubrique « J'ai mon propre contrat avec le fournisseur » ; notre équipe le vérifie avant de l'activer. Vous êtes prévenu par e-mail à chaque étape.
- Créez une clé Live dans Clés API, et remplacez la clé de test sur votre serveur de production.
- Vérifiez que l'adresse de webhook est en
https://et qu'elle répond. - Faites un vrai paiement d'un petit montant et vérifiez-le dans Paiements.
Adresses de l'API
Toutes les adresses commencent par https://pay.optgateway.com. Chaque requête porte l'en-tête Authorization: Bearer VOTRE_CLE.
| Adresse | Rôle |
|---|---|
POST /v1/checkout/sessions | Créer une demande de paiement (page hébergée) |
GET /v1/checkout/sessions/:id | Consulter une demande de paiement |
POST /v1/payments | Créer un paiement (API directe) |
GET /v1/payments/:id | Consulter un paiement |
GET /v1/payments | Lister les paiements (filtres : status, method, wallet, reference, limit, starting_after, et pour votre rapprochement de fin de journée succeeded_from / succeeded_to, dates ISO 8601, la seconde exclue) |
GET /v1/payment-methods | Lister les moyens de paiement actifs sur votre compte, dans le mode de la clé |
GET /v1/wallets | Lister vos wallets actifs : codes et numéros acceptés dans le champ wallet |
POST /v1/payments/:id/confirm | Envoyer le code SMS du client (illicocash) |
GET /v1/balance | Consulter votre solde à reverser |
GET /v1/payouts | Lister les reversements reçus |
Champs d'une demande de paiement (POST /v1/checkout/sessions)
| Champ | Obligatoire | Description |
|---|---|---|
amount | Oui | Montant en chaîne de caractères : "150000" ou "12.50" |
currency | Oui | CDF ou USD pour tous les moyens. Mobile money d'un autre pays ouvert : sa monnaie (par exemple XOF, KES), sans conversion ; seul le mobile money est alors proposé |
reference | Oui | Votre identifiant unique (numéro de facture) |
return_url | Oui | Page de votre site où revient le client |
methods | Oui | Moyens proposés au client, parmi illicocash, mobile_money, card et pepele, par exemple ["illicocash", "mobile_money"] |
wallet | Selon le compte | Code ou numéro du wallet qui encaisse. Obligatoire si plusieurs wallets encaissent un des moyens proposés, facultatif sinon |
cancel_url | Non | Page de votre site si le client annule |
description | Non | Texte affiché au client, par exemple « Frais académiques » |
customer.phone | Non | Numéro du client, au format international (+243…). S'il est absent, le client le saisit et choisit son pays parmi ceux ouverts dans la monnaie de la demande. |
metadata | Non | Informations libres, renvoyées telles quelles (matricule, identifiant interne…) |
expires_in_minutes | Non | Durée de validité, de 5 à 1440 minutes (60 par défaut) |
Champs d'un paiement direct (POST /v1/payments)
| Champ | Obligatoire | Description |
|---|---|---|
method | Oui | Moyen choisi par le client : illicocash, mobile_money, card ou pepele |
wallet | Selon le compte | Code ou numéro du wallet qui encaisse. Obligatoire si plusieurs wallets encaissent ce moyen, facultatif sinon |
amount, currency, reference | Oui | Comme pour une demande de paiement |
customer.phone | Selon le moyen | Obligatoire pour illicocash et mobile_money, au format international (+243…, +225…) |
customer.operator | Non | Hors RDC : opérateur du numéro (par exemple orange, mtn). Sans lui, l'opérateur est détecté ; s'il ne l'est pas, l'erreur operator_unknown liste les opérateurs possibles |
return_url | Selon le moyen | Obligatoire pour card : page où revient le client après sa banque |
description, metadata | Non | Comme pour une demande de paiement |
Montants d'un paiement renvoyé
Dans votre tableau de bord (Paramètres), vous choisissez qui paie la commission. À votre charge, elle est déduite de ce que vous recevez. À la charge de votre client, elle s'ajoute au montant qu'il paie, et vous recevez exactement votre prix ; le client voit le prix, les frais et le total avant de valider.
| Champ | Description |
|---|---|
amount | Votre prix, tel que vous l'avez demandé |
fees | Frais ajoutés au client (0.00 si vous payez la commission) |
amount_charged | Total débité au client : amount + fees |
fees_paid_by | merchant ou customer |
country | Pays du payeur pour le mobile money (code ISO à 3 lettres, par exemple COD, CIV) |
Statuts d'un paiement
| Statut | Signification | Que faire |
|---|---|---|
pending | En attente de validation par le client | Attendre |
requires_action | Le client doit agir (code SMS, carte) | Suivre next_action |
succeeded | Payé et vérifié | Livrer |
failed, expired, cancelled | Non payé | Proposer de réessayer |
refunded | Payé puis remboursé au client | Annuler la facture |
processing | L'opérateur n'a pas encore confirmé ; notre équipe vérifie | Ne pas faire payer une deuxième fois : attendre le webhook |
Événements (webhooks)
| Événement | Quand |
|---|---|
checkout.session.completed | Une demande de paiement est payée : c'est celui à écouter en priorité |
payment.succeeded | Un paiement est réussi |
payment.failed, payment.expired, payment.cancelled | Un paiement n'a pas abouti |
payment.review_required | Un paiement est en vérification par notre équipe |
checkout.session.expired | Une demande n'a pas été payée à temps |
checkout.session.cancelled | Le client a annulé sur la page de paiement |
payment.refunded | Un paiement réussi a été remboursé |
payout.succeeded, payout.failed | Un reversement de fonds vers votre compte |
Chaque webhook porte l'en-tête OPTGateway-Signature. Les fonctions constructEvent des SDK la vérifient pour vous. Votre serveur doit répondre par un code 2xx en moins de 10 secondes ; sinon OPTGateway recommence plus tard, avec 8 tentatives au total. Un même événement peut donc arriver deux fois : ignorez celui que vous avez déjà traité (même id).
Vérifier une signature sans SDK
Si vous n'utilisez pas nos SDK, la vérification tient en quelques lignes. L'en-tête a la forme t=HORODATAGE,v1=SIGNATURE. La signature est un HMAC-SHA256, en hexadécimal, calculé avec votre secret de webhook sur le texte HORODATAGE.CORPS, où le corps est le JSON brut reçu, tel quel.
<?php
// Sans SDK : vérifier soi-même la signature d'un webhook
$body = file_get_contents('php://input'); // corps brut, avant json_decode
$header = $_SERVER['HTTP_OPTGATEWAY_SIGNATURE'] ?? ''; // ex. t=1790000000,v1=5f2c...
$secret = getenv('OPTGATEWAY_WEBHOOK_SECRET');
parse_str(str_replace(',', '&', $header), $parts); // ['t' => ..., 'v1' => ...]
$attendu = hash_hmac('sha256', $parts['t'] . '.' . $body, $secret);
$valide = isset($parts['v1'])
&& hash_equals($attendu, $parts['v1']) // comparaison à temps constant
&& abs(time() - (int) $parts['t']) <= 300; // message de moins de 5 minutes
if (!$valide) { http_response_code(400); exit; }
$event = json_decode($body, true);import crypto from 'node:crypto';
// Sans SDK : vérifier soi-même la signature d'un webhook.
// Important : utilisez le corps BRUT, par exemple express.raw({ type: 'application/json' })
function verifier(rawBody, header, secret) {
const parts = Object.fromEntries(String(header).split(',').map((p) => p.split('=')));
const attendu = crypto.createHmac('sha256', secret).update(`${parts.t}.${rawBody}`).digest('hex');
const recu = Buffer.from(parts.v1 || '', 'hex');
const ok = recu.length === 32 && crypto.timingSafeEqual(recu, Buffer.from(attendu, 'hex'));
const recent = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
return ok && recent;
}Piège fréquent : calculer la signature sur un JSON déjà décodé puis réencodé. L'ordre des champs ou les espaces peuvent changer, et la signature ne correspond plus. Utilisez toujours le corps brut.
Erreurs
En cas d'erreur, la réponse contient un code et un message en français, par exemple :
{ "error": { "code": "invalid_request", "message": "Requete invalide", "details": ["currency : CDF ou USD"] } }| Code HTTP | Code | Cause fréquente |
|---|---|---|
| 400 | invalid_request | Un champ manque ou est mal écrit, par exemple methods absent d'une session ou method absent d'un paiement : lisez details |
| 400 | method_not_available | Ce moyen de paiement n'est pas actif sur votre compte dans ce mode. details.available_methods liste ceux qui le sont |
| 400 | wallet_required | Plusieurs wallets encaissent ce moyen de paiement : précisez wallet. details.available_wallets liste les codes possibles |
| 400 | wallet_not_found | Code ou numéro de wallet inconnu ou inactif sur votre compte |
| 400 | amount_below_minimum | Montant trop petit pour le moyen de paiement. Le minimum est indiqué dans details.min_amount (1 000 CDF pour illicocash) |
| 400 | amount_above_maximum | Montant trop élevé pour le moyen de paiement |
| 400 | monthly_volume_exceeded | Plafond mensuel atteint (vérification allégée). details donne le plafond, le volume du mois, le reste et la date de remise à zéro. Complétez votre dossier de vérification pour lever le plafond. |
| 401 | unauthorized | Clé absente, mal copiée ou révoquée |
| 403 | forbidden | Clé live utilisée avant l'activation du mode live |
| 404 | not_found | Identifiant inconnu, ou créé dans l'autre mode (test ou live) |
| 409 | duplicate_reference | Référence déjà utilisée avec un autre montant |
| 429 | rate_limited | Trop de requêtes : patientez quelques secondes |
Questions fréquentes
Faut-il un site web ?
Non. Les liens de paiement fonctionnent sans site : vous les envoyez par WhatsApp ou par SMS.
Le client ne reçoit pas le code SMS, que faire ?
Sur la page de paiement, il clique sur « Je n'ai pas reçu de code » pour relancer une demande. Vérifiez aussi que le numéro est bien celui de son compte illicocash.
Un paiement est « En vérification » : c'est grave ?
Non. L'opérateur n'a pas répondu à temps. Notre équipe vérifie auprès de lui, puis vous recevez le webhook final. Surtout, ne faites pas payer le client une deuxième fois.
Puis-je encaisser en dollars ?
Oui. Utilisez "currency": "USD". Les montants acceptent deux décimales : "12.50".
Quand est-ce que je reçois l'argent ?
Cela dépend de vos comptes d'encaissement. Si vous avez vos propres comptes (wallet illicocash, compte FlexPay), l'argent arrive directement chez vous, sans délai de notre côté. Sinon, vos paiements sont encaissés sur les comptes de notre équipe, qui vous reverse les fonds, commission déduite ; le calendrier est convenu à l'activation de votre compte. Votre espace marchand indique, pour chaque moyen, quel compte est utilisé.
Où saisir mes identifiants illicocash ou FlexPay ?
Dans l'espace marchand, page Wallets, bouton « Ajouter un wallet ». Seul un propriétaire du compte peut le faire. Notre équipe vérifie les identifiants puis active le wallet : l'argent arrive alors directement chez vous. Vous pouvez remplacer des identifiants plus tard ; le wallet continue d'encaisser avec les anciens jusqu'à la validation des nouveaux.
Mon établissement a plusieurs wallets, un par faculté. Comment séparer les paiements ?
Enregistrez chaque wallet avec son code (par exemple DROIT, MEDECINE), puis passez le bon code dans wallet à chaque paiement. Voir Plusieurs wallets.
J'ai une autre question
Contactez notre équipe depuis votre espace marchand ou avec les coordonnées de la page d'accueil.