认证与签名
Invoice、Transfer 和 Query 使用账户级 API Key + Ed25519 请求签名。API Key 用于定位凭证,Ed25519 用于证明请求来自持有私钥的商户系统。
必需 Header
Section titled “必需 Header”| Header | 必填 | 说明 |
|---|---|---|
X-API-ID |
是 | 账户级 API ID |
X-API-Key |
是 | 账户级 API Key |
X-Timestamp |
是 | Unix 秒级时间戳,默认允许 ±60 秒偏差 |
X-Signature |
是 | Ed25519 签名的标准 Base64 编码 |
X-Idempotency-Key 或 Idempotency-Key |
写请求二选一 | 两者同时发送时必须相等,否则返回 400 INVALID_REQUEST |
签名 Payload
Section titled “签名 Payload”v1{api_id}{merchant_account_id}{METHOD}{escaped_path}{canonical_query}{timestamp}{idempotency_key}{sha256(body)}字段之间使用单个 \n,末尾不额外追加换行。METHOD 使用大写;body hash 使用原始发送字节计算,不要先解析再序列化。
escaped_path必须与 HTTP 请求实际发送的 path 完全一致,不含 query。canonical_query的生成规则见下表;没有 query 时使用空字符串。- 空 body 也要计算 SHA-256,不要省略最后一行。
{idempotency_key}使用X-Idempotency-Key的值;未发送该 Header 时才使用Idempotency-Key。两者都没有的只读请求使用空字符串。Header 值两端空白会被忽略。- 每次重试可刷新 timestamp 和签名,但必须沿用原幂等键与完全相同的业务 payload。
字段取值与编码
Section titled “字段取值与编码”| 项 | 规则 |
|---|---|
api_id |
X-API-ID 的值,去除两端空白 |
merchant_account_id |
凭证所属商户账户的 UUID(小写、带连字符),由 PSP 在签发凭证时提供 |
METHOD |
大写,如 POST |
escaped_path |
请求 URL 中已转义的 path,按实际发送的值,不要重新编码 |
canonical_query |
先对 query 做 URL 解码,同名参数的值升序排列,再按参数名升序拼成 key=value&key=value(按 UTF-8 字节序比较);键和值重新做标准 query 转义:保留 A-Z a-z 0-9 - _ . ~,空格写 +,其余字节写大写 %XX(例如 * 写 %2A,/ 写 %2F)。无值参数 flag 视为 flag=,空片段(&&)忽略;含非法 % 转义(如 %ZZ、结尾的 %、不完整的 %2)或未转义分号 ; 的 query 会被拒绝并返回 400 INVALID_REQUEST,需要分号时请写成 %3B |
timestamp |
X-Timestamp 的十进制字符串,与 Header 完全一致 |
sha256(body) |
对实际发送的原始 body 字节计算 SHA-256,输出 64 位小写十六进制;空 body 为 e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 |
| 签名 | 对整个 payload 的 UTF-8 字节做 Ed25519 签名,结果用标准 Base64(带 = 填充);服务端也接受无填充的 Base64 |
| 公钥 | 上传给 PSP 的是 Ed25519 公钥原始 32 字节的标准 Base64 |
Invoice、Transfer 和 Query 使用同一种 payload 格式;服务之间的隔离靠 Host 与凭证类型(见服务与 Host),payload 中没有 service 字段。body 以实际发送的字节为准(JSON 通常是 UTF-8),不要在签名后再改动。
用下面的固定输入验证你的实现。向量由 PSP 的参考签名工具生成,并用独立实现交叉验证。
- 私钥种子(32 字节,Base64):
AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8= - 对应公钥(Base64):
A6EHv/POEL4dcN0Y50vAmWfk1jCbpQ1fHdyGZBJVMbg= api_id:api_demo;merchant_account_id:00000000-0000-7000-8000-000000000001;timestamp:1767225600
向量 1:POST,带 body 与幂等键
body(共一行,无尾部换行):
{"payment_method":"pix","rail_variant":"pix_qr","amount":10000,"currency":"BRL","merchant_order_id":"ORDER_20260827_001"}幂等键 invoice:create:ORDER_20260827_001,待签名 payload:
v1api_demo00000000-0000-7000-8000-000000000001POST/api/v1/payments/intents
1767225600invoice:create:ORDER_20260827_001b78e856a2e4eb24adf9807614503946dd9fc51d2403259858c7666d46c6ffeb7期望签名:
pHBYN6FdiNc1as4N/doyvJO4n9jXfm4cIIcXcnS2N/mOWxxwwNJsoW2AmrjqtnZeOwgiHpfRXKfaZNy5ipXWAA==向量 2:GET,带 query,无 body、无幂等键
实际发送的 URL 为 /api/v1/payments/intents?status=pending&limit=20&status=paid,规范化后的 canonical_query 是 limit=20&status=paid&status=pending。待签名 payload(第 8 行为空串):
v1api_demo00000000-0000-7000-8000-000000000001GET/api/v1/payments/intentslimit=20&status=paid&status=pending1767225600
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855期望签名:
cih5j8cxplQmgoxQjvWiitnV7dJKguV23HLCxbqENcP5W+WMc9Gl48unNTmXUQGFyBCrpXgG29lrDhlhbyYmCA==- 私钥放入 KMS、HSM 或受控 Secret Manager,不要写入源码、镜像或日志。
- API Key、签名、完整请求体和敏感付款信息不得作为常规日志字段。
- 分环境、分商户账户、分 credential kind 管理密钥。
- 轮换时保留短暂双公钥窗口,并先验证新密钥再撤销旧密钥。