身份验证
InfraIO Pay 有两套 API 表面,鉴权模型不同。挑选与调用方匹配的那一套:
| 表面 | 路径前缀 | 受众 | 鉴权 |
|---|---|---|---|
| 商户 B2B | /b2b/v1/* | 你的服务器 | HMAC-SHA256 请求签名 |
| 仪表板 | 供商户仪表板使用 | 商户仪表板的浏览器会话 | Bearer JWT |
本页面介绍 B2B 表面,也就是你用 API 密钥对从服务端调用的那一套。 仪表板表面供 InfraIO Pay 商户仪表板使用,不是公开的集成接口。
请始终发送包含 /b2b 前缀的完整路径,并对同一路径签名(见下文)。
端点
| 环境 | Base URL |
|---|---|
| Test | https://api-dev.infraio.xyz |
| Live | https://api.infraio.xyz |
URL 模式相同 — 环境由密钥前缀(pk_test_… vs pk_live_…)控制,
而不是 URL。
密钥对
你从商户仪表板(Developers → API keys → + Add key)获得两个值:
- Publishable key (
pk_test_…或pk_live_…)— 标识你的账户。 作为X-Client-ID发送。可安全嵌入浏览器 bundle(SDK 已经在用)。 - Secret key (
sk_test_…或sk_live_…)— HMAC 签名密钥。仅限 服务端。请像数据库密码一样对待。
如果 secret key 不慎进入浏览器 bundle、git 仓库、日志行,或被分享 到聊天 — 立刻从仪表板吊销。 吊销立即生效,没有重叠窗口。请重新签发并重新部署。
对请求签名
每次 /b2b/v1/* 调用都携带三个标头:
X-Client-ID: pk_live_70d0becbd7a53047b0dfe88a960d10ad
X-Timestamp: 1715990400
X-Signature: 9a8b7c6d… (hex HMAC-SHA256)签名在一个规范化字符串上计算:
METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + BODYMETHOD— 大写的 HTTP 动词(POST、GET、……)。PATH— 请求路径包含/b2b前缀,不含 host 且不含查询字符串 (例如/b2b/v1/checkout-sessions/quick)。前缀必须在。查询参数不参与签名 — 对于GET …?cursor=…&limit=20,只对路径签名,不要包含?…部分。TIMESTAMP— Unix 秒,十进制字符串(例如"1715990400"),与X-Timestamp完全一致。BODY— 原始请求 body 字节。GET/DELETE为空字符串。
用 secret key 作为密钥的 HMAC-SHA256 签名,输出 hex:
Node / TS
import { createHmac } from "node:crypto";
function sign({ method, path, body, secret }: {
method: string; path: string; body: string; secret: string;
}) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const input = [method.toUpperCase(), path, timestamp, body].join("\n");
const signature = createHmac("sha256", secret).update(input).digest("hex");
return { timestamp, signature };
}为什么选 HMAC,而不是 Bearer?
裸 Bearer-token API 会在每次请求里把你唯一的 secret 经线传输。任何 拿到某个 TLS 终止代理日志的人都能拿到你账户的钥匙。HMAC 签名意味着 secret 永远不上路 — 上路的只是其衍生签名,而且一次性(绑定到该次 请求 + 该分钟)。
代价是:每次调用都要计算签名。目前还没有服务端 SDK,但上面的 helper 每种语言大约 15 行。
时间戳容差
容差为 ±5 分钟(300 秒)。超出该窗口的请求会被以
401 invalid_signature 拒绝。两点含义:
- 同步服务器时钟到 NTP。时钟漂移的长跑 cron 会间歇失败。
- 不要预先计算并入队签名。 如果请求在重试队列里坐了 > 5 分钟, 它的签名就过期了。
密钥 scope
Secret key 携带一个或多个下面的 scope 包:
| Scope | 预期用途 |
|---|---|
read | 列出/读取订单、会话、退款 |
write_order | 创建结账会话、订单 |
write_refund | 发起退款、铸造退款申请 token |
webhook_manage | 创建/更新/删除 Webhook 端点 |
仪表板默认签发”全权限”密钥(全部四个 scope)。你可以从 Developers → API keys → + Add key 铸造受限 scope 的密钥,仅勾选 集成需要的 scope。
scope 目前尚未强制执行。 scope 会记录在密钥上并显示在仪表板中,
但任何有效的 sk_… 密钥都可以调用你商户的任意 /b2b/v1/* 端点。
请勿把 scope 当作安全边界。请通过轮换或吊销密钥来限制访问。
验证失败
如果签名、X-Client-ID 或时间戳无效,请求会在到达 API 之前被以
401 INVALID_SIGNATURE 拒绝。只有 /b2b/v1/* 请求使用这种方式签名。
Webhook 使用另一套方案(见下文)。
下一步
- 错误 — 4xx/5xx 的响应形态。
- 安全 → API 密钥 — 轮换、吊销,以及 secret 泄露时的处理。
- Webhooks → 签名验证 —
使用不同的 HMAC 方案(标头
X-Signature: sha256=…,签名X-Timestamp + "." + raw_body,加上 24 小时轮换宽限窗口内可选的X-Signature-Prev)。不要把两种方案搞混 — 它们共用哈希算法, 但签名字节和 secret 家族(whsec_…vssk_…)不同。