SHKeeper v2.5.32 API 使用详解
SHKeeper v2.5.32 REST API 全量实测详解:四种鉴权逐一验证、36 条路由分组 curl 实跑回显,覆盖余额/报价/收款/发票/地址/交易/提现/钱包/设置/回调全端点,含 HMAC-SHA256 回调验签复算、HTTP 状态码实测汇总与十二则排障 FAQ。
SHKeeper v2.5.32 API 使用详解
目标:把 SHKeeper v2.5.32 的 REST API 一次讲全——四种鉴权凭据逐一实测、36 条
/api/v1/*路由按业务分组逐条 curl 实跑、错误形态与 HTTP 状态码实测汇总、商户回调报文的 HMAC-SHA256 验签复算,以及十二则实测排障 FAQ。全部请求与回显于 2026-10-24 在 《SHKeeper v2.5.32 Docker Compose 部署指南》 搭建的 BTC regtest 实测栈上逐条执行采集;产品定位与架构背景见 《SHKeeper v2.5.32 介绍与选型指南》,环境变量与配置语义见 《SHKeeper v2.5.32 配置参数详解》。
一、实测环境与接口面总览
1.1 实测环境
| 项 | 值 | 说明 |
|---|---|---|
| 实测日期 | 2026-10-24 | 全部请求 curl 实跑,回显逐字照抄 |
| SHKeeper 核心 | vsyshost/shkeeper:2.5.32 | Python 3.13 / Flask / Gunicorn,服务监听 5000,宿主入口 8128 |
| BTC 适配器 | vsyshost/bitcoin-shkeeper:2.0.14 | HTTP 服务监听 6000,另有 Celery 任务进程 |
| BTC 全节点 | Bitcoin Core v31.1 | -regtest 模式,RPC 端口 18443 |
| 管理员账号 | admin / q1w2e3r4 | 登录管理台与 Session、Basic、Metrics 共用 |
| API Key | NjOwsKx8v5Kctk9aY0O20w | 商户业务接口凭据,请求头 X-Shkeeper-API-Key |
| Backend Key | q1w2e3r4 | 节点侧回调凭据,请求头 X-Shkeeper-Backend-Key,核心与适配器两侧一致 |
| 汇率设定 | USD/BTC manual 100000,费率 2% | 另设 EUR/CNY dynamic(Binance 源,实测环境外网不可达) |
| 回调接收器 | http://callback-receiver:9000/hook | 部署指南中的回调接收服务,验证回调并返回 202 |
下文 curl 统一用变量承载入口与凭据:
export SK=http://127.0.0.1:8128
export API=NjOwsKx8v5Kctk9aY0O20w
export BK=q1w2e3r4
1.2 接口面构成:36 条路由 + /metrics
v2.5.32 核心的 API 全部集中在 shkeeper/api_v1.py,共注册 36 条 /api/v1/* 路由,外加一条独立的 /metrics 监控端点。按鉴权方式分五组:
| 鉴权组 | 路由数 | 代表端点 |
|---|---|---|
| 免鉴权 | 1 | GET /api/v1/crypto |
API Key(X-Shkeeper-API-Key) | 15 | crypto/balances、payment_request、quote、balance、invoices、payout/status、addresses、transactions、tx-info、payouts、fee-deposit-address、decryption-key、test-callback-receiver |
| Session(管理台 Cookie) | 14 | payment-gateway(GET/POST)、payment-gateway/token、settings/<key>(GET/POST)、status、server、backup、exchange-rate、estimate-tx-fee、generate-address、transaction、payout_destinations、autopayout |
| HTTP Basic(可回退 Session) | 3 | payout、multipayout、task/<task_id> |
Backend Key(X-Shkeeper-Backend-Key) | 3 | walletnotify/<币种>/<txid>、payoutnotify/<币种>、<币种>/decrypt |
💡 两个容易踩坑的接口面事实:其一,源码引入了 flask_smorest 并为多数端点挂了 OpenAPI 文档装饰器,但
OPENAPI_URL_PREFIX未在配置中启用,生产实例没有/api-docsSwagger 页面——接口契约只能靠源码或本文这类实测清单获得;其二,路由按<币种>参数化(如/api/v1/BTC/...),只有*_WALLET环境变量启用为enabled的币种才有实例(本文栈只启用 BTC),调用未启用币种返回网关不可用(见 6.4)。
1.3 统一响应形态与两条集成铁律
业务响应是统一的 JSON 信封:成功 {"status":"success", ...},失败 {"status":"error","message":"..."}。实测得出两条集成铁律:
- HTTP 状态码不可靠:无 API Key、错 API Key、参数缺失、业务异常等绝大多数错误都返回 HTTP 200 + error JSON(第十二章实测汇总),集成方必须解析 body 的
status字段做判断,不能只看状态码; - 错误 message 可能直接是 Python 异常名:如缺字段返回
{"message":"'fee'","status":"error"}(KeyError的字符串形态),个别错误还带traceback字段,对接日志告警时注意识别。
二、鉴权体系:四种凭据逐一实测
四种凭据对应三类调用方:商户后端用 API Key,管理员/浏览器用 Session(打款另可用 Basic),区块链节点侧用 Backend Key。
2.1 免鉴权:GET /api/v1/crypto
币种能力发现接口,无需任何凭据:
curl -s $SK/api/v1/crypto
实测回显:
{"crypto":["BTC"],"crypto_list":[{"display_name":"Bitcoin","name":"BTC"}],"status":"success"}
crypto 是币种代码数组,crypto_list 携带显示名,适合做商户后台的币种下拉与可用性探测。
2.2 X-Shkeeper-API-Key:商户业务接口(15 条路由)
覆盖余额、报价、收款、发票、地址、交易、打款状态查询、钱包解密提交与回调测试等全部对外业务面。请求头不区分大小写(HTTP 头语义),本文统一写 X-Shkeeper-Api-Key。三个失败分支实测:
# 分支一:不带 key
curl -s $SK/api/v1/BTC/addresses
# 分支二:key 错误
curl -s -H 'X-Shkeeper-Api-Key: wrongkey' $SK/api/v1/BTC/addresses
# 分支三:拿 API Key 访问 Session 组端点
curl -s -H "X-Shkeeper-Api-Key: $API" $SK/api/v1/BTC/status
实测回显(三者 HTTP 状态码均为 200):
{"message":"No API key","status":"error"}
{"message":"Bad API key","status":"error"}
{"message":"This endpoint doesn't accept X-Shkeeper-Api-Key auth","status":"error"}
⚠️ 第三条与第二条 body 形态不同但都无 HTTP 错误码,若集成代码只判 HTTP 200 就把错误响应当成功解析,会在运行期才爆 JSON 字段缺失。务必先读
status字段。
2.3 Session Cookie:管理台与运维端点(14 条路由)
payment-gateway、settings、status、backup、exchange-rate、generate-address 等管理/运维端点要求登录会话。登录是标准表单 POST,字段名是 name 和 password:
curl -si -d 'name=admin&password=q1w2e3r4' $SK/login | head -6
实测回显:
HTTP/1.1 302 FOUND
Server: gunicorn
Date: Sat, 24 Oct 2026 10:56:14 GMT
Connection: keep-alive
Content-Type: text/html; charset=utf-8
Content-Length: 201
302 即登录成功(首次部署未设密码时先走 /set-password,已设密码后登录完成钱包解锁链路,见部署指南第三、四章)。之后携带 Cookie 访问受保护端点(本文实测使用会话文件):
curl -s -b cookies.txt $SK/api/v1/BTC/status
无凭据访问 Session 组端点的表现:
curl -si $SK/api/v1/BTC/status | head -6
实测回显:
HTTP/1.1 302 FOUND
Server: gunicorn
Date: Sat, 24 Oct 2026 10:50:32 GMT
Connection: keep-alive
Content-Type: text/html; charset=utf-8
Content-Length: 199
<!doctype html>
<html lang=en>
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to the target URL: <a href="/login">/login</a>. If not, click the link.
⚠️ 两个实测结论:其一,HTTP Basic 不能替代 Session——
curl -u admin:q1w2e3r4 $SK/api/v1/BTC/status同样得到 302 跳/login(Basic 只在打款异步端点生效,见 2.4);其二,登录表单字段名用错(如login=/username=)直接得到HTTP/1.1 400 BAD REQUEST,不是错误 JSON。
2.4 HTTP Basic:打款异步端点(3 条路由)
POST /api/v1/<币种>/payout、POST /api/v1/<币种>/multipayout、GET /api/v1/<币种>/task/<task_id> 三条打款链路端点用 basic_auth_optional + login_required 双装饰器:带 Basic 凭据则校验之,不带则回退 Session。凭据错误时的实测回显:
curl -s -u admin:wrongpass -H 'Content-Type: application/json' \
-X POST $SK/api/v1/BTC/payout \
-d '{"destination":"bcrt1qm4y0nf7cv0mhqjvm6shh57q7vhk6atjmz0rjtn","amount":"0.00002","fee":"2"}'
{"message":"Bad HTTP Basic Auth credentials","status":"error"}
(该分支 HTTP 状态码同样为 200。)
2.5 X-Shkeeper-Backend-Key:节点侧回调(3 条路由)
walletnotify、payoutnotify、<币种>/decrypt 是给区块链节点侧进程调用的:bitcoind 的 -walletnotify 脚本在新交易入块时回调核心,适配器解锁状态上报也走这一组。凭据通过请求头 X-Shkeeper-Backend-Key 传递,值必须与两侧 SHKEEPER_BACKEND_KEY 一致。缺头与错值的分支实测见 11.1(返回 403 + 错误 JSON)。
2.6 /metrics:Prometheus 指标端点
独立于 /api/v1 之外的监控端点,HTTP Basic 认证(管理员账号)。无凭据:
curl -s $SK/metrics
实测回显(HTTP 401):
{"msg":"authorization requred","status":"error"}
💡
requred为服务端响应原文拼写(源码如此),做告警规则匹配时别按正确拼写写。
带凭据的完整输出(本栈实测):
curl -s -u admin:q1w2e3r4 $SK/metrics
# HELP bitcoin_fullnode_status Connection status to bitcoin fullnode
# TYPE bitcoin_fullnode_status gauge
bitcoin_fullnode_status 1.0
# HELP bitcoin_fullnode_last_block Last block loaded to the bitcoin fullnode
# TYPE bitcoin_fullnode_last_block gauge
bitcoin_fullnode_last_block 109.0
# HELP bitcoin_wallet_last_block Last checked wallet block
# TYPE bitcoin_wallet_last_block gauge
bitcoin_wallet_last_block 108.0
# HELP bitcoin_fullnode_last_block_timestamp Fullnode block timestamp
# TYPE bitcoin_fullnode_last_block_timestamp gauge
bitcoin_fullnode_last_block_timestamp 1.792831002e+09
# HELP bitcoin_wallet_last_block_timestamp Wallet block timestamp
# TYPE bitcoin_wallet_last_block_timestamp gauge
bitcoin_wallet_last_block_timestamp 0.0
# HELP bitcoin_shkeeper_status Connection status to bitcoin_shkeeper
# TYPE bitcoin_shkeeper_status gauge
bitcoin_shkeeper_status 1.0
bitcoin_fullnode_last_block 与 bitcoin_wallet_last_block 的差值就是适配器扫块滞后量,是支付到账延迟排查的第一指标(实测中曾观察到钱包游标落后全节点 2 个块,见 11.1 的扫块时序说明)。
2.7 鉴权速查表
| 凭据 | 传递方式 | 适用路由 | 失败表现(实测) |
|---|---|---|---|
| 无 | — | GET /api/v1/crypto | — |
| API Key | 请求头 X-Shkeeper-API-Key | 15 条业务路由 | HTTP 200 + No API key / Bad API key |
| Session | POST /login(表单 name/password)取 Cookie | 14 条管理路由 | 302 跳 /login;API Key 冒用返回不接受提示 |
| HTTP Basic | -u admin:password | 3 条打款链路(可回退 Session) | HTTP 200 + Bad HTTP Basic Auth credentials |
| Backend Key | 请求头 X-Shkeeper-Backend-Key | 3 条节点回调路由 | 403 + No backend key provided / Wrong backend key |
| Basic(Metrics) | -u admin:password | GET /metrics | 401 + authorization requred |
三、加密币与余额查询
3.1 全量余额:GET /api/v1/crypto/balances
API Key 组,返回所有启用币种的余额、法币估值与汇率:
curl -s -H "X-Shkeeper-Api-Key: $API" $SK/api/v1/crypto/balances
实测回显:
[{"amount_crypto":"0.00018615","amount_fiat":"18.615","display_name":"Bitcoin","fiat":"USD","name":"BTC","rate":"100000","server_status":"Synced"}]
支持 ?includes=BTC 按币种过滤,单币种时输出与全量相同(实测确认)。server_status 非 Synced 时余额与汇率不可信,应停止开票。
3.2 单币余额:GET /api/v1/BTC/balance
curl -s -H "X-Shkeeper-Api-Key: $API" $SK/api/v1/BTC/balance
实测回显:
{"amount_crypto":"0.00018615","amount_fiat":"18.615","display_name":"Bitcoin","fiat":"USD","name":"BTC","rate":"100000.0000000000","server_status":"Synced"}
💡 精度差异:单币端点的
rate是100000.0000000000(10 位小数),全量列表是100000。做金额解析时按字符串高精度处理(Python 用Decimal),不要转 float。
3.3 钱包状态:GET /api/v1/BTC/status
Session 组(注意与 3.2 的 API Key 组区分):
curl -s -b cookies.txt $SK/api/v1/BTC/status
实测回显:
{"amount":"0.00018615","name":"BTC","server":"Synced"}
3.4 手续费归集地址:GET /api/v1/BTC/fee-deposit-address
curl -s -H "X-Shkeeper-Api-Key: $API" $SK/api/v1/BTC/fee-deposit-address
实测回显:
{"crypto":"BTC","fee_deposit_address":"","status":"success"}
BTC 适配器未启手续费归集账户时返回空串,属正常现象(该端点主要服务于支持手续费账户的币种,如 TRON 系);集成方应对空串做兼容,不要直接拿去生成收款码。
四、支付网关开关与共享令牌
4.1 查询网关状态与令牌:GET /api/v1/BTC/payment-gateway
Session 组:
curl -s -b cookies.txt $SK/api/v1/BTC/payment-gateway
实测回显:
{"enabled":true,"status":"success","token":"NjOwsKx8v5Kctk9aY0O20w"}
token 就是该币种的 API Key(商户请求头用值)。
4.2 启停网关:POST /api/v1/BTC/payment-gateway
curl -s -b cookies.txt -H 'Content-Type: application/json' \
-X POST $SK/api/v1/BTC/payment-gateway -d '{"enabled":true}'
实测回显:
{"status":"success"}
置 false 即停收:商户侧所有 payment_request 会得到 payment gateway is unavailable(见 6.4),已开发票的查询与回调不受影响。
4.3 重置共享令牌:POST /api/v1/BTC/payment-gateway/token
curl -s -b cookies.txt -H 'Content-Type: application/json' \
-X POST $SK/api/v1/BTC/payment-gateway/token -d '{"token":"NjOwsKx8v5Kctk9aY0O20w"}'
实测回显:
{"status":"success"}
💡 该端点设置的 token 是所有钱包共享的全局值(对多币种实例,一次设置全部生效);实测传回原值幂等成功。轮换密钥时注意:旧 Key 立即失效,商户端需同步更新,否则全部业务请求转
Bad API key。
五、报价与汇率
5.1 询价:POST /api/v1/BTC/quote
不生成发票的纯换算(API Key 组),金额已含费率:
curl -s -H "X-Shkeeper-Api-Key: $API" -H 'Content-Type: application/json' \
-X POST $SK/api/v1/BTC/quote -d '{"fiat":"USD","amount":"100"}'
实测回显:
{"amount_crypto":"0.00102000","amount_fiat":"100","crypto":"BTC","exchange_rate":"100000.0000000000","fiat":"USD","status":"success"}
100 USD 按汇率 100000 应为 0.001 BTC,返回 0.00102 正是上浮 2% 费率后的应收值。缺参分支:
curl -s -H "X-Shkeeper-Api-Key: $API" -H 'Content-Type: application/json' \
-X POST $SK/api/v1/BTC/quote -d '{}'
{"message":"'fiat' and 'amount' are required fields.","status":"error"}
dynamic 源币种在外网不可达环境下的实测表现(CNY 走 Binance 源,输出节选,实测 message 首行):
curl -s -H "X-Shkeeper-Api-Key: $API" -H 'Content-Type: application/json' \
-X POST $SK/api/v1/BTC/quote -d '{"fiat":"CNY","amount":"100"}'
{"message":"HTTPSConnectionPool(host='api.binance.com', port=443): Max retries exceeded with url: /api/v3/ticker/price?symbol=BTCCNY (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7aca6279d590>: Failed to establish a new connection: [Errno 101] Network is unreachable'"
💡 内网/离线部署(regtest、隔离机房)必须把汇率源切为
manual(见 5.2),dynamic 源不可达会直接导致开票失败。
5.2 写入手动汇率:POST /api/v1/BTC/exchange-rate
Session 组,写 manual 汇率与费率(与本栈既定值一致,幂等):
curl -s -b cookies.txt -H 'Content-Type: application/json' \
-X POST $SK/api/v1/BTC/exchange-rate \
-d '{"fiat":"USD","source":"manual","rate":"100000","fee":"2"}'
实测回显:
{"status":"success"}
5.3 链上费率估算:GET /api/v1/BTC/estimate-tx-fee/0.0001
Session 组,按金额估算转账矿工费:
curl -s -b cookies.txt $SK/api/v1/BTC/estimate-tx-fee/0.0001
实测回显:
{"msg":"Provider call failed for 'estimatefee': bitcoind: -32601: Method not found","status":"error"}
这是确定性失败:Bitcoin Core v31.1 已移除 estimatefee RPC,适配器调用即报 -32601,该端点在 31.1 节点下恒定不可用(影响与规避见 FAQ ②)。v2.5.32 的收款与打款主链路不依赖它,打款费率由请求显式传 fee。
六、收款请求与发票
6.1 创建收款发票:POST /api/v1/BTC/payment_request
商户集成的核心端点(API Key 组)。external_id 是商户侧订单号,callback_url 是回调地址,必填:
curl -s -H "X-Shkeeper-Api-Key: $API" -H 'Content-Type: application/json' \
-X POST $SK/api/v1/BTC/payment_request \
-d '{"external_id":"9002","fiat":"USD","amount":"1.00","callback_url":"http://callback-receiver:9000/hook"}'
实测回显:
{"amount":"0.0000102","display_name":"Bitcoin","exchange_rate":"100000","id":3,"recalculate_after":0,"status":"success","wallet":"bcrt1q44vceewtd3h6zt63meg4zzx5pxplkqaanyjltx"}
字段解读:wallet 是为该发票派生的收款地址;amount 是含费后的应收币量(1.00 USD × 1.02 ÷ 100000 = 0.0000102 BTC);recalculate_after 为发票汇率重算秒数(0 表示不重算,由 <币种>_RECALCULATE_AFTER 类环境变量控制,见配置详解)。缺 callback_url 的实测分支(HTTP 200,traceback 节选):
curl -s -H "X-Shkeeper-Api-Key: $API" -H 'Content-Type: application/json' \
-X POST $SK/api/v1/BTC/payment_request \
-d '{"external_id":"TEST-NOCB2","fiat":"USD","amount":"1"}'
{"message":"'callback_url'","status":"error","traceback":"Traceback (most recent call last):\n File \"/shkeeper.io/shkeeper/api_v1.py\", line 133, in payment_request\n invoice = Invoice.add(crypto=crypto, request=req)\n File \"/shkeeper.io/shkeeper/models.py\", line 427, in add\n callback_url=request[\"callback_url\"],\n ~~~~~~~^^^^^^^^^^^^^^^^\nKeyError: 'callback_url'\n"}
幂等性实测:同一 external_id 重复请求不新建发票,而是返回同一 id 与同一收款地址(实测用 external_id=2001 重复创建,两次均返回 id:2 与同一地址)——商户重试逻辑可以放心基于 external_id 做幂等。
6.2 查询发票:GET /api/v1/invoices/<external_id> 与 GET /api/v1/invoices
单笔查询(本节 6.1 的发票在支付入账后):
curl -s -H "X-Shkeeper-Api-Key: $API" $SK/api/v1/invoices/9002
实测回显:
{"invoices":[{"amount_fiat":"1","balance_fiat":"1.02","external_id":"9002","fiat":"USD","status":"OVERPAID","txs":[{"addr":"bcrt1q44vceewtd3h6zt63meg4zzx5pxplkqaanyjltx","amount":"0.0000102","crypto":"BTC","status":"CONFIRMED","txid":"0199c41bc9ee34e4a2ae99539ad8d120b141b18ed1085d9c4dfcefdc33cec547"}]}],"status":"success"}
全量查询(实测时点栈内两条发票):
curl -s -H "X-Shkeeper-Api-Key: $API" $SK/api/v1/invoices
{"invoices":[{"amount_fiat":"18.25","balance_fiat":"18.615","external_id":"1001","fiat":"USD","status":"PAID","txs":[{"addr":"bcrt1qtsag9mtsgdr5etcxmswank0jmvddmzyyedzj8r","amount":"0.00018615","crypto":"BTC","status":"CONFIRMED","txid":"42e4f277fe55b1d3a479ff1d15dca3e28faa7e524489d917e8246448072f4418"}]},{"amount_fiat":"29.9","balance_fiat":"0","external_id":"2001","fiat":"USD","status":"UNPAID","txs":[]}],"status":"success"}
查询不存在的 external_id 返回空列表而非报错:
{"invoices":[],"status":"success"}
⚠️ 两笔足额支付、两种终态(实测对比,集成必读):1001(18.25 USD)与 9002(1.00 USD)都是「按
payment_request返回的amount足额支付」,1001 终态PAID(回执amount_fiat_without_fee为 18.25,费额扣减后与订单额精确相等),9002 终态OVERPAID(balance_fiat1.02 大于订单额 1)。两例的差异说明费额入账比对在 v2.5.32 中并非全路径一致,商户侧不要假设amount精确等于到账值:以回调/轮询得到的status状态机为准(PAID/OVERPAID/PARTIAL均按到账处理,金额差异走对账),并以/invoices/<external_id>轮询兜底回调丢失。
6.3 发票状态机(实测观察)
| 状态 | 含义 | 实测触发 |
|---|---|---|
| UNPAID | 未收到链上入账 | 创建后初始态(2001) |
| PARTIAL | 部分支付 | 多笔累计未达额(回调报文 paid:false,见部署指南回调章节) |
| PAID | 达到应收额 | 1001:含费足额支付入块确认后 |
| OVERPAID | 超过应收额 | 9002:足额含费支付但比对口径为费前毛额 |
| EXPIRED / CANCELED | 过期 / 取消 | 管理台操作与超时(部署指南发票生命周期章节) |
6.4 未启用币种的请求:POST /api/v1/ETH/payment_request
本栈 ETH_WALLET=disabled,对其调用业务端点:
curl -s -H "X-Shkeeper-Api-Key: $API" -H 'Content-Type: application/json' \
-X POST $SK/api/v1/ETH/payment_request \
-d '{"external_id":"x","fiat":"USD","amount":"1","callback_url":"http://127.0.0.1:9000/h"}'
实测回显:
{"message":"ETH payment gateway is unavailable","status":"error"}
与配置详解 2.1 节的开关语义互证:disabled 币种无实例可路由,统一网关不可用错误。
七、地址与交易
7.1 列出收款地址:GET /api/v1/BTC/addresses
curl -s -H "X-Shkeeper-Api-Key: $API" $SK/api/v1/BTC/addresses
实测回显:
{"addresses":["bcrt1qtsag9mtsgdr5etcxmswank0jmvddmzyyedzj8r","bcrt1qm4y0nf7cv0mhqjvm6shh57q7vhk6atjmz0rjtn","bcrt1qj0mfqtqveqhlz9f66v35fpn6mv6htfqv3g2qm3","bcrt1q44vceewtd3h6zt63meg4zzx5pxplkqaanyjltx"],"status":"success"}
四条地址分别是发票 1001、2001、generate-address 生成、发票 9002 派生的地址,可对账核对。
7.2 按地址查交易:GET /api/v1/transactions/BTC/<地址>
已收款地址:
curl -s -H "X-Shkeeper-Api-Key: $API" \
$SK/api/v1/transactions/BTC/bcrt1qtsag9mtsgdr5etcxmswank0jmvddmzyyedzj8r
实测回显:
{"status":"success","transactions":[{"addr":"bcrt1qtsag9mtsgdr5etcxmswank0jmvddmzyyedzj8r","amount":"0.00018615","crypto":"BTC","status":"CONFIRMED","txid":"42e4f277fe55b1d3a479ff1d15dca3e28faa7e524489d917e8246448072f4418"}]}
未收款地址返回空数组(实测 2001 的地址):
{"status":"success","transactions":[]}
7.3 交易详情:GET /api/v1/tx-info/<txid>/<external_id>
curl -s -H "X-Shkeeper-Api-Key: $API" \
$SK/api/v1/tx-info/42e4f277fe55b1d3a479ff1d15dca3e28faa7e524489d917e8246448072f4418/1001
实测回显:
{"info":{"addr":"bcrt1qtsag9mtsgdr5etcxmswank0jmvddmzyyedzj8r","amount":"18.615","crypto":"BTC"},"status":"success"}
注意路径参数顺序是 txid 在前、external_id 在后,且该接口查的是 SHKeeper 记账视角(amount 为法币入账额 18.615),不是链上原始交易。
7.4 手动登记交易:POST /api/v1/BTC/transaction
Session 组端点,供管理台场景把链上交易手工补录进 SHKeeper 记账(body 为交易对象)。自动化对接一般用不到,排查「回调丢失 + 扫块滞后」时可配合 11.1 的 walletnotify 重放使用。
八、提现:Payout 与 Multipayout
打款链路是异步任务模型:提交(立即返回 task_id)→ Celery 执行 → 轮询任务与打款状态。
8.1 单笔打款:POST /api/v1/BTC/payout
Basic 或 Session 认证,body 四要素 destination/amount/fee/external_id(callback_url 可选,用于打款完成回调):
curl -s -u admin:q1w2e3r4 -H 'Content-Type: application/json' \
-X POST $SK/api/v1/BTC/payout \
-d '{"destination":"bcrt1qm4y0nf7cv0mhqjvm6shh57q7vhk6atjmz0rjtn","amount":"0.00002","fee":"2","external_id":"PO-3002","callback_url":"http://callback-receiver:9000/hook"}'
实测回显:
{"external_id":"PO-3002","task_id":"7f788915-5d2d-44a1-b7da-ef97449bfc47"}
三个失败分支实测:
{"message":"Bad HTTP Basic Auth credentials","status":"error"}
{"message":"'fee'","status":"error"}
{"message":"'destination'","status":"error"}
⚠️ 第三个分支是实测踩坑:请求键写
dest会得到{"message":"'destination'","status":"error"}——v2.5.32 单笔打款路径只认全拼destination(dest仅在部分配置路径兼容),且fee必填( 错误 message 就是KeyError的字段名字符串)。
8.2 任务轮询:GET /api/v1/BTC/task/<task_id>
curl -s -u admin:q1w2e3r4 $SK/api/v1/BTC/task/7f788915-5d2d-44a1-b7da-ef97449bfc47
实测回显(regtest 地址不被 bitcoinlib 识别,任务失败):
{"result":"Address bcrt1qm4y0nf7cv0mhqjvm6shh57q7vhk6atjmz0rjtn is not valid address","status":"FAILURE"}
这正好复现了部署指南排障实录的第 14 条:regtest 的 bcrt1 地址在适配器的 bitcoinlib 校验下不被认作合法地址,打款任务必然 FAILURE。主网/testnet 地址无此问题;regtest 环境验证打款链路时应预期失败在此环节(状态机、鉴权、任务轮询均已验证到执行期)。
8.3 打款状态查询:GET /api/v1/BTC/payout/status?external_id=
API Key 组,商户按自己的 external_id 查询(注意与 8.2 的 task 轮询是两条独立状态线):
curl -s -H "X-Shkeeper-Api-Key: $API" "$SK/api/v1/BTC/payout/status?external_id=PO-3002"
实测回显:
{"amount":"0.0000200000","crypto":"BTC","destination":"bcrt1qm4y0nf7cv0mhqjvm6shh57q7vhk6atjmz0rjtn","external_id":"PO-3002","id":15,"status":"IN_PROGRESS","txid":null}
实测还观察到终态 FAIL 的历史记录(external_id=PO-1003:"status":"FAIL","txid":null),即记录状态会随任务结果推进;但 8.1 的地址校验失败任务其记录停留在 IN_PROGRESS,两条状态线不完全同步,对账以 payout/status 为准并辅以 8.2 的任务结果定位失败原因。缺 external_id 参数时返回 {"error":"external_id is required"}。
8.4 按金额查询打款记录:GET /api/v1/BTC/payouts
curl -s -H "X-Shkeeper-Api-Key: $API" "$SK/api/v1/BTC/payouts?amount=0.0001"
实测回显:
{"status":"success"}
不带参数的实测回显(HTTP 200,traceback 节选):
{"message":"No amount provided.","status":"error","traceback":"Traceback (most recent call last):\n File \"/shkeeper.io/shkeeper/api_v1.py\", line 774, in list_payouts\n raise Exception(\"No amount provided.\")\nException: No amount provided.\n"}
amount 必填;该端点语义是「是否存在该金额的打款记录」,主要供节点侧脚本核对打款批次。
8.5 批量打款:POST /api/v1/BTC/multipayout
body 为打款请求数组(元素字段与单笔一致):
curl -s -u admin:q1w2e3r4 -H 'Content-Type: application/json' \
-X POST $SK/api/v1/BTC/multipayout \
-d '[{"destination":"bcrt1q9x8z7v6u5w4t3s2r1q0p9o8n7m6l5k4j3h2g1f","amount":"0.00005","fee":"2","external_id":"MPO-2001"}]'
实测回显:
{"external_ids":["MPO-2001"],"task_id":"c5c4b602-9391-4594-9ae0-cb55d2edcecc"}
注意与单笔的差异:响应是 external_ids 复数数组,对应批量里每个元素的 external_id(重复值会在提交期被拒绝)。任务轮询同样走到地址校验失败:
{"result":"Address bcrt1q9x8z7v6u5w4t3s2r1q0p9o8n7m6l5k4j3h2g1f is not valid address","status":"FAILURE"}
curl -s -H "X-Shkeeper-Api-Key: $API" "$SK/api/v1/BTC/payout/status?external_id=MPO-2001"
{"amount":"0.0000500000","crypto":"BTC","destination":"bcrt1q9x8z7v6u5w4t3s2r1q0p9o8n7m6l5k4j3h2g1f","external_id":"MPO-2001","id":12,"status":"IN_PROGRESS","txid":null}
💡 官方 README 明确 BTC 不支持 multipayout(仅部分币种适配器实现),实测表现是提交期受理、执行期报错——集成方不要靠「提交成功」判断支持度,以各币种官方说明为准。
8.6 提现目标与自动提现:payout_destinations 与 autopayout
Session 组。列出提现目标:
curl -s -b cookies.txt -H 'Content-Type: application/json' \
-X POST $SK/api/v1/BTC/payout_destinations -d '{"action":"list"}'
实测回显:
{"payout_destinations":[],"status":"success"}
autopayout 策略非法值的实测回显:
curl -s -b cookies.txt -H 'Content-Type: application/json' \
-X POST $SK/api/v1/BTC/autopayout -d '{"policy":"INVALID"}'
{"message":"Unknown payout policy: INVALID","status":"error"}
自动提现策略按管理台界面可选值传 policy,非法值在提交期即被拒绝(不会进入任务队列)。
九、钱包管理
9.1 生成地址:GET /api/v1/BTC/generate-address
Session 组,主动派生一个新收款地址(进 7.1 的地址池):
curl -s -b cookies.txt $SK/api/v1/BTC/generate-address
实测回显:
{"addr":"bcrt1qj0mfqtqveqhlz9f66v35fpn6mv6htfqv3g2qm3","status":"success"}
9.2 全钱包导出:GET /api/v1/BTC/backup
Session 组,返回全部派生地址的私钥/公钥 JSON(输出节选,私钥字段值已隐去):
curl -s -b cookies.txt $SK/api/v1/BTC/backup
{
"bcrt1q06kt5ha7xjnv60yz3fqwxvrkuhkazj6vcggp97": {
"private": "c153…(已隐去)",
"public": "02c0…(已隐去)",
"public_address": "bcrt1q06kt5ha7xjnv60yz3fqwxvrkuhkazj6vcggp97",
"wif": "zprv…(已隐去)"
},
"bcrt1q2hydwzp6qw0vldtw78yacqccqq845hldg0aald": {
…
}
}
⚠️ 这是明文私钥全量导出,等效于钱包完全失窃面:接口在 Session 组内(有登录态即可调),务必确保管理入口有 TLS 与双因素(部署指南生产化建议),导出文件离线加密保存,且不要把该端点暴露给任何自动化系统。
9.3 钱包解密状态:GET /api/v1/BTC/decrypt 与 POST /api/v1/decryption-key
decrypt(Backend Key 组)供适配器侧查询加密与解锁状态:
curl -s -H "X-Shkeeper-Backend-Key: $BK" $SK/api/v1/BTC/decrypt
实测回显:
{"key":"q1w2e3r4","persistent_status":"enabled","runtime_status":"success"}
persistent_status:enabled 表示钱包加密已启用,runtime_status:success 表示运行期已解锁,key 回显当前解密键(仅节点侧内网可信环境可调)。商户侧提交解密键走 POST /api/v1/decryption-key(API Key 组,form 表单字段 key,不是 JSON):
curl -s -H "X-Shkeeper-Api-Key: $API" -X POST $SK/api/v1/decryption-key -d 'key=whatever'
实测回显(已解锁态,无论键值对错):
{"message":"Decryption key was already entered","status":"success"}
不带 key 字段的实测回显:
{"message":"Decryption key is required","status":"error"}
源码逻辑(api_v1.py decryption_key):运行期已解锁直接返回 already entered(先于键值校验);持久态 enabled 时用 test_key 校验,对错分别返回 {"status":"success"} 与 Invalid decryption key;未加密返回 Wallet is not encrypted。钱包加密三态机制见配置详解第四章。
十、系统设置
10.1 设置读写:GET/POST /api/v1/settings/<key>
Session 组,白名单机制:v2.5.32 源码中 PUBLIC_SETTINGS 仅含 start_banner_shown 一个键,白名单外的读写一律 403:
curl -s -b cookies.txt $SK/api/v1/settings/start_banner_shown
实测回显(写入 1 之后):
{"key":"start_banner_shown","status":"success","value":"1"}
curl -si -b cookies.txt $SK/api/v1/settings/SECRET_KEY | head -1
实测回显(body 为 {"message":"forbidden","status":"error"}):
HTTP/1.1 403 FORBIDDEN
curl -s -b cookies.txt -H 'Content-Type: application/json' \
-X POST $SK/api/v1/settings/start_banner_shown -d '{"value":1}'
{"status":"success"}
💡 大多数运行期设置(汇率、费率、币种开关、回调开关等)并不通过该端点暴露,而是各自业务端点或环境变量(见配置详解二、三章);
settingsAPI 仅用于管理台横幅这类 UI 态。
10.2 未实现的端点:GET /api/v1/BTC/server
curl -si -b cookies.txt $SK/api/v1/BTC/server | head -1
实测回显(body 为 {"code":500,"status":"Internal Server Error"}):
HTTP/1.1 500 INTERNAL SERVER ERROR
确定性 500:BTC 适配器类未实现 get_rpc_credentials(api_v1.py 直接调用即抛异常),该端点只对内置直连 RPC 凭据管理的币种有意义。对 BTC 栈看到 500 不是故障,属版本边界。
十一、回调通知与 HMAC-SHA256 验签实测
11.1 节点侧回调:walletnotify 与 payoutnotify
bitcoind 通过 -walletnotify 脚本在新交易入块时回调 POST /api/v1/walletnotify/BTC/<txid>,打款完成由适配器回调 POST /api/v1/payoutnotify/BTC。两件事实测验证:
其一,必须是 POST。用 GET 触发:
curl -si $SK/api/v1/walletnotify/BTC/42e4f277fe55b1d3a479ff1d15dca3e28faa7e524489d917e8246448072f4418 | head -1
实测回显:
HTTP/1.1 405 METHOD NOT ALLOWED
其二,Backend Key 三分支(正确/错误/缺失):
curl -s -H "X-Shkeeper-Backend-Key: $BK" -X POST \
$SK/api/v1/walletnotify/BTC/42e4f277fe55b1d3a479ff1d15dca3e28faa7e524489d917e8246448072f4418
{"status":"success"}
{"message":"Wrong backend key","status":"error"}
{"message":"No backend key provided","status":"error"}
错值与缺失均为 403。payoutnotify 同样带 Backend Key 提交即回 {"status":"success"}。
扫块时序实测(支付到账延迟的关键):适配器扫块游标只推进到 safe_latest_height(全节点最新高度减 1,防重组),Celery 周期扫块。实测流程——向发票 9002 地址支付并入块后,等了一个扫块周期发票仍 UNPAID(/metrics 显示钱包游标 109 落后全节点 110);此时手动重放 walletnotify 是明确的即时补账手段:对已确认交易重放上述端点(回 success),发票随即入账转 OVERPAID。生产排查「链上已确认但发票未动」时,按此顺序:先看 /metrics 游标差,再重放 walletnotify,最后核对回调接收日志。
11.2 商户回调报文
发票入账后核心向 callback_url POST 报文,接收方必须返回 202,否则每 60 秒重发。完整报文与状态机见部署指南 8.3 节(实测样本:发票 1001 的 PAID 回调,含 balance_crypto/balance_fiat/fee_policy/transactions[] 等)。两个安全要点(与部署指南回调要点一致):新版回调带 X-Shkeeper-Signature(HMAC-SHA256)与 X-Shkeeper-Timestamp 头,务必验签后再信任;旧版 X-Shkeeper-Api-Key 头仅为兼容保留。
11.3 HMAC-SHA256 验签复算(实测 MATCH)
签名机制(源码 shkeeper/services/webhook_hmac.py):密钥为该钱包的 API Key;被签内容是紧凑序列化的回调 body——json.dumps(payload, separators=(",", ":"), sort_keys=True, ensure_ascii=False);消息 = "{ts}." + body;验签允许时钟偏差 max_age_sec=300。用发票 1001 的真实回调(接收器日志 ts=1792831233、sig=2ca8dec4aff2066d8802d2ddbfcae69ca50e8ca69ca4ff8733e45db7d789bcf3)复算:
import json, hmac, hashlib
payload = { # 回调报文(与接收器日志逐字段一致,含顶层 addr)
"addr": "bcrt1qtsag9mtsgdr5etcxmswank0jmvddmzyyedzj8r",
"balance_crypto": "0.00018615",
"balance_fiat": "18.615",
"crypto": "BTC",
"external_id": "1001",
"fee_fixed": "0",
"fee_percent": "2",
"fee_policy": "PERCENT_FEE",
"fiat": "USD",
"overpaid_fiat": "0.00",
"paid": True,
"status": "PAID",
"transactions": [{
"amount_crypto": "0.00018615", "amount_fiat": "18.615",
"amount_fiat_without_fee": "18.25", "crypto": "BTC",
"date": "2026-10-24 08:40:32", "fee_fiat": "0.365",
"trigger": True,
"txid": "42e4f277fe55b1d3a479ff1d15dca3e28faa7e524489d917e8246448072f4418"}],
}
ts, secret = 1792831233, b"NjOwsKx8v5Kctk9aY0O20w"
pretty = json.dumps(payload, indent=2, ensure_ascii=False).encode()
compact = json.dumps(payload, separators=(",", ":"), sort_keys=True, ensure_ascii=False).encode()
for name, body in (("pretty", pretty), ("compact", compact)):
sig = hmac.new(secret, f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
print(name, sig[:16], sig == "2ca8dec4aff2066d8802d2ddbfcae69ca50e8ca69ca4ff8733e45db7d789bcf3")
实测输出:
pretty beff1a5aba954bac False
compact 2ca8dec4aff2066d True
结论:紧凑序列化复算与请求头签名精确一致(MATCH),缩进序列化不匹配。验签实现照抄第 11.3 节开头四个要素即可;最常见的验签失败原因就是拿「展示用 pretty JSON」或未 sort_keys 的原始拼接去做摘要。接收端还应校验 abs(now - ts) <= 300 再比对摘要,防重放。
11.4 回调链路自测:POST /api/v1/test-callback-receiver
API Key 组,让核心把一条测试回调打到指定接收器,用于不发起真实支付就验证回调链路。空 body 会因 JSON 解析失败得到 400(实测):
{"code":400,"status":"Bad Request"}
带 body 实测:
curl -si -H "X-Shkeeper-Api-Key: $API" -H 'Content-Type: application/json' \
-X POST $SK/api/v1/test-callback-receiver \
-d '{"external_id":"TEST-CB","fiat":"USD","status":"PAID"}'
实测回显(HTTP 202):
{"message":"callback logged","status":"success"}
202
十二、错误处理与状态码实测汇总
全部错误分支逐一实测(HTTP 状态码 + body 实采):
| 场景 | HTTP | body 形态(实测) |
|---|---|---|
| 无 API Key / 错 API Key | 200 | {"message":"No API key"/"Bad API key","status":"error"} |
| API Key 访问 Session 端点 | 200 | {"message":"This endpoint doesn't accept X-Shkeeper-Api-Key auth","status":"error"} |
| Session 端点无凭据 / 用 Basic | 302 | 跳转 /login 页 |
| 打款 Basic 凭据错误 | 200 | {"message":"Bad HTTP Basic Auth credentials","status":"error"} |
| 必填字段缺失(quote/fee/destination/callback_url 等) | 200 | {"message":"'<字段名>'","status":"error"}(payment_request 缺 callback_url 另带 traceback) |
| 业务异常(dynamic 汇率不可达、地址校验失败、未知策略等) | 200 | {"message":"<异常信息>","status":"error"} |
| 未启用币种 | 200 | {"message":"<币种> payment gateway is unavailable","status":"error"} |
payouts 缺 amount | 200 | {"message":"No amount provided.","status":"error","traceback":"…"} |
| walletnotify 缺/错 Backend Key | 403 | {"message":"No backend key provided"/"Wrong backend key","status":"error"} |
| walletnotify 用 GET | 405 | Flask 默认方法不允许 |
| settings 白名单外键 | 403 | {"message":"forbidden","status":"error"} |
| metrics 无凭据 | 401 | {"msg":"authorization requred","status":"error"} |
| test-callback-receiver 空 body | 400 | {"code":400,"status":"Bad Request"} |
BTC/server(未实现) | 500 | {"code":500,"status":"Internal Server Error"} |
解读:SHKeeper 把「业务失败」几乎全部放在 HTTP 200 + status:error 里,HTTP 错误码只保留给协议层问题(方法、鉴权头、白名单、未实现)。集成代码的健壮性顺序应是:解析 JSON → 判 status → 才是 HTTP 码;同时对 message 里的 Python 异常字符串('fee'、'callback_url')做告警降噪。
十三、排障 FAQ(十二则实测踩坑)
① regtest 地址被打款任务拒绝:payout/multipayout 任务 FAILURE Address bcrt1q… is not valid address——适配器的 bitcoinlib 不识别 regtest bech32 地址(部署指南排障 14 同源)。regtest 只能验证打款链路前半段(鉴权、任务模型、状态查询),真实验打款用主网/testnet 小额。
② estimate-tx-fee 恒定报错:Provider call failed for 'estimatefee': bitcoind: -32601: Method not found——Bitcoin Core 31.1 移除了 estimatefee RPC。收款链路不依赖它;打款费率走请求里的 fee 字段。
③ payout 请求键名:必须是 destination(写 dest 得 {"message":"'destination'"}),amount/fee 必填,fee 为百分比数字(如 2 表示 2%)。
④ walletnotify 必须用 POST:GET 直接 405。bitcoind 侧 -walletnotify 脚本记得带 -X POST 与 Backend Key 头。
⑤ settings 端点 403:白名单只有 start_banner_shown,其余键一律 {"message":"forbidden"}。不要试图用该端点读写汇率/开关——走 5.2 的 exchange-rate 或环境变量。
⑥ BTC/server 500:BTC 适配器未实现 get_rpc_credentials,属版本确定性边界,非故障。
⑦ payment_request 缺 callback_url:HTTP 200 + KeyError traceback。商户 SDK 封装时把 callback_url 设为必填参数即可在客户端拦截。
⑧ dynamic 汇率源不可达:隔离网络部署 quote 报 HTTPSConnectionPool… Network is unreachable。切 manual 源(5.2);多币种实例注意逐币种配置。
⑨ 永远不要只看 HTTP 状态码:绝大多数错误是 200 + error JSON(第十二章)。 Webhook 侧同理——回调丢失时先轮询 /invoices/<external_id> 兜底。
⑩ 登录表单字段是 name:写 login/username 得 400 BAD REQUEST;Basic 又不能顶替 Session(302)。脚本自动化请先 curl -c cookies.txt -d 'name=…&password=…' /login 再带 Cookie。
⑪ 未启用币种统一「网关不可用」:ETH payment gateway is unavailable 并非网络故障,先查 <币种>_WALLET 开关与 GET /api/v1/crypto 能力列表。
⑫ 找不到 API 文档页:flask_smorest 已注册但 OpenAPI 前缀未启用,实例没有 /api-docs。以本文与源码 shkeeper/api_v1.py 为契约依据。
十四、参考与关联
- 《SHKeeper v2.5.32 介绍与选型指南》——产品定位、架构与选型对比,本文接口的背景语境;
- 《SHKeeper v2.5.32 Docker Compose 部署指南》——本文实测栈的搭建过程、发票全流程与部署侧排障(钱包解锁、扫块游标等);
- 《SHKeeper v2.5.32 配置参数详解》——本文涉及的全部环境变量(
SHKEEPER_BACKEND_KEY、*_WALLET、汇率费率、钱包加密)逐项展开; - 官方仓库:https://github.com/vsys-host/shkeeper.io (核心,接口契约见
shkeeper/api_v1.py、验签实现见shkeeper/services/webhook_hmac.py)、https://github.com/vsys-host/helm-charts ; - 官方镜像:https://hub.docker.com/u/vsyshost 。