原生 REST 接口

原生接口是 blockwallets 自己的 REST 接口:发票按美分计价,每个订单一个防重的 ref,充值账户一次覆盖所有网络,回调用 HMAC-SHA256 签名。它和 Cryptomus 兼容接口建的是同一种账单、同一个收银台,费率和余额都一样。

密钥在商户后台「开发者 › 接入与密钥」自助生成:正式和测试各一把密钥和对应的回调签名密钥,明文只显示一次,列表里只显示前缀。开了两步验证的账户,生成或轮换正式密钥要输验证码。还没有商户账号?在首页申请开通。已经接过 Cryptomus?看 Cryptomus 兼容接口(怎么选)。

密钥与请求

基础地址 https://blockwallets.io,请求和应答都是 JSON。每个请求带:

出错时回 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;不填让用户在收银台选
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 KB,查询和回调里原样返回
currency否只能是 USD(默认);别的回 422

所有地址都必须在你的网站域名下(后台「开发者 › 接入与密钥」)。应答就是下面的发票对象;选了网络以后才有 network、address 和 pay_amount。

发票对象

id 以 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(1 个币值多少美元,锁定 30 分钟)、rate_locked_until 和 paid_cents(已到账部分折合的美分)。

网络

把 id 填进 network。GET /v1/networks 返回你这个模式下现在开着的网络;网络可能临时暂停,最好读它而不是写死。

network币种链最低
USDT_TRC20USDTTRON$1
USDT_BEP20USDTBNB Chain$1
USDC_BEP20USDCBNB Chain$1
USDT_ERC20USDTEthereum$5
USDC_ERC20USDCEthereum$5
USDC_BASEUSDCBase$1
USDT_POLYGONUSDTPolygon$1
USDC_POLYGONUSDCPolygon$1
USDT_ARBITRUMUSDTArbitrum$1
USDC_ARBITRUMUSDCArbitrum$1
USDT_OPTIMISMUSDTOptimism$1
USDC_OPTIMISMUSDCOptimism$1
USDT_AVALANCHEUSDTAvalanche$1
USDC_AVALANCHEUSDCAvalanche$1
USDT_XLAYERUSDTX Layer$1
USDC_XLAYERUSDCX Layer$1
USD1_BEP20USD1BNB Chain$1
FDUSD_BEP20FDUSDBNB Chain$1
DAI_ERC20DAIEthereum$5
DAI_ARBITRUMDAIArbitrum$1
DAI_BASEDAIBase$1
BTCBTCBitcoin$5
LTCLTCLitecoin$1
DOGEDOGEDogecoin$1
BCHBCHBitcoin Cash$1
TRXTRXTRON$1
BNBBNBBNB Chain$1
ETHETHEthereum$5
ETH_BASEETHBase$1
ETH_ARBITRUMETHArbitrum$1
ETH_OPTIMISMETHOptimism$1
POLPOLPolygon$1
AVAXAVAXAvalanche$1
OKBOKBX Layer$1
USDT_SOLUSDTSolana$1
USDC_SOLUSDCSolana$1
SOLSOLSolana$1
USDT_TONUSDTTON$1
TONTONTON$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, … }
  ]
}

低于最低额(一般 $1,BTC $5)的转账记为 below_min,不自动入账;联系我们人工入账。

回调

我们往商户后台里填的回调地址 POST JSON(「开发者 › 接入与密钥」;Live 和 Test 各有自己的地址和密钥)。请求体就是事件:

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,…}}
事件什么时候
invoice.paid发票变成 paid 或 paid_over(包括过期后才到的钱)
invoice.wrong_amount发票以 wrong_amount 结束
invoice.closed发票到期没收到钱,或者被你关掉了
deposit.credited一笔转进充值账户的钱入账了

用原始请求体验签(别先解析再序列化),和本机时间差超过 300 秒的 t 拒掉,用常量时间比较。请求体里的 invoice / deposit 和查询返回的对象一样。

Node.js
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_ 密钥建的发票走测试网,不碰真钱。托管收银台上有「模拟付款」按钮(全额 / 一半 / 多付),不用测试币也能看到状态变化和回调。