Native REST API

Open in ChatGPTOpen in Claude

The native API is blockwallets’ own REST interface: invoices priced in US cents, an idempotent ref per order, static deposit accounts covering every network, and webhooks signed with HMAC-SHA256. It creates the same bills on the same hosted checkout as the Cryptomus-compatible API, with the same fees and balance.

Generate your keys yourself in the dashboard under “Developers › Integration & keys”: a Live and a Test key plus their webhook signing secrets. Each is shown once; the list only shows its prefix. If your account has two-factor authentication on, generating or rotating a Live key asks for a code. No merchant account yet? Request access. Already on Cryptomus? See the Cryptomus-compatible API (how to choose).

Keys and requests

Base URL https://blockwallets.io. Requests and responses are JSON. Every request carries:

Errors return an HTTP status and {"error": {"code": "…", "message": "…"}}: 400 invalid_request (a missing or malformed field), 401 unauthorized (missing, unknown or disabled key), 404 not_found, 409 conflict (the request clashes with the invoice’s current state), 422 currency_unsupported.

Endpoints

EndpointWhat it does
GET /v1/networksNetworks you can accept right now in this key’s mode: minimum, confirmations, typical arrival time and fee estimate; priced coins include the current rate
POST /v1/invoicesCreate an invoice (201), or return the existing one for the same ref (200)
GET /v1/invoices?ref=…Look up an invoice by your ref
GET /v1/invoices/:idLook up an invoice by id
POST /v1/invoices/:id/networkPick the network for an invoice yourself (white-label checkout): returns the address and pay_amount to show
POST /v1/invoices/:id/closeClose a pending invoice that has not seen any transfer
POST /v1/deposit-accountsOpen (201) or fetch (200) the deposit account of one customer: a permanent address on every network
GET /v1/deposit-accounts?customer_ref=…Look up a deposit account by customer_ref
GET /v1/deposit-accounts/:idLook up a deposit account by id
GET /v1/depositsList the deposits of one account (account_id or customer_ref, limit 1–200, default 50)
GET /v1/deposits/:idLook up one deposit
GET /v1/eventsWebhook deliveries for one invoice or deposit (invoice_id or deposit_id)
POST /v1/events/:id/redeliverSend a webhook again (202)

Add ?probe=1 to an invoice or deposit-account lookup to check the chain on the way (at most once every 10 seconds); otherwise you get the latest state from our scanner, which checks every minute.

Create an invoice POST /v1/invoices

Call POST /v1/invoices when the customer checks out and redirect them to pay_url in the response.

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}}'
FieldRequiredDescription
refYesYour order number: 1–64 characters of letters, digits and _ . : -. Unique per mode; sending the same ref with the same fields again returns the same invoice, with different fields it returns 409
amountYesAmount in US cents as an integer: 4200 = $42.00. Not below the network minimum once a network is chosen
titleYesWhat the customer sees on the checkout, up to 64 characters
return_urlYesWhere “Back” on the checkout goes; must be on one of your website domains
success_urlNoWhere the customer goes after paying; defaults to return_url
networkNoPreselect a network id from the network table; omit it to let the customer choose on the checkout
customer_refNoYour customer id, up to 64 characters, returned as is
lifetimeNoLifetime in seconds, 600 – 43200, default 3600
amount_ruleNo"any" = credit whatever arrives (top-ups): paid as soon as it confirms. Omit it to use the “Amount rule” under “Settings › Payment rules & fees” in the dashboard
accuracy_percentNoHow much underpayment still counts as paid, 0 – 5 (percent), default 0
subtract_percentNoShare of the platform fee the customer pays, 0 – 100 (percent), added to pay_amount; omit it to use the dashboard default
payment_multipleNoWhether a short payment can be topped up to the same address, default from the dashboard (on unless you changed it); false: the first short transfer ends as wrong_amount
metaNoAny JSON object up to 2 KB, returned as is in lookups and webhooks
currencyNoOnly USD (the default); anything else returns 422

Every URL must be on one of your website domains (dashboard → “Developers › Integration & keys”). The response is the invoice object below; network, address and pay_amount are filled in once a network is picked.

The invoice object

Ids start with inv_. amount is in US cents; pay_amount and paid_amount are coin amounts as decimal strings on the chosen network.

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
}
statusMeaningWhat you do
pendingAwaiting payment—
confirmingA transfer was seen and is confirming, or it arrived short and can still be topped up—
paidPaidShip / credit the order
paid_overPaid, and overpaidSame; the extra is in your balance
wrong_amountEnded with less than the full amount (at expiry, or at the first short transfer when payment_multiple is false)Top-up businesses can credit what arrived (paid_amount; priced coins also give paid_cents); shops ask the customer to pay the rest
closedExpired with nothing received, or you closed it— (money that arrives later is still credited and notified)

Other fields: pay_url (the hosted checkout), payments (every transfer with txid, amount, confirmations and confirmed), expires_at, paid_at, closed_at; BTC, LTC, DOGE, BCH and native coins also carry rate (US dollars per coin, locked for 30 minutes), rate_locked_until and paid_cents (what has arrived so far, in US cents).

Networks

Pass the id as network. GET /v1/networks returns the ones open right now in your mode; a network can be paused, so prefer reading it over hard-coding.

networkCoinChainMinimum
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

Deposit accounts (static addresses) POST /v1/deposit-accounts

POST /v1/deposit-accounts with {"customer_ref": "user_42"} gives that customer a permanent address on every network (all EVM chains share one address). Whatever they send is credited in US dollars, with one deposit.credited webhook per transfer.

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, … }
  ]
}

Transfers below the minimum (usually $1, BTC $5) are recorded as below_min and not credited automatically; contact us to have them credited.

Webhooks

We POST JSON to the webhook URL in the dashboard (“Developers › Integration & keys”; Live and Test each have their own URL and secret). The body is the event:

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,…}}
EventWhen
invoice.paidThe invoice became paid or paid_over (including money that arrived after expiry)
invoice.wrong_amountThe invoice ended as wrong_amount
invoice.closedThe invoice expired with nothing received, or you closed it
deposit.creditedA transfer to a deposit account was credited

Verify against the raw body before parsing it, reject a t more than 300 seconds from your clock, and compare in constant time. invoice / deposit in the body is the same object a lookup returns.

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

A 2xx response counts as delivered; otherwise we retry after 1 minute, 5 minutes, 30 minutes, 2 hours, 12 hours, 24 hours and 24 hours (8 attempts in total).

Events and redelivery

GET /v1/events?invoice_id=inv_… (or deposit_id=dep_…) lists the webhooks for one invoice or deposit with their state, attempts, last_status and last_error. POST /v1/events/ev_…/redeliver queues it again right away, for example after you fixed your endpoint. The dashboard’s “Developers › Webhook log” shows every attempt with request and response.

Test mode

Invoices created with a bw_test_ key run on testnets and never touch real money. The hosted checkout shows a “Simulate payment” button (full / half / overpay), so you can watch statuses and webhooks without testnet coins.