Sur cette page
Guide d’intégration
Documentation développeur
Créez une facture, envoyez le client vers la page de paiement et recevez un webhook signé une fois le paiement effectué. Commencez par les quatre étapes ci-dessous.
Ce qui est pris en charge
La passerelle accepte USDT et USDC. Ce tableau correspond à GET /v1/services, vérifié le 4 novembre 2026. Consultez le catalogue à jour avant de construire votre sélecteur de moyens de paiement.
| Actif | Réseau | Confirmations |
|---|---|---|
| USDT | Tron | 20 |
| USDT | Ethereum | 12 |
| USDT | Solana | 32 |
| USDT | Polygon | 8 |
| USDT | Arbitrum | 12 |
| USDT | TON | 5 |
| USDC | Ethereum | 12 |
| USDC | Solana | 32 |
| USDC | Polygon | 8 |
| USDC | Base | 12 |
| USDC | Arbitrum | 12 |
Les paiements sont dépositaires : les fonds arrivent sur des adresses contrôlées par la plateforme. Votre solde marchand correspond au montant qui vous est dû. Le portefeuille Telegram, distinct, est non dépositaire.
Limites
- Les versements et remboursements ne sont pas encore actifs :
GET /v1/servicesrenvoieboundaries.livePayoutsetboundaries.liveRefundsàfalse. - Les paiements entrants ne sont pas contrôlés par un prestataire KYT.
- Les factures dont les frais dépasseraient 20 % du montant sont rejetées avec
AMOUNT_BELOW_MINIMUM. - Aucun SLA contractuel.
Les frais de départ sont de 0,40 % plus les frais de réseau. Demandez un devis de frais pour obtenir le total et le montant minimum ; voir les tarifs.
Authentification
Les appels de serveur à serveur utilisent x-api-key. Les clés de projet sont limitées à un seul projet marchand. Gardez-les sur votre serveur.
Les clés de test commencent par ncp_test_… ; les clés réelles par ncp_live_…. Les clés émises auparavant commencent par ncp_sandbox_ ; elles continuent de fonctionner.
curl -sS "https://api.veilnebu.la/v1/balances" \
-H "x-api-key: $PROJECT_API_KEY"Signer les requêtes d’écriture
Les requêtes GET n’ont besoin que de x-api-key. Chaque requête POST, PUT, PATCH et DELETE authentifiée par une clé de projet nécessite aussi deux en-têtes, sinon l’API répond 401 Invalid API signature :
x-ncp-timestamp: ISO-8601, à moins de 5 minutes de l’heure actuelle.x-ncp-signature:hex(HMAC-SHA256(your API key, "<timestamp>.<METHOD>.<path incl. query>.<raw body>")).
Écrivez les méthodes en majuscules. Signez exactement les octets que vous envoyez, y compris la query string dans le chemin. Une requête sans corps signe une chaîne vide. Les helpers ci-dessous nécessitent votre clé de projet ; le helper shell nécessite Bash, curl et OpenSSL, et le helper Node.js utilise le fetch natif.
export API_BASE_URL="https://api.veilnebu.la"
export PROJECT_API_KEY="ncp_test_…"
ncp() {
local method="$1" path="$2" body="" ts sig
shift 2
if [ "$#" -gt 0 ]; then body="$1"; shift; fi
ts=$(date -u +%Y-%m-%dT%H:%M:%S.000Z) || return
sig=$(printf '%s.%s.%s.%s' "$ts" "$method" "$path" "$body" |
openssl dgst -sha256 -hmac "$PROJECT_API_KEY" -hex) || return
sig=${sig##* }
if [ -n "$body" ]; then
curl -sS --request "$method" "$API_BASE_URL$path" \
-H "x-api-key: $PROJECT_API_KEY" \
-H "x-ncp-timestamp: $ts" -H "x-ncp-signature: $sig" \
-H "content-type: application/json" --data-binary "$body" "$@"
else
curl -sS --request "$method" "$API_BASE_URL$path" \
-H "x-api-key: $PROJECT_API_KEY" \
-H "x-ncp-timestamp: $ts" -H "x-ncp-signature: $sig" "$@"
fi
}Créer votre première facture
Créez un projet dans le tableau de bord, copiez sa clé API et créez une facture. La réponse contient checkoutUrl : redirigez-y votre client.
ncp POST /v1/payments '{
"amountMinor":"10000",
"currencyCode":"USD",
"settlementAssetCode":"USDT",
"externalId":"order-1001"
}' -H "idempotency-key: order-1001"Extrait de réponse. L’ID est généré pour chaque facture.
{
"id": "cm3k9x2f40001qz8e7h5v1abc",
"status": "new",
"amountMinor": "10000",
"currencyCode": "USD",
"settlementAssetCode": "USDT",
"checkoutUrl": "https://app.veilnebu.la/checkout/cm3k9x2f40001qz8e7h5v1abc"
}amountMinor est une chaîne contenant un entier exprimé dans la plus petite unité de la devise : 10000 = 100.00 USD. Le champ décimal amount n’est accepté que par l’endpoint navigateur /v1/public/payments.
Paiement depuis le navigateur et origines autorisées
Créez une clé publiable dans le tableau de bord et indiquez les origines exactes utilisées par votre boutique. Les requêtes du navigateur l’envoient comme x-publishable-key à POST /v1/public/payments.
L’origine, successUrl et cancelUrl doivent toutes figurer dans la liste des origines autorisées de la clé. Une origine se compose du schéma, de l’hôte et d’un port facultatif, sans chemin ni joker. Les origines absentes ou non listées sont rejetées.
Le montant décimal accepte jusqu’à deux décimales pour les devises fiat, et jusqu’au nombre de décimales propre à l’actif pour les cryptos (USDT et USDC : 6 sur tous les réseaux que nous prenons en charge aujourd’hui).
const response = await fetch('https://api.veilnebu.la/v1/public/payments', {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-publishable-key': 'pk_test_…',
},
body: JSON.stringify({
amount: '100.00',
currencyCode: 'USD',
settlementAssetCode: 'USDT',
successUrl: 'https://shop.example/thanks',
cancelUrl: 'https://shop.example/cart',
}),
});
if (!response.ok) throw new Error(await response.text());
const invoice = await response.json();
window.location.assign(invoice.checkoutUrl);Statuts de paiement
| Statut | Action suivante |
|---|---|
new | Partager le lien de paiement |
pending | En attente du paiement |
confirming | En attente des confirmations |
paid | Prêt pour le règlement |
partially_paid | Demander au client de compléter le paiement |
overpaid | Vérifier le montant excédentaire crédité |
expired | Créer une nouvelle facture |
cancelled | Aucune action requise |
failed | Ouvrir les détails |
refunded | Aucune action requise |
Clés d’idempotence
Pour créer une facture, envoyez une Idempotency-Key stable dérivée de l’ID de votre commande. Une nouvelle tentative avec la même clé et le même corps renvoie la facture d’origine. La réutiliser avec un corps différent renvoie 409. Gardez la clé et le corps inchangés lorsque vous réessayez après un timeout.
Voir l’exemple de première facture pour la requête.
Erreurs
Les erreurs sont au format JSON. message est une chaîne, ou un tableau de chaînes pour les erreurs de validation.
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Invalid API key"
}| Statut HTTP | Signification et action |
|---|---|
400 | Champ invalide ; "message" indique le champ. Corrigez-le avant de réessayer. |
401 | Invalid API key ou Invalid API signature. Vérifiez la clé, les octets signés et l’horodatage. |
404 | Payment invoice not found. Vérifiez l’ID et le projet. |
409 | Même Idempotency-Key avec un corps différent, ou Insufficient merchant payable balance. |
429 | Limite de débit atteinte. Patientez avant de réessayer. |
5xx | Réessayez avec la même Idempotency-Key et le même corps. |
Webhooks (HMAC-SHA256)
Chaque livraison contient x-nebula-signature: t=<unix>,v1=<hex>. Vérifiez le HMAC avec le secret de l’endpoint renvoyé lors de sa création. Rejetez les horodatages éloignés de plus de 300 secondes de l’heure actuelle.
Vérifier en Node.js
Le vérificateur Node.js utilise uniquement le module crypto intégré. Passez-lui l’en-tête de signature, les octets bruts du corps et le secret de l’endpoint.
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(header, rawBody, secret) {
const parts = {};
for (const piece of (header ?? '').split(',')) {
const separator = piece.indexOf('=');
if (separator <= 0) continue;
parts[piece.slice(0, separator).trim()] = piece.slice(separator + 1).trim();
}
if (!/^[0-9]{1,16}$/.test(parts.t ?? '')) return false;
if (!/^[0-9a-f]{64}$/.test(parts.v1 ?? '')) return false;
if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
const expected = createHmac('sha256', secret)
.update(parts.t + '.')
.update(rawBody)
.digest('hex');
if (expected.length !== parts.v1.length) return false;
return timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Vérifiez sur le corps brut de la requête, avant le parsing JSON. La livraison a lieu au moins une fois : dédupliquez sur x-nebula-event-id, qui correspond à eventId dans le payload. Les nouvelles tentatives automatiques conservent l’ID de livraison ; un renvoi manuel crée un nouvel ID de livraison avec le même ID d’événement. Enregistrez l’événement avant de répondre 2xx.
Tester les webhooks en local
Les URL de webhook doivent utiliser un hôte public sur Internet. Les adresses locales et privées sont rejetées. Exposez votre récepteur via un tunnel HTTPS public, puis enregistrez son URL avec le helper de signature.
ncp POST /v1/webhooks/endpoints '{
"url":"https://receiver.example/webhook",
"events":["payment.paid","payment.reorged"]
}'Remplacez l’URL par celle de votre récepteur. Conservez le secret de l’endpoint renvoyé à la création.
Types d’événements
Les versements et remboursements ne sont pas encore actifs (GET /v1/services renvoie boundaries.livePayouts à false). D’ici là, les versements et lots de versements ne font qu’enregistrer le transfert dans votre solde ; payout.completed et refund.completed ne signifient pas que des fonds sont sortis.
Le catalogue ci-dessous correspond à GET /v1/services. Une entrée du catalogue ne signifie pas que le mouvement de fonds correspondant est actif.
| Événement | Signification |
|---|---|
webhook. | Livraison de test demandée pour un endpoint de webhook. |
payment. | Le client a choisi un moyen de paiement ; en attente du premier dépôt. |
payment. | Dépôt détecté ; en attente des confirmations requises. |
payment. | Facture entièrement payée et réglée dans le grand livre. |
payment. | Montant reçu inférieur à celui demandé ; en attente du reste. |
payment. | Montant reçu supérieur à celui demandé ; réglé dans le grand livre avec le trop-perçu. |
payment. | Facture expirée sans fonds suffisants. |
payment. | Facture annulée avant paiement. |
payment. | Échec du paiement dû à une erreur de chaîne, de politique de frais ou d’observation. |
payment. | Événement de risque informatif concernant un dépôt déjà crédité. Le contrôle KYT des paiements entrants n’est pas actif. |
payment. | Un dépôt confirmé a été annulé par une réorganisation de la chaîne ; la facture n’est plus payée, suspendez ou annulez donc l’exécution de la commande. |
payment. | Un jeton non pris en charge a été déposé ; il n’a pas été crédité. |
payout. | Versement en cours de traitement. Les transferts réels ne sont pas ouverts. |
payout. | Versement enregistré dans le solde ; cela ne signifie pas que des fonds sont sortis. |
payout. | Échec du versement. |
payout. | Versement annulé avant son achèvement. |
refund. | Remboursement en cours de traitement. Les remboursements réels ne sont pas ouverts. |
refund. | Remboursement enregistré dans le solde ; cela ne signifie pas que des fonds sont sortis. |
refund. | Échec du remboursement. |
internal_transfer. | Transfert de solde entre marchands validé pour l’expéditeur. |
internal_transfer. | Transfert de solde entre marchands crédité au destinataire. |
static_wallet. | Une adresse de dépôt permanente a été créée pour un client. |
static_wallet. | Un portefeuille statique a été bloqué. |
static_wallet. | Le blocage d’un portefeuille statique a été levé. |
static_wallet. | Un dépôt sur un portefeuille statique a été détecté et réglé dans le grand livre. |
dispute. | Un litige a été ouvert sur une facture. |
dispute. | Un litige a été résolu. Une résolution seule ne prouve pas un remboursement on-chain. |
dispute. | Un litige a été rejeté ; aucune action du marchand n’est requise. |
subscription. | Un cycle de facturation a créé une nouvelle facture. |
Politique de nouvelles tentatives
Si votre endpoint ne répond pas 2xx, nous effectuons 8 tentatives au total : la tentative initiale, puis de nouvelles tentatives après 1 minute, 5 minutes, 30 minutes, 2 heures, 6 heures, 12 heures et 24 heures. Chaque délai commence après la tentative échouée précédente. Les erreurs réseau et les timeouts comptent aussi comme des échecs. Les livraisons de test n’ont qu’une seule tentative.
Après le dernier échec, la livraison est marquée comme échouée ; renvoyez-la avec POST /v1/webhooks/deliveries/{deliveryId}/resend. En secours, effectuez le rapprochement avec GET /v1/payments/{id}.
Les versements et remboursements ne sont pas encore actifs (GET /v1/services renvoie boundaries.livePayouts à false). D’ici là, les versements et lots de versements ne font qu’enregistrer le transfert dans votre solde ; payout.completed et refund.completed ne signifient pas que des fonds sont sortis.
Lots de versements
Un lot regroupe des enregistrements de transferts. Sa politique d’approbation et le nombre d’approbations requises sont renvoyés dans approvalPolicy et approvalsRequired. S’il y a plusieurs approbateurs, le créateur ne peut pas approuver son propre lot. Un opérateur seul habilité peut approuver un lot créé dans sa propre session. Approuvez les lots avec WebAuthn dans le tableau de bord.
export PROJECT_API_KEY="ncp_test_…"
ncp POST /v1/payout-batches '{
"assetCode":"USDT",
"networkCode":"TRON",
"externalId":"batch-1001",
"items":[{
"destinationAddress":"T…",
"amountMinor":"15000000",
"externalRef":"recipient-1001"
}]
}' -H "idempotency-key: batch-1001"Remplacez la destination par une adresse valide dans votre propre projet de test.
Facturation récurrente
Un abonnement crée une nouvelle facture à chaque cycle de facturation et la signale avec subscription.invoice_created. Il ne débite pas automatiquement le portefeuille du client. Envoyez au client la nouvelle URL de la page de paiement.
interval : DAY, WEEK, MONTH ou YEAR.
ncp POST /v1/subscriptions '{
"customerRef":"customer-42",
"assetCode":"USDT",
"networkCode":"TRON",
"amountMinor":"9000000",
"interval":"MONTH",
"intervalCount":1
}'Mettez en pause, reprenez ou annulez avec les opérations de cycle de vie décrites dans la référence de l’API.
Contrôle des risques
Les destinations des versements et remboursements sont comparées aux listes de sanctions publiques avant la création du transfert ; une correspondance entraîne le rejet de la requête. Les paiements entrants ne sont pas contrôlés.
Bot Telegram pour marchands
@veilnebula_bot fonctionne avec un seul projet à la fois. Associez-le dans une conversation privée avec la clé API de ce projet. Le bot tente de supprimer votre message et chiffre la clé stockée. Si la suppression échoue, révoquez la clé et remplacez-la. Ne publiez jamais une clé dans une conversation partagée.
Les notifications concernent le projet associé et la clé active. Ouvrez le tableau de bord pour approuver les versements.
/link <API_KEY>
/balance
/invoices
/notify on
/unlink