Z Zise Developers
POST /v1/withdraw-addresses

新增提现地址(挂强认证 + 24 小时冷静期

Scope withdrawals:write 代会员调用 · 必带 x-on-behalf-of x-idempotency-key

链上地址是提现里唯一由用户自由输入、且错了不可挽回的东西。

银行账号打错钱会退回来,链上地址打错就是没了。所以这里三件事一起做,

不做二选一:强认证 + 冷静期 + 下单时把地址快照进订单行。

强认证走我方托管屏,商户不能自证

请求体里的 step_up_passed: true 这种形态一律不做 ——

那等于把闸门交给对端。交互是两趟:

1. 第一次调用(不带 x-step-up)→ 400 step_up_required

响应里带 challenge_id / hosted_url / expires_at(Unix 秒,

有效期 300 秒);

2. 把 hosted_url 给终端用户打开,他在我方页面上过真实因子校验;

3. 你用同一把 x-idempotency-key、同一份请求体重发,

并带上 x-step-up: <challenge_id>

⚠ 第 3 步之所以能成立,是因为幂等层对 step_up_required 这一种

400 刻意不做回放request_hash 只算 body、不含请求头,

带不带 x-step-up 算出来的哈希一模一样)。除它以外的任何错误,

同键重发都会把首次那个错误原样还给你 —— 那时要换新键。

票据是一次性的,且三重绑定(绑会员、绑动作、限时)。

用完即废,不要缓存复用。

24 小时冷静期不可由商户跳过

冷静期长度由渠道决定(withdraw_channels.cooling_hours

缺省 24 小时),不由你传参,也没有任何参数能缩短它。

它存在的全部意义是给那三条通知(邮件 + 推送 + 站内信)时间被看到 ——

那是整套地址安全模型里唯一能让终端用户自己发现异常的环节,

而你的服务器不在那条通路上。所以我方也不会把地址新增这件事

作为 Webhook 事件外发给你。

七道校验的顺序(任一不过即拒,不会落库)

条数上限 → 本地格式与 EIP-55 校验和 → 我方自己的充值地址 →

全平台黑名单 → 上游校验(挂了放行并留痕)→ 唯一索引 → 三条通知。

⚠ 未知网络一律:我方不认识那条链的地址规则时放行,

等于把校验完全交给用户的手指。

前置条件

  • 已拿到一张通过的强认证票据(第一次调用必然拿不到,见正文两趟交互)
  • 该资产与网络组合上存在启用的提现渠道
  • 该会员的地址条数未达上限(缺省 20 条)
字段类型必填说明
x-on-behalf-of string 必填 给哪个会员加地址
x-idempotency-key string 必填 UUID。强认证那一趟必须沿用同一把键,见正文。
x-step-up string 可选 上一趟拿到的 challenge_id。首次调用不带(也带不了)。 票据必须已由终端用户在托管屏上完成校验,否则视同没带。

请求体

字段类型必填说明
asset string 可选 资产代码。留空默认 USDT(这是唯一一个有隐含默认值的 字段,别依赖它,显式传)。
network string 必填 网络机读码,如 BSC大小写敏感。 未知网络一律拒绝(当前只有 EVM 系那几条)。
address string 必填 链上地址。EVM 系必须是 0x + 40 位十六进制, 且混合大小写时 EIP-55 校验和必须对得上 (全小写或全大写放行)。落库前会归一化成 EIP-55 形态, 响应里返回的就是归一化之后的那一串。
label string 可选 备注名,超过 40 字符会被截断(不报错)。

响应

201已加入地址簿,冷静期开始计时
{
  "id": "wad_3c81f0d2-9a44-4d17-8e0b-2f6a1c9d4e77",
  "address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
  "usable_at": "2026-08-13T09:20:00Z",
  "cooling_hours": 24,
  "upstream_valid": 1
}
400step_up_required 需要强认证,响应带 challenge_id / hosted_url / expires_at这是流程的一步,不是错误) · invalid_request 请求体不是合法 JSON · invalid_fields 缺 network 或 address · address_not_allowed 格式不对 / 是我方自己的充值地址 / 在全平台黑名单里(三种同一响应,不要据它反推是哪一种) · limit_exceeded 该会员的地址条数已达上限 · product_not_available 这个资产与网络的组合上没有可用渠道
409duplicate_resource 这条地址已经在簿子里了 · idempotency_key_reused · idempotency_in_progress
500已知偏差:EIP-55 校验和不对、填了零地址/销毁地址、 网络与地址规则不匹配 —— 这三种本该是 400,当前会以 api_error + 500 返回(内部码没登记对外映射)。 不要对它做无限重试 —— 同样的地址重试多少次都不会成功, 先把地址本身检查一遍。
调用样例
curl -X POST 'https://api.zise.com/v1/withdraw-addresses' \
  -H 'authorization: Bearer $TOKEN' \
  -H 'x-zise-merchant: $MERCHANT_ID' \
  -H 'x-on-behalf-of: $MEMBER_ID' \
  -H 'x-idempotency-key: $(uuidgen)' \
  -H 'content-type: application/json' \
  -d '{
    "asset": "USDT",
    "network": "BSC",
    "address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
    "label": "我的冷钱包"
  }'
const res = await fetch(
  "https://api.zise.com/v1/withdraw-addresses",
  {
    method: "POST",
    headers: {
      "authorization": "Bearer $TOKEN",
      "x-zise-merchant": "MERCHANT_ID",
      "x-on-behalf-of": "MEMBER_ID",
      "x-idempotency-key": "crypto.randomUUID()",
      "content-type": "application/json"
    },
    body: JSON.stringify({
      "asset": "USDT",
      "network": "BSC",
      "address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
      "label": "我的冷钱包"
    }),
  },
);
// 金额一律按字符串读,不要 JSON.parse 成 number
const data = await res.json();
import requests

res = requests.post(
    "https://api.zise.com/v1/withdraw-addresses",
    headers={
        "authorization": "Bearer $TOKEN",
        "x-zise-merchant": "$MERCHANT_ID",
        "x-on-behalf-of": "$MEMBER_ID",
        "x-idempotency-key": "$(uuidgen)",
        "content-type": "application/json"
    },
    json={
        "asset": "USDT",
        "network": "BSC",
        "address": "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
        "label": "我的冷钱包"
    },
)
# 金额用 Decimal(str(...)),不要 float
data = res.json()