API compatible NOWPayments
Les chemins, l’en-tête x-api-key, les champs de requête et de réponse, les statuts et la signature IPN x-nowpayments-sig suivent la documentation de NOWPayments : le code existant n’a donc généralement besoin que d’une nouvelle URL de base, d’une nouvelle API key et d’un nouvel IPN secret. Vous venez d’ailleurs ? Consultez l’aperçu de la migration.
Dessous, c’est une facture blockwallets ordinaire : les adresses de réception sont les vôtres, la page de paiement est notre caisse hébergée, et un paiement ne compte qu’une fois confirmé on-chain et recoupé avec un second nœud. Ci-dessous : trois étapes, puis ce que chaque API prend en charge et en quoi elle diffère de l’originale — les paramètres que nous n’implémentons pas renvoient une erreur au lieu d’être ignorés en silence.
Trois étapes
- Obtenez un compte marchand (demandez-le sur la page d’accueil) et ajoutez vos domaines dans « Domaines du site web », sous « Développeurs › Intégration et clés » du tableau de bord. Les URL de callback (
ipn_callback_url) et de redirection (success_url/cancel_url/redirect_url) doivent se trouver sur ces domaines ou sur le domaine de votre URL de callback — une clé divulguée ne peut pas rediriger les webhooks ailleurs. - Générez des identifiants compatibles dans le tableau de bord marchand, sous « Développeurs › Intégration et clés », un jeu pour live et un pour test : NOWPayments reçoit une API key + IPN secret, Coinbase Commerce une API key + webhook shared secret. Ils ne sont affichés qu’une fois ; nous ne conservons que des hashs / du texte chiffré.
- Pointez votre code vers l’URL de base ci-dessous et utilisez les nouvelles clés. Lancez d’abord sur les testnets avec les identifiants de test, puis passez en live.
| Avant | Après | En-tête d’authentification | Signature du webhook |
|---|---|---|---|
https://api.nowpayments.io/v1 | https://blockwallets.io/nowpayments/v1 | x-api-key | x-nowpayments-sig (HMAC-SHA512) |
NOWPayments
- const NP = "https://api.nowpayments.io/v1";
+ const NP = "https://blockwallets.io/nowpayments/v1";
const res = await fetch(`${NP}/payment`, {
method: "POST",
headers: { "x-api-key": process.env.NOWPAYMENTS_API_KEY, "content-type": "application/json" },
body: JSON.stringify({
price_amount: 49.9, price_currency: "usd", pay_currency: "usdttrc20",
order_id: "A-1001", order_description: "Pro plan",
ipn_callback_url: "https://shop.example/ipn/nowpayments",
}),
});
const { payment_id, pay_address, pay_amount } = await res.json();Endpoints pris en charge
| Endpoint | Remarques |
|---|---|
GET /status | {"message":"OK"}, aucune clé nécessaire |
GET /currencies · /merchant/coins · /full-currencies | Cryptos que vous pouvez accepter en ce moment (tickers ci-dessous) ; fixed_rate est ignoré |
GET /min-amount · GET /estimate | Minimums et estimations ; prix en USD uniquement (USDT / USDC comptent 1:1) |
POST /payment | Crée un paiement ; renvoie pay_address / pay_amount immédiatement (marque blanche) |
POST /invoice | Crée une facture (invoice) ; invoice_url est notre caisse hébergée où le client choisit crypto et réseau |
POST /invoice-payment | Choisit la crypto d’une facture (invoice), renvoie le paiement |
GET /payment/:id | Statut du paiement |
GET /payment/ | Liste : limit (1–500) / page / orderBy / dateFrom / dateTo / invoiceId ; sortBy uniquement created_at |
POST /auth | La liste n’a besoin que de x-api-key, sans JWT ; cet endpoint renvoie un jeton factice non vérifié pour que le code « se connecter, puis lister » continue de fonctionner (e-mail / mot de passe ne sont pas vérifiés) |
Non disponibles : payouts, conversions, sous-partenaires en garde, abonnements, update-merchant-estimate, paiements en monnaie fiat. Ces chemins renvoient 404.
Callbacks IPN
Quand un paiement atteint un statut final, nous envoyons un POST à l’ipn_callback_url donnée à la création, avec x-nowpayments-sig = HMAC-SHA512(IPN secret, le JSON trié récursivement par clé) — exactement le code de vérification de NOWPayments. Le corps que nous envoyons est déjà un JSON compact et trié, donc un HMAC sur le corps brut fonctionne aussi (et c’est le choix le plus sûr en PHP / Python, dont le ré-encodage des très petits décimaux peut différer de JS). Les callbacks en échec sont réessayés après 1 min, 5 min, 30 min, … — 8 tentatives en tout.
import crypto from "node:crypto";
// Same as the NOWPayments docs (our body is already sorted, so hashing the raw body works too)
function sortObject(obj) {
return Object.keys(obj).sort().reduce((r, k) => {
r[k] = obj[k] && typeof obj[k] === "object" ? sortObject(obj[k]) : obj[k];
return r;
}, {});
}
function verifyIpn(params, sig, ipnSecret) {
const hmac = crypto.createHmac("sha512", ipnSecret).update(JSON.stringify(sortObject(params))).digest("hex");
return crypto.timingSafeEqual(Buffer.from(hmac), Buffer.from(sig || ""));
}
// verifyIpn(req.body, req.headers["x-nowpayments-sig"], process.env.NOWPAYMENTS_IPN_SECRET)$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
tksort($data); // the recursive ksort from the NOWPayments docs
$hmac = hash_hmac('sha512', json_encode($data, JSON_UNESCAPED_SLASHES), trim($ipnSecret));
$ok = hash_equals($hmac, $_SERVER['HTTP_X_NOWPAYMENTS_SIG'] ?? '');| payment_status | Quand | IPN envoyé |
|---|---|---|
waiting | En attente de paiement (y compris une facture (invoice) sans crypto choisie) | — |
confirming | Paiement vu on-chain, en attente de confirmations | — (visible lors d’une requête) |
finished | Payé (y compris avec un trop-perçu ; actually_paid est ce qui est arrivé) | ✓ |
partially_paid | Insuffisant : le client peut encore compléter à la même adresse avant l’expiration (envoyé une fois au premier paiement insuffisant), et de nouveau s’il manque encore quelque chose à l’expiration | ✓ |
expired | Expiré sans paiement | ✓ |
refunded | Vous avez remboursé la totalité du paiement depuis le tableau de bord et il a été envoyé | — (visible lors d’une requête) |
confirmed, sending et failed n’apparaissent jamais : une fois confirmé, c’est finished et crédité sur votre solde ; rien n’est transféré.
Tickers des cryptos
| Crypto | Réseau | pay_currency | network | Minimum |
|---|---|---|---|---|
| USDT | TRON | usdttrc20 | trx | $1 |
| USDT | BNB Chain | usdtbsc usdtbep20 | bsc | $1 |
| USDC | BNB Chain | usdcbsc usdcbep20 | bsc | $1 |
| USDT | Ethereum | usdterc20 | eth | $5 |
| USDC | Ethereum | usdc usdcerc20 | eth | $5 |
| USDC | Base | usdcbase | base | $1 |
| USDT | Polygon | usdtmatic usdtpolygon | matic | $1 |
| USDC | Polygon | usdcmatic usdcpolygon | matic | $1 |
| USDT | Arbitrum | usdtarb | arbitrum | $1 |
| USDC | Arbitrum | usdcarb | arbitrum | $1 |
| USDT | Optimism | usdtop | op | $1 |
| USDC | Optimism | usdcop | op | $1 |
| USDT | Avalanche | usdtarc20 usdtavax / usdtavaxc | avaxc | $1 |
| USDC | Avalanche | usdcarc20 usdcavax / usdcavaxc | avaxc | $1 |
| USDT | X Layer | usdtxlayer | xlayer | $1 |
| USDC | X Layer | usdcxlayer | xlayer | $1 |
| USD1 | BNB Chain | usd1bsc | bsc | $1 |
| FDUSD | BNB Chain | fdusdbsc | bsc | $1 |
| DAI | Ethereum | dai daierc20 | eth | $5 |
| DAI | Arbitrum | daiarb | arbitrum | $1 |
| DAI | Base | daibase | base | $1 |
| BTC | Bitcoin | btc | btc | $5 |
| LTC | Litecoin | ltc | ltc | $1 |
| DOGE | Dogecoin | doge | doge | $1 |
| BCH | Bitcoin Cash | bch | bch | $1 |
| TRX | TRON | trx | trx | $1 |
| BNB | BNB Chain | bnbbsc bnb | bsc | $1 |
| ETH | Ethereum | eth | eth | $5 |
| ETH | Base | ethbase | base | $1 |
| ETH | Arbitrum | etharb | arbitrum | $1 |
| ETH | Optimism | ethop | op | $1 |
| POL | Polygon | maticmainnet pol / polmainnet / matic | matic | $1 |
| AVAX | Avalanche | avaxc avax | avaxc | $1 |
| OKB | X Layer | okbxlayer | xlayer | $1 |
| USDT | Solana | usdtsol | sol | $1 |
| USDC | Solana | usdcsol | sol | $1 |
| SOL | Solana | sol | sol | $1 |
| USDT | TON | usdtton | ton | $1 |
| TON | TON | ton | ton | $1 |
Différences avec NOWPayments
price_currencyvaut uniquementusd(plususdt/usdcà 1:1) ; toute autre monnaie fiat renvoie 400.pay_amount(un montant crypto fixe),payout_address/payout_currency/payout_extra_idetcustomer_emailne sont pas pris en charge et renvoient 400.is_fixed_rateest accepté mais sans effet : nous verrouillons toujours le taux (BTC / LTC, etc. à la médiane de plusieurs plateformes, pendant 30 minutes). Avecis_fee_paid_by_userà true, les frais de plateforme sont ajoutés àpay_amount(les frais de plateforme sont de 1%, moins avec un volume plus élevé ; voir les tarifs).- Les paiements sont valables jusqu’à 12 heures (NOWPayments : 7 jours).
partially_paid_urlest stocké et renvoyé tel quel, mais la caisse n’y redirige pas. payment_idest un nombre à 10 chiffres ; pour les factures (invoices) créées avec/invoice, lepayment_iddu paiement est égal à l’idde la facture, et une facture a exactement un paiement (pas de redépôts avecparent_payment_id).- Seuls les IPN de statut final et de
partially_paidsont envoyés (tableau ci-dessus), aucun pourwaiting/confirming;fee.serviceFeecorrespond aux frais de plateforme (le montant reçu × le taux à la création de la facture, 1% en standard), les autres frais sont à 0.
Mode test
Les charges et paiements créés avec des identifiants de test fonctionnent sur le testnet de chaque chaîne et leurs webhooks sont signés avec l’IPN secret / shared secret de test — totalement séparés du live : une clé de test ne voit pas les paiements live.
Tout le reste (caisse, confirmations, réseaux et minimums, remboursements) fonctionne comme dans la documentation principale.