# API de blockwallets compatible con Cryptomus

> La API de blockwallets compatible con Cryptomus: facturas y billeteras estáticas, firma, webhooks y verificación, modo de prueba, redes, errores y migración desde Cryptomus cambiando el dominio.
>
> HTML: https://blockwallets.io/docs/cryptomus?lang=es · English: https://blockwallets.io/docs/cryptomus.md?lang=en

## 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 integraciones**Facturas**
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`

Opcional**Billeteras 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](https://blockwallets.io/docs/cryptomus?lang=es#links) del panel son facturas creadas por ti automáticamente. ¿Aún no eres comercio? Solicita acceso en la [página de inicio](https://blockwallets.io/?lang=es#join); cuando te aprueben, entra en el [panel del comercio](https://blockwallets.io/dashboard) 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.

En resumen, cinco pasos:

- 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í.

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

- 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).

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

- El dinero se acredita en tu saldo; retíralo a tu propia billetera desde el panel del comercio.

#### Estado de la factura

| status | Significado | Qué haces tú |
| --- | --- | --- |
| `check` | Pendiente de pago | — |
| `confirm_check` | Transferencia detectada, esperando confirmaciones | — |
| `wrong_amount_waiting` | Recibido, 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) |
| `paid` | Pagada | Envía / acredita según `payment_amount_usd` |
| `paid_over` | Pagada, con pago de más | Igual (`payment_amount_usd` es el importe recibido) |
| `wrong_amount` | Caducada 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 |
| `cancel` | Caducada 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).

### Enlaces de pago (sin código)

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.

- Cada vez que alguien abre el enlace se crea una factura nueva, así que un mismo enlace se puede reutilizar (p. ej. “Membresía $99”).

- Cada pago aparece en “Cobros › Transacciones”; retíralo a tu billetera desde “Fondos › Saldo y retiros”.

- Si has creado credenciales y una URL de webhook en “Desarrolladores › Integración y claves”, los pagos hechos con enlaces también se notifican con el formato descrito más abajo.

- Para dejar de aceptar pagos, pulsa “Desactivar” y el enlace deja de funcionar de inmediato.

### 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:

- `merchant`: el UUID de tu merchant (esto decide si es Live o Test)

- `sign`: `md5(base64(request body) + API key)`, donde el cuerpo es exactamente la cadena JSON que envías

Si todo va bien devuelve `{"state": 0, "result": {…}}`; si falla, `{"state": 1, "message": "…"}` (consulta [Errores](https://blockwallets.io/docs/cryptomus?lang=es#errors)).

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

```nodejs
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ámetro | Obligatorio | Descripción |
| --- | --- | --- |
| `amount` | Sí | 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](https://blockwallets.io/docs/cryptomus?lang=es#networks)) |
| `currency` | Sí | `USD`, `USDT` o `USDC`, todas 1:1 |
| `order_id` | Sí | 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_callback` | No | URL del webhook. Si la omites, no se envía ningún webhook |
| `url_return` | No | A dónde lleva “Volver” antes de pagar |
| `url_success` | No | A dónde va el cliente después de pagar |
| `network` | No | Preselecciona 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_currency` | No | Moneda en esa red: `USDT` / `USDC`; en `btc` / `ltc` / `doge` / `bch` es `BTC` / `LTC` / `DOGE` / `BCH` (opcional) |
| `lifetime` | No | Duración de la factura en segundos, de 300 a 43200, 3600 por defecto; cualquier valor inferior a 600 cuenta como 600 |
| `accuracy_payment_percent` | No | Cuá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 |
| `subtract` | No | Parte 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](https://blockwallets.io/pricing?lang=es)). 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_multiple` | No | Si 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_data` | No | Cualquier 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

- `POST /v1/payment/info` `{"order_id": "…"}` o `{"uuid": "…"}`: obtiene una factura y comprueba la cadena de paso (como máximo una vez cada 10 segundos).

- `POST /v1/payment/list`: listado, 15 por página, con `date_from` / `date_to` opcionales; la página siguiente se pide con el `paginate.nextCursor` devuelto (`?cursor=…`).

- `POST /v1/payment/services`: redes disponibles y el importe mínimo de cada una.

- `POST /v1/payment/resend` `{"order_id": "…"}`: reenvía el webhook de una factura pagada o con pago insuficiente.

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…"
}
```

| type | Cuándo | Acreditación |
| --- | --- | --- |
| `payment` | Cambió el estado de una factura: consulta `status` | Acredita `payment_amount_usd` con `paid` / `paid_over`; deduplica por `uuid` |
| `wallet` | Llegó 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](https://blockwallets.io/docs/sign?lang=es): 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.

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

- `POST /v1/test-webhook/payment`, `/v1/test-webhook/wallet`: envían un webhook de ejemplo firmado a `url_callback` (pasa `url_callback`, obligatorio y en uno de tus dominios registrados, además de `currency`, `network` y, opcionalmente, `status` y `order_id`) para depurar tu código de verificación.

- Panel “Desarrolladores › Integración y claves” → “Enviar una notificación de prueba”: envía un ejemplo a tu URL de webhook.

- Panel “Desarrolladores › Registro de webhooks”: cada intento de entrega con su hora, URL de destino, estado HTTP, duración, cuerpo de la petición y la respuesta de tu servidor (primeros 4 KB); filtra por pedido o estado y reenvía con un clic. Se conserva durante 30 días.

### Migrar desde Cryptomus

- Panel “Desarrolladores › Integración y claves” → genera un merchant y una API key.

- Sustituye `https://api.cryptomus.com` en tu código por `https://blockwallets.io` y usa el nuevo merchant y la nueva API key.

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

### Redes e importes

| Moneda | Red | Parámetro network | Mínimo | Llegada |
| --- | --- | --- | --- | --- |
| USDT | TRON | `tron` | $1 | ~1 min |
| USDT | BNB Chain | `bsc` | $1 | ~1 min |
| USDC | BNB Chain | `bsc` | $1 | ~1 min |
| USDT | Ethereum | `eth` | $5 | ~1 min |
| USDC | Ethereum | `eth` | $5 | ~1 min |
| USDC | Base | `base` | $1 | ~1 min |
| USDT | Polygon | `polygon` | $1 | ~1 min |
| USDC | Polygon | `polygon` | $1 | ~1 min |
| USDT | Arbitrum | `arbitrum` | $1 | ~1 min |
| USDC | Arbitrum | `arbitrum` | $1 | ~1 min |
| USDT | Optimism | `optimism` | $1 | ~1 min |
| USDC | Optimism | `optimism` | $1 | ~1 min |
| USDT | Avalanche | `avalanche` | $1 | ~1 min |
| USDC | Avalanche | `avalanche` | $1 | ~1 min |
| USDT | X Layer | `xlayer` | $1 | ~1 min |
| USDC | X Layer | `xlayer` | $1 | ~1 min |
| USD1 | BNB Chain | `bsc` | $1 | ~1 min |
| FDUSD | BNB Chain | `bsc` | $1 | ~1 min |
| DAI | Ethereum | `eth` | $5 | ~1 min |
| DAI | Arbitrum | `arbitrum` | $1 | ~1 min |
| DAI | Base | `base` | $1 | ~1 min |
| BTC | Bitcoin | `btc` | $5 | ~10 min |
| LTC | Litecoin | `ltc` | $1 | ~3 min |
| DOGE | Dogecoin | `doge` | $1 | ~1 min |
| BCH | Bitcoin Cash | `bch` | $1 | ~10 min |
| TRX | TRON | `tron` | $1 | ~1 min |
| BNB | BNB Chain | `bsc` | $1 | ~1 min |
| ETH | Ethereum | `eth` | $5 | ~1 min |
| ETH | Base | `base` | $1 | ~1 min |
| ETH | Arbitrum | `arbitrum` | $1 | ~1 min |
| ETH | Optimism | `optimism` | $1 | ~1 min |
| POL | Polygon | `polygon` | $1 | ~1 min |
| AVAX | Avalanche | `avalanche` | $1 | ~1 min |
| OKB | X Layer | `xlayer` | $1 | ~1 min |
| USDT | Solana | `sol` | $1 | ~1 min |
| USDC | Solana | `sol` | $1 | ~1 min |
| SOL | Solana | `sol` | $1 | ~1 min |
| USDT | TON | `ton` | $1 | ~1 min |
| TON | TON | `ton` | $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" }
```

| HTTP | Causas habituales |
| --- | --- |
| 422 | Pará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` |
| 401 | Merchant incorrecto, `sign` incorrecta (`Invalid Sign.`) o una API key rotada |
| 404 | La 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`) |
| 503 | La 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](https://blockwallets.io/docs/sign?lang=es) 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](https://blockwallets.io/pricing?lang=es).

#### ¿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.
