API REST nativa
La API nativa es la interfaz REST propia de blockwallets: facturas con precio en centavos de dólar, un ref idempotente por pedido, cuentas de depósito estáticas que cubren todas las redes y webhooks firmados con HMAC-SHA256. Crea las mismas facturas en la misma página de pago alojada que la API compatible con Cryptomus, con las mismas comisiones y el mismo saldo.
Genera tus claves tú mismo en el panel, en “Desarrolladores › Integración y claves”: una clave Live y una Test con sus claves de firma de webhooks. Cada una se muestra una sola vez; la lista solo muestra su prefijo. Si tu cuenta tiene la verificación en dos pasos activada, generar o rotar una clave Live pide un código. ¿Aún no tienes cuenta de comercio? Solicita acceso. ¿Ya usas Cryptomus? Consulta la API compatible con Cryptomus (cómo elegir).
Claves y peticiones
URL base https://blockwallets.io. Las peticiones y respuestas son JSON. Cada petición lleva:
Authorization: Bearer bw_live_…oBearer bw_test_…: el prefijo decide el modo, así que una clave Test solo ve facturas Test y funciona en testnetsContent-Type: application/jsonen las peticionesPOST- Guarda las claves solo en tu servidor, nunca en una página web ni en una app; nosotros solo almacenamos su hash
Los errores devuelven un estado HTTP y {"error": {"code": "…", "message": "…"}}: 400 invalid_request (un campo ausente o mal formado), 401 unauthorized (clave ausente, desconocida o desactivada), 404 not_found, 409 conflict (la petición choca con el estado actual de la factura), 422 currency_unsupported.
Endpoints
| Endpoint | Qué hace |
|---|---|
GET /v1/networks | Redes que puedes aceptar ahora mismo en el modo de esta clave: mínimo, confirmaciones, tiempo de llegada habitual y comisión estimada; las monedas con precio incluyen la tasa actual |
POST /v1/invoices | Crea una factura (201), o devuelve la existente para el mismo ref (200) |
GET /v1/invoices?ref=… | Consulta una factura por tu ref |
GET /v1/invoices/:id | Consulta una factura por id |
POST /v1/invoices/:id/network | Elige tú mismo la red de una factura (página de pago de marca blanca): devuelve la address y el pay_amount que debes mostrar |
POST /v1/invoices/:id/close | Cierra una factura pendiente que no ha recibido ninguna transferencia |
POST /v1/deposit-accounts | Abre (201) u obtiene (200) la cuenta de depósito de un cliente: una dirección permanente en cada red |
GET /v1/deposit-accounts?customer_ref=… | Consulta una cuenta de depósito por customer_ref |
GET /v1/deposit-accounts/:id | Consulta una cuenta de depósito por id |
GET /v1/deposits | Lista los depósitos de una cuenta (account_id o customer_ref, limit 1–200, por defecto 50) |
GET /v1/deposits/:id | Consulta un depósito |
GET /v1/events | Envíos de webhook de una factura o un depósito (invoice_id o deposit_id) |
POST /v1/events/:id/redeliver | Vuelve a enviar un webhook (202) |
Añade ?probe=1 a la consulta de una factura o de una cuenta de depósito para comprobar la cadena en el momento (como máximo una vez cada 10 segundos); si no, obtienes el último estado de nuestro escáner, que revisa cada minuto.
Crear una factura POST /v1/invoices
Llama a POST /v1/invoices cuando el cliente haga el pedido y redirígelo a la pay_url de la respuesta.
curl -X POST https://blockwallets.io/v1/invoices \
-H "Authorization: Bearer $BW_KEY" -H "Content-Type: application/json" \
-d '{"ref":"order_1042","amount":4200,"title":"Order #1042",
"return_url":"https://shop.example/cart","success_url":"https://shop.example/thanks?order=1042",
"meta":{"user_id":42}}'| Campo | Obligatorio | Descripción |
|---|---|---|
ref | Sí | Tu número de pedido: 1–64 caracteres entre letras, dígitos y _ . : -. Único por modo; si envías el mismo ref con los mismos campos se devuelve la misma factura, y con campos distintos devuelve 409 |
amount | Sí | Importe en centavos de dólar como entero: 4200 = $42.00. No inferior al mínimo de la red una vez elegida |
title | Sí | Lo que ve el cliente en la página de pago, hasta 64 caracteres |
return_url | Sí | A dónde lleva “Volver” en la página de pago; debe estar en uno de los dominios de tu sitio web |
success_url | No | A dónde va el cliente después de pagar; por defecto, return_url |
network | No | Preselecciona un id de red de la tabla de redes; omítelo para que el cliente elija en la página de pago |
customer_ref | No | El id de tu cliente, hasta 64 caracteres, devuelto tal cual |
lifetime | No | Duración en segundos, 600 – 43200, por defecto 3600 |
amount_rule | No | "any" = acredita lo que llegue (recargas): paid en cuanto se confirma. Omítelo para usar la “Regla de importe” de “Ajustes › Reglas de cobro y tarifas” en el panel |
accuracy_percent | No | Cuánto pago de menos se sigue considerando pagado, 0 – 5 (por ciento), por defecto 0 |
subtract_percent | No | Parte de la comisión de la plataforma que paga el cliente, 0 – 100 (por ciento), añadida al pay_amount; omítelo para usar el valor por defecto del panel |
payment_multiple | No | Si un pago insuficiente se puede completar en la misma dirección; por defecto, el valor del panel (activado salvo que lo cambies); false: la primera transferencia insuficiente termina como wrong_amount |
meta | No | Cualquier objeto JSON de hasta 2 KB, devuelto tal cual en las consultas y los webhooks |
currency | No | Solo USD (el valor por defecto); cualquier otro devuelve 422 |
Cada URL debe estar en uno de los dominios de tu sitio web (panel → “Desarrolladores › Integración y claves”). La respuesta es el objeto factura de abajo; network, address y pay_amount se rellenan cuando se elige una red.
El objeto factura
Los ids empiezan por inv_. amount está en centavos de dólar; pay_amount y paid_amount son importes de la moneda, como cadenas decimales, en la red elegida.
{
"id": "inv_01J8…",
"ref": "order_1042",
"mode": "live",
"status": "pending",
"amount": 4200,
"currency": "USD",
"title": "Order #1042",
"meta": { "user_id": 42 },
"network": null,
"address": null,
"pay_amount": null,
"paid_amount": null,
"payments": [],
"pay_url": "https://blockwallets.io/pay/inv_01J8…",
"return_url": "https://shop.example/cart",
"success_url": "https://shop.example/thanks?order=1042",
"created_at": "2026-11-03T08:00:00.000Z",
"expires_at": "2026-11-03T09:00:00.000Z",
"paid_at": null,
"closed_at": null
}| status | Significado | Qué haces tú |
|---|---|---|
pending | Pendiente de pago | — |
confirming | Se detectó una transferencia y se está confirmando, o llegó incompleta y aún se puede completar | — |
paid | Pagada | Envía / acredita el pedido |
paid_over | Pagada, con pago de más | Igual; el extra está en tu saldo |
wrong_amount | Terminó con menos del importe total (al caducar, o en la primera transferencia insuficiente cuando payment_multiple es false) | Los negocios de recargas pueden acreditar lo que llegó (paid_amount; las monedas con tasa también traen paid_cents); las tiendas piden al cliente que pague el resto |
closed | Caducó sin recibir nada, o la cerraste tú | — (el dinero que llegue más tarde se acredita y se notifica igualmente) |
Otros campos: pay_url (la página de pago alojada), payments (cada transferencia con txid, amount, confirmations y confirmed), expires_at, paid_at, closed_at; BTC, LTC, DOGE, BCH y las monedas nativas también incluyen rate (dólares por moneda, bloqueada durante 30 minutos), rate_locked_until y paid_cents (lo recibido hasta ahora, en centavos de dólar).
Redes
Pasa el id como network. GET /v1/networks devuelve las abiertas ahora mismo en tu modo; una red puede estar en pausa, así que es mejor consultarla que fijarla en el código.
| network | Moneda | Cadena | Mínimo |
|---|---|---|---|
USDT_TRC20 | USDT | TRON | $1 |
USDT_BEP20 | USDT | BNB Chain | $1 |
USDC_BEP20 | USDC | BNB Chain | $1 |
USDT_ERC20 | USDT | Ethereum | $5 |
USDC_ERC20 | USDC | Ethereum | $5 |
USDC_BASE | USDC | Base | $1 |
USDT_POLYGON | USDT | Polygon | $1 |
USDC_POLYGON | USDC | Polygon | $1 |
USDT_ARBITRUM | USDT | Arbitrum | $1 |
USDC_ARBITRUM | USDC | Arbitrum | $1 |
USDT_OPTIMISM | USDT | Optimism | $1 |
USDC_OPTIMISM | USDC | Optimism | $1 |
USDT_AVALANCHE | USDT | Avalanche | $1 |
USDC_AVALANCHE | USDC | Avalanche | $1 |
USDT_XLAYER | USDT | X Layer | $1 |
USDC_XLAYER | USDC | X Layer | $1 |
USD1_BEP20 | USD1 | BNB Chain | $1 |
FDUSD_BEP20 | FDUSD | BNB Chain | $1 |
DAI_ERC20 | DAI | Ethereum | $5 |
DAI_ARBITRUM | DAI | Arbitrum | $1 |
DAI_BASE | DAI | Base | $1 |
BTC | BTC | Bitcoin | $5 |
LTC | LTC | Litecoin | $1 |
DOGE | DOGE | Dogecoin | $1 |
BCH | BCH | Bitcoin Cash | $1 |
TRX | TRX | TRON | $1 |
BNB | BNB | BNB Chain | $1 |
ETH | ETH | Ethereum | $5 |
ETH_BASE | ETH | Base | $1 |
ETH_ARBITRUM | ETH | Arbitrum | $1 |
ETH_OPTIMISM | ETH | Optimism | $1 |
POL | POL | Polygon | $1 |
AVAX | AVAX | Avalanche | $1 |
OKB | OKB | X Layer | $1 |
USDT_SOL | USDT | Solana | $1 |
USDC_SOL | USDC | Solana | $1 |
SOL | SOL | Solana | $1 |
USDT_TON | USDT | TON | $1 |
TON | TON | TON | $1 |
Cuentas de depósito (direcciones estáticas) POST /v1/deposit-accounts
POST /v1/deposit-accounts con {"customer_ref": "user_42"} da a ese cliente una dirección permanente en cada red (todas las cadenas EVM comparten una dirección). Todo lo que envíe se acredita en dólares estadounidenses, con un webhook deposit.credited por transferencia.
curl -X POST https://blockwallets.io/v1/deposit-accounts \
-H "Authorization: Bearer $BW_KEY" -H "Content-Type: application/json" \
-d '{"customer_ref":"user_42"}'{
"id": "dac_01J8…",
"mode": "live",
"customer_ref": "user_42",
"deposit_url": "https://blockwallets.io/deposit/dac_01J8…",
"min_amount": 100,
"currency": "USD",
"networks": [
{ "id": "USDT_TRC20", "chain": "tron", "token": "USDT", "address": "TXy4…8kQ2", "min_amount": 100, "confirmations": 1, … },
{ "id": "USDC_BASE", "chain": "base", "token": "USDC", "address": "0x5aE1…", "min_amount": 100, "confirmations": 20, … }
]
}customer_ref: 1–128 caracteres entre letras, dígitos y_ . : @ -; el mismo valor devuelve siempre la misma cuenta.return_urlopcional en tus dominios- La respuesta incluye
deposit_url(una página alojada con las direcciones, los códigos QR y los depósitos recientes) ynetworks(id,address,min_amountyconfirmationsde cada una) - Los ids de depósito empiezan por
dep_;amountestá en centavos de dólar,amount_tokenes el importe en la moneda y las monedas con precio incluyen larateusada
Las transferencias por debajo del mínimo (normalmente $1, BTC $5) se registran como below_min y no se acreditan automáticamente; contáctanos para que las acreditemos.
Webhooks
Enviamos un POST con JSON a la URL de webhook del panel (“Desarrolladores › Integración y claves”; Live y Test tienen cada uno su propia URL y su propio secret). El cuerpo es el evento:
POST /your/webhook
X-BW-Event: ev_01J8…
X-BW-Delivery: 1
X-BW-Signature: t=1793693052,v1=5f0c9b…
{"id":"ev_01J8…","type":"invoice.paid","created_at":"2026-11-03T08:04:12.000Z","invoice":{"id":"inv_01J8…","ref":"order_1042","status":"paid","amount":4200,…}}X-BW-Signature:t=<unix seconds>,v1=<hex>, dondev1= HMAC-SHA256(webhook secret de ese modo,t+.+ cuerpo sin procesar)X-BW-Event: el id del evento (ev_…); deduplica por élX-BW-Delivery: el número de intento, empezando en 1
| Evento | Cuándo |
|---|---|
invoice.paid | La factura pasó a paid o paid_over (incluido el dinero que llegó tras caducar) |
invoice.wrong_amount | La factura terminó como wrong_amount |
invoice.closed | La factura caducó sin recibir nada, o la cerraste tú |
deposit.credited | Se acreditó una transferencia a una cuenta de depósito |
Verifica contra el cuerpo sin procesar antes de analizarlo, rechaza una t que difiera más de 300 segundos de tu reloj y compara en tiempo constante. invoice / deposit en el cuerpo es el mismo objeto que devuelve una consulta.
import crypto from "node:crypto";
// rawBody: the request body exactly as received (a string), header: X-BW-Signature
function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(",").map((p) => p.trim().split("=")));
const t = Number(parts.t);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
return typeof parts.v1 === "string" && parts.v1.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}Una respuesta 2xx cuenta como entregada; si no, reintentamos tras 1 minuto, 5 minutos, 30 minutos, 2 horas, 12 horas, 24 horas y 24 horas (8 intentos en total).
Eventos y reenvío
GET /v1/events?invoice_id=inv_… (o deposit_id=dep_…) lista los webhooks de una factura o un depósito con su state, attempts, last_status y last_error. POST /v1/events/ev_…/redeliver lo pone de nuevo en cola al instante, por ejemplo después de corregir tu endpoint. La página “Desarrolladores › Registro de webhooks” del panel muestra cada intento con su petición y su respuesta.
Modo de prueba
Las facturas creadas con una clave bw_test_ funcionan en testnets y nunca tocan dinero real. La página de pago alojada muestra un botón “Simular pago” (completo / mitad / de más), así que puedes ver los estados y los webhooks sin monedas de testnet.