API compatible con Cryptomus

blockwallets te permite aceptar USDT y USDC, además de BTC, LTC, DOGE, BCH y monedas nativas (acreditadas en dólares estadounidenses a una tasa bloqueada): el cliente paga, lo confirmamos en la cadena y te avisamos. La API, las firmas y los webhooks son los mismos que los de Cryptomus: si ya integraste Cryptomus, cambia el dominio y listo. Primero verás cómo funciona y después la API.

Dos formas de cobrar

Ambas aceptan cualquier importe: pedidos de precio fijo y recargas en las que el cliente escribe el importe. Una vez pagado, envías el pedido o acreditas el saldo del cliente. La única diferencia es si cada pago recibe una dirección nueva.

Recomendado · la mayoría de integracionesFacturas

El cliente elige un producto o escribe un importe en tu página, tú creas una factura y él envía el importe que muestra la página de pago. Cada factura tiene una dirección nueva y una cuenta atrás; cuando llega el pago te avisamos con el importe realmente recibido.

POST /v1/payment → webhook type: payment

OpcionalBilleteras estáticas

Sin importe previo: cada cliente recibe una dirección permanente por red para guardarla en su billetera y enviar a ella cuando quiera. Cada transferencia genera su propio webhook.

POST /v1/wallet → webhook type: wallet

Si dudas, usa facturas. Sin código, los enlaces de pago del panel son facturas creadas por ti automáticamente. ¿Aún no eres comercio? Solicita acceso en la página de inicio; cuando te aprueben, entra en el panel del comercio y empieza por “Probar un pago” en la página de inicio del panel, pagando un importe pequeño (el dinero va a tu propio saldo).

Cómo avanza una factura

En un pago intervienen cuatro partes: el cliente, tu sitio, blockwallets y la blockchain. El diagrama avanza de arriba abajo en el tiempo; la columna de la derecha es el estado de la factura en cada paso.

① Crear② Pagar③ Confirmar④ Notificar⑤ Cerrar Clientenavegador + billeteraTu sitiopáginas + servidorblockwalletspágina de pago + escánerBlockchainTRON + 14 más Estado de la facturastatus 1Pulsa “Recargar”2POST /v1/payment3uuid + urlcheck4Redirige a url5Elige moneda + red6Dirección + importe propios7Envía a esa dirección8Escaneo cada minuto9Detectada, esperando confirmacionesconfirm_check10Contraste con un segundo nodopaid11POST url_callback12Responde 2xx13Verifica, envía / acredita14Vuelve a url_success15Acreditado en el saldo

El diagrama es ancho: desplázate hacia los lados.

15 pasos, de arriba abajo en orden cronológico. Pulsa “Reproducir” para verlo en marcha, o toca cualquier paso para ver los detalles.

En resumen, cinco pasos:

  1. Creas una factura (un enlace de pago lo hace por ti, o tu servidor llama a /v1/payment), obtienes la url de la página de pago y rediriges al cliente allí.
  2. En la página de pago el cliente elige moneda y red, recibe una dirección y un importe exclusivos, y paga desde una billetera o un exchange.
  3. Vemos la transferencia en la cadena, esperamos las confirmaciones necesarias y la contrastamos con un segundo proveedor de nodos (para que un nodo no pueda falsearla).
  4. La factura pasa a paid y enviamos de inmediato un webhook firmado por POST a tu url_callback; unos segundos después la página de pago devuelve al cliente a tu url_success.
  5. El dinero se acredita en tu saldo; retíralo a tu propia billetera desde el panel del comercio.

Estado de la factura

statusSignificadoQué haces tú
checkPendiente de pago—
confirm_checkTransferencia detectada, esperando confirmaciones—
wrong_amount_waitingRecibido, pero insuficiente; el cliente puede completar el pago en la misma dirección (si is_payment_multiple es true; se avisa una vez)— (recibirás paid cuando se complete)
paidPagadaEnvía / acredita según payment_amount_usd
paid_overPagada, con pago de másIgual (payment_amount_usd es el importe recibido)
wrong_amountCaducada con menos del importe total (con is_payment_multiple en false: la primera transferencia insuficiente)Los negocios de recargas pueden acreditar el payment_amount_usd recibido; las tiendas piden al cliente que pague el resto
cancelCaducada sin recibir nada— (el dinero que llegue más tarde se acredita y se notifica igualmente)

Cuando is_final es true, el estado ya no cambiará (salvo por pagos tardíos).

Panel del comercio → “Cobros › Enlaces de pago” → escribe una descripción y un importe (fija una red o deja que el cliente elija) → crear. Cada enlace tiene una URL y un código QR.

Integración por API

1. Credenciales y firma

Panel del comercio → “Desarrolladores › Integración y claves” → genera un merchant (UUID) y una API key. Hay un juego para Live y otro para Test: Live mueve dinero real, Test funciona en testnets. Añade también tus dominios del sitio web: url_callback, url_return y url_success deben estar en ellos. La API key se muestra una sola vez: guárdala bien y solo en tu servidor.

URL base https://blockwallets.io. Todos los endpoints son POST + JSON, con estas cabeceras:

Si todo va bien devuelve {"state": 0, "result": {…}}; si falla, {"state": 1, "message": "…"} (consulta Errores).

Los endpoints principales de abajo tienen un panel “Probar”: introduce tu merchant y API key de Test y la página firma la petición en tu navegador, llama al endpoint y muestra la petición y la respuesta completas (las credenciales quedan solo en la memoria de la página). Si una firma no coincide, compárala paso a paso con el verificador de firmas.

Node.js
import crypto from "node:crypto";

async function call(path, data) {
  const body = JSON.stringify(data);
  const sign = crypto.createHash("md5").update(Buffer.from(body).toString("base64") + process.env.BW_API_KEY).digest("hex");
  const res = await fetch("https://blockwallets.io" + path, {
    method: "POST",
    headers: { merchant: process.env.BW_MERCHANT, sign, "Content-Type": "application/json" },
    body,
  });
  return res.json();
}
PHP
function bw_call(string $path, array $data): array {
    $body = json_encode($data, JSON_UNESCAPED_UNICODE);
    $sign = md5(base64_encode($body) . getenv('BW_API_KEY'));
    $ch = curl_init('https://blockwallets.io' . $path);
    curl_setopt_array($ch, [
        CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ['merchant: ' . getenv('BW_MERCHANT'), 'sign: ' . $sign, 'Content-Type: application/json'],
    ]);
    return json_decode(curl_exec($ch), true);
}
Python
import base64, hashlib, json, os, requests

def bw_call(path: str, data: dict) -> dict:
    body = json.dumps(data, separators=(",", ":"), ensure_ascii=False)
    sign = hashlib.md5((base64.b64encode(body.encode()).decode() + os.environ["BW_API_KEY"]).encode()).hexdigest()
    headers = {"merchant": os.environ["BW_MERCHANT"], "sign": sign, "Content-Type": "application/json"}
    return requests.post("https://blockwallets.io" + path, data=body.encode(), headers=headers).json()

2. Crear una factura POST /v1/payment

Cuando el cliente hace un pedido, o escribe un importe en tu página de recarga, llama a https://blockwallets.io/v1/payment y redirígelo a la result.url devuelta.

json
{
  "amount": "42.00",
  "currency": "USD",
  "order_id": "order_1042",
  "url_callback": "https://shop.example/webhooks/crypto",
  "url_return": "https://shop.example/cart",
  "url_success": "https://shop.example/thanks?order=1042"
}
ParámetroObligatorioDescripción
amountSíImporte como cadena, con hasta dos decimales: "42.00". Tu precio o un número que escribe el cliente; no inferior al mínimo de la red elegida (consulta redes)
currencySíUSD, USDT o USDC, todas 1:1
order_idSíTu número de pedido: letras, dígitos, -, _, ≤ 100 caracteres. Si llamas de nuevo con el mismo order_id se devuelve la factura existente (sin pedidos duplicados)
url_callbackNoURL del webhook. Si la omites, no se envía ningún webhook
url_returnNoA dónde lleva “Volver” antes de pagar
url_successNoA dónde va el cliente después de pagar
networkNoPreselecciona una red: tron / bsc / eth / base / polygon / arbitrum / optimism / avalanche / xlayer / btc / ltc / doge / bch / sol / ton; si la omites, el cliente elige en la página de pago
to_currencyNoMoneda en esa red: USDT / USDC; en btc / ltc / doge / bch es BTC / LTC / DOGE / BCH (opcional)
lifetimeNoDuración de la factura en segundos, de 300 a 43200, 3600 por defecto; cualquier valor inferior a 600 cuenta como 600
accuracy_payment_percentNoCuánto pago insuficiente se sigue considerando pagado, de 0 a 5, 0 por defecto. 100 = aceptar lo que llegue: paid en cuanto se confirma (paid_over si es más), y en el webhook se acredita según payment_amount_usd; ideal para recargas. Si se omite, se aplica la “Regla de importe” de “Ajustes › Reglas de cobro y tarifas” en el panel: “acreditar lo que llegue” equivale a 100
subtractNoParte de la comisión de plataforma que paga el cliente, de 0 a 100, sumada a payer_amount (la comisión de plataforma es 1%, menor con más volumen; con 1% y un valor de 100, una factura de $100 le cuesta al cliente $101.00; consulta los precios). Si se omite, se aplica el ajuste “Comisiones y pagos de menos” de “Ajustes › Reglas de cobro y tarifas” en el panel (0 por defecto)
is_payment_multipleNoSi se puede completar un pago insuficiente, true por defecto: estado wrong_amount_waiting; al pagar el resto a la misma dirección pasa a paid (si en el primer pago insuficiente quedan menos de 30 minutos, el plazo se traslada a 30 minutos después). false: la primera transferencia insuficiente deja la factura en wrong_amount. Si se omite, se aplica el ajuste del panel
additional_dataNoCualquier cadena (≤ 255), devuelta tal cual en las consultas y los webhooks
json
{
  "state": 0,
  "result": {
    "uuid": "6f1c2a7e-…",
    "order_id": "order_1042",
    "amount": "42.00",
    "currency": "USD",
    "network": null,
    "address": null,
    "payer_amount": null,
    "payment_amount": null,
    "url": "https://blockwallets.io/pay/inv_01J8…",
    "expired_at": 1760965200,
    "status": "check",
    "is_final": false
  }
}

network, address y payer_amount se rellenan cuando el cliente elige una red.

3. Consultar facturas

Los webhooks son el canal principal; las consultas son el plan B (por ejemplo, si tu servidor estuvo caído y perdió un webhook).

4. Billeteras estáticas POST /v1/wallet

Abre una dirección permanente para un cliente en una red. El mismo order_id (normalmente tu id de usuario) + red da siempre la misma dirección; todas las cadenas EVM (BNB Chain, Ethereum, Base, Polygon, Arbitrum, Optimism, Avalanche, X Layer) comparten una dirección, mientras que TRON, BTC, LTC, DOGE, BCH, Solana y TON tienen cada una la suya. Todo lo que envíe el cliente se acredita, con un webhook por transferencia; las transferencias en BTC / LTC / DOGE / BCH / TRX / BNB / ETH / POL / AVAX / OKB / SOL / TON se convierten a dólares estadounidenses con la tasa del momento en que se vio cada una (payment_amount_usd).

json
{
  "order_id": "user_42",
  "currency": "USDT",
  "network": "tron",
  "url_callback": "https://shop.example/webhooks/crypto"
}
json
{
  "state": 0,
  "result": {
    "wallet_uuid": "0b9d…",
    "uuid": "5e2f…",
    "order_id": "user_42",
    "currency": "USDT",
    "network": "tron",
    "address": "TXy4…8kQ2",
    "url": "https://blockwallets.io/deposit/dac_01J8…"
  }
}

result.url es una página de depósito alojada (dirección + código QR + depósitos recientes) que puedes entregar al cliente. Las transferencias por debajo del mínimo de la red (normalmente $1, BTC $5) no se acreditan automáticamente; contáctanos para que las acreditemos. Bloquear direcciones (/v1/wallet/block-address) aún no está disponible.

Webhooks

Cuando cambia el estado de una factura (paid, paid_over, wrong_amount_waiting, wrong_amount, cancel) y por cada transferencia a una billetera estática, enviamos un POST con JSON a url_callback. Una respuesta 2xx cuenta como entregado; si no, reintentamos tras 1 minuto, 5 minutos, 30 minutos, 2 horas, 12 horas, 24 horas y 24 horas. También puedes reenviar desde el panel o con /v1/payment/resend.

json
{
  "type": "payment",
  "uuid": "6f1c2a7e-…",
  "order_id": "order_1042",
  "amount": "42.00",
  "payment_amount": "42",
  "payment_amount_usd": "42.00",
  "merchant_amount": "41.58",
  "commission": "0.42",
  "is_final": true,
  "status": "paid",
  "from": "TQ8m…",
  "wallet_address_uuid": null,
  "network": "tron",
  "currency": "USD",
  "payer_currency": "USDT",
  "payer_amount": "42",
  "payer_amount_exchange_rate": "1",
  "additional_data": null,
  "txid": "9c1e…",
  "sign": "5f0c9b…"
}
typeCuándoAcreditación
paymentCambió el estado de una factura: consulta statusAcredita payment_amount_usd con paid / paid_over; deduplica por uuid
walletLlegó una transferencia a una billetera estática (status es siempre paid; order_id es el que usaste al abrir la billetera)Acredita payment_amount_usd; cada transferencia tiene su propio uuid: deduplica por él

Verificar la firma (obligatorio)

La firma es el campo sign del cuerpo del webhook: quita sign, vuelve a codificar el resto como lo hace json_encode de PHP (compacto, / escapada como \/, sin escapar los caracteres no ASCII), y md5(base64(encoded) + API key) debe ser igual a sign. Usa la API key del mismo modo. Un mismo webhook puede llegar más de una vez: deduplica por uuid + status.

Si la verificación falla, pega el cuerpo bruto del webhook en el verificador de firmas: muestra el resultado de cada paso y marca dónde difiere el tuyo (todo se ejecuta en tu navegador; la API key nunca se sube). Cada webhook de “Desarrolladores › Registro de webhooks” en el panel se abre en el verificador con un clic.

Node.js
import crypto from "node:crypto";

function verify(rawBody, apiKey) {
  const { sign, ...data } = JSON.parse(rawBody);
  const json = JSON.stringify(data).replaceAll("/", "\\/");
  const expected = crypto.createHash("md5").update(Buffer.from(json).toString("base64") + apiKey).digest("hex");
  return typeof sign === "string" && sign.length === expected.length && crypto.timingSafeEqual(Buffer.from(sign), Buffer.from(expected));
}
PHP
$data = json_decode(file_get_contents('php://input'), true);
$sign = $data['sign'] ?? '';
unset($data['sign']);
$expected = md5(base64_encode(json_encode($data, JSON_UNESCAPED_UNICODE)) . $apiKey);
if (!hash_equals($expected, $sign)) {
    http_response_code(401); exit;
}
Python
import base64, hashlib, hmac, json

def verify(raw_body: bytes, api_key: str) -> bool:
    data = json.loads(raw_body)
    sign = data.pop("sign", "")
    encoded = json.dumps(data, separators=(",", ":"), ensure_ascii=False).replace("/", "\\/")
    expected = hashlib.md5((base64.b64encode(encoded.encode()).decode() + api_key).encode()).hexdigest()
    return hmac.compare_digest(expected, sign)

Modo de prueba

Las facturas creadas con el merchant y la API key de Test funcionan en testnets, sin dinero real. La página de pago muestra un botón “Simular pago” (completo / mitad / de más) para que veas los cambios de estado y los webhooks sin necesitar monedas de testnet.

Migrar desde Cryptomus

  1. Panel “Desarrolladores › Integración y claves” → genera un merchant y una API key.
  2. Sustituye https://api.cryptomus.com en tu código por https://blockwallets.io y usa el nuevo merchant y la nueva API key.
  3. Añade tus dominios en “Dominios del sitio web” (url_callback / url_return / url_success solo aceptan estos).

Compatibles: /v1/payment, /v1/payment/info, /v1/payment/list, /v1/payment/services, /v1/payment/resend, /v1/wallet, /v1/test-webhook/payment, /v1/test-webhook/wallet. Diferencias: las facturas se fijan solo en USD / USDT / USDC (todas 1:1, sin diferencial de cambio); commission es nuestra comisión de plataforma (el importe recibido × la tarifa del comercio cuando se creó la factura: 1% estándar, hasta 0.5% según el volumen; no se cobra dos veces la parte que cubre el cliente) y merchant_amount = recibido − commission; cuando el cliente paga en BTC / LTC / DOGE / BCH / TRX / BNB / ETH / POL / AVAX / OKB / SOL / TON cotizamos la mediana de varios exchanges, bloqueada 30 minutos: payer_amount es el número de monedas a enviar, payer_amount_exchange_rate es el valor en USD de una moneda, y el pago se convierte a payment_amount_usd con la tasa bloqueada (las monedas se quedan con nosotros, no se cambia nada); redes tron / bsc / eth / base / polygon / arbitrum / optimism / avalanche / xlayer / btc / ltc / doge / bch / sol / ton; duración mínima de la factura de 10 minutos; sin is_refresh y sin bloqueo de billeteras estáticas; por defecto los webhooks salen desde IP cambiantes; si usas una lista de IP permitidas, activa “Webhooks desde IP fija” en el panel, en “Desarrolladores › Integración y claves”, y los webhooks solo saldrán desde 18.163.145.41: una lista de permitidos es una capa extra, sigue verificando sign.

¿Vienes de NOWPayments o Coinbase Commerce? Tenemos APIs compatibles con ambos: cambia la URL base y las claves; consulta la guía de migración.

Redes e importes

MonedaRedParámetro networkMínimoLlegada
USDTTRONtron$1~1 min
USDTBNB Chainbsc$1~1 min
USDCBNB Chainbsc$1~1 min
USDTEthereumeth$5~1 min
USDCEthereumeth$5~1 min
USDCBasebase$1~1 min
USDTPolygonpolygon$1~1 min
USDCPolygonpolygon$1~1 min
USDTArbitrumarbitrum$1~1 min
USDCArbitrumarbitrum$1~1 min
USDTOptimismoptimism$1~1 min
USDCOptimismoptimism$1~1 min
USDTAvalancheavalanche$1~1 min
USDCAvalancheavalanche$1~1 min
USDTX Layerxlayer$1~1 min
USDCX Layerxlayer$1~1 min
USD1BNB Chainbsc$1~1 min
FDUSDBNB Chainbsc$1~1 min
DAIEthereumeth$5~1 min
DAIArbitrumarbitrum$1~1 min
DAIBasebase$1~1 min
BTCBitcoinbtc$5~10 min
LTCLitecoinltc$1~3 min
DOGEDogecoindoge$1~1 min
BCHBitcoin Cashbch$1~10 min
TRXTRONtron$1~1 min
BNBBNB Chainbsc$1~1 min
ETHEthereumeth$5~1 min
ETHBasebase$1~1 min
ETHArbitrumarbitrum$1~1 min
ETHOptimismoptimism$1~1 min
POLPolygonpolygon$1~1 min
AVAXAvalancheavalanche$1~1 min
OKBX Layerxlayer$1~1 min
USDTSolanasol$1~1 min
USDCSolanasol$1~1 min
SOLSolanasol$1~1 min
USDTTONton$1~1 min
TONTONton$1~1 min

Las comisiones de ERC-20 son altas, así que el mínimo es $5; las de Bitcoin tampoco son bajas, mínimo $5; todo lo demás es $1. Los clientes deben enviar por la red que muestra la página de pago: el dinero enviado por otra red no se acredita automáticamente. BTC / LTC / DOGE / BCH aparecen como “detectado” en cuanto están en el mempool y cuentan cuando tienen suficientes confirmaciones; las transacciones reemplazadas (RBF) o descartadas no cuentan. Las monedas nativas (TRX, BNB, ETH, POL, AVAX, OKB, SOL, TON) se cotizan a una tasa bloqueada y se acreditan en dólares estadounidenses como BTC / LTC / DOGE / BCH; indica red y moneda: en facturas network: "bsc", to_currency: "BNB", en billeteras estáticas network: "bsc", currency: "BNB". Una red sola equivale por defecto a USDT en esa cadena (USDC en Base). También se reciben las transferencias hechas por contratos (multifirma, pagos por lotes); en algunas cadenas no está disponible su hash de transacción y txid en el webhook es null.

Errores

json
{ "state": 1, "message": "amount must be a number like 10.28" }
HTTPCausas habituales
422Parámetros ausentes o mal formados; importe inferior al mínimo de la red; URL de callback / redirección fuera de tus dominios registrados; moneda / red no admitida en /v1/payment
401Merchant incorrecto, sign incorrecta (Invalid Sign.) o una API key rotada
404La factura no existe o no es tuya (Payment not found); en /v1/wallet, combinación de moneda / red no admitida (The service was not found)
503La red no está disponible temporalmente: elige otra o no preselecciones ninguna

Cuando la firma es incorrecta (Invalid Sign.) la respuesta incluye un objeto debug: body_base64 es el base64 del cuerpo de la petición que recibimos (tu propio contenido, nunca la API key) y body_bytes su longitud. Compáralo con el base64 que firmaste: si difieren, el cuerpo que enviaste no es la cadena que firmaste (tu framework lo volvió a serializar, un proxy cambió la codificación, hay un salto de línea al final); si coinciden, la API key es incorrecta. Pégalo en el verificador de firmas para ver la primera diferencia.

json
{
  "state": 1,
  "message": "Invalid Sign.",
  "debug": {
    "body_base64": "eyJhbW91bnQiOiI0Mi4wMCIsImN1cnJlbmN5IjoiVVNEIiwi…",
    "body_bytes": 158,
    "sign_formula": "md5(body_base64 + API key)"
  }
}

Preguntas frecuentes

Mis clientes recargan el importe que quieran: ¿qué modo uso?

Facturas. Deja que el cliente escriba un importe en tu página de recarga, crea una factura por ese importe y acredita payment_amount_usd cuando llegue. Usa billeteras estáticas solo si los clientes deben guardar una dirección y enviar a ella repetidamente.

¿Qué pasa si un cliente paga de menos?

Antes de que caduque, el cliente puede enviar la diferencia desde la página de pago (muestra cuánto falta; estado wrong_amount_waiting, y si en el primer pago insuficiente quedan menos de 30 minutos, el plazo se traslada a 30 minutos después). Si al caducar sigue faltando, la factura pasa a wrong_amount y payment_amount_usd en el webhook es el importe recibido; los negocios de recargas pueden acreditar simplemente eso.

¿Y si un cliente paga tarde, o dos veces?

El dinero que llega después de caducar se acredita y se notifica igualmente; el dinero que llega cuando la factura ya está pagada por completo también se registra y se ve en el panel.

¿Y si un cliente envía por la red equivocada?

Por ejemplo, la factura es en TRON y envía desde BNB Chain: no se acredita automáticamente; contáctanos y lo gestionamos manualmente.

¿Cuánto cuesta?

1% de cada pago recibido, reducido automáticamente según el volumen del mes anterior (0.9% desde $50,000, 0.8% desde $100,000, 0.7% desde $250,000, 0.6% desde $500,000 y 0.5% desde $1,000,000), sin cuota mensual ni de alta. El cliente paga la comisión de red al enviar; para trasladarle esa comisión, define subtract al crear una factura o como valor por defecto en el panel. Los retiros no tienen comisión adicional. Consulta los precios.

¿Cuándo me llega el dinero?

Se acredita en tu saldo en cuanto se confirma el pago. Solicita un retiro a tu propia billetera en “Fondos › Saldo y retiros”; cuando lo aprobamos, hacemos el pago y la transacción en la cadena aparece en tu historial de retiros.

También puedes activar el retiro automático: cuando tu saldo disponible alcance un importe que fijes, o una vez al día a las 00:00 UTC, se retira todo el saldo disponible. Los retiros automáticos se revisan igual, y cada cambio de ajustes envía un aviso a tu correo.