# API compatible NOWPayments

> Passez de NOWPayments à blockwallets en changeant l’URL de base et les clés : endpoints pris en charge, callbacks et signatures IPN, tickers des cryptos et différences.
>
> HTML: https://blockwallets.io/docs/nowpayments?lang=fr · English: https://blockwallets.io/docs/nowpayments.md?lang=en

## 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](https://blockwallets.io/docs/migrate?lang=fr).

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](https://blockwallets.io/?lang=fr#join)) 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

```javascript
- 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.

```nodejs
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)
```

```php
$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_currency` vaut uniquement `usd` (plus `usdt` / `usdc` à 1:1) ; toute autre monnaie fiat renvoie 400.

- `pay_amount` (un montant crypto fixe), `payout_address` / `payout_currency` / `payout_extra_id` et `customer_email` ne sont pas pris en charge et renvoient 400.

- `is_fixed_rate` est accepté mais sans effet : nous verrouillons toujours le taux (BTC / LTC, etc. à la médiane de plusieurs plateformes, pendant 30 minutes). Avec `is_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](https://blockwallets.io/pricing?lang=fr)).

- Les paiements sont valables jusqu’à 12 heures (NOWPayments : 7 jours). `partially_paid_url` est stocké et renvoyé tel quel, mais la caisse n’y redirige pas.

- `payment_id` est un nombre à 10 chiffres ; pour les factures (invoices) créées avec `/invoice`, le `payment_id` du paiement est égal à l’`id` de la facture, et une facture a exactement un paiement (pas de redépôts avec `parent_payment_id`).

- Seuls les IPN de statut final et de `partially_paid` sont envoyés (tableau ci-dessus), aucun pour `waiting` / `confirming` ; `fee.serviceFee` correspond 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](https://blockwallets.io/docs/cryptomus?lang=fr).
