# Собственный REST API blockwallets

> Собственный REST API blockwallets: ключи Bearer, счета в центах США с идемпотентными ref, депозитные аккаунты, вебхуки с подписью HMAC-SHA256, события и повторная отправка.
>
> HTML: https://blockwallets.io/docs/api?lang=ru · English: https://blockwallets.io/docs/api.md?lang=en

## Собственный REST API

Собственный API — это REST-интерфейс blockwallets: счета в центах США, идемпотентный `ref` для каждого заказа, статические депозитные аккаунты на всех сетях и вебхуки с подписью HMAC-SHA256. Он создаёт такие же счета на той же странице оплаты, что и [API, совместимый с Cryptomus](https://blockwallets.io/docs/cryptomus?lang=ru), с теми же комиссиями и балансом.

Ключи вы создаёте сами в кабинете, в разделе «Разработчикам › Интеграция и ключи»: ключ Live и ключ Test и секреты подписи их вебхуков. Каждый показывается один раз, в списке виден только префикс. Если в аккаунте включена двухфакторная аутентификация, для создания или обновления ключа Live нужно ввести код. Ещё нет аккаунта продавца? [Запросите доступ](https://blockwallets.io/?lang=ru#join). Уже работаете с Cryptomus? Посмотрите [API, совместимый с Cryptomus](https://blockwallets.io/docs/cryptomus?lang=ru) ([как выбрать](https://blockwallets.io/docs?lang=ru#which)).

### Ключи и запросы

Базовый URL: `https://blockwallets.io`. Запросы и ответы — JSON. Каждый запрос содержит:

- `Authorization: Bearer bw_live_…` или `Bearer bw_test_…`: префикс определяет режим, поэтому ключ Test видит только счета Test и работает в тестовых сетях

- `Content-Type: application/json` в запросах `POST`

- Храните ключи только на сервере, никогда не размещайте их на веб-странице или в приложении; мы храним только их хеш

Ошибки возвращаются с HTTP-статусом и телом `{"error": {"code": "…", "message": "…"}}`: 400 `invalid_request` (отсутствует поле или оно неверного формата), 401 `unauthorized` (ключ отсутствует, неизвестен или отключён), 404 `not_found`, 409 `conflict` (запрос противоречит текущему состоянию счёта), 422 `currency_unsupported`.

### Эндпоинты

| Эндпоинт | Что делает |
| --- | --- |
| `GET /v1/networks` | Сети, которые можно принимать прямо сейчас в режиме этого ключа: минимум, подтверждения, обычное время поступления и оценка комиссии; для монет с курсом указан текущий курс |
| `POST /v1/invoices` | Создаёт счёт (201) или возвращает существующий для того же `ref` (200) |
| `GET /v1/invoices?ref=…` | Находит счёт по вашему `ref` |
| `GET /v1/invoices/:id` | Находит счёт по id |
| `POST /v1/invoices/:id/network` | Самостоятельный выбор сети для счёта (страница оплаты в вашем оформлении): возвращает `address` и `pay_amount` для показа клиенту |
| `POST /v1/invoices/:id/close` | Закрывает ожидающий оплаты счёт, по которому не было переводов |
| `POST /v1/deposit-accounts` | Открывает (201) или возвращает (200) депозитный аккаунт одного клиента: постоянный адрес в каждой сети |
| `GET /v1/deposit-accounts?customer_ref=…` | Находит депозитный аккаунт по `customer_ref` |
| `GET /v1/deposit-accounts/:id` | Находит депозитный аккаунт по id |
| `GET /v1/deposits` | Список депозитов одного аккаунта (`account_id` или `customer_ref`, `limit` 1–200, по умолчанию 50) |
| `GET /v1/deposits/:id` | Находит один депозит |
| `GET /v1/events` | Доставки вебхуков по одному счёту или депозиту (`invoice_id` или `deposit_id`) |
| `POST /v1/events/:id/redeliver` | Повторно отправляет вебхук (202) |

Добавьте `?probe=1` к запросу счёта или депозитного аккаунта, чтобы заодно проверить блокчейн (не чаще раза в 10 секунд); без него вы получите последнее состояние от нашего сканера, который проверяет каждую минуту.

### Создание счёта POST /v1/invoices

Вызовите `POST /v1/invoices`, когда клиент оформляет заказ, и перенаправьте его на `pay_url` из ответа.

```curl
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}}'
```

| Поле | Обязательное | Описание |
| --- | --- | --- |
| `ref` | Да | Номер вашего заказа: 1–64 символа — буквы, цифры и `_ . : -`. Уникален в пределах режима; повторная отправка того же `ref` с теми же полями возвращает тот же счёт, с другими полями — 409 |
| `amount` | Да | Сумма в центах США целым числом: `4200` = $42.00. После выбора сети не меньше минимума этой сети |
| `title` | Да | Что клиент видит на странице оплаты, до 64 символов |
| `return_url` | Да | Куда ведёт «Назад» на странице оплаты; должен быть на одном из доменов вашего сайта |
| `success_url` | Нет | Куда попадает клиент после оплаты; по умолчанию `return_url` |
| `network` | Нет | Заранее выбрать id сети из [таблицы сетей](https://blockwallets.io/docs/api?lang=ru#networks); если не указать, клиент выберет на странице оплаты |
| `customer_ref` | Нет | Ваш id клиента, до 64 символов, возвращается как есть |
| `lifetime` | Нет | Срок жизни в секундах, 600 – 43200, по умолчанию 3600 |
| `amount_rule` | Нет | `"any"` = зачислять всё, что пришло (пополнения): `paid` сразу после подтверждения. Если не указать, действует «Правило суммы» в разделе «Настройки › Правила приёма платежей и ставки» кабинета |
| `accuracy_percent` | Нет | Какая недоплата ещё считается оплатой, 0 – 5 (процентов), по умолчанию 0 |
| `subtract_percent` | Нет | Доля комиссии платформы, которую платит клиент, 0 – 100 (процентов), добавляется к `pay_amount`; если не указать, действует значение по умолчанию из кабинета |
| `payment_multiple` | Нет | Можно ли доплатить недостающую сумму на тот же адрес; по умолчанию берётся из кабинета (включено, если вы не меняли). `false`: первый же недоплаченный перевод завершается как `wrong_amount` |
| `meta` | Нет | Любой JSON-объект до 2 КБ, возвращается как есть в запросах и вебхуках |
| `currency` | Нет | Только `USD` (по умолчанию); любое другое значение даёт 422 |

Каждый URL должен быть на одном из доменов вашего сайта (кабинет → «Разработчикам › Интеграция и ключи»). Ответ — объект счёта ниже; `network`, `address` и `pay_amount` заполняются после выбора сети.

### Объект счёта

Идентификаторы начинаются с `inv_`. `amount` — в центах США; `pay_amount` и `paid_amount` — количество монет десятичной строкой в выбранной сети.

```json
{
  "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 | Значение | Что делаете вы |
| --- | --- | --- |
| `pending` | Ожидает оплаты | — |
| `confirming` | Перевод виден и подтверждается, либо пришло меньше нужного и можно доплатить | — |
| `paid` | Оплачен | Отгрузите или зачислите заказ |
| `paid_over` | Оплачен с переплатой | То же; излишек на вашем балансе |
| `wrong_amount` | Завершён с суммой меньше полной (по истечении срока или при первом недоплаченном переводе, если `payment_multiple` равен false) | Сервисы с пополнением могут зачислить пришедшее (`paid_amount`; у монет с курсом есть и `paid_cents`); магазины просят клиента доплатить остаток |
| `closed` | Истёк, ничего не получено, или вы закрыли его | — (деньги, пришедшие позже, всё равно зачисляются и приходят в уведомлении) |

Другие поля: `pay_url` (страница оплаты), `payments` (каждый перевод с `txid`, `amount`, `confirmations` и `confirmed`), `expires_at`, `paid_at`, `closed_at`; у BTC, LTC, DOGE, BCH и родных монет есть также `rate` (долларов США за монету, фиксируется на 30 минут), `rate_locked_until` и `paid_cents` (сколько уже пришло, в центах США).

### Сети

Передавайте id как `network`. `GET /v1/networks` возвращает сети, открытые сейчас в вашем режиме; сеть может быть приостановлена, поэтому лучше читать этот список, чем зашивать значения в код.

| network | Монета | Блокчейн | Минимум |
| --- | --- | --- | --- |
| `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 |

### Депозитные аккаунты (статические адреса) POST /v1/deposit-accounts

`POST /v1/deposit-accounts` с `{"customer_ref": "user_42"}` выдаёт этому клиенту постоянный адрес в каждой сети (все сети EVM используют один адрес). Всё, что он отправит, зачисляется в долларах США, по одному вебхуку `deposit.credited` на каждый перевод.

```curl
curl -X POST https://blockwallets.io/v1/deposit-accounts \
  -H "Authorization: Bearer $BW_KEY" -H "Content-Type: application/json" \
  -d '{"customer_ref":"user_42"}'
```

```json
{
  "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 символов — буквы, цифры и `_ . : @ -`; одно и то же значение всегда возвращает один и тот же аккаунт. Необязательный `return_url` на ваших доменах

- В ответе есть `deposit_url` (страница с адресами, QR-кодами и последними депозитами) и `networks` (для каждой сети `id`, `address`, `min_amount`, `confirmations`)

- Идентификаторы депозитов начинаются с `dep_`; `amount` — центы США, `amount_token` — количество монет, а у монет с курсом указан применённый `rate`

Переводы ниже минимума (обычно $1, BTC $5) записываются как `below_min` и автоматически не зачисляются; чтобы их зачислили, свяжитесь с нами.

### Вебхуки

Мы отправляем POST с JSON на URL вебхука из кабинета («Разработчикам › Интеграция и ключи»; у Live и Test свои URL и секрет). Тело — это событие:

```http
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>`, где `v1` = HMAC-SHA256(секрет вебхука этого режима, `t` + `.` + исходное тело)

- `X-BW-Event`: id события (`ev_…`); убирайте дубликаты по нему

- `X-BW-Delivery`: номер попытки, начиная с 1

| Событие | Когда |
| --- | --- |
| `invoice.paid` | Счёт стал `paid` или `paid_over` (включая деньги, пришедшие после истечения срока) |
| `invoice.wrong_amount` | Счёт завершился как `wrong_amount` |
| `invoice.closed` | Счёт истёк, ничего не получено, или вы закрыли его |
| `deposit.credited` | Перевод на депозитный аккаунт зачислен |

Проверяйте по исходному телу до разбора, отклоняйте `t`, отличающийся от ваших часов более чем на 300 секунд, и сравнивайте за постоянное время. `invoice` / `deposit` в теле — тот же объект, что возвращает запрос.

```nodejs
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));
}
```

Ответ 2xx считается доставкой; иначе повторяем через 1 минуту, 5 минут, 30 минут, 2 часа, 12 часов, 24 часа и ещё 24 часа (всего 8 попыток).

### События и повторная отправка

`GET /v1/events?invoice_id=inv_…` (или `deposit_id=dep_…`) показывает вебхуки по одному счёту или депозиту с их `state`, `attempts`, `last_status` и `last_error`. `POST /v1/events/ev_…/redeliver` сразу ставит вебхук в очередь заново, например после того, как вы исправили свой эндпоинт. Раздел «Разработчикам › Журнал вебхуков» в кабинете показывает каждую попытку с запросом и ответом.

### Тестовый режим

Счета, созданные ключом `bw_test_`, работают в тестовых сетях и не затрагивают настоящие деньги. На странице оплаты есть кнопка «Симулировать оплату» (полная / половина / переплата), так что вы увидите смену статусов и вебхуки, не добывая тестовые монеты.
