네이티브 REST API

ChatGPT에서 열기Claude에서 열기

네이티브 API는 blockwallets 고유의 REST 인터페이스입니다. 미국 센트 단위 청구서, 주문마다 멱등한 ref, 모든 네트워크를 아우르는 고정 입금 계정, HMAC-SHA256으로 서명된 웹훅을 제공합니다. Cryptomus 호환 API와 같은 호스팅 결제 페이지에 같은 청구서를 만들며 수수료와 잔액도 같습니다.

키는 관리 화면의 “개발자 › 연동 및 키”에서 직접 생성합니다: Live 키와 Test 키, 그리고 각각의 웹훅 서명 시크릿. 모두 한 번만 표시되며 목록에는 접두사만 보입니다. 계정에서 2단계 인증을 켜 두었다면 Live 키를 생성하거나 교체할 때 코드를 입력해야 합니다. 아직 가맹점 계정이 없다면 이용을 신청하세요. 이미 Cryptomus를 쓰고 있다면 Cryptomus 호환 API를 보세요(고르는 방법).

키와 요청

기본 URL은 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/:idid로 청구서 조회
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/:idid로 입금 계정 조회
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아니요2 KB 이하의 임의의 JSON 객체. 조회와 웹훅에서 그대로 돌려줍니다
currency아니요USD만 가능(기본값). 다른 값은 422를 돌려줍니다

모든 URL은 웹사이트 도메인에 속해야 합니다(관리 화면 → “개발자 › 연동 및 키”). 응답은 아래 청구서 객체입니다. 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으로 기록되며 자동으로 입금 처리되지 않습니다. 입금 처리가 필요하면 문의해 주세요.

웹훅

관리 화면의 웹훅 URL(“개발자 › 연동 및 키”, Live와 Test는 각각 자체 URL과 시크릿이 있음)로 JSON을 POST합니다. 본문이 곧 이벤트입니다.

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입금 계정으로 들어온 송금이 입금 처리됨

본문을 파싱하기 전에 원본 본문으로 검증하고, t가 서버 시계에서 300초 넘게 벗어나면 거부하며, 비교는 상수 시간으로 하세요. 본문의 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_ 키로 만든 청구서는 테스트넷에서 동작하며 실제 돈이 오가지 않습니다. 호스팅 결제 페이지에 “결제 시뮬레이션” 버튼(전액 / 절반 / 초과)이 표시되므로 테스트넷 코인 없이도 상태 변화와 웹훅을 확인할 수 있습니다.