API compatible con NOWPayments

Abrir en ChatGPTAbrir en Claude

Las rutas, la cabecera x-api-key, los campos de petición y respuesta, los estados y la firma IPN x-nowpayments-sig siguen la documentación de NOWPayments, así que el código existente normalmente solo necesita una URL base, una API key y un IPN secret nuevos. ¿Vienes de otra pasarela? Consulta el resumen de migración.

Por debajo es una factura normal de blockwallets: las direcciones de cobro son tuyas, la página de pago es nuestra caja alojada, y un pago solo cuenta cuando está confirmado en la cadena y contrastado con un segundo nodo. A continuación: tres pasos y después qué admite cada API y en qué se diferencia de la original: los parámetros que no implementamos devuelven un error en lugar de ignorarse en silencio.

Tres pasos

  1. Consigue una cuenta de comercio (solicítala en la página de inicio) y añade tus dominios en “Dominios del sitio web”, en “Desarrolladores › Integración y claves” del panel. Las URL de callback (ipn_callback_url) y de redirección (success_url / cancel_url / redirect_url) deben estar en esos dominios o en el dominio de tu URL de callback: una clave filtrada no puede apuntar los webhooks a otro sitio.
  2. Genera credenciales compatibles en el panel del comercio, en “Desarrolladores › Integración y claves”, un juego para live y otro para test: NOWPayments recibe una API key + IPN secret, Coinbase Commerce una API key + webhook shared secret. Se muestran una sola vez; nosotros solo guardamos hashes / texto cifrado.
  3. Apunta tu código a la URL base de abajo y usa las nuevas claves. Pruébalo primero en testnets con las credenciales de test y después pasa a live.
AntesDespuésCabecera de autenticaciónFirma del webhook
https://api.nowpayments.io/v1https://blockwallets.io/nowpayments/v1x-api-keyx-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 admitidos

EndpointNotas
GET /status{"message":"OK"}, no necesita clave
GET /currencies · /merchant/coins · /full-currenciesMonedas que puedes aceptar ahora mismo (tickers abajo); fixed_rate se ignora
GET /min-amount · GET /estimateMínimos y estimaciones; solo precios en USD (USDT / USDC cuentan 1:1)
POST /paymentCrea un pago; devuelve pay_address / pay_amount al instante (marca blanca)
POST /invoiceCrea una factura (invoice); invoice_url es nuestra caja alojada, donde el cliente elige moneda y red
POST /invoice-paymentElige la moneda de una factura (invoice) y devuelve el pago
GET /payment/:idEstado del pago
GET /payment/Listado: limit (1–500) / page / orderBy / dateFrom / dateTo / invoiceId; sortBy solo created_at
POST /authEl listado solo necesita x-api-key, sin JWT; este devuelve un token de relleno sin comprobar para que el código de “iniciar sesión y luego listar” siga funcionando (email / contraseña no se comprueban)

No disponibles: payouts, conversiones, sub-partners de custodia, suscripciones, update-merchant-estimate, pagos en moneda fiat. Esas rutas devuelven 404.

Callbacks IPN

Cuando un pago llega a un estado final enviamos un POST a la ipn_callback_url indicada al crearlo, con x-nowpayments-sig = HMAC-SHA512(IPN secret, el JSON ordenado recursivamente por clave), exactamente el código de verificación de NOWPayments. El cuerpo que enviamos ya es JSON compacto y ordenado, así que un HMAC sobre el cuerpo bruto también funciona (y es la opción más segura en PHP / Python, cuya recodificación de decimales muy pequeños puede diferir de JS). Los callbacks fallidos se reintentan tras 1 min, 5 min, 30 min, …: 8 intentos en total.

Node.js
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_statusCuándoIPN enviado
waitingPendiente de pago (también una factura (invoice) sin moneda elegida todavía)—
confirmingPago visto en la cadena, esperando confirmaciones— (visible al consultar)
finishedPagado (incluido con pago de más; actually_paid es lo que llegó)✓
partially_paidInsuficiente: el cliente aún puede completar el pago en la misma dirección antes de que caduque (se envía una vez en el primer pago insuficiente), y otra vez si sigue faltando al caducar✓
expiredCaducado sin pagar✓
refundedReembolsaste el pago completo desde el panel y se envió— (visible al consultar)

confirmed, sending y failed nunca aparecen: una vez confirmado pasa a finished y se acredita en tu saldo; no se reenvía nada.

Tickers de monedas

MonedaRedpay_currencynetworkMínimo
USDTTRONusdttrc20trx$1
USDTBNB Chainusdtbsc usdtbep20bsc$1
USDCBNB Chainusdcbsc usdcbep20bsc$1
USDTEthereumusdterc20eth$5
USDCEthereumusdc usdcerc20eth$5
USDCBaseusdcbasebase$1
USDTPolygonusdtmatic usdtpolygonmatic$1
USDCPolygonusdcmatic usdcpolygonmatic$1
USDTArbitrumusdtarbarbitrum$1
USDCArbitrumusdcarbarbitrum$1
USDTOptimismusdtopop$1
USDCOptimismusdcopop$1
USDTAvalancheusdtarc20 usdtavax / usdtavaxcavaxc$1
USDCAvalancheusdcarc20 usdcavax / usdcavaxcavaxc$1
USDTX Layerusdtxlayerxlayer$1
USDCX Layerusdcxlayerxlayer$1
USD1BNB Chainusd1bscbsc$1
FDUSDBNB Chainfdusdbscbsc$1
DAIEthereumdai daierc20eth$5
DAIArbitrumdaiarbarbitrum$1
DAIBasedaibasebase$1
BTCBitcoinbtcbtc$5
LTCLitecoinltcltc$1
DOGEDogecoindogedoge$1
BCHBitcoin Cashbchbch$1
TRXTRONtrxtrx$1
BNBBNB Chainbnbbsc bnbbsc$1
ETHEthereumetheth$5
ETHBaseethbasebase$1
ETHArbitrumetharbarbitrum$1
ETHOptimismethopop$1
POLPolygonmaticmainnet pol / polmainnet / maticmatic$1
AVAXAvalancheavaxc avaxavaxc$1
OKBX Layerokbxlayerxlayer$1
USDTSolanausdtsolsol$1
USDCSolanausdcsolsol$1
SOLSolanasolsol$1
USDTTONusdttonton$1
TONTONtonton$1

Diferencias con NOWPayments

Modo de prueba

Los cargos y pagos creados con credenciales de test funcionan en la testnet de cada cadena y sus webhooks se firman con el IPN secret / shared secret de test, totalmente separados de live: una clave de test no puede ver pagos de live.

Todo lo demás (caja, confirmaciones, redes y mínimos, reembolsos) funciona como en la documentación principal.