Documentation API Verzapay
L'API Verzapay vous permet d'intégrer l'encaissement et le décaissement directement dans votre site ou votre application, avec une API REST simple, des réponses JSON prévisibles et des webhooks fiables.
URL de base : https://www.verzapay.com/api/v1
Authentification
Toutes les requêtes doivent inclure votre clé secrète dans l'en-tête Authorization. Vous pouvez générer votre clé depuis l'espace Développeur de votre tableau de bord — elle ne sera affichée qu'une seule fois.
Authorization: Bearer vpz_sk_votre_cle_secrete
Pays disponibles
Verzapay s'appuie sur son réseau de partenaires de paiement pour couvrir progressivement de nouveaux marchés. Cette liste reflète en temps réel les pays et méthodes de paiement réellement actifs sur la plateforme.
Créer un paiement
Génère un lien de paiement que votre client peut utiliser pour régler par Mobile Money ou carte bancaire. Le pays est déduit automatiquement à partir du numéro de téléphone du client — voir la liste des pays disponibles.
customer_phone). Il doit être au format international, avec l'indicatif du pays, par exemple +22996000000. Sans ce numéro, le pays ne peut pas être déterminé et la requête est rejetée avec une erreur 400./paymentscurl -X POST https://www.verzapay.com/api/v1/payments \
-H "Authorization: Bearer vpz_sk_votre_cle" \
-H "Content-Type: application/json" \
-d '{
"amount": 25000,
"currency": "XOF",
"description": "Commande #1234",
"customer_name": "Kofi Mensah",
"customer_phone": "+22996000000",
"merchant_reference": "CMD-1234"
}'Réponse
{
"id": "pay_8f3a9b2c",
"status": "pending",
"amount": 25000,
"currency": "XOF",
"charged_amount": 25000,
"charged_currency": "XOF",
"exchange_rate": 1,
"checkout_url": "https://www.verzapay.com/pay/8f3a9b2c"
}Devises et conversion automatique
amount est exprimé dans la devise currency (code ISO à 3 lettres, XOF par défaut). Le client est débité dans la devise de son pays (déduit du numéro de téléphone, voir Pays disponibles) : Verzapay convertit automatiquement le montant. Exemple : amount: 19.98, currency: "EUR" avec un numéro français +33… est débité 19,98 EUR, sans conversion.
- La réponse indique le montant réellement débité (
charged_amount,charged_currency) et le taux appliqué (exchange_rate: unités decharged_currencypour 1 unité decurrency). - XOF, XAF et EUR sont liés à la parité fixe 1 EUR = 655,957 XOF/XAF. Les autres taux proviennent d'une source de taux de change publique, rafraîchie plusieurs fois par jour.
- Si aucun taux fiable n'est disponible, la requête est refusée (
503) : aucun taux n'est jamais estimé. - Les limites (minimum 100 XOF, maximum 50 000 000 XOF) s'appliquent à l'équivalent en XOF.
- Votre solde et vos commissions restent exprimés en XOF.
merchant_reference(128 caractères max) est renvoyé tel quel dans le webhook : utilisez-le pour retrouver votre commande.
/payments/{id}Retourne le statut d'un paiement de votre compte (pending, completed ou failed). C'est la méthode recommandée pour vérifier une notification webhook.
curl https://www.verzapay.com/api/v1/payments/pay_8f3a9b2c \
-H "Authorization: Bearer vpz_sk_votre_cle"
{
"id": "pay_8f3a9b2c",
"status": "completed",
"amount": 19.98,
"currency": "EUR",
"charged_amount": 19.98,
"charged_currency": "EUR",
"exchange_rate": 1,
"merchant_reference": "CMD-1234",
"created_at": 1792664000000,
"completed_at": 1792664120000
}Créer un décaissement
Envoie des fonds depuis votre solde Verzapay vers un compte Mobile Money. Disponible uniquement pour les pays marqués Disponible dans la colonne Décaissement ci-dessus. Comme pour les paiements, currency(XOF par défaut) peut être n'importe quelle devise : le bénéficiaire reçoit dans la devise de son pays, et votre solde (en XOF) est débité de l'équivalent. Minimum 500 XOF (équivalent).
/payoutscurl -X POST https://www.verzapay.com/api/v1/payouts \
-H "Authorization: Bearer vpz_sk_votre_cle" \
-H "Content-Type: application/json" \
-d '{
"amount": 10000,
"currency": "XOF",
"recipient_phone": "+22996000000",
"recipient_name": "Kofi Mensah"
}'Webhooks
Configurez une URL de webhook depuis votre espace Développeur pour recevoir des notifications en temps réel à chaque événement.
https://. Une URL en http://entraîne une redirection que certains de nos partenaires ne suivent pas, ce qui empêche la notification d'arriver.Comment vérifier qu'un webhook vient bien de Verzapay ?
Les webhooks Verzapay ne sont pas signés: il n'y a pas de secret de webhook ni d'en-tête de signature à vérifier. La vérification se fait donc de votre côté, avec les règles ci-dessous :
- Utilisez une URL secrète. Ajoutez à votre URL un jeton long et aléatoire que vous seul connaissez, par exemple
https://monsite.com/webhook/verzapay?token=VOTRE_JETON_ALEATOIRE, et refusez (401) toute requête dont le jeton est absent ou différent. - Rapprochez le paiement de votre commande. Vérifiez que
paymentId(oumerchantReference) correspond à un paiement que vous avez réellement créé, puis queamountetcurrencycorrespondent à la commande. Ignorez tout identifiant inconnu. - Ne faites confiance qu'à votre propre base.Ne livrez pas une commande uniquement parce qu'un webhook l'annonce : contrôlez qu'elle est bien en attente chez vous.
- Soyez idempotent. Un même événement peut arriver plusieurs fois : si la commande est déjà marquée payée, répondez
200sans la retraiter. - Répondez vite en HTTPS avec un statut
2xx.
payment.completedPaiement reçu avec succèspayment.failedPaiement échouépayout.completedDécaissement effectuépayout.failedDécaissement échouéExemple de handler (Node.js)
app.post('/webhook/verzapay', (req, res) => {
// 1. Vérifier le jeton secret placé dans l'URL du webhook
if (req.query.token !== process.env.VERZAPAY_WEBHOOK_TOKEN) {
return res.status(401).end();
}
const event = req.body;
// 2. Retrouver la commande via event.paymentId / event.merchantReference,
// comparer event.amount et ignorer si déjà traitée (idempotence)
switch (event.type) {
case 'payment.completed':
// Activer la commande
break;
case 'payment.failed':
// Notifier l'utilisateur
break;
case 'payout.completed':
// Marquer le retrait comme traité
break;
}
res.status(200).json({ received: true });
});Contenu d'une notification
{
"type": "payment.completed",
"paymentId": "pay_8f3a9b2c",
"merchantReference": "CMD-1234",
"amount": 19.98,
"currency": "EUR",
"chargedAmount": 19.98,
"chargedCurrency": "EUR",
"status": "completed",
"customerName": "Kofi Mensah",
"customerPhone": "+33612345678",
"companyId": "..."
}amount et currency reprennent ce que vous avez demandé à la création. Pour les décaissements, payoutId remplace paymentId.
Sécuriser votre webhook
- Ajoutez un jeton secret à l'URL que vous déclarez dans l'espace Développeur, par exemple
https://votre-site.com/webhook/verzapay?k=UNE_VALEUR_ALEATOIRE_DE_32_CARACTERES. Générez-le vous-même, gardez-le secret (variable d'environnement), et rejetez (401) toute requête dontkne correspond pas, avec une comparaison à temps constant. - Ne faites jamais confiance au contenu. À la réception de
payment.completed, appelezGET /payments/{paymentId}avec votre clé secrète et n'activez la commande que sistatusvautcompleted. - Recoupez avec votre commande :
merchant_reference, montant et devise doivent correspondre à ce que vous avez créé. - Soyez idempotent : la même notification peut arriver plusieurs fois. Mémorisez les
paymentIddéjà traités. - HTTPS uniquement, réponse
200rapide, traitement long en tâche de fond. Ignorez lestypeinconnus.
app.post('/webhook/verzapay', async (req, res) => {
// 1) jeton secret dans l'URL (comparaison à temps constant)
const given = Buffer.from(String(req.query.k || ''));
const secret = Buffer.from(process.env.VERZAPAY_WEBHOOK_TOKEN);
if (given.length !== secret.length || !crypto.timingSafeEqual(given, secret)) {
return res.status(401).end();
}
const event = req.body;
if (event.type === 'payment.completed') {
// 2) contre-vérification auprès de l'API (la notification seule ne prouve rien)
const r = await fetch(`https://www.verzapay.com/api/v1/payments/${event.paymentId}`, {
headers: { Authorization: 'Bearer ' + process.env.VERZAPAY_SECRET_KEY },
});
const pay = await r.json();
// 3) recoupement avec votre commande
if (pay.status === 'completed' && pay.merchant_reference === order.ref && pay.amount === order.amount) {
// 4) idempotence : ignorer si déjà traité, sinon activer la commande
}
}
res.status(200).json({ received: true });
});Codes d'erreur
| Code | Signification |
|---|---|
| 200 | Succès |
| 400 | Requête invalide — vérifiez les paramètres |
| 401 | Clé API invalide ou manquante |
| 403 | KYC non approuvé ou permission insuffisante |
| 404 | Ressource introuvable |
| 429 | Trop de requêtes — limite atteinte |
| 500 | Erreur serveur — réessayez plus tard |
Aide à l'intégration
Vous n'arrivez pas à intégrer l'API ? Vous pouvez confier l'intégration à un développeur web en le contactant sur WhatsApp.
WhatsApp : +229 01 90 08 38 57
Service payant :30 000 F pour l'intégration.