DocsIntroduction

ZyndPay API Reference

API v1 · USDT · Carte · Mobile Money

L'API ZyndPay est une passerelle de paiement RESTful permettant d'accepter des paiements sur trois rails : USDT sur la blockchain TRON, carte bancaire (XOF) et mobile money (XOF). Des URL orientées ressources, des corps JSON et une enveloppe de réponse cohérente facilitent l'intégration dans tout environnement.

Tous les montants et frais sont évalués selon le rail de paiement et la configuration du compte marchand. Le dashboard, le checkout, la réponse API et les webhooks sont la source de vérité du frais effectif appliqué.

Base URLhttps://api.zyndpay.io/v1
json
{
  "success": true,
  "data": { ... },
  "meta": { "page": 1, "limit": 20, "total": 100 }
}

Authentification

Incluez votre clé API dans l'en-tête X-Api-Key à chaque requête. Vous pouvez aussi la passer comme jeton Bearer dans l'en-tête Authorization.

bash
curl https://api.zyndpay.io/v1/payments \
  -H "X-Api-Key: zyp_live_sk_..."
ParamètreTypeRequisDescription
zyp_live_sk_*stringrequisClé secrète live — accès API complet. Ne jamais exposer côté client.
zyp_live_pk_*stringoptionnelClé publiable live — accès lecture limité, sûre pour les navigateurs.
zyp_test_sk_*stringrequisClé secrète sandbox — identique au live mais sans transactions réelles.
zyp_test_pk_*stringoptionnelClé publiable sandbox.
zyp_rk_*stringoptionnelClé restreinte — limitée à des opérations spécifiques.
Attention
N'intégrez jamais <ic>zyp_live_sk_</ic> ou <ic>zyp_test_sk_</ic> dans du code côté client, des applications mobiles ou des dépôts publics.

Démarrage Rapide

Acceptez votre premier paiement USDT en moins de 5 minutes. Sélectionnez votre langage ci-dessous — chaque exemple montre l'installation, la création d'un encaissement, la gestion du webhook et la vérification du statut.

Conseil
Pas seulement USDT — ZyndPay accepte également les paiements par carte bancaire et mobile money en XOF. Voir Paiement par carte et Mobile Money pour les détails.
typescript
// 1. Install
// npm install @zyndpay/sdk

// 2. Initialize
import { ZyndPay } from '@zyndpay/sdk';

const zyndpay = new ZyndPay({
  apiKey: process.env.ZYNDPAY_API_KEY!,
  webhookSecret: process.env.ZYNDPAY_WEBHOOK_SECRET,
});

// 3. Create a pay-in and get a payment URL
const payin = await zyndpay.payins.create({
  amount: '100',
  externalRef: 'order_' + Date.now(), // your order ID
  successUrl: 'https://yoursite.com/success',
  cancelUrl: 'https://yoursite.com/cancel',
});
console.log('Redirect customer to:', payin.paymentUrl);
// Or show payin.address if you want to display the TRON address directly

// 4. Handle the webhook (Express.js)
import express from 'express';
const app = express();

app.post('/webhooks/zyndpay', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['zyndpay-signature'] as string;

  let event;
  try {
    event = zyndpay.webhooks.verify(req.body, signature);
  } catch {
    return res.status(400).send('Invalid signature');
  }

  switch (event.event) {
    case 'payin.confirmed':
      // Payment received — fulfill the order
      console.log('Paid:', event.data.externalRef, event.data.amount, 'USDT');
      break;
    case 'payin.expired':
      console.log('Expired:', event.data.externalRef);
      break;
    case 'payin.underpaid':
      console.log('Underpaid:', event.data.externalRef,
        'got', event.data.amount, 'expected', event.data.amountRequested);
      break;
  }

  res.json({ received: true });
});

// 5. Poll / check status programmatically
const tx = await zyndpay.payins.get(payin.transactionId);
console.log('Status:', tx.status); // "CONFIRMED"
Conseil
Pas encore prêt pour de vrais fonds ? Utilisez une clé sandbox zyp_test_sk_... et ajoutez sandbox: true à la requête d'encaissement. Appelez ensuite POST /v1/sandbox/payments/:id/simulate pour le confirmer instantanément — sans blockchain.

Mise en Production

Avant d'accepter de vrais paiements, parcourez cette liste. La plupart des éléments prennent moins de 5 minutes chacun.

1
Compléter le KYB
Soumettez vos documents commerciaux dans le tableau de bord. Tous les marchands doivent compléter la vérification KYB et signer le MSA avant de traiter des transactions réelles.
2
Passer aux clés API live
Remplacez vos clés zyp_test_sk_... par des clés zyp_live_sk_.... Les clés live créent de vraies transactions on-chain.
3
Enregistrer un endpoint webhook
Ajoutez votre URL webhook de production dans le tableau de bord et stockez le secret webhook dans vos variables d'environnement.
4
Tester de bout en bout en production
Envoyez un vrai paiement de 1 USDT via votre intégration. Vérifiez que le webhook se déclenche et que votre logique de commande s'exécute correctement.
5
Configurer la surveillance des erreurs
Abonnez-vous aux alertes d'échec de livraison webhook dans le tableau de bord. Implémentez une logique de retry pour les traitements webhook échoués.
6
Réviser les limites de débit
Par défaut : 30 encaissements/min, 10 paiements sortants/min, 5 retraits/min. Contactez [email protected] si vous avez besoin de limites plus élevées.
Conseil
Commencez par une transaction test unique avec une vraie clé live avant d'annoncer à vos clients — cela détecte les problèmes de variables d'environnement que les tests sandbox ne peuvent pas révéler.

Encaissements

Créer un Encaissement

POST/v1/payments

Génère une adresse de portefeuille TRON unique pour que votre client y envoie des USDT. L'adresse expire après <ic>expiresInSeconds</ic> secondes (par défaut 30 minutes).

ParamètreTypeRequisDescription
amountstringrequisMontant en USDT sous forme de chaîne décimale (ex. « 100.00 »). Minimum : « 1.00 ».
externalRefstringoptionnelVotre ID de commande ou référence (optionnel). Doit être unique par marchand si fourni.
expiresInSecondsnumberoptionnelSecondes avant l'expiration de l'adresse. Minimum : 900. Défaut : 1800 (30 minutes).
successUrlstringoptionnelURL de redirection du client après un paiement réussi.
cancelUrlstringoptionnelURL de redirection du client si le paiement expire ou est annulé.
metadataobjectoptionnelPaires clé-valeur arbitraires stockées avec le paiement et incluses dans les webhooks.
paymentMethodstringoptionnelRail de paiement. "USDT_TRC20" (défaut), "CARD" ou "MOBILE_MONEY".
customerNamestringoptionnelNom complet du client. Obligatoire pour les paiements par carte.
customerEmailstringoptionnelAdresse e-mail du client. Obligatoire pour les paiements par carte.
bash
curl -X POST https://api.zyndpay.io/v1/payments \
  -H "X-Api-Key: zyp_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "100.00",
    "externalRef": "order_123",
    "expiresInSeconds": 3600,
    "successUrl": "https://yoursite.com/payment/success",
    "cancelUrl": "https://yoursite.com/payment/cancel"
  }'
json
{
  "success": true,
  "data": {
    "transactionId": "pay_abc123...",
    "address": "TRXabc123def456ghi789jkl",
    "paymentUrl": "https://checkout.zyndpay.io/pay_abc123...",
    "qrCodeUrl": "data:image/png;base64,iVBOR...",
    "amount": "100.00",
    "amountExpected": "101.00",
    "networkFee": "1",
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "status": "AWAITING_PAYMENT",
    "expiresAt": "2026-03-06T12:00:00.000Z"
  }
}

Mobile Money

POST/v1/payments

Collectez des paiements mobile money via un checkout ZyndPay hébergé — votre client saisit son OTP (Orange Money) ou approuve la notification push (Moov, MTN, Wave) sur une page aux couleurs de ZyndPay. Disponible en XOF uniquement.

ParamètreTypeRequisDescription
paymentMethodstringrequisDoit valoir "MOBILE_MONEY" pour un paiement mobile money.
amountstringrequisMontant en XOF sous forme de chaîne (ex. « 1000 »). Le XOF n'a pas de décimales — passez une chaîne entière. Minimum : « 500 ».
customerPhonestringrequisNuméro de téléphone du client au format E.164 (ex. « +22670123456 »). Requis pour MOBILE_MONEY.
operatorCodestringoptionnelSurcharge l'opérateur mobile money détecté depuis le préfixe téléphonique. Valeurs autorisées : ORANGE_BF, MOOV_BF. Ignoré pour les rails autres que MOBILE_MONEY.
externalRefstringoptionnelVotre ID de commande ou référence (optionnel). Doit être unique par marchand si fourni.
metadataobjectoptionnelPaires clé-valeur arbitraires stockées avec le paiement et incluses dans les webhooks.

L'opérateur est résolu côté serveur à partir du préfixe téléphonique. Vous pouvez le surcharger avec <ic>operatorCode</ic> quand le client en choisit un explicitement (recommandé). Codes supportés : ORANGE_BF, MOOV_BF.

bash
curl -X POST https://api.zyndpay.io/v1/payments \
  -H "X-Api-Key: zyp_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "1000",
    "paymentMethod": "MOBILE_MONEY",
    "customerPhone": "+22670123456",
    "customerName": "Aïcha Traoré",
    "operatorCode": "ORANGE_BF",
    "externalRef": "order_123"
  }'

La réponse inclut <ic>hostedPaymentUrl</ic> — redirigez votre client vers cette URL pour finaliser le paiement. ZyndPay gère le formulaire OTP, l'instruction opérateur, le polling de statut et la page de résultat. Le statut commence à AWAITING_PAYMENT et passe à CONFIRMED (ou FAILED) via webhook.

Si vous préférez piloter le flux OTP vous-même, la réponse inclut également <ic>nextStep</ic> (<ic>"otp"</ic> ou <ic>"approval"</ic>), <ic>operatorCode</ic> et <ic>instruction</ic>. Affichez votre propre formulaire, appelez Submit OTP pour les opérateurs en mode OTP, et pollez Get Payin. Les nouvelles intégrations devraient privilégier le flux hébergé ci-dessus.

Le champ <ic>nextStep</ic> de la réponse indique l'action suivante : <ic>"otp"</ic> signifie que votre UI doit demander le code que le client vient de recevoir par SMS, puis appeler Submit OTP. <ic>"approval"</ic> signifie que l'opérateur a poussé une demande de confirmation vers le téléphone du client — affichez la chaîne <ic>instruction</ic> pour indiquer au client quoi faire (ex. « Composez *555*6# »), puis pollez Get Payin jusqu'à ce que le statut quitte AWAITING_PAYMENT.

json
{
  "success": true,
  "data": {
    "transactionId": "pay_momo_abc123",
    "paymentMethod": "MOBILE_MONEY",
    "hostedPaymentUrl": "https://checkout.zyndpay.io/m/pay_momo_abc123",
    "nextStep": "otp",
    "operatorCode": "ORANGE_BF",
    "instruction": {
      "fr": "Vous allez recevoir un SMS avec votre code de validation. Saisissez-le ci-dessous.",
      "en": "You will receive an SMS with your validation code. Enter it below."
    },
    "amount": "1000",
    "currency": "XOF",
    "status": "AWAITING_PAYMENT",
    "expiresAt": "2026-03-06T12:05:00.000Z"
  }
}
json
{
  "success": true,
  "data": {
    "transactionId": "pay_momo_xyz456",
    "paymentMethod": "MOBILE_MONEY",
    "hostedPaymentUrl": "https://checkout.zyndpay.io/m/pay_momo_xyz456",
    "nextStep": "approval",
    "operatorCode": "MOOV_BF",
    "instruction": {
      "fr": "Composez *555*6# sur votre téléphone pour valider le paiement.",
      "en": "Dial *555*6# on your phone to approve the payment."
    },
    "amount": "1000",
    "currency": "XOF",
    "status": "AWAITING_PAYMENT",
    "expiresAt": "2026-03-06T12:03:00.000Z"
  }
}

Valider l'OTP

POST/v1/payments/:id/submit-otp
POST/v1/payins/:id/submit-otp

Soumet l'OTP que votre client a reçu par SMS sur un rail MoMo en mode OTP (Orange BF). Retourne immédiatement avec le statut AWAITING_PAYMENT ; le CONFIRMED final arrive via votre webhook une fois la transaction finalisée. <ic>POST /v1/payments/:id/submit-otp</ic> et <ic>POST /v1/payins/:id/submit-otp</ic> routent vers le même handler — la forme <ic>/payments</ic> est cohérente avec le reste du cycle de vie payin et préférée pour les nouvelles intégrations ; <ic>/payins</ic> est conservé pour les appelants existants.

ParamètreTypeRequisDescription
otpstringrequisLe code à 4-8 chiffres que le client a reçu par SMS. Applicable uniquement aux opérateurs en mode OTP.
bash
curl -X POST https://api.zyndpay.io/v1/payins/pay_momo_abc123/submit-otp \
  -H "X-Api-Key: zyp_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "otp": "123456"
  }'
json
{
  "success": true,
  "data": {
    "transactionId": "pay_momo_abc123",
    "status": "AWAITING_PAYMENT"
  }
}

Récupérer un Encaissement

GET/v1/payments/:id
json
{
  "success": true,
  "data": {
    "id": "f4b2cb0f-08ce-4408-88d4-0a678ca0aae2",
    "address": "TRXabc123def456ghi789jkl",
    "amount": "100",
    "amountReceived": "100.00",
    "zyndpayFee": "1.00",
    "status": "CONFIRMED",
    "txHash": "abc123def456...",
    "externalRef": "order_123",
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "paymentUrl": "https://checkout.zyndpay.io/f4b2cb0f...",
    "isSandbox": false,
    "createdAt": "2026-03-06T11:00:00.000Z",
    "updatedAt": "2026-03-06T11:01:02.000Z"
  }
}
AWAITING_PAYMENTStatut initial — adresse de dépôt active, en attente de fonds
PENDINGAdresse générée, aucun fonds reçu pour l'instant
CONFIRMINGFonds reçus, en attente de 5 confirmations
CONFIRMED5+ confirmations — solde crédité
UNDERPAIDMontant reçu inférieur à l'attendu
OVERPAIDMontant reçu supérieur à l'attendu
EXPIREDAdresse expirée sans paiement
FAILEDPaiement échoué — aucun fonds reçu

Paiement par Carte

Acceptez des paiements par carte en XOF en redirigeant votre client vers une page de paiement hébergée. Le client finalise le paiement dans son navigateur ; ZyndPay envoie un webhook payin.confirmed une fois le débit carte confirmé.

Attention
Les paiements par carte doivent être activés pour votre compte dans le tableau de bord avant utilisation. La devise de règlement est XOF.
POST/v1/payments
ParamètreTypeRequisDescription
amountstringrequisMontant en XOF sous forme de chaîne décimale (ex. : "5000"). Le XOF n'a pas de décimales — passez une chaîne entière. Minimum : « 100 ». La devise de règlement est fixée à XOF — ne passez pas de champ `currency` ; l'API rejette les propriétés inconnues.
paymentMethodstringrequisRail de paiement. "USDT_TRC20" (défaut), "CARD" ou "MOBILE_MONEY".
customerNamestringrequisNom complet du client. Obligatoire pour les paiements par carte.
customerEmailstringrequisAdresse e-mail du client. Obligatoire pour les paiements par carte.
Note
La réponse inclut hostedPaymentUrl — redirigez votre client vers cette URL pour finaliser le paiement par carte. Le statut commence à AWAITING_PAYMENT et passe à CONFIRMED (ou FAILED) via webhook.
Note
Les frais de traitement par carte sont déduits du montant reçu. La devise doit être XOF.
bash
curl -X POST https://api.zyndpay.io/v1/payments \
  -H "X-Api-Key: zyp_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "5000",
    "paymentMethod": "CARD",
    "customerName": "Kofi Mensah",
    "customerEmail": "[email protected]",
    "externalRef": "order_abc123"
  }'
typescript
const payin = await zyndpay.payins.create({
  amount: '5000',
  paymentMethod: 'CARD',
  customerName: 'Kofi Mensah',
  customerEmail: '[email protected]',
  externalRef: 'order_abc123',
});
// Redirect customer to complete payment
window.location.href = payin.hostedPaymentUrl;

Récupérer un Encaissement

GET/v1/payments/:id
bash
curl https://api.zyndpay.io/v1/payments/TXN_ID \
  -H "X-Api-Key: YOUR_API_KEY"

Lister les Encaissements

GET/v1/payments
ParamètreTypeRequisDescription
pagenumberoptionnelNuméro de page. Défaut : 1.
limitnumberoptionnelRésultats par page. Défaut : 20. Max : 100.
statusstringoptionnelFiltrer par statut : <ic>AWAITING_PAYMENT</ic> (état par défaut pour un payin MoMo ou CARD fraîchement créé — le plus fréquent à l'ouverture de la liste), <ic>PENDING</ic>, <ic>CONFIRMING</ic>, <ic>CONFIRMED</ic>, <ic>EXPIRED</ic>, <ic>UNDERPAID</ic>, <ic>OVERPAID</ic>, ou <ic>FAILED</ic>.
currencystringoptionnelFiltrer par devise (ex. USDT_TRC20).
bash
curl https://api.zyndpay.io/v1/payments \
  -H "X-Api-Key: YOUR_API_KEY"
python
payins = client.payins.list(limit=20, status="CONFIRMED")
for p in payins["items"]:
    print(p["transactionId"], p["status"])
json
{
  "success": true,
  "data": {
    "items": [ ... ],
    "meta": {
      "page": 1,
      "limit": 20,
      "total": 134,
      "totalPages": 7,
      "hasNext": true,
      "hasPrev": false
    }
  }
}

Tarifs

Les frais dépendent du rail et du compte. La documentation publique explique où les frais apparaissent ; le dashboard authentifié, le checkout, la réponse API et l’accord marchand affichent le taux effectif actuel.

  • USDT (TRC20) : taux applicable actuel affiché dans votre compte et les réponses API.
  • Mobile Money (XOF) : taux applicable actuel selon opérateur, pays, coûts fournisseur et conditions du compte.
  • Carte (XOF — Visa / Mastercard) : taux applicable actuel selon rail carte, coûts fournisseur et conditions du compte.

Les frais sont libellés dans la devise de transaction pertinente. La valeur exacte `zyndpayFee` appliquée est retournée par l’API et les webhooks lorsque applicable.

Créer un Encaissement — SDK

python
import zyndpay

client = zyndpay.ZyndPay(api_key="YOUR_API_KEY")

payin = client.payins.create(
    amount="25.00",
    external_ref="order_123",
    sandbox=True,  # remove in production
)
print(payin["transactionId"])
print(payin["paymentUrl"])
php
<?php
require 'vendor/autoload.php';

use ZyndPay\ZyndPay;

$client = new ZyndPay('YOUR_API_KEY');

$payin = $client->payins->create([
    'amount' => '25.00',
    'externalRef' => 'order_123',
], sandbox: true);  // remove sandbox in production

echo $payin['transactionId'];
echo $payin['paymentUrl'];

Liens de Paiement
POST/v1/paylinks

Crée un lien de paiement partageable avec un ou plusieurs produits. Partagez le <ic>paymentUrl</ic> retourné avec vos clients. Supporte les types à prix fixe, variable et facturation récurrente.

ParamètreTypeRequisDescription
namestringoptionnelNom interne du lien affiché dans le tableau de bord marchand. Optionnel, jamais visible par le client.
typestringrequisType de lien : FIXED (défaut), VARIABLE ou RECURRING.
productsarrayrequisTableau de produits. Au moins un produit est requis.
products[].namestringrequisNom du produit affiché au client.
products[].pricestringrequisPrix en USDT sous forme de chaîne décimale.
products[].productTypestringrequisPHYSICAL (défaut) ou DIGITAL.
products[].digitalUrlstringoptionnelURL externe pour la livraison du produit numérique (Google Drive, Mega, etc.).
collectEmailstringoptionnelCollecte d'email client : HIDDEN (défaut), OPTIONAL ou REQUIRED.
collectNamestringoptionnelCollecte du nom client : HIDDEN, OPTIONAL ou REQUIRED.
brandColorstringoptionnelCouleur hexadécimale pour la page de checkout personnalisée (ex. #635BFF).
successUrlstringoptionnelURL de redirection du client après un paiement réussi.
recurringIntervalstringoptionnelIntervalle de facturation pour les liens récurrents : WEEKLY, MONTHLY ou YEARLY.
bash
curl -X POST https://api.zyndpay.io/v1/paylinks \
  -H "X-Api-Key: zyp_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "type": "FIXED",
    "products": [
      {
        "name": "Premium Course",
        "price": "49.99",
        "productType": "DIGITAL",
        "digitalUrl": "https://drive.google.com/file/d/abc123"
      }
    ],
    "collectEmail": "REQUIRED",
    "brandColor": "#635BFF"
  }'
json
{
  "success": true,
  "data": {
    "id": "pl_cma1xyz8f0001yx5k",
    "slug": "aB3dE6fG7hI9",
    "name": "Summer collection 2026",
    "type": "FIXED",
    "status": "ACTIVE",
    "currency": "USDT_TRC20",
    "paymentUrl": "https://dashboard.zyndpay.io/pay/link/aB3dE6fG7hI9",
    "products": [
      {
        "id": "prod_abc123",
        "name": "Premium Course",
        "description": "Lifetime access. PDF + video bundle.",
        "price": "49.990000000000000000",
        "productType": "DIGITAL",
        "digitalUrl": "https://drive.google.com/file/d/abc123",
        "imageKey": null,
        "imageUrl": null,
        "stockEnabled": false,
        "stockQty": 0,
        "lowStockThreshold": 0,
        "sortOrder": 0
      }
    ],
    "promoCodes": [],
    "useCount": 0,
    "maxUses": null,
    "ordersCount": 0,
    "expiresAt": null,
    "autoDisableOnDepletion": false,
    "collectEmail": "REQUIRED",
    "collectName": "OPTIONAL",
    "collectPhone": "OPTIONAL",
    "collectAddress": "HIDDEN",
    "recurringInterval": null,
    "recurringIntervalCount": null,
    "brandColor": null,
    "logoUrl": null,
    "coverImageKey": null,
    "coverImageUrl": null,
    "postPaymentAction": null,
    "cancelUrl": null,
    "createdAt": "2026-03-08T10:00:00.000Z",
    "updatedAt": "2026-03-08T10:00:00.000Z"
  }
}
GET/v1/paylinks/:id
json
{
  "success": true,
  "data": {
    "id": "pl_cma1xyz8f0001yx5k",
    "slug": "aB3dE6fG7hI9",
    "name": "Summer collection 2026",
    "type": "FIXED",
    "status": "ACTIVE",
    "currency": "USDT_TRC20",
    "paymentUrl": "https://dashboard.zyndpay.io/pay/link/aB3dE6fG7hI9",
    "products": [
      {
        "id": "prod_abc123",
        "name": "Premium Course",
        "description": "Lifetime access. PDF + video bundle.",
        "price": "49.990000000000000000",
        "productType": "DIGITAL",
        "digitalUrl": "https://drive.google.com/file/d/abc123",
        "imageKey": null,
        "imageUrl": null,
        "stockEnabled": false,
        "stockQty": 0,
        "lowStockThreshold": 0,
        "sortOrder": 0
      }
    ],
    "promoCodes": [],
    "useCount": 0,
    "maxUses": null,
    "ordersCount": 0,
    "expiresAt": null,
    "autoDisableOnDepletion": false,
    "collectEmail": "REQUIRED",
    "collectName": "OPTIONAL",
    "collectPhone": "OPTIONAL",
    "collectAddress": "HIDDEN",
    "recurringInterval": null,
    "recurringIntervalCount": null,
    "brandColor": null,
    "logoUrl": null,
    "coverImageKey": null,
    "coverImageUrl": null,
    "postPaymentAction": null,
    "cancelUrl": null,
    "createdAt": "2026-03-08T10:00:00.000Z",
    "updatedAt": "2026-03-08T10:00:00.000Z"
  }
}
GET/v1/paylinks
ParamètreTypeRequisDescription
pagenumberoptionnelNuméro de page. Défaut : 1.
limitnumberoptionnelRésultats par page. Défaut : 20. Max : 100.
json
{
  "success": true,
  "data": {
    "items": [ ... ],
    "meta": {
      "page": 1,
      "limit": 20,
      "total": 134,
      "totalPages": 7,
      "hasNext": true,
      "hasPrev": false
    }
  }
}
POST/v1/pay/link/:slug/checkout

Endpoint public (pas d'auth requise). Crée une commande et un encaissement à partir d'un lien de paiement. Le client reçoit une adresse TRON pour envoyer les USDT.

ParamètreTypeRequisDescription
itemsarrayrequisTableau d'IDs de produits et quantités à acheter.
customerEmailstringoptionnelAdresse email du client (requise pour RECURRING et produits numériques).
customerNamestringoptionnelNom du client (optionnel).
bash
curl -X POST https://api.zyndpay.io/v1/pay/link/aB3dE6fG7hI9/checkout \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "productId": "prod_abc123", "quantity": 1 }
    ],
    "customerEmail": "[email protected]",
    "customerName": "John Doe"
  }'
json
{
  "success": true,
  "data": {
    "orderId": "ord_xyz789",
    "transactionId": "cma2abc9g0002yz6l0def",
    "address": "TRXabc123def456ghi789jkl",
    "amount": "49.99",
    "expiresAt": "2026-03-08T11:00:00.000Z"
  }
}

La devise d'affichage d'un paylink (USDT ou XOF) est un choix purement visuel. Le checkout client convertit le prix vers le rail choisi par le client au taux du jour. Vous recevez les fonds dans la devise native du rail — USDT pour USDT_TRC20, XOF pour CARD et MOBILE_MONEY — sans aucun risque de change pour ZyndPay.

python
paylink = client.paylinks.create(
    products=[{"name": "Premium Plan", "price": "49.99", "productType": "DIGITAL"}],
    type="FIXED",
    collect_email="REQUIRED",
    sandbox=True,  # remove in production
)
print(paylink["id"])
print(paylink["slug"])
php
$paylink = $client->paylinks->create([
    'products' => [[
        'name' => 'Premium Plan',
        'price' => '49.99',
        'productType' => 'DIGITAL',
    ]],
    'type' => 'FIXED',
    'collectEmail' => 'REQUIRED',
], sandbox: true);  // remove in production

echo $paylink['id'];
echo $paylink['slug'];
POST/v1/paylinks/simulate
bash
curl -X POST "https://api.zyndpay.io/v1/paylinks/simulate?sandbox=true" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount": "25.00", "methods": ["USDT_TRC20"], "currency": "USDT_TRC20", "feeBearer": "CLIENT"}'
GET/v1/paylinks/:id/stats
bash
curl https://api.zyndpay.io/v1/paylinks/PAYLINK_ID/stats \
  -H "X-Api-Key: YOUR_API_KEY"

Récupérez toutes les commandes passées via un lien de paiement. Chaque commande retrace le client, le montant payé, la méthode de paiement et le statut.

GET/v1/paylinks/:id/orders
bash
curl https://api.zyndpay.io/v1/paylinks/PAYLINK_ID/orders \
  -H "X-Api-Key: YOUR_API_KEY"
Conseil
GET /v1/paylinks/:id/orders/export — téléchargez toutes les commandes en CSV.

Attachez des codes de réduction à un paylink que les clients peuvent utiliser au paiement. Chaque code a une remise (pourcentage ou montant fixe), une limite d'utilisation optionnelle, et une date d'expiration optionnelle. Les codes sont liés à un seul paylink — la même chaîne sur un autre paylink est un code différent.

POST/v1/paylinks/:id/promo-codes
ParamètreTypeRequisDescription
codestringrequisLe code visible par le client, normalisé en majuscules par l'API (ex. `"tabaski"` → `"TABASKI"`).
discountTypestringrequis`PERCENT` ou `FIXED`. `PERCENT` applique `discountValue`% sur le sous-total ; `FIXED` soustrait un montant fixe dans la devise du paylink.
discountValuestringrequisPour `PERCENT`, le pourcentage en chaîne décimale (ex. `"15"` pour 15 %). Pour `FIXED`, le montant dans la devise du paylink.
maxUsesintegeroptionnelNombre total maximal de redemptions pour tous les clients. Omettez pour un nombre illimité.
expiresAtstringoptionnelHorodatage ISO 8601 après lequel le code n'est plus utilisable (ex. `"2026-12-31T23:59:59Z"`). Omettez pour aucune limite de temps.
bash
curl -X POST https://api.zyndpay.io/v1/paylinks/PAYLINK_ID/promo-codes \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "TABASKI",
    "discountType": "PERCENT",
    "discountValue": "15",
    "maxUses": 100,
    "expiresAt": "2026-07-07T23:59:59.000Z"
  }'
Conseil
Utilisez `expiresAt` pour les promotions à durée limitée (Tabaski, fin de mois, Black Friday) afin que la remise s'arrête automatiquement. Combinez avec `maxUses` pour des limites hybrides.
GET/v1/paylinks/:id/promo-codes
PATCH/v1/paylinks/:id/promo-codes/:codeId
DELETE/v1/paylinks/:id/promo-codes/:codeId

Endpoint public que votre interface panier appelle quand un client saisit un code. Renvoie l'aperçu de la remise si le code est utilisable, ou l'un des quatre codes d'erreur (`PROMO_CODE_INVALID`, `PROMO_CODE_INACTIVE`, `PROMO_CODE_EXPIRED`, `PROMO_CODE_USAGE_LIMIT_REACHED`) afin que vous puissiez afficher un message adapté plutôt qu'une erreur générique.

POST/v1/pay/link/:slug/apply-promo
Note
Aucune authentification — conçu pour être appelé depuis le navigateur du client. Le même contrôle est exécuté à nouveau lors de la soumission du paiement, donc les courses (le code atteint sa limite entre l'aperçu et la soumission) sont rattrapées de façon défensive.
bash
# Public endpoint — call from the cart UI on promo-input blur.
curl -X POST https://api.zyndpay.io/v1/pay/link/PAYLINK_SLUG/apply-promo \
  -H "Content-Type: application/json" \
  -d '{"code": "TABASKI"}'
json
{
  "success": false,
  "error": {
    "code": "PROMO_CODE_EXPIRED",
    "message": "This promo code has expired."
  },
  "statusCode": 400
}

Codes d'erreur de redemption

Effectuez un switch sur `error.code` dans votre interface pour afficher le bon message pour chaque mode d'échec :

error.codeWhenRequisDescription
PROMO_CODE_INVALIDlookupoptionnelLe code n'existe pas sur ce paylink.
PROMO_CODE_INACTIVEstateoptionnelLe marchand a désactivé le code via `PATCH .../promo-codes/:codeId` avec `isActive: false`.
PROMO_CODE_EXPIREDtimeoptionnelAu-delà de `expiresAt`. Rafraîchissez le panier — `expiresAt` est inclus dans la réponse de la liste.
PROMO_CODE_USAGE_LIMIT_REACHEDcountoptionnel`useCount >= maxUses`. Le code a été utilisé par suffisamment de clients.

Les modèles vous permettent de sauvegarder une configuration de lien de paiement et de la réutiliser pour créer plusieurs liens rapidement. Les modèles stockent les produits, les paramètres et la marque — mais pas l'historique des commandes.

POST/v1/paylinks/templates
GET/v1/paylinks/templates
DELETE/v1/paylinks/templates/:id
bash
curl -X POST https://api.zyndpay.io/v1/paylinks/templates \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Template",
    "config": {
      "type": "FIXED",
      "products": [{"name": "Product", "price": "10.00", "productType": "DIGITAL"}],
      "collectEmail": "HIDDEN"
    }
  }'
Conseil
POST /v1/paylinks/:id/save-as-template — sauvegardez la configuration d'un lien existant comme nouveau modèle.

Téléchargez une image de couverture pour la page de paiement, et des images par produit. Les téléchargements utilisent multipart/form-data.

Note
Les téléchargements d'images nécessitent multipart/form-data. En TypeScript, utilisez uploadCoverImage() du SDK qui gère le FormData. En Python et PHP, passez un chemin de fichier local.
POST/v1/paylinks/:id/cover-image
DELETE/v1/paylinks/:id/cover-image
POST/v1/paylinks/:id/products/:productId/image
Conseil
POST /v1/paylinks/:id/products/import-csv — importez des produits en masse depuis un fichier CSV.
POST/v1/paylinks/:id/products/import-csv

Paiements Sortants

Créer un Paiement Sortant

POST/v1/payout

Les virements sortants permettent d’envoyer des soldes supportés depuis ZyndPay vers une destination éligible — fournisseurs, remboursements ou commissions. Le frais applicable est affiché ou retourné avant exécution.

Note
<strong>API canonique :</strong> les transferts sont désormais le produit unique de décaissement marchand. Les anciens payouts et retraits sont des routes de compatibilité.
ParamètreTypeRequisDescription
amountstringrequisMontant à envoyer (par exemple « 50.00 »). Le frais applicable est calculé selon les conditions du compte et retourné avant exécution.
destinationAddressstringrequisAdresse de portefeuille TRON (TRC20) du destinataire. Doit correspondre au format T suivi de 33 caractères base58.
currencystringoptionnelDevise à envoyer. Défaut : USDT_TRC20.
chainstringoptionnelRéseau blockchain. Défaut : TRON.
externalRefstringoptionnelVotre ID de référence interne pour ce paiement (ex. numéro de facture fournisseur).
metadataobjectoptionnelPaires clé-valeur arbitraires stockées avec le paiement.
bash
curl -X POST https://api.zyndpay.io/v1/payout \
  -H "X-Api-Key: zyp_live_sk_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -d '{
    "amount": "50.00",
    "destinationAddress": "TXYZabc123def456ghi789jkl012mno345",
    "externalRef": "payout_vendor_456"
  }'
json
{
  "success": true,
  "data": {
    "transactionId": "cma2abc9g0002yz6l0def5678",
    "status": "PROCESSING",
    "processingFee": "1.50",
    "requiresManualApproval": false,
    "currentPayinFee": "1%",
    "currentTier": "flat"
  }
}

Estimer un Virement Sortant

Prévisualisez les frais de traitement et le coût total avant de créer un virement sortant. Renvoie fee, totalDebited, availableBalance et sufficient.

POST/v1/payout/estimate
ParamètreTypeRequisDescription
amountstringrequisMontant en USDT à estimer.
currencystringoptionnelDevise (défaut : USDT_TRC20).
chainstringoptionnelBlockchain (défaut : TRON).
destinationAddressstringrequisAdresse TRON de destination. Requis — l'estimation valide l'adresse et exécute un pré-contrôle AML.
bash
curl -X POST https://api.zyndpay.io/v1/payout/estimate \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount": "100.00", "destinationAddress": "TRON_ADDRESS"}'
typescript
const estimate = await zyndpay.payouts.estimate({
  amount: '100.00',
  currency: 'USDT_TRC20',
  chain: 'TRON',
  destinationAddress: 'TXyz1234567890abcdef1234567890abcde',
});
console.log(`Fee: ${estimate.fee} USDT`);
console.log(`Total deducted: ${estimate.totalDebited} USDT`);
console.log(`Sufficient balance: ${estimate.sufficient}`);
python
estimate = client.payouts.estimate(
    amount="100.00",
    destination_address="TRON_ADDRESS",
)
print(f"Fee: {estimate['fee']} USDT")
print(f"Total debited: {estimate['totalDebited']} USDT")
php
$estimate = $client->payouts->estimate([
    'amount' => '100.00',
    'destinationAddress' => 'TRON_ADDRESS',
]);

echo "Fee: " . $estimate['fee'] . " USDT\n";
echo "Total: " . $estimate['totalDebited'] . " USDT\n";

Créer un Paiement Sortant — SDK

python
payout = client.payouts.create(
    amount="100.00",
    destination_address="TRON_ADDRESS",
    idempotency_key="payout_vendor_apr_001",
)
print(payout["id"], payout["status"])
php
$payout = $client->payouts->create([
    'amount' => '100.00',
    'destinationAddress' => 'TRON_ADDRESS',
], idempotencyKey: 'payout_vendor_apr_001');

echo $payout['id'];

Récupérer un Paiement Sortant

GET/v1/payout/:id
json
{
  "success": true,
  "data": {
    "transactionId": "cma2abc9g0002yz6l0def5678",
    "status": "PROCESSING",
    "processingFee": "1.50",
    "requiresManualApproval": false,
    "currentPayinFee": "1%",
    "currentTier": "flat"
  }
}
PENDINGEn attente d'approbation manuelle (montant > 50 K $)
PROCESSINGApprouvé, préparation de la diffusion
BROADCASTSoumis au réseau TRON
CONFIRMEDConfirmé on-chain
FAILEDDiffusion échouée — solde remboursé
CANCELLEDAnnulé avant diffusion

Lister les Paiements Sortants

GET/v1/payout
json
{
  "success": true,
  "data": {
    "items": [ ... ],
    "meta": {
      "page": 1,
      "limit": 20,
      "total": 42,
      "totalPages": 3,
      "hasNext": true,
      "hasPrev": false
    }
  }
}

Transferts

Demander un Retrait

POST/v1/withdrawals

Les retraits permettent aux marchands de déplacer leurs soldes réglés hors de ZyndPay vers une destination enregistrée lorsque supporté. Le frais applicable est calculé selon le compte et affiché avant exécution.

Note
Les destinations doivent être enregistrées avant de pouvoir recevoir des transferts. Si <ic>whitelistAddressId</ic> est omis, ZyndPay utilise votre destination crypto principale enregistrée.
ParamètreTypeRequisDescription
amountstringrequisMontant à retirer (par exemple « 500.00 »). Le frais applicable est calculé selon les conditions du compte et affiché avant exécution.
whitelistAddressIdstringoptionnelID de l'adresse de retrait enregistrée à utiliser. Utilise votre adresse principale si omis.
bash
curl -X POST https://api.zyndpay.io/v1/withdrawals \
  -H "X-Api-Key: zyp_live_sk_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -d '{
    "amount": "500.00",
    "whitelistAddressId": "d2f64e1f-3c3f-4c8b-bc21-1037682d691c"
  }'
json
{
  "success": true,
  "data": {
    "id": "wdr_cma1xyz8f0001yx5k",
    "amount": "500.00",
    "fee": "5.00",
    "netAmount": "495.00",
    "destinationAddress": "TRXabc123def456ghi789jkl",
    "status": "PENDING_REVIEW",
    "createdAt": "2026-03-06T11:00:00.000Z"
  }
}

Récupérer un Retrait — List

GET/v1/withdrawals
bash
curl https://api.zyndpay.io/v1/withdrawals \
  -H "X-Api-Key: YOUR_API_KEY"
python
withdrawal = client.withdrawals.create(
    amount="50.00",
    idempotency_key="withdrawal_001",
)
print(withdrawal["id"], withdrawal["status"])

Récupérer un Retrait

GET/v1/withdrawals/:id
bash
curl https://api.zyndpay.io/v1/withdrawals/WD_ID \
  -H "X-Api-Key: YOUR_API_KEY"
json
{
  "success": true,
  "data": {
    "id": "wdr_cma1xyz8f0001yx5k",
    "amount": "500.00",
    "fee": "5.00",
    "netAmount": "495.00",
    "destinationAddress": "TRXabc123def456ghi789jkl",
    "status": "PENDING_REVIEW",
    "createdAt": "2026-03-06T11:00:00.000Z"
  }
}
PENDING_REVIEWEn attente de vérification de conformité (PENDING_REVIEW)
APPROVEDApprouvé, en file d'attente pour traitement
BROADCASTSoumis au réseau TRON
CONFIRMEDConfirmé on-chain
REJECTEDRejeté par la validation admin
FAILEDDiffusion échouée — solde remboursé
CANCELLEDAnnulé avant diffusion
Note
Every withdrawal request is reviewed by the ZyndPay compliance team before approval.

Annuler un Retrait

DELETE/v1/withdrawals/:id

Annule une demande de retrait encore au statut <ic>PENDING_REVIEW</ic> (avant son approbation et sa diffusion sur le réseau TRON). Le montant est immédiatement retourné à votre solde.

bash
curl -X DELETE https://api.zyndpay.io/v1/withdrawals/WD_ID \
  -H "X-Api-Key: YOUR_API_KEY"

Paiements en Masse

Paiements en Masse

Les paiements en masse vous permettent d'envoyer des fonds à des centaines de destinataires en un seul lot. Créez un lot en DRAFT, validez-le (aperçu des frais + vérification du solde), puis exécutez — chaque élément est traité indépendamment afin qu'un échec isolé ne bloque pas les autres.

Note
Machine d'état du lot : DRAFT → VALIDATED → PROCESSING → COMPLETED (ou PARTIALLY_COMPLETED en cas d'échecs partiels).

Créer un Lot

Créez un lot vide. Vous pouvez lui attribuer un libellé pour référence interne.

POST/v1/bulk-payments
ParamètreTypeRequisDescription
labelstringoptionnelLibellé interne pour ce lot (optionnel).
bash
curl -X POST https://api.zyndpay.io/v1/bulk-payments \
  -H "X-Api-Key: zyp_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "label": "Vendor payouts April 2026" }'

Ajouter des Éléments

Ajoutez des destinataires à un lot en statut DRAFT. Chaque élément nécessite une adresse de portefeuille, un montant et des métadonnées optionnelles. Vous pouvez également importer un fichier CSV ou XLSX.

POST/v1/bulk-payments/:id/items
ParamètreTypeRequisDescription
itemsarrayrequisTableau d'éléments de paiement. Chaque élément : walletAddress, amount, recipientName (optionnel), reference (optionnel).
Conseil
POST /v1/bulk-payments/:id/import — téléchargez un fichier CSV ou XLSX. Téléchargez d'abord le modèle : GET /v1/bulk-payments/template.
bash
curl -X POST https://api.zyndpay.io/v1/bulk-payments/batch_abc/items \
  -H "X-Api-Key: zyp_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "walletAddress": "TXyz1...", "amount": "100.00", "recipientName": "Vendor A", "reference": "inv_001" },
      { "walletAddress": "TXyz2...", "amount": "250.00", "recipientName": "Vendor B", "reference": "inv_002" }
    ]
  }'

Valider et Exécuter

La validation vérifie la disponibilité du solde et prévisualise les frais totaux sans exécuter. La réponse inclut <ic>balanceCheck</ic> : sur les lots live, elle renvoie <ic>{ sufficient, required, available }</ic> (et 400 INSUFFICIENT_BALANCE si le grand livre est insuffisant) ; sur les lots sandbox, elle renvoie <ic>{ sandbox: true, skipped: true, required }</ic> — les lots sandbox ne touchent pas le grand livre, donc la vérification du solde est intentionnellement ignorée. Execute relance la vérification du solde live dans une transaction sérialisable (au cas où le solde a changé entre validate et execute) avant de débiter et de mettre en file.

Note
POST /v1/bulk-payments/:id/validate puis POST /v1/bulk-payments/:id/execute. L'exécution est asynchrone — suivez la progression via GET /v1/bulk-payments/:id.
bash
curl -X POST https://api.zyndpay.io/v1/bulk-payments/batch_abc/validate \
  -H "X-Api-Key: zyp_live_sk_..."
bash
curl -X POST https://api.zyndpay.io/v1/bulk-payments/batch_abc/execute \
  -H "X-Api-Key: zyp_live_sk_..."
typescript
// Full bulk payment workflow
const batch = await zyndpay.bulkPayments.create({ label: 'Vendor payouts April 2026' });

await zyndpay.bulkPayments.addItems(batch.id, {
  items: [
    { walletAddress: 'TXyz1...', amount: '100.00', recipientName: 'Vendor A', reference: 'inv_001' },
    { walletAddress: 'TXyz2...', amount: '250.00', recipientName: 'Vendor B', reference: 'inv_002' },
  ],
});

const validation = await zyndpay.bulkPayments.validate(batch.id);
console.log(`Total fee: ${validation.totalFee} USDT`);

await zyndpay.bulkPayments.execute(batch.id);

// Poll for completion
const status = await zyndpay.bulkPayments.get(batch.id);
console.log(status.status); // PROCESSING | COMPLETED | PARTIALLY_COMPLETED
python
# Create batch
batch = client.bulk_payments.create(label="April payouts")

# Add recipients
client.bulk_payments.add_items(batch["id"], [
    {"walletAddress": "TRON_ADDRESS_1", "amount": "50.00", "recipientName": "Vendor A"},
    {"walletAddress": "TRON_ADDRESS_2", "amount": "75.00", "recipientName": "Vendor B"},
])

# Validate then execute
client.bulk_payments.validate(batch["id"])
client.bulk_payments.execute(batch["id"])
php
// Create batch
$batch = $client->bulkPayments->create(['label' => 'April payouts']);

// Add recipients
$client->bulkPayments->addItems($batch['id'], [
    ['walletAddress' => 'TRON_ADDRESS_1', 'amount' => '50.00', 'recipientName' => 'Vendor A'],
    ['walletAddress' => 'TRON_ADDRESS_2', 'amount' => '75.00', 'recipientName' => 'Vendor B'],
]);

// Validate and execute
$client->bulkPayments->validate($batch['id']);
$client->bulkPayments->execute($batch['id']);

Relancer les Éléments Échoués

Après qu'un lot atteint le statut PARTIALLY_COMPLETED, appelez retry pour remettre en file d'attente uniquement les éléments échoués. Les éléments déjà réussis ne sont pas retraités.

POST/v1/bulk-payments/:id/retry

Annuler un Lot

Annulez un lot encore en statut DRAFT ou VALIDATED. Les fonds ne sont pas réservés avant l'appel à execute(), l'annulation est donc immédiate sans impact sur le solde.

POST/v1/bulk-payments/:id/cancel

Suivre

Interrogez le statut du lot et les résultats par élément. Exportez un rapport CSV à la fin.

GET/v1/bulk-payments/:id
GET/v1/bulk-payments/:id/export
bash
curl https://api.zyndpay.io/v1/bulk-payments/batch_abc \
  -H "X-Api-Key: zyp_live_sk_..."

Portefeuilles & Conversions

Portefeuilles & Conversions

ZyndPay maintient un portefeuille USDT (rail TRON, on-chain) ainsi qu'un portefeuille XOF par rail fiat (actuellement <ic>MOMO</ic> et <ic>CARD</ic>) — trois portefeuilles au total. Chaque entrée expose ses propres <ic>currency</ic>, <ic>rail</ic>, solde, flux de règlement et voie de retrait. Identifiez toujours un portefeuille par la paire <ic>(currency, rail)</ic> : filtrer uniquement sur <ic>currency === 'XOF'</ic> ne renvoie que la première entrée XOF et ignore silencieusement l'autre rail. Les conversions permettent de transférer de la valeur entre eux au taux en temps réel.

Note
Portefeuille USDT : reçoit les encaissements USDT, envoie des virements USDT, retire vers des adresses TRON. Portefeuille XOF : reçoit les encaissements carte et mobile money, envoie des virements XOF, retire vers des comptes mobile money ou bancaires via des destinations fiat.

Liste des Portefeuilles

Renvoie un portefeuille par paire (devise, rail). Utilisez l'id du portefeuille lors des appels de conversion ou de retrait.

GET/v1/merchants/wallets
bash
curl https://api.zyndpay.io/v1/merchants/wallets \
  -H "X-Api-Key: YOUR_API_KEY"
typescript
const wallets = await zyndpay.wallets.list();
// Always identify a wallet by the (currency, rail) pair — XOF is split per rail.
const usdtWallet = wallets.find(w => w.currency === 'USDT_TRC20' && w.rail === 'TRC20');
const xofMomoWallet = wallets.find(w => w.currency === 'XOF' && w.rail === 'MOMO');
const xofCardWallet = wallets.find(w => w.currency === 'XOF' && w.rail === 'CARD');
console.log(usdtWallet.id, usdtWallet.balance);
console.log(xofMomoWallet.balance, xofCardWallet.balance);
python
wallets = client.wallets.list()
for w in wallets:
    print(f"{w['currency']} ({w['rail']}): {w['balance']}")
php
$wallets = $client->wallets->list();
foreach ($wallets as $wallet) {
    echo $wallet['currency'] . ': ' . $wallet['balance'] . "\n";
}

Liste blanche d'adresses

La liste blanche d'adresses regroupe les adresses TRON autorisées à recevoir vos retraits USDT. Chaque destination de retrait — depuis le tableau de bord ou via l'API — doit préalablement figurer dans votre liste blanche. Les adresses peuvent être ajoutées une par une ou en lot (jusqu'à 500).

Note
Les endpoints de liste blanche nécessitent une clé API avec le scope <ic>wallets_write</ic> (mutations) ou <ic>wallets_read</ic> (lecture). Créez une nouvelle clé sous Tableau de bord → Clés API et sélectionnez le groupe de scopes <strong>Portefeuilles</strong>.
Attention
Un délai de sécurité de 24 heures s'applique après chaque ajout d'adresse — via le tableau de bord ou l'API. Durant cette fenêtre, l'adresse est présente dans votre liste blanche mais ne peut pas encore recevoir de retrait. Chaque réponse inclut un horodatage <ic>availableAt</ic> indiquant la date d'éligibilité. Anticipez en ajoutant les adresses dès l'inscription du client ou au moment du dépôt.

Ajouter une adresse

Ajoute une adresse TRON à la liste blanche. Si l'adresse existe déjà, l'appel réussit et fusionne optionnellement les nouveaux contextes d'utilisation.

POST/v1/wallets/whitelist
bash
curl -X POST https://api.zyndpay.io/v1/wallets/whitelist \
  -H "Authorization: Bearer zyp_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "address": "TRX_ADDRESS_HERE",
    "label": "My payout wallet",
    "usageContexts": ["WITHDRAWAL", "PAYOUT"]
  }'
typescript
const entry = await zyndpay.whitelist.add({
  currency: 'USDT_TRC20',
  chain: 'TRON',
  address: 'TRX_ADDRESS_HERE',
  label: 'My payout wallet',
  usageContexts: ['WITHDRAWAL', 'PAYOUT'], // optional, defaults to ['WITHDRAWAL']
});
// entry.availableAt — earliest time this address can receive a withdrawal
console.log('Available for withdrawal at:', entry.availableAt);

Ajout en lot

Ajoutez jusqu'à 500 adresses en un seul appel. Passez un tableau <ic>usageContexts</ic> optionnel (par défaut <ic>['WITHDRAWAL']</ic>) — appliqué à chaque adresse du lot. Pour rendre les adresses éligibles aux retraits et aux paiements dès la création, envoyez <ic>['WITHDRAWAL', 'PAYOUT']</ic>. La réponse distingue <ic>added</ic> (nouvellement insérées), <ic>alreadyExists</ic> (ignorées — déjà présentes), et <ic>invalid</ic> (échec de validation d'adresse TRON). Le délai de 24h s'applique individuellement à chaque nouvelle adresse.

POST/v1/wallets/whitelist/bulk
bash
curl -X POST https://api.zyndpay.io/v1/wallets/whitelist/bulk \
  -H "Authorization: Bearer zyp_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "addresses": [
      { "address": "TRX_ADDRESS_1", "label": "Customer wallet A" },
      { "address": "TRX_ADDRESS_2", "label": "Customer wallet B" }
    ],
    "usageContexts": ["WITHDRAWAL", "PAYOUT"]
  }'
typescript
// Add up to 500 addresses per call
const result = await zyndpay.whitelist.bulkAdd({
  addresses: [
    { address: 'TRX_ADDRESS_1', label: 'Customer wallet A' },
    { address: 'TRX_ADDRESS_2', label: 'Customer wallet B' },
  ],
  usageContexts: ['WITHDRAWAL', 'PAYOUT'], // applied to every address in the batch
});
console.log(`Added: ${result.added.length}`);
console.log(`Already existed: ${result.alreadyExists.length}`);
console.log(`Invalid: ${result.invalid.length}`);
// Each added entry includes availableAt (24h from now)

Mettre à jour les contextes d'utilisation

Remplace le tableau <ic>usageContexts</ic> sur une entrée existante — par exemple pour étendre une adresse WITHDRAWAL-uniquement vers PAYOUT, sans la ré-ajouter (ce qui redémarrerait le délai de 24h). Le corps doit contenir au moins un contexte valide.

PATCH/v1/wallets/whitelist/:id/contexts
bash
curl -X PATCH https://api.zyndpay.io/v1/wallets/whitelist/ENTRY_ID/contexts \
  -H "Authorization: Bearer zyp_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "usageContexts": ["WITHDRAWAL", "PAYOUT"]
  }'

Lister la liste blanche

Retourne toutes les adresses de votre liste blanche, triées par date d'ajout décroissante. Filtrage optionnel par contexte d'utilisation.

GET/v1/wallets/whitelist
bash
curl https://api.zyndpay.io/v1/wallets/whitelist \
  -H "Authorization: Bearer zyp_live_sk_..."

Les adresses enregistrées ne peuvent pas être supprimées

Les destinataires crypto enregistrés sont permanents. Ajoutez de nouveaux destinataires si nécessaire ; les destinataires existants restent visibles pour l’historique d’audit et la sécurité des transferts.

Destinations Fiat

Une destination fiat est un numéro mobile money ou un compte bancaire pouvant recevoir des retraits XOF. Au moins une destination doit être enregistrée avant de pouvoir retirer des fonds XOF.

Note
Les endpoints des destinations fiat exigent une clé API avec le scope <ic>fiat_destinations_write</ic> (création/mise à jour/suppression) ou <ic>fiat_destinations_read</ic> (lecture). Créez une nouvelle clé dans Dashboard → Clés API et sélectionnez le groupe <strong>Destinations fiat</strong>.
Attention
Un délai de conformité BCEAO de 24 heures s'applique après l'enregistrement d'une nouvelle destination avant son utilisation pour les retraits.
GET/v1/merchants/fiat-destinations
POST/v1/merchants/fiat-destinations
PATCH/v1/merchants/fiat-destinations/:id
DELETE/v1/merchants/fiat-destinations/:id

Pour MOMO : kind, label, momoOperator (ORANGE_BF, MOOV_BF), momoPhone (format E.164).

Pour BANK : kind, label, bankName, bankAccountName, bankIban ou bankAccountNumber, bankCode.

bash
curl -X POST https://api.zyndpay.io/v1/merchants/fiat-destinations \
  -H "X-Api-Key: zyp_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "MOMO",
    "label": "Orange Money BF",
    "momoOperator": "ORANGE_BF",
    "momoPhone": "+22670123456",
    "isPrimary": true
  }'
typescript
// Register a mobile money destination
const dest = await zyndpay.fiatDestinations.create({
  kind: 'MOMO',
  label: 'Orange Money BF',
  momoOperator: 'ORANGE_BF',
  momoPhone: '+22670123456',
  isPrimary: true,
});
// Note: 24-hour cooldown before first use (BCEAO compliance)

Convertir entre Portefeuilles — Preview

Prévisualisez le montant de destination avant d'engager une conversion entre portefeuilles. Les devises source et destination viennent des portefeuilles sélectionnés ; le contrat n'est pas lié à une seule paire de devises.

Note
Utilisez la même paire de portefeuilles pour la prévisualisation et la conversion. Le chemin d'écriture canonique est <ic>POST /v1/conversions/wallet</ic> avec l'ID du portefeuille source, l'ID du portefeuille destination et un montant source décimal.
GET/v1/conversions/wallet/preview
bash
curl "https://api.zyndpay.io/v1/conversions/wallet/preview?fromCurrency=XOF&toCurrency=USDT&amount=50000" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json"

Convertir entre Portefeuilles — List

GET/v1/conversions
bash
curl https://api.zyndpay.io/v1/conversions \
  -H "X-Api-Key: YOUR_API_KEY"

Convertir entre Portefeuilles

Convertissez des fonds d'un portefeuille à un autre au taux ZyndPay en temps réel. Usage typique : convertir des XOF reçus d'encaissements carte ou mobile money en USDT dans votre portefeuille USDT.

Conseil
Passez un en-tête Idempotency-Key pour éviter les doublons en cas de nouvelle tentative après une erreur réseau.
POST/v1/conversions/wallet
ParamètreTypeRequisDescription
fromWalletIdstringrequisIdentifiant du portefeuille source (depuis la liste des portefeuilles).
toWalletIdstringrequisIdentifiant du portefeuille de destination.
fromAmountstringrequisMontant à convertir depuis le portefeuille source.
typescript
// Convert XOF earnings (MOMO rail) to USDT
const wallets = await zyndpay.wallets.list();
const xof = wallets.find(w => w.currency === 'XOF' && w.rail === 'MOMO')!;
const usdt = wallets.find(w => w.currency === 'USDT_TRC20' && w.rail === 'TRC20')!;

const conversion = await zyndpay.conversions.convertBetweenWallets({
  fromWalletId: xof.id,
  toWalletId: usdt.id,
  fromAmount: '50000',
});
console.log(`Converted: ${conversion.fromAmount} ${conversion.fromCurrency}`);
console.log(`Received: ${conversion.toAmountNet} ${conversion.toCurrency}`);
python
from zyndpay import ZyndPay

client = ZyndPay(api_key="YOUR_API_KEY")

wallets = client.wallets.list()
usdt_wallet = next(w for w in wallets if w["currency"] == "USDT_TRC20")
xof_wallet = next(w for w in wallets if w["currency"] == "XOF")

conversion = client.conversions.convert_between_wallets(
    from_wallet_id=usdt_wallet["id"],
    to_wallet_id=xof_wallet["id"],
    from_amount="100.00",
)
print(conversion["id"], conversion["status"])

Transactions

Récupérer une Transaction

Récupérez une transaction individuelle par son identifiant. L'ID peut être un transactionId d'encaissement, un ID de virement sortant ou un ID de retrait — tous les types partagent le même endpoint.

GET/v1/transactions/:id
bash
curl https://api.zyndpay.io/v1/transactions/TX_ID \
  -H "X-Api-Key: YOUR_API_KEY"

Liste des Transactions

Historique unifié des encaissements, virements sortants et retraits. Filtrez par type, statut, devise ou plage de dates.

GET/v1/transactions
ParamètreTypeRequisDescription
typestringoptionnelFiltrer par type : PAYIN, PAYOUT, WITHDRAWAL.
statusstringoptionnelFiltrer par statut.
currencystringoptionnelFiltrer par devise (ex. USDT_TRC20).
fromstringoptionnelDate de début (ISO 8601).
tostringoptionnelDate de fin (ISO 8601).
pagenumberoptionnelNuméro de page. Défaut : 1.
limitnumberoptionnelRésultats par page. Défaut : 20. Max : 100.
bash
curl "https://api.zyndpay.io/v1/transactions?limit=20&type=PAYIN" \
  -H "X-Api-Key: YOUR_API_KEY"
typescript
const txs = await zyndpay.transactions.list({
  type: 'PAYIN',
  status: 'CONFIRMED',
  from: '2026-01-01',
  to: '2026-04-30',
});
console.log(`${txs.total} transactions found`);
python
txns = client.transactions.list(limit=20, type="PAYIN")
for t in txns["items"]:
    print(t["id"], t["type"], t["status"], t["amountRequested"])
php
$result = $client->transactions->list([
    'limit' => 20,
    'type' => 'PAYIN',
]);

foreach ($result['items'] as $tx) {
    echo $tx['id'] . ' ' . $tx['status'] . "\n";
}

Exporter les Transactions

Générez un CSV ou PDF de votre historique de transactions. Les exports CSV s'exécutent en tâche de fond ; le PDF est renvoyé de manière synchrone.

Note
<ic>GET /v1/transactions/export</ic> démarre une tâche CSV en arrière-plan et renvoie <ic>{ jobId, status }</ic>. Interrogez <ic>GET /v1/exports/:jobId/status</ic> jusqu'à <ic>status: "completed"</ic>, puis récupérez <ic>GET /v1/exports/:jobId/download</ic> (l'URL de téléchargement est valable 10 minutes). <ic>GET /v1/transactions/export/pdf</ic> renvoie un rapport PDF de manière synchrone (<ic>application/pdf</ic>).
GET/v1/transactions/export
GET/v1/transactions/export/pdf
bash
curl "https://api.zyndpay.io/v1/transactions/export?from=2026-01-01&to=2026-12-31" \
  -H "X-Api-Key: YOUR_API_KEY" -o transactions.csv
typescript
// Download CSV export
const csv = await zyndpay.transactions.export({
  from: '2026-01-01',
  to: '2026-04-30',
});
// csv is a string — write to file or send to browser

// Download PDF report
const pdf = await zyndpay.transactions.exportPdf();

Webhooks

Vue d'ensemble

ZyndPay envoie des requêtes HTTP POST à vos URLs d'endpoints enregistrés lorsque des événements de paiement se produisent. Enregistrez des endpoints depuis la page <strong>Webhooks</strong> de votre tableau de bord marchand.

Conseil
Votre endpoint doit retourner un statut <ic>2xx</ic> dans les 10 secondes. Les livraisons échouées sont relancées avec un backoff exponentiel jusqu'à 5 fois sur ~2 heures.
json
{
  "event": "payin.confirmed",
  "data": {
    "transactionId": "cma1xyz8f0001yx5k9abc1234",
    "status": "CONFIRMED",
    "currency": "XOF",
    "chain": "TRON",
    "externalRef": "order_123",
    "amount": "9300.00",
    "amountRequested": "9300.00",
    "txHash": "abc123def456...",
    "confirmedAt": "2026-03-06T11:01:02.000Z",
    "pricingCurrency": "USDT_TRC20",
    "pricingAmount": "15.00",
    "fxRate": "620.00000000"
  },
  "createdAt": "2026-03-06T11:01:03.000Z"
}

Vérification de Signature

Chaque livraison de webhook inclut un en-tête <ic>Zyndpay-Signature</ic>. Vérifiez-le toujours avant de traiter l'événement.

Zyndpay-Signature
t=1680000000,v1=5257a869e7ecebeda32af...

La signature est <ic>HMAC-SHA256(timestamp + "." + raw_body, webhook_secret)</ic>. Utilisez toujours le <strong>corps brut de la requête</strong> — analyser le JSON d'abord causera des échecs de vérification.

javascript
const crypto = require('crypto');

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['zyndpay-signature'];
  const [tPart, v1Part] = sig.split(',');
  const timestamp = tPart.split('=')[1];
  const received  = v1Part.split('=')[1];

  // Reject events older than 5 minutes
  const age = Math.abs(Date.now() / 1000 - parseInt(timestamp) / 1000);
  if (age > 300) return res.status(400).send('Webhook too old');

  const expected = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(`${timestamp}.${req.body}`)
    .digest('hex');

  // Use timing-safe comparison to prevent timing attacks
  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
    return res.status(400).send('Invalid signature');
  }

  const event = JSON.parse(req.body);
  // Handle event...
  res.json({ received: true });
});
Attention
Lisez toujours le corps brut de la requête avant tout parsing JSON. Un middleware qui parse automatiquement le JSON (ex. express.json()) corrompra le corps et causera l'échec de la vérification. Utilisez express.raw({ type: 'application/json' }) pour la route webhook.

Points de Terminaison Webhook

Gérez vos points de terminaison webhook par programmation plutôt que (ou en plus de) l'interface du tableau de bord.

Attention
La création d'un point de terminaison renvoie le champ <ic>secret</ic> (préfixé <ic>whs_</ic>) une seule fois — stockez-le immédiatement dans vos variables d'environnement. La réponse inclut aussi <ic>secretHint</ic> (4 derniers caractères, affichables) et <ic>secretHash</ic> (référence serveur). Le <ic>secret</ic> complet ne pourra plus être récupéré.
POST/v1/webhooks/endpoints
ParamètreTypeRequisDescription
urlstringrequisURL HTTPS vers laquelle ZyndPay enverra les événements en POST.
eventsarrayrequisTableau de noms d'événements auxquels s'abonner (ex. : ["payin.confirmed", "payout.failed"]).
retryConfigobjectoptionnelRéglage des nouvelles tentatives (optionnel). La réponse expose <ic>maxRetries</ic> (défaut 3), <ic>retryBackoff</ic> (énum : <ic>EXPONENTIAL</ic>) et <ic>retryIntervalSeconds</ic> (défaut 60). Le corps de la requête accepte les mêmes champs au niveau racine.
GET/v1/webhooks/endpoints
PATCH/v1/webhooks/endpoints/:id
DELETE/v1/webhooks/endpoints/:id
PATCH/v1/webhooks/endpoints/:id/rotate-secret
Note
PATCH /v1/webhooks/endpoints/:id/rotate-secret — génère un nouveau secret de signature. L'ancien secret reste valide 24 heures pour permettre une rotation sans interruption de service.
PATCH/v1/webhooks/endpoints/:id/reactivate
Note
PATCH /v1/webhooks/endpoints/:id/reactivate — réactive un point de terminaison suspendu automatiquement après trop d'échecs de livraison consécutifs.
Conseil
GET /v1/webhooks/deliveries — consultez l'historique des livraisons, les codes de réponse et le nombre de tentatives pour chaque événement.
typescript
const endpoint = await zyndpay.webhooks.createEndpoint({
  url: 'https://example.com/webhooks/zyndpay',
  events: ['payin.confirmed', 'payout.failed', 'withdrawal.confirmed'],
});
// Store endpoint.secret (whs_* prefix) — shown only once!
process.env.ZYNDPAY_WEBHOOK_SECRET = endpoint.secret;

Événements

Note
Les événements payin.* se déclenchent pour les paiements créés via POST /v1/payments (checkout client). Les événements deposit.* se déclenchent pour les transferts USDT directs vers votre adresse de portefeuille non créés via l'API. Abonnez-vous aux deux si votre intégration accepte aussi les transferts manuels ou de gré à gré.
ÉvénementTypeRequisDescription
payin.createdeventrequisDéclenché quand un nouvel encaissement est créé et une adresse de dépôt est générée.
payin.confirmingeventrequisDéclenché quand des fonds sont reçus et en attente de 5 confirmations de blocs.
payin.confirmedeventrequisDéclenché quand un encaissement est entièrement réglé — pour USDT après 5 confirmations on-chain, pour carte après la capture par la banque émettrice, pour mobile money après confirmation de l'opérateur. Le solde marchand est crédité.
payin.expiredeventrequisDéclenché quand une adresse d'encaissement expire sans recevoir le montant attendu.
payin.failedeventrequisDéclenché quand un encaissement échoue sur l'un des rails — refus carte, échec opérateur mobile money, ou USDT reçu en deçà du seuil de poussière. L'enveloppe `data` inclut un champ `reason` avec la cause de l'échec lisible par l'opérateur.
payin.underpaideventrequisDéclenché quand un encaissement reçoit moins que le montant demandé.
payin.overpaideventrequisDéclenché quand un encaissement reçoit plus que le montant demandé.
deposit.confirmedeventrequisDéclenché quand un dépôt de portefeuille atteint 20 confirmations on-chain. Le solde marchand est crédité.
deposit.failedeventrequisDéclenché quand un dépôt de portefeuille échoue à se confirmer.
deposit.overpaideventrequisDéclenché quand un dépôt de portefeuille reçoit plus que le montant attendu.
deposit.underpaideventrequisDéclenché quand un dépôt de portefeuille reçoit moins que le montant attendu.
payout.requestedeventrequisDéclenché lorsqu'un nouveau paiement sortant est demandé et mis en file d'attente pour vérification de conformité. Émis en parallèle de l'événement hérité `withdrawal.requested` pour le même cycle de vie.
payout.approvedeventrequisDéclenché quand un paiement sortant passe la vérification et est approuvé pour diffusion on-chain. Émis en parallèle de l'événement hérité `withdrawal.approved` pour le même cycle de vie.
payout.broadcasteventrequisDéclenché quand une transaction de paiement sortant est signée et diffusée sur le réseau TRON.
payout.confirmedeventrequisDéclenché quand un paiement sortant atteint 20 confirmations on-chain.
payout.failedeventrequisDéclenché quand la diffusion d'un paiement sortant échoue. Le montant est remboursé au solde marchand.
withdrawal.requestedeventrequisDéclenché lorsqu'un nouveau retrait est demandé et mis en file d'attente pour vérification de conformité.
withdrawal.broadcasteventrequisDéclenché lorsqu'un retrait est signé et diffusé sur TRON.
withdrawal.approvedeventrequisDéclenché quand un retrait passe la validation et est approuvé pour diffusion on-chain.
withdrawal.confirmedeventrequisDéclenché quand un retrait est confirmé on-chain.
withdrawal.failedeventrequisDéclenché quand la diffusion d'un retrait échoue. Le montant est remboursé au solde.
conversion.confirmedeventrequisDéclenché quand une conversion entre portefeuilles est complétée avec succès.
conversion.failedeventrequisDéclenché quand une conversion échoue. Les fonds source sont retournés au portefeuille d'origine.
subscription.createdeventrequisDéclenché quand un abonnement récurrent est créé pour la première fois lors d'un paiement paylink. L'enveloppe `data` contient subscriptionId, paylinkId, les infos client et la première période de facturation.
subscription.renewedeventrequisDéclenché quand un abonnement est renouvelé avec succès pour le cycle suivant.
subscription.renewal_initiatedeventrequisDéclenché au début du cycle de renouvellement d'abonnement (avant la réussite du débit).
subscription.failedeventrequisDéclenché quand le débit de renouvellement d'abonnement échoue.
subscription.cancelledeventrequisDéclenché quand un abonnement est annulé (par le marchand ou le client).
subscription.pausedeventrequisDéclenché quand un abonnement est mis en pause.
subscription.resumedeventrequisDéclenché quand un abonnement en pause est repris.
subscription.updatedeventrequisDéclenché quand les détails d'abonnement (montant, intervalle) sont mis à jour.
refund.createdeventrequisDéclenché quand une demande de remboursement est créée (en attente d'approbation).
refund.approvedeventrequisDéclenché quand un remboursement est approuvé par les opérations.
refund.rejectedeventrequisDéclenché quand un remboursement est rejeté.
refund.completedeventrequisDéclenché quand un remboursement est entièrement versé au client.
refund.failedeventrequisDéclenché quand un versement de remboursement échoue.
dispute.openedeventrequisDéclenché quand un litige est ouvert sur une transaction.
dispute.resolvedeventrequisDéclenché quand un litige est résolu.
dispute.rejectedeventrequisDéclenché quand un litige est rejeté.
dispute.escalatedeventrequisDéclenché quand un litige est escaladé à un niveau supérieur.
aml.flaggedeventrequisDéclenché quand un paiement sortant est bloqué par le filtrage AML. Abonnez-vous pour réagir aux blocages de conformité.
splitpayment.createdeventrequisMarketplace / Connect uniquement. Déclenché sur l'endpoint du marchand plateforme après qu'un encaissement a été réparti entre les sous-marchands. Inclut la répartition des allocations pour que la plateforme puisse rapprocher avec son propre grand livre.
kyb.approvedeventrequisdocs.eventKybApprovedDesc
kyb.rejectedeventrequisdocs.eventKybRejectedDesc
kyb.thread.createdeventrequisA new compliance thread was created on a KYB review.
kyb.thread.repliedeventrequisA merchant or admin replied to a KYB compliance thread (author field distinguishes).
kyb.thread.resolvedeventrequisAn admin resolved a KYB compliance thread.
kyb.thread.dismissedeventrequisAn admin dismissed a KYB compliance thread.
agreement.resign_requestedeventrequisMerchant received re-sign request for MSA v3. Action required within 30 days.
agreement.resignedeventrequisMerchant completed re-sign of MSA v3.

Schémas de Charge Utile

Chaque webhook est un POST JSON avec la même enveloppe : event, data, createdAt. La forme de data dépend du type d'événement. Cliquez sur un événement ci-dessous pour voir sa charge utile exacte.

Événements d'encaissement
json
{
  "event": "payin.confirmed",
  "data": {
    "transactionId": "cma1xyz8f0001yx5k9abc1234",
    "status": "CONFIRMED",
    "currency": "XOF",
    "chain": "TRON",
    "externalRef": "order_123",
    "amount": "9300.00",
    "amountRequested": "9300.00",
    "txHash": "abc123def456...",
    "confirmedAt": "2026-03-06T11:01:02.000Z",
    "pricingCurrency": "USDT_TRC20",
    "pricingAmount": "15.00",
    "fxRate": "620.00000000"
  },
  "createdAt": "2026-03-06T11:01:03.000Z"
}
Événements de paiement sortant
json
{
  "event": "payout.confirmed",
  "data": {
    "transactionId": "tx_sample_payout123",
    "status": "CONFIRMED",
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "amount": "50.00",
    "fee": "1.50",
    "destinationAddress": "TXyz1234567890AbCdEfGhIjKlMnOpQrSt",
    "txHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
    "externalRef": "vendor_invoice_456",
    "confirmedAt": "2026-01-15T12:01:00.000Z"
  },
  "createdAt": "2026-01-15T12:01:05.000Z"
}
Événements de retrait
json
{
  "event": "withdrawal.confirmed",
  "data": {
    "transactionId": "wdr_sample_abc123",
    "status": "CONFIRMED",
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "amount": "500.00",
    "fee": "5.00",
    "netAmount": "495.00",
    "toAddress": "TXyz1234567890AbCdEfGhIjKlMnOpQrSt",
    "txHash": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
    "confirmedAt": "2026-01-15T12:01:00.000Z"
  },
  "createdAt": "2026-01-15T12:01:05.000Z"
}

Tester un Webhook

Envoyez un événement de test à l'un de vos endpoints webhook enregistrés. Utile pour vérifier que votre endpoint est accessible et que votre code de vérification de signature fonctionne correctement.

POST/v1/webhooks/test
ParamètreTypeRequisDescription
endpointIdstringrequisL'ID de l'endpoint webhook auquel envoyer l'événement de test. Obtenez-le via GET /webhooks/endpoints.
eventTypestringrequisLe type d'événement à simuler (ex. payin.confirmed, payout.confirmed). Doit être un nom d'événement valide.
bash
curl -X POST https://api.zyndpay.io/v1/webhooks/test \
  -H "X-Api-Key: zyp_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "endpointId": "your-endpoint-id",
    "eventType": "payin.confirmed"
  }'
json
{
  "success": true,
  "data": {
    "message": "Test event sent",
    "deliveryId": "9270e4aa-8391-4e3f-a70b-23c431714b84"
  }
}

Schémas de Charge

Chaque livraison webhook partage la même enveloppe : une chaîne event, un objet data avec des champs spécifiques à l'événement, et un horodatage createdAt. Les exemples ci-dessous montrent les noms et types de champs exacts.

payin.created

Déclenché immédiatement lorsqu'un nouvel encaissement est créé et qu'une adresse TRON est assignée.

json
{
  "event": "payin.created",
  "data": {
    "transactionId": "cma1xyz8f0001yx5k9abc1234",
    "address": "TRXabc123def456ghi789jkl",
    "amount": "100.00",
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "externalRef": "order_123",
    "status": "AWAITING_PAYMENT",
    "expiresAt": "2026-03-06T12:00:00.000Z",
    "paymentUrl": "https://checkout.zyndpay.io/cma1xyz8f0001yx5k9abc1234"
  },
  "createdAt": "2026-03-06T11:00:00.000Z"
}
payin.confirmed

Déclenché lorsqu'un encaissement atteint 5 confirmations on-chain. Le solde marchand est crédité à ce moment.

json
{
  "event": "payin.confirmed",
  "data": {
    "transactionId": "cma1xyz8f0001yx5k9abc1234",
    "address": "TRXabc123def456ghi789jkl",
    "amount": "100.00",
    "amountReceived": "100.00",
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "externalRef": "order_123",
    "txHash": "abc123def456...",
    "status": "CONFIRMED",
    "confirmedAt": "2026-03-06T11:01:02.000Z"
  },
  "createdAt": "2026-03-06T11:01:03.000Z"
}
payin.expired

Déclenché lorsqu'une adresse d'encaissement expire sans avoir reçu le montant USDT attendu.

json
{
  "event": "payin.expired",
  "data": {
    "transactionId": "cma1xyz8f0001yx5k9abc1234",
    "address": "TRXabc123def456ghi789jkl",
    "amount": "100.00",
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "externalRef": "order_123",
    "status": "EXPIRED",
    "expiredAt": "2026-03-06T12:00:00.000Z"
  },
  "createdAt": "2026-03-06T12:00:01.000Z"
}
payin.failed

Déclenché lorsqu'un encaissement échoue sur l'un des rails — refus carte, échec opérateur mobile money, ou USDT reçu en deçà du seuil de poussière. Le champ `reason` porte la cause de l'échec lisible par l'opérateur. Aucun fonds n'a été capturé.

json
{
  "event": "payin.failed",
  "data": {
    "transactionId": "cma1xyz8f0001yx5k9abc1234",
    "address": "TRXabc123def456ghi789jkl",
    "amount": "100.00",
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "externalRef": "order_123",
    "status": "FAILED",
    "reason": "Network processing error"
  },
  "createdAt": "2026-03-06T11:05:00.000Z"
}
payin.underpaid

Déclenché lorsque le client envoie moins d'USDT que le montant demandé. Le champ shortfall indique le manque.

json
{
  "event": "payin.underpaid",
  "data": {
    "transactionId": "cma1xyz8f0001yx5k9abc1234",
    "address": "TRXabc123def456ghi789jkl",
    "amount": "100.00",
    "amountReceived": "90.00",
    "shortfall": "10.00",
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "externalRef": "order_123",
    "txHash": "abc123def456...",
    "status": "UNDERPAID",
    "confirmedAt": "2026-03-06T11:01:02.000Z"
  },
  "createdAt": "2026-03-06T11:01:03.000Z"
}
payin.overpaid

Déclenché lorsque le client envoie plus d'USDT que le montant demandé. Le champ surplus indique l'excédent.

json
{
  "event": "payin.overpaid",
  "data": {
    "transactionId": "cma1xyz8f0001yx5k9abc1234",
    "address": "TRXabc123def456ghi789jkl",
    "amount": "100.00",
    "amountReceived": "110.00",
    "surplus": "10.00",
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "externalRef": "order_123",
    "txHash": "abc123def456...",
    "status": "OVERPAID",
    "confirmedAt": "2026-03-06T11:01:02.000Z"
  },
  "createdAt": "2026-03-06T11:01:03.000Z"
}
payout.confirmed

Déclenché lorsqu'un paiement sortant est confirmé on-chain. Le txHash est l'ID de transaction on-chain.

json
{
  "event": "payout.confirmed",
  "data": {
    "transactionId": "cma2abc9g0002yz6l0def5678",
    "destinationAddress": "TXYZabc123def456ghi789jkl012mno345",
    "amount": "50.00",
    "fee": "1.50",
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "externalRef": "payout_vendor_456",
    "txHash": "def456abc123...",
    "status": "CONFIRMED",
    "confirmedAt": "2026-03-06T11:05:00.000Z"
  },
  "createdAt": "2026-03-06T11:05:01.000Z"
}
payout.failed

Déclenché lorsque la diffusion d'un paiement sortant échoue. Le montant est automatiquement remboursé sur votre solde marchand.

json
{
  "event": "payout.failed",
  "data": {
    "transactionId": "cma2abc9g0002yz6l0def5678",
    "destinationAddress": "TXYZabc123def456ghi789jkl012mno345",
    "amount": "50.00",
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "externalRef": "payout_vendor_456",
    "status": "FAILED",
    "reason": "Broadcast failed — balance refunded",
    "failedAt": "2026-03-06T11:05:00.000Z"
  },
  "createdAt": "2026-03-06T11:05:01.000Z"
}
withdrawal.approved

Déclenché lorsqu'une demande de retrait passe la révision administrateur et est mise en file pour la diffusion on-chain.

json
{
  "event": "withdrawal.approved",
  "data": {
    "id": "wdr_cma1xyz8f0001yx5k",
    "amount": "500.00",
    "fee": "5.00",
    "netAmount": "495.00",
    "destinationAddress": "TRXabc123def456ghi789jkl",
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "status": "APPROVED",
    "approvedAt": "2026-03-06T12:00:00.000Z"
  },
  "createdAt": "2026-03-06T12:00:01.000Z"
}
withdrawal.confirmed

Déclenché lorsqu'un retrait est confirmé on-chain. netAmount est ce que votre portefeuille a réellement reçu après frais.

json
{
  "event": "withdrawal.confirmed",
  "data": {
    "id": "wdr_cma1xyz8f0001yx5k",
    "amount": "500.00",
    "fee": "5.00",
    "netAmount": "495.00",
    "destinationAddress": "TRXabc123def456ghi789jkl",
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "txHash": "ghi789abc123...",
    "status": "CONFIRMED",
    "confirmedAt": "2026-03-06T12:30:00.000Z"
  },
  "createdAt": "2026-03-06T12:30:01.000Z"
}
withdrawal.failed

Déclenché lorsque la diffusion d'un retrait échoue. Le montant est automatiquement remboursé sur votre solde.

json
{
  "event": "withdrawal.failed",
  "data": {
    "id": "wdr_cma1xyz8f0001yx5k",
    "amount": "500.00",
    "destinationAddress": "TRXabc123def456ghi789jkl",
    "currency": "USDT_TRC20",
    "chain": "TRON",
    "status": "FAILED",
    "reason": "Broadcast failed — balance refunded",
    "failedAt": "2026-03-06T12:30:00.000Z"
  },
  "createdAt": "2026-03-06T12:30:01.000Z"
}

SDKs & Plugins

SDK Node.js

Le SDK TypeScript officiel avec typage complet et vérification de webhook intégrée.

bash
npm install @zyndpay/sdk
javascript
const { ZyndPay } = require('@zyndpay/sdk');

const client = new ZyndPay({
  apiKey: 'zyp_live_sk_...',
  webhookSecret: process.env.ZYNDPAY_WEBHOOK_SECRET,
});

// Create a payment
const payment = await client.payins.create({
  amount: '100.00',
  externalRef: 'order_123',
});
console.log(payment.address);

// Verify a webhook (uses raw body, not parsed JSON)
const event = client.webhooks.verify(
  rawBody,
  req.headers['zyndpay-signature']
);

Démarrage TypeScript

Acceptez votre premier paiement USDT en moins de 5 minutes. Cet exemple utilise Express — adaptez à votre framework.

typescript
import { ZyndPay } from '@zyndpay/sdk';
import express from 'express';

// 1. Initialize the SDK
const client = new ZyndPay({
  apiKey: process.env.ZYNDPAY_API_KEY,       // zyp_live_sk_...
  webhookSecret: process.env.WEBHOOK_SECRET,
});

// 2. Create a pay-in (generates a TRON deposit address for your customer)
const payment = await client.payins.create({
  amount: '100.00',
  externalRef: 'order_123',
  expiresInSeconds: 3600,
  successUrl: 'https://yoursite.com/payment/success',
});
// Send payment.paymentUrl or payment.address to your customer
console.log('Deposit address:', payment.address);
console.log('Payment page:', payment.paymentUrl);

// 3. Handle webhook events
const app = express();
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  // Always verify using the raw body — never parsed JSON
  const event = client.webhooks.verify(
    req.body,
    req.headers['zyndpay-signature'] as string,
  );

  switch (event.event) {
    case 'payin.confirmed':
      console.log(`Payment confirmed: ${event.data.amount} USDT — ${event.data.externalRef}`);
      // Fulfill the order, unlock access, send receipt, etc.
      break;
    case 'payin.expired':
      console.log(`Payment expired: ${event.data.externalRef}`);
      break;
  }
  res.json({ received: true });
});

// 4. Check payment status anytime
const status = await client.payins.get(payment.transactionId);
console.log('Status:', status.status); // AWAITING_PAYMENT → CONFIRMING → CONFIRMED
Conseil
Les gestionnaires webhook doivent retourner un statut 2xx dans les 10 secondes. Les livraisons échouées sont relancées avec un backoff exponentiel jusqu'à 5 fois.

New Resources (v1.5.0+)

The following resources are available as of SDK v1.5.0: wallets, fiat destinations, conversions, bulk payments, transactions export, and balances.

typescript
// Convert XOF earnings (MOMO rail) to USDT
const wallets = await zyndpay.wallets.list();
const xof = wallets.find(w => w.currency === 'XOF' && w.rail === 'MOMO')!;
const usdt = wallets.find(w => w.currency === 'USDT_TRC20' && w.rail === 'TRC20')!;

const conversion = await zyndpay.conversions.convertBetweenWallets({
  fromWalletId: xof.id,
  toWalletId: usdt.id,
  fromAmount: '50000',
});
console.log(`Converted: ${conversion.fromAmount} ${conversion.fromCurrency}`);
console.log(`Received: ${conversion.toAmountNet} ${conversion.toCurrency}`);
typescript
// Register a mobile money destination
const dest = await zyndpay.fiatDestinations.create({
  kind: 'MOMO',
  label: 'Orange Money BF',
  momoOperator: 'ORANGE_BF',
  momoPhone: '+22670123456',
  isPrimary: true,
});
// Note: 24-hour cooldown before first use (BCEAO compliance)
typescript
const wallets = await zyndpay.wallets.list();
// Always identify a wallet by the (currency, rail) pair — XOF is split per rail.
const usdtWallet = wallets.find(w => w.currency === 'USDT_TRC20' && w.rail === 'TRC20');
const xofMomoWallet = wallets.find(w => w.currency === 'XOF' && w.rail === 'MOMO');
const xofCardWallet = wallets.find(w => w.currency === 'XOF' && w.rail === 'CARD');
console.log(usdtWallet.id, usdtWallet.balance);
console.log(xofMomoWallet.balance, xofCardWallet.balance);
typescript
// Download CSV export
const csv = await zyndpay.transactions.export({
  from: '2026-01-01',
  to: '2026-04-30',
});
// csv is a string — write to file or send to browser

// Download PDF report
const pdf = await zyndpay.transactions.exportPdf();
typescript
const balances = await zyndpay.balances.getAll();
// { USDT_TRC20: '245.50', XOF: '150000', USD: '0' }
typescript
const estimate = await zyndpay.payouts.estimate({
  amount: '100.00',
  currency: 'USDT_TRC20',
  chain: 'TRON',
  destinationAddress: 'TXyz1234567890abcdef1234567890abcde',
});
console.log(`Fee: ${estimate.fee} USDT`);
console.log(`Total deducted: ${estimate.totalDebited} USDT`);
console.log(`Sufficient balance: ${estimate.sufficient}`);
Conseil
For bulk payments, use zyndpay.bulkPayments — see the Bulk Payments section for the full workflow.

SDK Python

Le SDK Python officiel basé sur <ic>requests</ic> avec une interface synchrone simple.

bash
pip install zyndpay
python
from zyndpay import ZyndPay

client = ZyndPay(
    api_key="zyp_live_sk_...",
    webhook_secret=os.environ.get("ZYNDPAY_WEBHOOK_SECRET"),
)

payment = client.payins.create(
    amount="100.00",
    external_ref="order_123"
)
print(payment["address"])

Démarrage Python

Acceptez votre premier paiement USDT en moins de 5 minutes. Cet exemple utilise Flask — adaptez à votre framework.

python
import os
from zyndpay import ZyndPay
from flask import Flask, request, jsonify

# 1. Initialize the SDK
client = ZyndPay(
    api_key=os.environ["ZYNDPAY_API_KEY"],        # zyp_live_sk_...
    webhook_secret=os.environ["WEBHOOK_SECRET"],
)

# 2. Create a pay-in (generates a TRON deposit address for your customer)
payment = client.payins.create(
    amount="100.00",
    external_ref="order_123",
    expires_in_seconds=3600,
    success_url="https://yoursite.com/payment/success",
)
# Send payment["paymentUrl"] or payment["address"] to your customer
print("Deposit address:", payment["address"])
print("Payment page:", payment["paymentUrl"])

# 3. Handle webhook events
app = Flask(__name__)

@app.post("/webhook")
def handle_webhook():
    # Always verify using the raw body — never parsed JSON
    event = client.webhooks.verify(
        request.get_data(),
        request.headers.get("Zyndpay-Signature"),
    )

    if event["event"] == "payin.confirmed":
        data = event["data"]
        print(f"Payment confirmed: {data['amount']} USDT — {data['externalRef']}")
        # Fulfill the order, unlock access, send receipt, etc.
    elif event["event"] == "payin.expired":
        print("Payment expired:", event["data"]["externalRef"])

    return jsonify(received=True)

# 4. Check payment status anytime
status = client.payins.get(payment["transactionId"])
print("Status:", status["status"])  # AWAITING_PAYMENT → CONFIRMING → CONFIRMED
Conseil
Les gestionnaires webhook doivent retourner un statut 2xx dans les 10 secondes. Les livraisons échouées sont relancées avec un backoff exponentiel jusqu'à 5 fois.

New Resources (v1.5.0+)

As of SDK v1.5.0, the Python SDK supports: wallets.list(), fiat_destinations.create(), conversions.convert_between_wallets(), bulk_payments workflows, transactions.export(), and balances.get_all().

python
estimate = zyndpay.payouts.estimate(
    amount='100.00',
    destination_address='TXyz1234567890abcdef1234567890abcde',
)
print(f"Fee: {estimate['fee']} USDT")
print(f"Total deducted: {estimate['totalDebited']} USDT")
print(f"Sufficient: {estimate['sufficient']}")

SDK PHP

Le SDK PHP officiel avec client HTTP basé sur cURL et vérification de webhook. Nécessite PHP 8.0+.

bash
composer require zyndpay/zyndpay-php
php
<?php
require_once 'vendor/autoload.php';

$client = new ZyndPay\ZyndPay('zyp_live_sk_...', [
    'webhook_secret' => getenv('ZYNDPAY_WEBHOOK_SECRET'),
]);

// Create a payment
$payment = $client->payins->create([
    'amount' => '100.00',
    'externalRef' => 'order_123',
]);
echo $payment['address'];

// Verify a webhook
$event = $client->webhooks->verify(
    $rawBody,
    $_SERVER['HTTP_ZYNDPAY_SIGNATURE']
);

Démarrage PHP

Acceptez votre premier paiement USDT en moins de 5 minutes. Cet exemple utilise du PHP simple — adaptez le gestionnaire webhook à votre framework.

php
<?php
require_once 'vendor/autoload.php';

// 1. Initialize the SDK
$client = new ZyndPay\ZyndPay(
    getenv('ZYNDPAY_API_KEY'),   // zyp_live_sk_...
    ['webhook_secret' => getenv('WEBHOOK_SECRET')]
);

// 2. Create a pay-in (generates a TRON deposit address for your customer)
$payment = $client->payins->create([
    'amount' => '100.00',
    'externalRef' => 'order_123',
    'expiresInSeconds' => 3600,
    'successUrl' => 'https://yoursite.com/payment/success',
]);
// Send $payment['paymentUrl'] or $payment['address'] to your customer
echo "Deposit address: " . $payment['address'] . "\n";
echo "Payment page: " . $payment['paymentUrl'] . "\n";

// 3. Handle webhook events (in your webhook endpoint file)
$rawBody   = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_ZYNDPAY_SIGNATURE'];
$event     = $client->webhooks->verify($rawBody, $sigHeader);

switch ($event['event']) {
    case 'payin.confirmed':
        $data = $event['data'];
        echo "Payment confirmed: {$data['amount']} USDT — {$data['externalRef']}\n";
        // Fulfill the order, unlock access, send receipt, etc.
        break;
    case 'payin.expired':
        echo "Payment expired: " . $event['data']['externalRef'] . "\n";
        break;
}
http_response_code(200);
echo json_encode(['received' => true]);

// 4. Check payment status anytime
$status = $client->payins->get($payment['transactionId']);
echo "Status: " . $status['status'] . "\n"; // AWAITING_PAYMENT → CONFIRMING → CONFIRMED
Conseil
Les gestionnaires webhook doivent retourner un statut 2xx dans les 10 secondes. Les livraisons échouées sont relancées avec un backoff exponentiel jusqu'à 5 fois.
Vérification de Signature PHP

Implémentation PHP brute sans le SDK. Produit la même vérification HMAC-SHA256 que les exemples Node.js et Python ci-dessus.

php
<?php
// IMPORTANT: read raw body before any JSON parsing
$payload   = file_get_contents('php://input');
$sigHeader = $_SERVER['HTTP_ZYNDPAY_SIGNATURE'] ?? '';

function verifyWebhook(string $payload, string $sigHeader, string $secret): bool {
    $parts = [];
    foreach (explode(',', $sigHeader) as $part) {
        [$k, $v] = explode('=', $part, 2);
        $parts[$k] = $v;
    }

    if (empty($parts['t']) || empty($parts['v1'])) {
        return false;
    }

    // Reject events older than 5 minutes
    if (abs(time() - (int) $parts['t']) > 300) {
        return false;
    }

    $expected = hash_hmac('sha256', $parts['t'] . '.' . $payload, $secret);

    // Timing-safe comparison to prevent timing attacks
    return hash_equals($expected, $parts['v1']);
}

if (!verifyWebhook($payload, $sigHeader, getenv('ZYNDPAY_WEBHOOK_SECRET'))) {
    http_response_code(400);
    echo 'Invalid signature';
    exit;
}

$event = json_decode($payload, true);
// Handle $event['event'] ...

New Resources (v1.5.0+)

As of SDK v1.5.0, the PHP SDK supports: wallets->list(), fiatDestinations->create(), conversions->convertBetweenWallets(), bulkPayments workflows, transactions->export(), and balances->getAll().

php
$estimate = $zyndpay->payouts->estimate([
    'amount' => '100.00',
    'destinationAddress' => 'TXyz1234567890abcdef1234567890abcde',
]);
echo "Fee: "   . $estimate['fee']          . " USDT\n";
echo "Total: " . $estimate['totalDebited'] . " USDT\n";

Plugin WooCommerce

Acceptez des paiements USDT dans votre boutique WordPress / WooCommerce sans écrire une seule ligne de code. Le plugin génère des adresses de dépôt, surveille le statut de confirmation et exécute automatiquement les commandes.

1

Téléchargez le ZIP du plugin depuis le dépôt GitHub ZyndPay.

2

Dans l'administration WordPress, allez dans Extensions → Ajouter → Téléverser une extension.

3

Téléversez le ZIP et cliquez sur Activer.

4

Naviguez vers WooCommerce → Réglages → Paiements → ZyndPay.

5

Entrez votre clé API et votre secret webhook, puis sauvegardez.


Sandbox

Vue d'ensemble

Le sandbox vous permet de tester toute votre intégration sans toucher à de vrais fonds ni au réseau TRON. Utilisez votre clé <ic>zyp_test_sk_...</ic> — l'API est identique à la production.

Solde isolé
Les fonds sandbox sont complètement séparés de votre solde live.
API identique
Mêmes endpoints, mêmes formats de réponse, mêmes événements webhook.
Confirmation instantanée
Simulez la confirmation de paiement en un seul appel API — sans attente.
Sandbox avant KYB
Disponible pour tous les marchands dès le premier jour, sans approbation nécessaire.

Guide de Test

Suivez ces étapes pour tester votre flux de paiement complet de bout en bout avant de passer en production.

1
Obtenez votre clé API sandbox
Dans votre tableau de bord, allez dans Clés API et créez une clé avec le préfixe zyp_test_sk_.... Les clés sandbox sont gratuites et toujours disponibles.
2
Créez un encaissement de test
Appelez POST /v1/payments avec votre clé zyp_test_sk_... et ajoutez soit ?sandbox=true dans l'URL, soit l'en-tête x-sandbox: true. Le préfixe de la clé seul ne suffit pas — le flag sandbox est requis sur chaque création de paiement. Vous recevez une vraie adresse TRON et URL de paiement, sans activité blockchain.
3
Simulez la confirmation blockchain
Appelez POST /v1/sandbox/payments/:id/simulate. Cela passe instantanément la transaction au statut CONFIRMED, déclenche le webhook payin.confirmed sur votre endpoint et crédite votre solde sandbox.
4
Vérifiez que votre webhook se déclenche
Vérifiez vos logs serveur ou utilisez un outil comme ngrok ou webhook.site pour exposer un endpoint local. L'appel simulate déclenche un vrai webhook avec une vraie signature — exécutez votre code de vérification dessus.
5
Réinitialisez et recommencez
Appelez POST /v1/sandbox/reset pour effacer toutes les transactions sandbox et repartir de zéro. Utile pour les suites de tests automatisés.
bash
# Using the sandbox API key (zyp_test_sk_...)
curl -X POST "https://api.zyndpay.io/v1/payments?sandbox=true" \
  -H "X-Api-Key: zyp_test_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "100",
    "externalRef": "test_order_001"
  }'
typescript
import { ZyndPay } from '@zyndpay/sdk';

const zyndpay = new ZyndPay({ apiKey: 'zyp_test_sk_...' });

// 1. Create a sandbox payin
const payin = await zyndpay.payins.create({
  amount: '100',
  sandbox: true,
  externalRef: 'test_order_001',
});

// 2. Instantly simulate confirmation (fires the payin.confirmed webhook)
//    simulate() returns an ack { message, transactionId }, not the payin —
//    fetch the post-confirmation state with get().
await zyndpay.payins.simulate(payin.transactionId);

// 3. Read the final status
const tx = await zyndpay.payins.get(payin.transactionId);
console.log('Final status:', tx.status); // "CONFIRMED"
bash
# Reset all sandbox transactions (useful for clean test runs)
curl -X POST https://api.zyndpay.io/v1/sandbox/reset \
  -H "X-Api-Key: zyp_test_sk_..."
Pièges courants
Utiliser une clé live (zyp_live_sk_...) avec sandbox: true retourne LIVE_KEY_SANDBOX_REQUEST. Utilisez une clé test pour les requêtes sandbox.
L'endpoint simulate ne fonctionne que sur les transactions au statut AWAITING_PAYMENT. Si vous l'appelez deux fois ou après expiration, vous obtenez une erreur 400.
Les signatures webhook en sandbox sont réelles — le même algorithme HMAC-SHA256 s'applique. Votre code de vérification doit fonctionner exactement comme en production.
Les soldes sandbox ne sont pas transférables en production. Ils existent uniquement pour les tests et sont effacés à la réinitialisation.

Simuler un Paiement

POST/v1/sandbox/payments/:id/simulate

Confirme instantanément un encaissement sandbox, déclenche le webhook <ic>payin.confirmed</ic> et crédite le solde sandbox — exactement comme une vraie confirmation on-chain.

bash
curl -X POST \
  https://api.zyndpay.io/v1/sandbox/payments/{id}/simulate \
  -H "X-Api-Key: zyp_test_sk_..."
json
{
  "success": true,
  "data": {
    "message": "Payin simulation triggered",
    "transactionId": "f4b2cb0f-08ce-4408-88d4-0a678ca0aae2"
  }
}
Conseil
Appelez POST /v1/sandbox/reset pour supprimer toutes les transactions sandbox et repartir d'un état propre. Utile pour les suites de tests automatisés qui nécessitent un état de départ cohérent.

Référence

Codes d'Erreur

Toutes les erreurs retournent la même enveloppe JSON. Utilisez le champ <ic>code</ic> pour gérer les erreurs par programmation — le champ <ic>message</ic> est lisible par l'humain et peut changer entre les versions.

json
{
  "success": false,
  "error": {
    "code": "AMOUNT_TOO_LOW",
    "message": "Minimum payin amount is 1 USDT"
  },
  "statusCode": 400
}
CodeStatut HTTPRequisDescription
UNAUTHORIZED401requisClé API manquante ou invalide. Vérifiez que vous envoyez l'en-tête X-Api-Key et que la clé existe dans votre tableau de bord.
FORBIDDEN403requisLa clé API n'a pas le scope requis pour cette opération (<ic>INSUFFICIENT_SCOPE</ic>). Utilisez une clé secrète (sk) et non une clé publiable (pk), et vérifiez que la clé a été créée avec le scope requis (ex. <ic>wallets_write</ic> pour les endpoints de liste blanche).
INVALID_API_KEY401requisThe provided API key is invalid or has been revoked.
INSUFFICIENT_SCOPE403requisThe API key does not have permission for this action.
TOTP_REQUIRED403requisTwo-factor authentication is required to perform this action.
TOTP_INVALID400requisThe provided TOTP code is invalid or expired.
BACKUP_CODE_INVALID400requisThe backup code provided is invalid.
EMAIL_UNVERIFIED403requisAccount email address has not been verified.
ACCOUNT_CLOSED403requisThis merchant account has been closed.
REGISTRATION_BLOCKED403requisNew registrations are currently blocked for this account.
SANDBOX_KEY_LIVE_REQUEST400requisVous avez envoyé une clé sandbox mais la ressource est une transaction live. Utilisez une clé zyp_live_sk_... pour les requêtes de production.
LIVE_KEY_SANDBOX_REQUEST400requisVous avez envoyé sandbox: true avec une clé API live. Utilisez une clé zyp_test_sk_... pour les requêtes sandbox.
IP_ALLOWLIST_SELF_LOCKOUT400requisThis change would lock out your own IP address.
MERCHANT_NOT_FOUND404requisThe specified merchant was not found.
MERCHANT_STATUS_INVALID403requisThe merchant account is not in a valid state for this action.
KYB_REQUIRED403requisCette opération nécessite une vérification KYB (Know Your Business) complète. Complétez le KYB dans votre tableau de bord pour débloquer les limites complètes.
RATE_LIMIT_EXCEEDED429requisTrop de requêtes. Consultez l'en-tête de réponse Retry-After pour connaître le nombre de secondes à attendre avant de réessayer.
RATE_LIMITED429requisToo many requests. Please slow down.
MERCHANT_LIMIT_EXCEEDED403requisMerchant-level transaction limit has been exceeded.
MONTHLY_LIMIT_EXCEEDED403requisLimite mensuelle atteinte. Réinitialisation le 1er du mois suivant.
BALANCE_CAP_REACHED403requisWallet balance cap has been reached.
DAILY_CAP_EXCEEDED403requisDaily transaction cap has been exceeded.
DAILY_CONVERSION_LIMIT403requisDaily conversion volume limit reached.
DAILY_CONVERSION_COUNT_LIMIT403requisDaily conversion count limit reached.
LIMIT_EXCEEDED_PAYLINKS403requisMaximum number of paylinks for this plan has been reached.
LIMIT_EXCEEDED_WEBHOOKS403requisMaximum number of webhook endpoints has been reached.
LIMIT_EXCEEDED_API_KEYS403requisMaximum number of API keys has been reached.
LIMIT_EXCEEDED_TEAM_MEMBERS403requisMaximum number of team members has been reached.
LIMIT_EXCEEDED_BULK_BATCH403requisMaximum number of items in a bulk batch has been reached.
INVALID_ADDRESS400requisL'adresse de portefeuille TRON est mal formée. Les adresses doivent commencer par T et faire 34 caractères au format base58.
ADDRESS_IN_USE409requisThis address is already in use by another transaction.
ADDRESS_NOT_FOUND404requisLe whitelistAddressId ne correspond à aucune adresse de retrait enregistrée sur votre compte. Ajoutez des adresses dans votre tableau de bord sous Adresses de Retrait.
NO_WHITELISTED_ADDRESS400requisNo whitelisted withdrawal address found for this merchant.
NO_PAYOUT_ADDRESS400requisNo payout address configured for this merchant.
ADDRESS_NOT_WHITELISTED403requisL'adresse de destination n'est pas sur votre liste blanche de marchand. Ajoutez-la dans votre tableau de bord avant d'y envoyer des fonds.
ADDRESS_COOLDOWN403requisThis address is in a cooldown period and cannot be used yet.
ADDRESS_REMOVAL_DISABLED410requisSaved crypto recipients cannot be deleted.
WHITELIST_VALIDATION_FAILED400requisAddress whitelist validation failed.
INVALID_CONTEXT400requisInvalid context provided for this operation.
WALLET_NOT_FOUND404requisThe specified wallet was not found.
WALLET_DIRECT_WITHDRAW_DISABLED403requisDirect withdrawal from this wallet type is disabled.
INSUFFICIENT_BALANCE400requisVotre solde USDT est insuffisant pour le montant de paiement sortant ou retrait demandé. Vérifiez votre solde via GET /v1/wallets/balance.
AMOUNT_TOO_SMALL400requisLe montant est inférieur au minimum requis pour cette opération (5 USDT pour les encaissements, 5 USDT pour les paiements sortants et retraits).
AMOUNT_TOO_LARGE400requisLe montant dépasse le maximum autorisé pour cette opération ou votre niveau de conformité actuel.
INVALID_AMOUNT400requisThe amount provided is not a valid number.
INVALID_TRANSACTION_TYPE400requisThe transaction type specified is not valid.
INVALID_TRANSACTION_STATUS400requisL'opération n'est pas autorisée dans le statut actuel de la transaction (ex. tenter d'annuler un paiement sortant confirmé).
REFUND_WINDOW_EXPIRED400requisThe refund window for this transaction has expired.
REFUND_EXCEEDS_AMOUNT400requisRefund amount exceeds the original transaction amount.
REASON_NOTE_REQUIRED400requisA reason note is required for this action.
CANNOT_CANCEL400requisCette ressource ne peut plus être annulée (elle a déjà été approuvée, diffusée ou complétée).
DUPLICATE_EXTERNAL_REF409requisUne transaction avec cet externalRef existe déjà pour votre compte. Utilisez une référence unique par requête, ou omettez-la.
MISSING_IDEMPOTENCY_KEY400requisCet endpoint nécessite un en-tête Idempotency-Key. Passez un UUID unique par requête.
IDEMPOTENCY_KEY_INVALID400requisThe Idempotency-Key header is malformed.
IDEMPOTENCY_KEY_MISMATCH409requisUne requête existante avec la même Idempotency-Key a des paramètres différents. Utilisez une nouvelle clé pour une requête différente.
CONFIG_MISSING500requisRequired system configuration is missing.
AML_BLOCKED403requisCette transaction a été signalée par notre moteur de filtrage AML et ne peut pas être traitée. Contactez [email protected].
AML_SCREENING_UNAVAILABLE503requisAML screening service is temporarily unavailable.
COMPLIANCE_LIMIT_REACHED403requisVotre volume de transactions mensuel a atteint la limite de conformité de votre niveau actuel. Complétez le KYB pour augmenter vos limites.
CONVERSION_NOT_ALLOWED403requisConversion between these currencies is not permitted.
RATE_LOCK_EXPIRED400requisThe FX rate lock has expired. Request a new rate.
RATE_LOCK_NOT_FOUND404requisThe specified rate lock was not found.
RATE_LOCK_ALREADY_USED409requisThis rate lock has already been used.
RATE_UNAVAILABLE503requisExchange rate is currently unavailable. Try again shortly.
RATE_STALE400requisThe exchange rate is stale. Refresh and try again.
FX_UNAVAILABLE503requisFX conversion service is temporarily unavailable.
INVALID_CONVERSION_PAIR400requisThe specified currency conversion pair is not supported.
NEGATIVE_REVENUE400requisThis conversion would result in negative revenue.
MISSING_PHONE400requisCustomer phone number is required for this payment method.
MISSING_OPERATOR400requisMobile money operator code is required.
MISSING_BANK_DETAILS400requisBank account details are required.
MISSING_MOBILE_MONEY_FIELDS400requisRequired mobile money fields are missing.
MISSING_BANK_FIELDS400requisRequired bank transfer fields are missing.
FIAT_DESTINATION_REQUIRED400requisA fiat destination must be configured before withdrawing.
FIAT_DESTINATION_NOT_FOUND404requisThe specified fiat destination was not found.
FIAT_DESTINATION_INVALID400requisThe fiat destination configuration is invalid.
REFUND_RAIL_NOT_AVAILABLE400requisRefund is not available via the original payment rail.
WITHDRAWAL_NOT_FIAT400requisThis operation requires a fiat withdrawal.
WITHDRAWAL_APPROVE_NOT_SUPPORTED_FOR_FIAT400requisFiat withdrawals cannot be approved via the standard approve endpoint — they would be routed to the on-chain TRON broadcast path. Use the complete-fiat endpoint with a providerRef once the bank wire has been executed.
WITHDRAWAL_NOT_PENDING_REVIEW400requisThe withdrawal is not in PENDING_REVIEW status.
BENEFICIARY_REQUIRED400requisA beneficiary must be specified for this payout.
BENEFICIARY_INVALID400requisThe beneficiary details are invalid.
BENEFICIARY_NOT_FOUND404requisThe specified beneficiary was not found.
BENEFICIARY_ALREADY_EXISTS409requisA beneficiary with these details already exists.
BENEFICIARY_IN_USE409requisThis beneficiary is in use by an active transaction.
BENEFICIARY_REJECTED403requisThis beneficiary has been rejected by compliance.
BENEFICIARY_COOLDOWN403requisThis beneficiary is in a cooldown period.
BENEFICIARY_NOT_VERIFIED403requisThis beneficiary has not been verified yet.
SELF_OWNED_REQUIRED400requisA self-owned beneficiary is required for this operation.
MAX_BENEFICIARIES_REACHED403requisMaximum number of beneficiaries has been reached.
THIRD_PARTY_NOT_ALLOWED403requisThird-party beneficiaries are not allowed for this operation.
CARD_PAYMENTS_DISABLED403requisCard payments are not enabled for this merchant.
MOBILE_MONEY_PAYMENTS_DISABLED403requisMobile money payments are not enabled for this merchant.
USDT_PAYMENTS_DISABLED403requisUSDT payments are not enabled for this merchant.
NO_METHODS_ENABLED403requisNo payment methods are enabled for this paylink.
FEE_NOT_CONFIGURED500requisFee configuration is missing for this payment method.
PAYMENT_METHOD_NOT_ACCEPTED400requisThe specified payment method is not accepted for this transaction.
PROVIDER_INITIATE_FAILED502requisThe payment provider failed to initiate the transaction.
PROVIDER_MISSING_URL502requisThe payment provider did not return a redirect URL.
OPERATOR_NOT_SUPPORTED400requisThe mobile money operator is not supported in this region.
INVALID_STATE400requisThe transaction is not in the correct state for this action.
OTP_INVALID400requisThe OTP code provided is incorrect.
OTP_EXPIRED400requisThe OTP code has expired. Request a new one.
OTP_NOT_APPLICABLE400requisOTP submission is not applicable for this transaction.
MOMO_PROVIDER_ERROR502requisThe mobile money provider returned an error.
CUSTOMER_PHONE_REQUIRED400requisCustomer phone number is required for mobile money payments.
CANNOT_CHANGE_CURRENCY_AFTER_PRODUCTS400requisCurrency cannot be changed after products have been added.
PAYLINK_EMPTY400requisThe paylink has no products configured.
MARKETPLACE_DISABLED403requisMarketplace / Connect features are not enabled for this account.
NOT_A_PLATFORM_MERCHANT403requisThis merchant is not a platform merchant.
SPLIT_RULE_NOT_FOUND404requisThe specified split rule was not found.
SPLIT_RULE_IN_USE409requisThis split rule is in use and cannot be deleted.
SPLIT_RULE_INVALID_BPS_SUM400requisSplit rule basis points do not sum to 10000.
SPLIT_RULE_MISSING_ZYNDPAY_RECIPIENT400requisSplit rule is missing the ZyndPay fee recipient.
SPLIT_RULE_MISSING_PLATFORM_RECIPIENT400requisSplit rule is missing the platform merchant recipient.
SPLIT_RULE_MISSING_SUB_MERCHANT_RECIPIENT400requisSplit rule is missing the sub-merchant recipient.
SPLIT_RULE_INVALID_ZYNDPAY_BPS400requisZyndPay fee basis points in this split rule are below minimum.
SUB_MERCHANT_NOT_CONNECTED403requisThis sub-merchant is not connected to your platform.
SUB_MERCHANT_ALREADY_CONNECTED409requisThis sub-merchant is already connected to a platform.
SUB_MERCHANT_KYB_REQUIRED403requisThe sub-merchant must complete KYB before this action.
SUB_MERCHANT_SUSPENDED403requisThe sub-merchant account is suspended.
SUB_MERCHANT_AML_FLAGGED403requisThe sub-merchant has been flagged by AML screening.
SUB_MERCHANT_INVITATION_NOT_FOUND404requisThe sub-merchant invitation was not found.
SUB_MERCHANT_INVITATION_EXPIRED400requisThe sub-merchant invitation has expired.
SUB_MERCHANT_INVITATION_ALREADY_USED409requisThis invitation has already been accepted.
SUB_MERCHANT_HAS_PENDING_BALANCE409requisThe sub-merchant has a pending balance that must be settled first.
SPLIT_PAYMENT_NOT_FOUND404requisThe specified split payment was not found.
SPLIT_PAYMENT_ALREADY_REVERSED409requisThis split payment has already been reversed.
IMPORT_VALIDATION_FAILED400requisOne or more items in the bulk import failed validation.
VALIDATION_ERROR400requisLe corps de la requête a échoué à la validation. Le champ details liste chaque champ invalide et la raison.
NOT_FOUND404requisLa ressource demandée n'existe pas ou n'appartient pas à votre compte marchand.
DUPLICATE_RESOURCE409requisUne ressource avec cette valeur unique existe déjà (ex. email, slug ou champ en doublon).
CONFLICT409requisA conflict occurred with an existing resource.
BAD_REQUEST400requisThe request was malformed or contained invalid parameters.
INVALID_WEBHOOK_SIGNATURE400requisLa vérification de signature webhook a échoué. Vérifiez votre secret webhook et que vous utilisez le corps brut de la requête.
INTERNAL_ERROR500requisErreur serveur inattendue. Incluez le requestId de la réponse lorsque vous contactez [email protected].
SERVICE_UNAVAILABLE503requisThe service is temporarily unavailable. Try again shortly.

Limites de Taux

Les limites sont appliquées par clé API. Les requêtes dépassant les limites reçoivent une réponse <ic>429</ic> avec un en-tête <ic>Retry-After</ic>.

Point de terminaisonLimite de débitRequisDescription
Default100/60srequisLimite globale par clé API.
POST /payments30/60srequisCréation d'encaissement (POST /payments) par marchand.
POST /payout10/60srequisCréation de paiement sortant (POST /payout) par marchand.
POST /withdrawals5/60srequisDemandes de retrait par marchand.
Conseil
Les marchands Enterprise peuvent demander des limites plus élevées — contactez <ic>[email protected]</ic>.