Z Zise Developers 简体中文
Account Center › Guides

From receiving your API Key to your first real call in five minutes.

Quickstart

Enter these three values to update every code block on this page

These values stay in your browser and are not sent to us. This is a static page.

Make your first call in 15 minutes.

1 · Obtain Credentials

Create an API Key under “Developer → API Keys” in the merchant portal. Creation returns three values once only:

ValuePurposeDo we retain it?
client_idIdentifies you when obtaining a tokenYes (visible in the persistent list)
api_keySecret for obtaining tokensHash only; create a new key if lost
signing_keySymmetric key for request signingYes (HMAC is symmetric; a one-way hash cannot verify signatures)

Live keys require an IP allowlist (it cannot be empty).

2 · Obtain an Access Token


POST https://api.zinfra.dev/v1/connect/token
x-client-id: zc_7f3a9b2e4c1d
x-api-key: sk_••••••••••••

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

Valid for 30 minutes. Multiple tokens can coexist—separate processes can obtain their own without invalidating each other (unlike some comparable platforms, where that behavior causes multiprocess problems).

Issuance is limited to 20 per minute. Cache and reuse tokens; frequent issuance itself signals an integration error.

3 · Sign Write Requests

The signature string has five segments, joined by newlines in this exact order:


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

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

Three common pitfalls:

4 · Create a Member


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 is your own system's user ID. Use it for all subsequent member-scoped calls (x-on-behalf-of: u_88213).

5 · Report a Deposit

After receiving an on-chain transfer in your own custody system, call this endpoint to increase the member's balance:


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

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

reference is your business transaction reference (usually the on-chain transaction hash). It participates in idempotency— reporting the same deposit again returns replayed: true and does not credit it twice.

6 · Integrate Webhooks

Configure an https endpoint in the merchant portal. Our deliveries include:


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

Four required actions:

  1. Verify the signature and ensure t is within ±300 seconds (replay protection);
  2. Deduplicate by event_id—delivery is at least once, not exactly once;
  3. Merge forward by status_version—discard out-of-order events. Without this, a slow response can turn “received” back into “processing”;
  4. Any 2xx means success; we do not parse the response body. Timeout is 10 seconds.

Failures are retried five times with backoff (1min / 5min / 30min / 2h / 6h), then marked dead and reported in the merchant portal. They are never silently discarded.

Before Going Live

Read the “Go-live Checklist.” A frequently missed point: sandbox quotas are no higher than production (both use production values), so passing sandbox load tests means production will not rate-limit that load either.

Reference SDKs

Two single-file clients with zero or minimal dependencies are distributed with the documentation:

LanguageFileDependencies
Nodesdk/node/zise.mjsNone (Node 18+ built-in fetch + node:crypto)
Pythonsdk/python/zise_client.pyOnly requests

Each directory's README.md contains complete usage instructions. They are not official SDKs, are not published to npm / PyPI, and carry no backward-compatibility guarantee. This documentation takes precedence if behavior differs. Copy the files into your project and adapt them as needed.

They are useful starting points because signing and retries cause real integration incidents, and both can fail without obvious errors. The SDKs enforce six commonly reversed rules:

  1. Single-flight token acquisition—without it, concurrent first-screen requests each obtain a token, and a 20-per-minute issuance limit means three refreshes with eight concurrent calls rate-limit you;
  2. Sign every delivery again (new nonce and timestamp), while retaining the exact idempotency key— old signature headers cause nonce_reused; a new idempotency key creates a second real payment;
  3. “Retry the same operation” and “start a new operation” use the same API header, but the SDK exposes separate actions (client.post() creates a new key / call.send() retains the same key);
  4. Automatically retry only 504 and network failures, never 4xx— reversing this causes endless retries of a request that cannot succeed, exhausting the rate limit too;
  5. Keep all amounts as strings; calculate using BigInt / Decimal(str(x));
  6. Error objects include code and request_id, so you branch on code rather than HTTP status.

Your First Call in 5 Minutes

The following has no dependencies (run node first-call.mjs directly on Node 18+), and walks the entire chain: obtain token → sign → create member. Understanding it covers all three stages.


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

const BASE = "https://api.zinfra.dev";   // ⚠ 环境判据是域名,不是请求头
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"));

Before running, create an API Key under “Developer → API Keys” in the merchant portal and place the three values in environment variables. We store only the hash of api_key; it cannot be retrieved after closing the creation page.

If the call fails, check in this order: invalid_credentials = wrong credentials or credentials from another deployment environment; timestamp_out_of_range = clock drift; invalid_signature = usually (90%) a reserialized body or a signature encoded as hex.

With the SDK, the entire example above becomes these three lines:


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"})