# blockwallets > blockwallets (https://blockwallets.io) is a crypto payment gateway for merchants. Customers pay in stablecoins (USDT and USDC) or in BTC, LTC, TRX, BNB, ETH, POL, AVAX, OKB, SOL; the merchant is credited in US dollars and withdraws to their own wallet. It works without code (payment links) or through an API that is compatible with Cryptomus. The private keys that can move funds are never stored on an internet-facing server. Status: private beta. Merchants request access at https://blockwallets.io/#join and are onboarded by email. ## What it does - Accept USDT and USDC on TRON, BNB Chain, Ethereum, Base, Polygon, Arbitrum, Optimism, Avalanche, X Layer, Solana. Stablecoins are credited at par: 1 USDT = 1 USDC = 1 USD, with no exchange rate and no spread. - Also accept BTC, LTC, TRX, BNB, ETH, POL, AVAX, OKB, SOL. These are quoted at a market rate locked for 30 minutes and credited in US dollars. - Payment links (no code): set an amount in the merchant dashboard and get a link and QR code to share in a chat, on a website or a social profile. Every visit creates a fresh bill. - API: create a payment with an amount and an order number, redirect the customer to the hosted payment page, and receive a signed webhook when the money arrives. - Static wallets: a permanent deposit address per user and per network, for top-ups without entering an amount first. - Hosted payment page in English and Chinese with QR code, exact amount, countdown and redirect back to the merchant's site. - Every bill gets its own receiving address, so payments match orders without memos. The same `order_id` always returns the same bill, so retries never double-charge. - Underpaid, overpaid and late payments are all recorded and reported with the exact amount received. - Withdrawals to the merchant's own wallet on demand, or automatically once the balance reaches a chosen amount or once a day. Every payout is reviewed and shows its on-chain transaction. - Test mode with separate credentials on public testnets and a one-click simulated payment. ## Pricing - 1% of each payment received as standard, lowered automatically by the previous calendar month's live volume: 0.9% from $10,000, 0.8% from $25,000, 0.7% from $50,000, 0.6% from $100,000 and 0.5% from $200,000 (never below 0.5%). The same tiers for every merchant; no negotiation, monthly fee, setup fee or minimum volume. - The customer pays the blockchain network fee when sending; the bill amount arrives in full. - The fee can be passed on to the customer with `subtract` (0–100%), as with Cryptomus. Webhooks report it as `commission`, with `merchant_amount` = received − commission. - Withdrawals are free (we pay the network fee), minimum $10.00. Test mode is free. - Details and a comparison with Cryptomus, NOWPayments, OxaPay, Plisio and Stripe: https://blockwallets.io/pricing ## How a payment is confirmed 1. The merchant (or a payment link) creates a bill with an amount in USD and an order number. 2. The customer picks a coin and network on the hosted payment page and sends to a fresh address. 3. blockwallets scans the chain every minute and waits for enough confirmations (see the table below), then a second, independent node provider must see the same transfer, amount and address before it counts. 4. The bill becomes `paid`, the merchant's server gets a signed webhook (retried for about two and a half days if it is down), and the amount is credited to the merchant balance in USD. ## Security model - Online servers hold only an extended public key (xpub): they can derive receiving addresses and read balances but cannot send funds. Pasting a private key into the system is refused. - Receiving wallets are pinned in the deployment config, so a stolen admin login or an edited database cannot redirect payments. - Two independent data sources from an allowlist must agree before a payment counts. - Moves to cold storage are signed on a separate offline machine that decodes and checks every field first. - If any safeguard is missing, that network stops accepting payments instead of accepting them unsafely. ## Migrating from Cryptomus The merchant API uses the same endpoints (`POST /v1/payment`, `POST /v1/payment/info`, `POST /v1/wallet`, …), parameters, `sign` header (`md5(base64(body) + API key)`) and webhook format as Cryptomus. To migrate, change the base URL to https://blockwallets.io and swap the merchant ID and API key; the rest of the integration stays the same. ## Supported networks | Coin | Network | Pricing | Confirmation | Typical arrival | Minimum | |---|---|---|---|---|---| | USDT | TRON (TRC-20) | 1:1 with USD | finalized block | ~1 min | $1.00 | | USDT | BNB Smart Chain (BEP-20) | 1:1 with USD | 15 blocks | ~1 min | $1.00 | | USDC | BNB Smart Chain (BEP-20) | 1:1 with USD | 15 blocks | ~1 min | $1.00 | | USDT | Ethereum (ERC-20) | 1:1 with USD | 6 blocks | ~10 min | $5.00 | | USDC | Ethereum (ERC-20) | 1:1 with USD | 6 blocks | ~10 min | $5.00 | | USDC | Base | 1:1 with USD | 20 blocks | ~3 min | $1.00 | | USDT | Polygon PoS | 1:1 with USD | 10 blocks | ~1 min | $1.00 | | USDC | Polygon PoS | 1:1 with USD | 10 blocks | ~1 min | $1.00 | | USDT | Arbitrum One | 1:1 with USD | 20 blocks | ~12 min | $1.00 | | USDC | Arbitrum One | 1:1 with USD | 20 blocks | ~12 min | $1.00 | | USDT | OP Mainnet (Optimism) | 1:1 with USD | 20 blocks | ~5 min | $1.00 | | USDC | OP Mainnet (Optimism) | 1:1 with USD | 20 blocks | ~5 min | $1.00 | | USDT | Avalanche C-Chain | 1:1 with USD | 3 blocks | ~1 min | $1.00 | | USDC | Avalanche C-Chain | 1:1 with USD | 3 blocks | ~1 min | $1.00 | | USDT | X Layer | 1:1 with USD | 20 blocks | ~2 min | $1.00 | | USDC | X Layer | 1:1 with USD | 20 blocks | ~2 min | $1.00 | | BTC | Bitcoin | market rate, locked 30 min | 1 block | ~10 min | $5.00 | | LTC | Litecoin | market rate, locked 30 min | 3 blocks | ~8 min | $1.00 | | TRX | TRON | market rate, locked 30 min | finalized block | ~1 min | $1.00 | | BNB | BNB Smart Chain | market rate, locked 30 min | 15 blocks | ~1 min | $1.00 | | ETH | Ethereum | market rate, locked 30 min | 6 blocks | ~10 min | $5.00 | | ETH | Base | market rate, locked 30 min | 20 blocks | ~3 min | $1.00 | | ETH | Arbitrum One | market rate, locked 30 min | 20 blocks | ~12 min | $1.00 | | ETH | OP Mainnet (Optimism) | market rate, locked 30 min | 20 blocks | ~5 min | $1.00 | | POL | Polygon PoS | market rate, locked 30 min | 10 blocks | ~1 min | $1.00 | | AVAX | Avalanche C-Chain | market rate, locked 30 min | 3 blocks | ~1 min | $1.00 | | OKB | X Layer | market rate, locked 30 min | 20 blocks | ~2 min | $1.00 | | USDT | Solana (SPL) | 1:1 with USD | finalized block | ~1 min | $1.00 | | USDC | Solana (SPL) | 1:1 with USD | finalized block | ~1 min | $1.00 | | SOL | Solana | market rate, locked 30 min | finalized block | ~1 min | $1.00 | Next network: TON. ## FAQ - **What is blockwallets?** A payment gateway that lets online businesses accept USDT, USDC and other cryptocurrencies, credited in US dollars, without running their own crypto infrastructure. - **Do I need to understand crypto or write code?** No. Payment links work with zero code; customers pay from any exchange account or crypto wallet by scanning a QR code. - **Is it an alternative to Cryptomus?** Yes. It is API-compatible with Cryptomus, so existing Cryptomus integrations work by changing the base URL and credentials. - **How much does it cost?** 1% per payment received, down to 0.5% with monthly volume (0.9% from $10,000, 0.8% from $25,000, 0.7% from $50,000, 0.6% from $100,000 and 0.5% from $200,000); no monthly or setup fee; withdrawals are free. See https://blockwallets.io/pricing. - **Can payments be charged back?** No. A confirmed blockchain transfer cannot be pulled back like a card payment; refunds are the merchant's decision. - **Where does the money go?** To the merchant's balance in USD, withdrawable to the merchant's own wallet at any time or automatically. - **What if the customer sends the wrong amount or pays late?** Late payments still count; underpayments and overpayments are reported with the exact amount received. ## Links - [Website (English)](https://blockwallets.io/) - [Website (Chinese)](https://blockwallets.io/?lang=zh) - [Integration docs (English)](https://blockwallets.io/docs?lang=en) - [Integration docs (Chinese)](https://blockwallets.io/docs) - [Migrating from NOWPayments / Coinbase Commerce](https://blockwallets.io/docs/migrate?lang=en): compatible APIs, change only the base URL and keys - [Pricing (English)](https://blockwallets.io/pricing): from 1% per payment, volume tiers down to 0.5%, no monthly fee, comparison with other gateways - [Pricing (Chinese)](https://blockwallets.io/pricing?lang=zh) - [Full docs as Markdown](https://blockwallets.io/llms-full.txt) - [Request access](https://blockwallets.io/#join) --- # Integration docs (HTML: https://blockwallets.io/docs?lang=en · 中文: https://blockwallets.io/docs) ## Docs blockwallets lets you accept USDT and USDC, plus BTC, LTC and native coins (credited in US dollars at a locked rate): the customer pays, we confirm it on chain, then we notify you. The API, signatures and webhooks are the same as **Cryptomus** — if you have integrated Cryptomus, change the domain and you are done. How it works comes first, then the API. ### Two ways to get paid Both accept **any amount**: fixed-price orders and top-ups where the customer types the amount. Once paid, you ship or credit the customer’s balance. The only difference is **whether every payment gets a fresh address**. Recommended · most integrations**Bills** The customer picks a product or enters an amount on your page, you create a bill, and they send the amount shown on the payment page. Every bill has a new address and a countdown; when it arrives we notify you with the amount actually received. `POST /v1/payment` → webhook `type: payment` Optional**Static wallets** No amount up front: each customer gets a permanent address per network to save in their wallet and send to any time. Every transfer triggers its own webhook. `POST /v1/wallet` → webhook `type: wallet` When in doubt, use bills. Without code, the dashboard’s [payment links](https://blockwallets.io/docs?lang=en#links) are bills created for you. Not a merchant yet? Request access on the [home page](https://blockwallets.io/#join); once approved, sign in to the [merchant dashboard](https://blockwallets.io/dashboard) and try “Test a payment” with a small amount first (the money goes to your own balance). ### How a bill flows A payment involves four parties: the customer, your site, blockwallets and the blockchain. The diagram runs top to bottom in time; the right column is the bill’s status at each step. In short, five steps: - You create a bill (a payment link does it for you, or your server calls `/v1/payment`), get the payment page `url`, and redirect the customer there. - On the payment page the customer picks a coin and a network, gets a dedicated address and amount, and pays from a wallet or exchange. - We see the transfer on chain, wait for enough confirmations, then cross-check it with a second node provider (so a node can’t fake it). - The bill becomes `paid` and we immediately POST a signed webhook to your `url_callback`; a few seconds later the payment page sends the customer back to your `url_success`. - The money is credited to your balance; withdraw to your own wallet from the merchant dashboard. #### Bill status | status | Meaning | What you do | | --- | --- | --- | | `check` | Awaiting payment | — | | `confirm_check` | Transfer seen, waiting for confirmations | — | | `wrong_amount_waiting` | Received but short; the customer can top up to the same address (when `is_payment_multiple` is true; notified once) | — (you get `paid` once it is topped up) | | `paid` | Paid | Ship / credit by `payment_amount_usd` | | `paid_over` | Paid, and overpaid | Same (`payment_amount_usd` is the amount received) | | `wrong_amount` | Expired with less than the full amount (with `is_payment_multiple` false: the first short transfer) | Top-up businesses can credit the received `payment_amount_usd`; shops ask the customer to pay the rest | | `cancel` | Expired with nothing received | — (money that arrives later is still credited and notified) | When `is_final` is `true` the status will not change again (except for late payments). ### Payment links (no code) Merchant dashboard → “Payment links” → enter a description and amount (fix a network or let the customer choose) → create. Each link has a URL and a QR code. - Every time someone opens the link a new bill is created, so one link can be reused (e.g. “Membership $99”). - Every payment shows up under “Payments”; withdraw it to your wallet from “Balance & withdrawals”. - If you have created credentials and a webhook URL under “Integration & developers”, payments through links are also notified in the format below. - To stop accepting, click “Disable” and the link stops working immediately. ### API integration #### 1. Credentials and signing Merchant dashboard → “Integration & developers” → generate a **merchant** (UUID) and an **API key**. There is one set for Live and one for Test: Live takes real money, Test runs on testnets. Also add your **website domains**: `url_callback`, `url_return` and `url_success` must be on them. The API key is shown only once — keep it safe and only on your server. Base URL `https://blockwallets.io`. Every endpoint is `POST` + JSON, with these headers: - `merchant`: your merchant UUID (this decides Live or Test) - `sign`: `md5(base64(request body) + API key)`, where the body is the exact JSON string you send Success returns `{"state": 0, "result": {…}}`; failure returns `{"state": 1, "message": "…"}` (see [Errors](https://blockwallets.io/docs?lang=en#errors)). The key endpoints below have a “Try it” panel: enter your Test merchant and API key, and the page signs the request in your browser, calls the endpoint and shows the full request and response (credentials stay in the page’s memory). When a signature doesn’t match, compare it step by step with the [signature checker](https://blockwallets.io/docs/sign?lang=en). ```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. Create a bill POST /v1/payment When the customer places an order, or enters an amount on your top-up page, call `https://blockwallets.io/v1/payment` and redirect them to the returned `result.url`. ```json { "amount": "42.00", "currency": "USD", "order_id": "order_1042", "url_callback": "https://your-site.com/webhooks/crypto", "url_return": "https://your-site.com/cart", "url_success": "https://your-site.com/thanks?order=1042" } ``` | Parameter | Required | Description | | --- | --- | --- | | `amount` | Yes | Amount as a string, up to two decimals: `"42.00"`. Your price or a number the customer entered; not below the chosen network’s minimum (see [networks](https://blockwallets.io/docs?lang=en#networks)) | | `currency` | Yes | `USD`, `USDT` or `USDC`, all 1:1 | | `order_id` | Yes | Your order number: letters, digits, `-`, `_`, ≤ 100 characters. Calling again with the same order_id returns the existing bill (no duplicate orders) | | `url_callback` | No | Webhook URL. Omit it and no webhook is sent | | `url_return` | No | Where “Back” goes before paying | | `url_success` | No | Where the customer goes after paying | | `network` | No | Preselect a network: `tron` / `bsc` / `eth` / `base` / `polygon` / `arbitrum` / `optimism` / `avalanche` / `xlayer` / `btc` / `ltc` / `sol`; omit it to let the customer choose on the payment page | | `to_currency` | No | Coin on that network: `USDT` / `USDC`; on `btc` / `ltc` it is `BTC` / `LTC` (optional) | | `lifetime` | No | Bill lifetime in seconds, 300 – 43200, default 3600; anything under 600 counts as 600 | | `accuracy_payment_percent` | No | How much underpayment still counts as paid, 0 – 5, default 0. `100` = accept whatever arrives: `paid` as soon as it confirms (`paid_over` if more), credit by `payment_amount_usd` in the webhook — good for top-ups. If omitted, the dashboard’s “Amount rule” applies: “credit what arrives” means 100 | | `subtract` | No | Share of the platform fee paid by the customer, 0 – 100, added to `payer_amount` (the platform fee is 1%, lower at higher volume; at 1%, with 100 a $100 bill costs the customer $101.00; see [pricing](https://blockwallets.io/pricing)). If omitted, the dashboard’s “Fees and underpayment” setting applies (default 0) | | `is_payment_multiple` | No | Whether a short payment can be topped up, default true: status `wrong_amount_waiting`, paying the rest to the same address makes it `paid` (if under 30 minutes remain at the first short payment, the deadline moves to 30 minutes later). false: the first short transfer makes it `wrong_amount`. If omitted, the dashboard setting applies | | `additional_data` | No | Any string (≤ 255), returned as-is in queries and 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` and `payer_amount` are filled in once the customer picks a network. #### 3. Query bills - `POST /v1/payment/info` `{"order_id": "…"}` or `{"uuid": "…"}`: fetch one bill and check the chain on the way (at most once per 10 seconds). - `POST /v1/payment/list`: list, 15 per page, optional `date_from` / `date_to`; next page via the returned `paginate.nextCursor` (`?cursor=…`). - `POST /v1/payment/services`: available networks and each network’s minimum amount. - `POST /v1/payment/resend` `{"order_id": "…"}`: resend the webhook of a paid / underpaid bill. Webhooks are the primary channel; queries are the fallback (for example if your server was down and missed a webhook). #### 4. Static wallets POST /v1/wallet Opens a permanent address for one customer on one network. The same `order_id` (usually your user id) + network always gives the same address; all EVM chains (BNB Chain, Ethereum, Base, Polygon, Arbitrum, Optimism, Avalanche, X Layer, Solana) share one address, while TRON, BTC and LTC each have their own. Whatever the customer sends is credited, with a webhook per transfer; BTC / LTC transfers are converted to US dollars at the rate when each was seen (`payment_amount_usd`). ```json { "order_id": "user_42", "currency": "USDT", "network": "tron", "url_callback": "https://your-site.com/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` is a hosted deposit page (address + QR code + recent deposits) you can hand to the customer. Transfers under $1 are not credited automatically; handle them in the dashboard. Blocking addresses (`/v1/wallet/block-address`) is not supported yet. ### Webhooks When a bill’s status changes (`paid`, `paid_over`, `wrong_amount_waiting`, `wrong_amount`, `cancel`) and for every transfer to a static wallet, we POST JSON to `url_callback`. 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. You can also resend from the dashboard or with `/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.832", "commission": "0.168", "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 | When | Crediting | | --- | --- | --- | | `payment` | A bill’s status changed — see `status` | Credit `payment_amount_usd` on `paid` / `paid_over`; deduplicate by `uuid` | | `wallet` | A transfer reached a static wallet (`status` is always `paid`; `order_id` is the one you opened the wallet with) | Credit `payment_amount_usd`; every transfer has its own `uuid` — deduplicate by it | #### Verify the signature (required) The signature is the `sign` field of the webhook body: remove `sign`, re-encode the rest the way PHP `json_encode` does (compact, `/` escaped as `\/`, non-ASCII not escaped), and `md5(base64(encoded) + API key)` must equal `sign`. Use the API key of the same mode. The same webhook may arrive more than once — deduplicate by `uuid` + `status`. If verification fails, paste the raw webhook body into the [signature checker](https://blockwallets.io/docs/sign?lang=en): it shows the result of every step and marks where yours differs (it all runs in your browser; the API key is never uploaded). Every webhook in the dashboard’s “Webhook log” opens in the checker with one click. ```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) ``` ### Test mode Bills created with the Test merchant and API key run on testnets, with no real money involved. The payment page shows a “Simulate payment” button (full / half / overpay), so you can see status changes and webhooks without getting testnet coins. - `POST /v1/test-webhook/payment`, `/v1/test-webhook/wallet`: send a signed sample webhook to `url_callback` (pass `currency`, `network`, optionally `status`, `order_id`) to debug your verification code. - Dashboard “Integration & developers” → “Send a test notification”: sends a sample to your webhook URL. - Dashboard “Webhook log”: every delivery attempt with its time, target URL, HTTP status, duration, request body and your server’s response (first 4 KB); filter by order or status and resend with one click. Kept for 30 days. ### Migrating from Cryptomus - Dashboard “Integration & developers” → generate a merchant and API key. - Replace `https://api.cryptomus.com` in your code with `https://blockwallets.io`, and use the new merchant and API key. - Add your domains under “Website domains” (`url_callback` / `url_return` / `url_success` only accept these). Supported: `/v1/payment`, `/v1/payment/info`, `/v1/payment/list`, `/v1/payment/services`, `/v1/payment/resend`, `/v1/wallet`, `/v1/test-webhook/payment`, `/v1/test-webhook/wallet`. Differences: bills are priced only in USD / USDT / USDC (all 1:1, no exchange spread); `commission` is our platform fee (the amount received × the merchant’s rate when the bill was created — 1% standard, down to 0.5% by volume; nothing is charged twice on the part the customer covers) and `merchant_amount` = received − `commission`; when the customer pays in BTC / LTC we quote the median of several exchanges, locked for 30 minutes — `payer_amount` is the number of coins to send, `payer_amount_exchange_rate` is the USD value of one coin, and the payment is converted to `payment_amount_usd` at the locked rate (the coins stay with us, nothing is swapped); networks `tron` / `bsc` / `eth` / `base` / `polygon` / `arbitrum` / `optimism` / `avalanche` / `xlayer` / `btc` / `ltc` / `sol`; minimum bill lifetime 10 minutes; no `is_refresh` and no blocking of static wallets; webhooks come from changing IPs, so verify `sign` rather than allow-listing IPs. Coming from **NOWPayments** or **Coinbase Commerce**? We have compatible APIs for both — change the base URL and keys; see the [migration guide](https://blockwallets.io/docs/migrate?lang=en). ### Networks and amounts | Coin | Network | network parameter | Minimum | Arrival | | --- | --- | --- | --- | --- | | USDT | TRON | `tron` | $1 | ~1 min | | USDT | BNB Chain | `bsc` | $1 | ~1 min | | USDC | BNB Chain | `bsc` | $1 | ~1 min | | USDT | Ethereum | `eth` | $5 | ~10 min | | USDC | Ethereum | `eth` | $5 | ~10 min | | USDC | Base | `base` | $1 | ~3 min | | USDT | Polygon | `polygon` | $1 | ~1 min | | USDC | Polygon | `polygon` | $1 | ~1 min | | USDT | Arbitrum | `arbitrum` | $1 | ~12 min | | USDC | Arbitrum | `arbitrum` | $1 | ~12 min | | USDT | Optimism | `optimism` | $1 | ~5 min | | USDC | Optimism | `optimism` | $1 | ~5 min | | USDT | Avalanche | `avalanche` | $1 | ~1 min | | USDC | Avalanche | `avalanche` | $1 | ~1 min | | USDT | X Layer | `xlayer` | $1 | ~2 min | | USDC | X Layer | `xlayer` | $1 | ~2 min | | BTC | Bitcoin | `btc` | $5 | ~10 min | | LTC | Litecoin | `ltc` | $1 | ~8 min | | TRX | TRON | `tron` | $1 | ~1 min | | BNB | BNB Chain | `bsc` | $1 | ~1 min | | ETH | Ethereum | `eth` | $5 | ~10 min | | ETH | Base | `base` | $1 | ~3 min | | ETH | Arbitrum | `arbitrum` | $1 | ~12 min | | ETH | Optimism | `optimism` | $1 | ~5 min | | POL | Polygon | `polygon` | $1 | ~1 min | | AVAX | Avalanche | `avalanche` | $1 | ~1 min | | OKB | X Layer | `xlayer` | $1 | ~2 min | | USDT | Solana | `sol` | $1 | ~1 min | | USDC | Solana | `sol` | $1 | ~1 min | | SOL | Solana | `sol` | $1 | ~1 min | ERC-20 fees are high, so the minimum is $5; Bitcoin fees are not low either, minimum $5; everything else is $1. Customers must send on the network shown on the payment page — money sent on another network is not credited automatically. BTC / LTC show “detected” as soon as they are in the mempool and count once they have enough confirmations; replaced (RBF) or dropped transactions do not count. Native coins (TRX, BNB, ETH, POL, AVAX, OKB, SOL) are quoted at a locked rate and credited in US dollars like BTC / LTC; specify both network and coin: bills `network: "bsc", to_currency: "BNB"`, static wallets `network: "bsc", currency: "BNB"`. A network on its own defaults to USDT on that chain (USDC on Base). Transfers made by contracts (multisig, batch payouts) are received too; on some chains their transaction hash is unavailable and `txid` in the webhook is `null`. ### Errors ```json { "state": 1, "message": "amount must be a number like 10.28" } ``` | HTTP | Common causes | | --- | --- | | 422 | Missing or malformed parameters; amount below the network minimum; callback / redirect URL not on your registered domains | | 401 | Wrong merchant, wrong `sign` (`Invalid Sign.`) or a rotated API key | | 404 | Bill does not exist or isn’t yours (`Payment not found`); unsupported coin / network combination (`The service was not found`) | | 503 | The network is temporarily unavailable — pick another or don’t preselect one | When the signature is wrong (`Invalid Sign.`) the response carries a `debug` object: `body_base64` is the base64 of the request body we received (your own content, never the API key) and `body_bytes` its length. Compare it with the base64 you signed: if they differ, the body you sent is not the string you signed (your framework re-serialized it, a proxy changed the encoding, a trailing newline); if they match, the API key is wrong. Paste it into the [signature checker](https://blockwallets.io/docs/sign?lang=en) to see the first difference. ```json { "state": 1, "message": "Invalid Sign.", "debug": { "body_base64": "eyJhbW91bnQiOiI0Mi4wMCIsImN1cnJlbmN5IjoiVVNEIiwi…", "body_bytes": 158, "sign_formula": "md5(body_base64 + API key)" } } ``` ### FAQ #### Customers top up any amount they like — which mode? Bills. Let the customer enter an amount on your top-up page, create a bill for it, and credit `payment_amount_usd` when it arrives. Use static wallets only if customers should save one address and send to it repeatedly. #### What if a customer underpays? Before it expires the customer can send the difference on the payment page (it shows how much is still due; status `wrong_amount_waiting`, and if under 30 minutes remain at the first short payment the deadline moves to 30 minutes later). If it is still short at expiry, the bill becomes `wrong_amount` and `payment_amount_usd` in the webhook is the amount received; top-up businesses can simply credit that. #### What if a customer pays late, or twice? Money that arrives after expiry is still credited and notified; money that arrives after a bill is fully paid is recorded too and visible in the dashboard. #### What if a customer sends on the wrong network? For example the bill is on TRON and they send from BNB Chain: it is not credited automatically — contact us and we handle it manually. #### How much does it cost? 1% of each payment received, lowered automatically by last month’s volume (0.9% from $10,000, 0.8% from $25,000, 0.7% from $50,000, 0.6% from $100,000 and 0.5% from $200,000), with no monthly or setup fees. The customer pays the network fee when sending; to pass the fee on to the customer, set `subtract` when creating a bill or as a default in the dashboard. Withdrawals carry no extra fee. See [pricing](https://blockwallets.io/pricing). #### When does the money reach me? It is credited to your balance as soon as the payment confirms. Request a withdrawal to your own wallet under “Balance & withdrawals”; once we approve it we pay out, and the on-chain transaction appears in your withdrawal history. You can also turn on auto-withdrawal: once your available balance reaches an amount you set, or once a day at 00:00 UTC, the whole available balance is withdrawn. Automatic withdrawals are reviewed the same way, and every settings change sends a notice to your email.