API compatível com a NOWPayments
Os caminhos, o cabeçalho x-api-key, os campos de requisição e resposta, os status e a assinatura IPN x-nowpayments-sig seguem a documentação da NOWPayments, então o código existente normalmente só precisa de uma nova URL base, uma nova API key e um novo IPN secret. Vindo de outro lugar? Veja a visão geral da migração.
Por baixo é uma cobrança comum da blockwallets: os endereços de recebimento são seus, a página de pagamento é o nosso checkout hospedado, e um pagamento só vale depois de confirmado na blockchain e conferido com um segundo nó. A seguir: três passos, depois o que cada API suporta e como ela difere da original — parâmetros que não implementamos retornam erro em vez de serem ignorados em silêncio.
Três passos
- Consiga uma conta de lojista (solicite na página inicial) e cadastre os seus domínios em “Domínios do site”, em “Desenvolvedores › Integração e chaves” no painel. As URLs de callback (
ipn_callback_url) e de redirecionamento (success_url/cancel_url/redirect_url) precisam estar nesses domínios ou no domínio da sua URL de callback — uma chave vazada não consegue apontar webhooks para outro lugar. - Gere credenciais compatíveis no painel do lojista, em “Desenvolvedores › Integração e chaves”, um conjunto para live e outro para test: a NOWPayments recebe uma API key + IPN secret, a Coinbase Commerce uma API key + webhook shared secret. Elas são exibidas uma única vez; guardamos apenas hashes / texto cifrado.
- Aponte o seu código para a URL base abaixo e use as novas chaves. Rode primeiro nas testnets com as credenciais de test e depois passe para live.
| Antes | Depois | Cabeçalho de autenticação | Assinatura do 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 suportados
| Endpoint | Observações |
|---|---|
GET /status | {"message":"OK"}, sem necessidade de chave |
GET /currencies · /merchant/coins · /full-currencies | Moedas que você pode aceitar agora (tickers abaixo); fixed_rate é ignorado |
GET /min-amount · GET /estimate | Mínimos e estimativas; precificação apenas em USD (USDT / USDC contam 1:1) |
POST /payment | Cria um pagamento; retorna pay_address / pay_amount na hora (white-label) |
POST /invoice | Cria uma fatura (invoice); invoice_url é o nosso checkout hospedado, onde o cliente escolhe moeda e rede |
POST /invoice-payment | Escolhe a moeda de uma fatura (invoice) e retorna o pagamento |
GET /payment/:id | Status do pagamento |
GET /payment/ | Lista: limit (1–500) / page / orderBy / dateFrom / dateTo / invoiceId; sortBy somente created_at |
POST /auth | A lista só precisa de x-api-key, sem JWT; este retorna um token de preenchimento sem verificação para que o código “fazer login e depois listar” continue funcionando (e-mail / senha não são verificados) |
Não disponíveis: payouts, conversões, subparceiros de custódia, assinaturas, update-merchant-estimate, pagamentos em moeda fiduciária. Esses caminhos retornam 404.
Callbacks IPN
Quando um pagamento chega a um status final, enviamos um POST para a ipn_callback_url informada na criação, com x-nowpayments-sig = HMAC-SHA512(IPN secret, o JSON ordenado recursivamente por chave) — exatamente o código de verificação da NOWPayments. O corpo que enviamos já é um JSON compacto e ordenado, então um HMAC sobre o corpo bruto também funciona (e é a escolha mais segura em PHP / Python, cuja recodificação de decimais muito pequenos pode diferir da do JS). Callbacks que falham são reenviados após 1 min, 5 min, 30 min, … — 8 tentativas no total.
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 | Quando | IPN enviado |
|---|---|---|
waiting | Aguardando pagamento (inclusive uma fatura (invoice) sem moeda escolhida ainda) | — |
confirming | Pagamento visto na blockchain, aguardando confirmações | — (visível ao consultar) |
finished | Pago (inclusive com pagamento a mais; actually_paid é o que chegou) | ✓ |
partially_paid | Insuficiente: o cliente ainda pode complementar no mesmo endereço antes de expirar (enviado uma vez no primeiro pagamento insuficiente), e de novo se ainda faltar na expiração | ✓ |
expired | Expirado sem pagamento | ✓ |
refunded | Você reembolsou o pagamento inteiro pelo painel e ele foi enviado | — (visível ao consultar) |
confirmed, sending e failed nunca aparecem: depois de confirmado vira finished e é creditado no seu saldo; nada é repassado.
Tickers das moedas
| Moeda | Rede | pay_currency | network | Mínimo |
|---|---|---|---|---|
| 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 |
Diferenças em relação à NOWPayments
price_currencyé apenasusd(maisusdt/usdca 1:1); qualquer outra moeda fiduciária retorna 400.pay_amount(um valor fixo em cripto),payout_address/payout_currency/payout_extra_idecustomer_emailnão são suportados e retornam 400.is_fixed_rateé aceito mas não tem efeito: sempre travamos a cotação (BTC / LTC etc. pela mediana de várias corretoras, por 30 minutos). Comis_fee_paid_by_usertrue, a taxa da plataforma é somada apay_amount(a taxa da plataforma é 1%, menor com mais volume; veja os preços).- Os pagamentos valem por até 12 horas (NOWPayments: 7 dias).
partially_paid_urlé guardado e devolvido como está, mas o checkout não redireciona para ele. payment_idé um número de 10 dígitos; nas faturas (invoices) criadas com/invoice, opayment_iddo pagamento é igual aoidda fatura, e uma fatura tem exatamente um pagamento (sem redepósitos comparent_payment_id).- Só são enviados os IPNs de status final e de
partially_paid(tabela acima), nenhum dewaiting/confirming;fee.serviceFeeé a taxa da plataforma (o valor recebido × a taxa quando a cobrança foi criada, 1% padrão), as demais taxas são 0.
Modo de teste
Cobranças e pagamentos criados com credenciais de test rodam na testnet de cada rede e os webhooks deles são assinados com o IPN secret / shared secret de test — totalmente separados do live: uma chave de test não enxerga pagamentos live.
Todo o resto (checkout, confirmações, redes e mínimos, reembolsos) funciona como na documentação principal.