Z Zise Developers
账户中心 › 指南

快速开始

15 分钟跑通第一笔调用。

1 · 拿到凭据

在商户后台「开发者 → API Keys」创建一把 sandbox Key。创建时会一次性

返回三个值:

用途我方是否保存
client_id换令牌时标识你是谁是(常驻列表可见)
api_key换令牌的密钥只存哈希,丢了只能新建一把
signing_key请求签名的对称密钥是(HMAC 是对称的,单向哈希验不了签名)

live 环境的 Key 必须配 IP 白名单(强制非空)。

2 · 换取访问令牌


POST https://api-sandbox.zise.com/v1/connect/token
x-client-id: zc_test_7f3a9b2e4c1d
x-api-key: sk_test_••••••••••••

→ 200
{
  "auth_token": "eyJhbGciOiJIUzI1NiIs…",
  "expired_at": 1754872200,
  "token_type": "Bearer",
  "scopes": ["members:write","members:read", …]
}

有效期 30 分钟。多令牌可并存 —— 你的多个进程各换各的,不会互相踢掉

(这一点与某些同类平台相反,那个行为在多进程侧是个坑)。

签发限流 20 次/分钟:令牌该被缓存复用,高频换取本身是接入错误的信号。

3 · 给写请求签名

签名串五段,换行连接,顺序不可换


POST
/v1/members
1754870400
2b7e1516-…-0f3c
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

x-signature = base64(HMAC-SHA256(signing_key, 签名串))

三件容易踩的事:

键序,签名会恒不匹配 —— 而报错长得像「密钥配错了」。

而一笔重放的付款就是第二笔真实付款。

4 · 建一个会员


POST /v1/members
x-auth-token: Bearer eyJ…
x-idempotency-key: 6f1c2d80-…-9a4e     ← UUID v4,写请求必带
x-timestamp / x-nonce / x-signature

{ "external_member_id": "u_88213", "email": "a@example.com" }

→ 201
{ "id": "mem_…", "external_member_id": "u_88213", "kyc_level": 0, … }

external_member_id你自己体系里的用户 ID —— 之后所有会员作用域的

调用都用它切入(x-on-behalf-of: u_88213)。

5 · 上报一笔入金

你在自己的托管侧收到链上转账之后,调这个接口让会员余额增加:


POST /v1/deposits
x-on-behalf-of: u_88213

{ "asset": "USDT", "amount": "100.000000", "reference": "0xabc…" }

reference你的业务流水号(通常是链上交易哈希)。它进幂等键 ——

重复上报同一笔时我方返回 replayed: true不会记第二笔余额。

6 · 接 Webhook

在商户后台配一个 https 端点。我方推送时带:


z-signature: t=<unix秒>,v1=<base64>
v1 = HMAC-SHA256(webhook_secret, "<t>." + rawBody)

四件必须做的事:

1. 校验签名,并检查 t 在 ±300 秒内(防重放);

2. event_id 幂等 —— 我方保证至少一次,不保证恰好一次;

3. status_version 向前合并 —— 收到倒序事件应丢弃。少了它,

一次慢响应会把「已到账」打回「处理中」;

4. 任意 2xx 即算成功,响应体我方不解析。超时 10 秒。

失败退避 5 次(1min / 5min / 30min / 2h / 6h),之后转 dead 并在商户后台

告警。不会静默丢弃。

上线前

去看「Go-live 检查清单」。其中最容易漏的一条:沙盒配额不高于生产

(我方两边取生产值),所以沙盒压测通过 = 生产也不会被限流。

参考 SDK

两份单文件、零/极少依赖的客户端随文档一起分发:

语言文件依赖
Nodesdk/node/zise.mjs无(Node 18+ 内置 fetch + node:crypto
Pythonsdk/python/zise_client.py只有 requests

各自目录下的 README.md 有完整用法。它们不是官方 SDK,不发布到

npm / PyPI、不承诺向后兼容 —— 行为与本文档冲突时以本文档为准。

建议直接把文件复制进你的工程再按需改。

之所以值得用它们起步:接入期真正出事故的是签名与重试,而这两件事的失败

形态都不报错。SDK 里封死了六件容易写反的:

1. 换令牌单飞 —— 首屏那几个并发不做单飞会各换一次,

而签发限流是 20 次/分钟,八并发刷三次就把自己限流了;

2. 每次投递重新签名(nonce 换新、时间戳刷新),而幂等键原封不动——

沿用旧签名头是 nonce_reused,换新幂等键是第二笔真实付款;

3. 「重试同一笔」与「发起新的一笔」在 API 上是同一个头,SDK 用两个不同的

动作表达(client.post() 生成新键 / call.send() 沿用同键);

4. 只对 504 与网络层失败自动重试,4xx 一律不重试 ——

写反了就是对一个永远不会成功的请求无限重试,把限流配额也一起烧光;

5. 金额一律留在字符串里,运算走 BigInt / Decimal(str(x))

6. 错误对象带 coderequest_id,让你按 code 分支而不是按 HTTP 码。

5 分钟跑通第一个调用

下面这段不依赖任何东西(Node 18+ 直接 node first-call.mjs),

把整条链走一遍:换令牌 → 签名 → 建会员。看懂它就看懂了全部三段。


// first-call.mjs
import { createHash, createHmac, randomUUID } from "node:crypto";

const BASE = "https://api-sandbox.zise.com";   // ⚠ 环境判据是域名,不是请求头
const CLIENT_ID  = process.env.ZISE_CLIENT_ID;
const API_KEY    = process.env.ZISE_API_KEY;      // 换令牌用
const SIGNING_KEY = process.env.ZISE_SIGNING_KEY; // 验签用(是另一个值)

// ① 换令牌(30 分钟,缓存复用;这是整条链上唯一不带令牌的端点)
const tokRes = await fetch(`${BASE}/v1/connect/token`, {
  method: "POST",
  headers: { "x-client-id": CLIENT_ID, "x-api-key": API_KEY },
});
const tok = await tokRes.json();
if (!tokRes.ok) throw new Error(`${tok.code}: ${tok.message}`);
console.log("token 到期于", new Date(tok.expired_at * 1000).toISOString());

// ② 签名。⚠ 序列化「只做一次」—— 哈希与发送必须是同一串字节,
//    否则键顺序一变签名恒不匹配,而报错长得像「密钥配错了」。
const path = "/v1/members";
const rawBody = JSON.stringify({ external_member_id: "u_88213", email: "u88213@example.com" });
const timestamp = String(Math.floor(Date.now() / 1000));   // 秒,不是毫秒
const nonce = randomUUID();                                // 每次换新
const bodyHash = createHash("sha256").update(rawBody, "utf8").digest("hex");
const signature = createHmac("sha256", SIGNING_KEY)
  // 五段,换行连接,顺序不可换。第二段含 query(这里没有)。
  .update([ "POST", path, timestamp, nonce, bodyHash ].join("\n"), "utf8")
  .digest("base64");                                       // ⚠ 是 base64,不是 hex

// ③ 调业务
const res = await fetch(BASE + path, {
  method: "POST",
  headers: {
    "x-auth-token": `Bearer ${tok.auth_token}`,
    "content-type": "application/json",
    "x-idempotency-key": randomUUID(),   // 写请求必带;重试要沿用「同一把」
    "x-timestamp": timestamp,
    "x-nonce": nonce,
    "x-signature": signature,
  },
  body: rawBody,                          // 与算哈希的是同一个字符串
});
const out = await res.json();
console.log(res.status, out.id ?? `${out.code}: ${out.message}`, "req", res.headers.get("X-Request-Id"));

跑之前先在商户后台「开发者 → API Keys」建一把 sandbox Key,把三个值

放进环境变量。api_key 我方只存哈希,关掉创建页面就取不回来了

调不通时按这个顺序查:environment_mismatch = 沙盒 Key 打到了生产域名;

timestamp_out_of_range = 机器时钟漂了;invalid_signature = 九成是

body 被重新序列化过,或者签名写成了 hex。

用 SDK 的话,上面整段就是这三行:


import { ZiseClient } from "./sdk/node/zise.mjs";
const zise = new ZiseClient({ baseUrl: BASE, clientId: CLIENT_ID, apiKey: API_KEY, signingKey: SIGNING_KEY });
const r = await zise.post("/v1/members", { external_member_id: "u_88213", email: "u88213@example.com" });

from zise_client import ZiseClient
zise = ZiseClient(BASE, CLIENT_ID, API_KEY, SIGNING_KEY)
r = zise.post("/v1/members", {"external_member_id": "u_88213", "email": "u88213@example.com"})