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
Ne partagez jamais votre clé secrète et ne l'exposez jamais côté frontend. Utilisez toujours un appel serveur à serveur.

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.

Chargement de la liste des pays…

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.

Le numéro de téléphone du client est requis pour générer le lien de paiement (paramètre 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.
POST/payments
curl -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 de charged_currency pour 1 unité de currency).
  • 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.
GET/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).

POST/payouts
curl -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.

Où renseigner l'URL ? Connectez-vous à votre tableau de bord, ouvrez la page Développeur, saisissez l'URL de votre webhook dans le champ prévu à cet effet, puis enregistrez. Les notifications ne sont envoyées qu'une fois cette URL renseignée.
Votre URL de webhook doit impérativement commencer par 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 :

  1. 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.
  2. Rapprochez le paiement de votre commande. Vérifiez que paymentId (ou merchantReference) correspond à un paiement que vous avez réellement créé, puis que amount et currency correspondent à la commande. Ignorez tout identifiant inconnu.
  3. 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.
  4. Soyez idempotent. Un même événement peut arriver plusieurs fois : si la commande est déjà marquée payée, répondez 200 sans la retraiter.
  5. Répondez vite en HTTPS avec un statut 2xx.
payment.completedPaiement reçu avec succès
payment.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

Les notifications Verzapay ne sont pas signéeset il n'existe pas de clé de webhook : n'importe qui connaissant votre URL pourrait y envoyer une fausse notification. Appliquez impérativement les mesures ci-dessous.
  1. 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 dont k ne correspond pas, avec une comparaison à temps constant.
  2. Ne faites jamais confiance au contenu. À la réception de payment.completed, appelez GET /payments/{paymentId} avec votre clé secrète et n'activez la commande que si status vaut completed.
  3. Recoupez avec votre commande : merchant_reference, montant et devise doivent correspondre à ce que vous avez créé.
  4. Soyez idempotent : la même notification peut arriver plusieurs fois. Mémorisez les paymentId déjà traités.
  5. HTTPS uniquement, réponse 200 rapide, traitement long en tâche de fond. Ignorez les type inconnus.
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

CodeSignification
200Succès
400Requête invalide — vérifiez les paramètres
401Clé API invalide ou manquante
403KYC non approuvé ou permission insuffisante
404Ressource introuvable
429Trop de requêtes — limite atteinte
500Erreur 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.