네이티브 REST API
네이티브 API는 blockwallets 고유의 REST 인터페이스입니다. 미국 센트 단위 청구서, 주문마다 멱등한 ref, 모든 네트워크를 아우르는 고정 입금 계정, HMAC-SHA256으로 서명된 웹훅을 제공합니다. Cryptomus 호환 API와 같은 호스팅 결제 페이지에 같은 청구서를 만들며 수수료와 잔액도 같습니다.
키는 관리 화면의 “개발자 › 연동 및 키”에서 직접 생성합니다: Live 키와 Test 키, 그리고 각각의 웹훅 서명 시크릿. 모두 한 번만 표시되며 목록에는 접두사만 보입니다. 계정에서 2단계 인증을 켜 두었다면 Live 키를 생성하거나 교체할 때 코드를 입력해야 합니다. 아직 가맹점 계정이 없다면 이용을 신청하세요. 이미 Cryptomus를 쓰고 있다면 Cryptomus 호환 API를 보세요(고르는 방법).
키와 요청
기본 URL은 https://blockwallets.io입니다. 요청과 응답은 JSON이며, 모든 요청에는 다음이 들어갑니다.
Authorization: Bearer bw_live_…또는Bearer bw_test_…: 접두사가 모드를 정하므로 Test 키는 Test 청구서만 볼 수 있고 테스트넷에서 동작합니다POST요청에는Content-Type: application/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 -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 | 아니요 | 2 KB 이하의 임의의 JSON 객체. 조회와 웹훅에서 그대로 돌려줍니다 |
currency | 아니요 | USD만 가능(기본값). 다른 값은 422를 돌려줍니다 |
모든 URL은 웹사이트 도메인에 속해야 합니다(관리 화면 → “개발자 › 연동 및 키”). 응답은 아래 청구서 객체입니다. network, address, pay_amount는 네트워크가 선택된 뒤에 채워집니다.
청구서 객체
id는 inv_로 시작합니다. amount는 미국 센트이고, pay_amount와 paid_amount는 선택한 네트워크의 코인 수량(소수 문자열)입니다.
{
"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_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 -X POST https://blockwallets.io/v1/deposit-accounts \
-H "Authorization: Bearer $BW_KEY" -H "Content-Type: application/json" \
-d '{"customer_ref":"user_42"}'{
"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)가 들어갑니다 - 입금 id는
dep_로 시작합니다.amount는 미국 센트,amount_token은 코인 수량이며, 시세가 필요한 코인에는 적용한rate가 들어갑니다
최소 금액(보통 $1, BTC $5) 미만의 송금은 below_min으로 기록되며 자동으로 입금 처리되지 않습니다. 입금 처리가 필요하면 문의해 주세요.
웹훅
관리 화면의 웹훅 URL(“개발자 › 연동 및 키”, Live와 Test는 각각 자체 URL과 시크릿이 있음)로 JSON을 POST합니다. 본문이 곧 이벤트입니다.
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은 조회가 돌려주는 것과 같은 객체입니다.
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_ 키로 만든 청구서는 테스트넷에서 동작하며 실제 돈이 오가지 않습니다. 호스팅 결제 페이지에 “결제 시뮬레이션” 버튼(전액 / 절반 / 초과)이 표시되므로 테스트넷 코인 없이도 상태 변화와 웹훅을 확인할 수 있습니다.