POST /apiv2/withdraw
从您的 Netts 余额提取 TRX 至 TRON 地址。请求会立即返回订单号;实际的链上打款由后端异步执行(约 5 分钟内完成)。可通过轮询状态端点或配置 webhook 来跟踪处理结果。
ℹ️ 运作机制。 提交提现请求会立即从您的余额中预扣该金额(在订单被接受的瞬间即完成余额扣除)。随后后端守护进程会发送 TRX 并将订单标记为
completed或failed。初始响应中不包含同步的链上结果 — 您首先收到的始终是pending确认。在订单被接受前,系统会对目标地址进行检查。若无法向该地址提现,或当前无法完成检查,请求将被拒绝:不会创建订单,也不会从您的余额中预扣任何资金。
端点 URL
POST https://netts.io/apiv2/withdraw请求头
| 请求头 | 必填 | 描述 |
|---|---|---|
| Content-Type | 是 | application/json |
| X-API-KEY | 是 | 来自 Netts 控制台的 API 密钥 |
| X-Idempotency-Key | 否 | 客户端生成的防重放密钥(base64,可选),用于安全重试以避免重复提现。如果省略,服务器将自动派生一个。该值会成为您的 orderId。 |
请求体
{
"amount": 15,
"address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}请求参数
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| amount | 浮点数 | 是 | 以 TRX 为单位的总金额(最低为 3)。手续费将从此金额中扣除 — 收款方收到 amount − fee(即 net)。 |
| address | 字符串 | 是 | 目标 TRON 地址(T…,34 个字符,base58 编码)。在订单被接受前会进行检查。 |
| sub_and_robot_out | 布尔值 | 否 | 机器人/子账户出金模式:收取 2 TRX 手续费而非 1 TRX。默认为 false。 |
手续费。 从总金额
amount中扣除固定手续费:正常情况下为 1 TRX,当sub_and_robot_out = true时为 2 TRX。如果amount − fee ≤ 0,订单将被拒绝。
请求示例
以下示例还构建并发送了
X-Idempotency-Key,以防止意外重复请求导致二次提现。完整规则请参见幂等性。
cURL
API_KEY="your_api_key"
ADDR="TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
AMOUNT=15
NONCE=$(( $(date +%s) / 2 )) # 在 2 秒窗口内重试保持稳定;或使用您自己的订单 UUID
# X-Idempotency-Key = base64url( HMAC-SHA256( API_KEY, "addr:amount:nonce" ) )
IDEMP=$(printf '%s' "${ADDR}:${AMOUNT}:${NONCE}" \
| openssl dgst -sha256 -hmac "$API_KEY" -binary | basenc --base64url | tr -d '=')
curl -X POST https://netts.io/apiv2/withdraw \
-H "Content-Type: application/json" \
-H "X-API-KEY: $API_KEY" \
-H "X-Idempotency-Key: $IDEMP" \
-d "{\"amount\": $AMOUNT, \"address\": \"$ADDR\"}"Python
import time, hmac, hashlib, base64, requests
API_KEY = "your_api_key"
url = "https://netts.io/apiv2/withdraw"
payload = {"amount": 15, "address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
# X-Idempotency-Key = base64url( HMAC-SHA256( API_KEY, "addr:amount:nonce" ) ),去除填充符。
# 每个订单仅生成一次,并在每次重试时重新发送完全相同的值。
nonce = str(int(time.time() // 2)) # 2 秒时间窗口;或使用您自己的订单 UUID
message = f"{payload['address']}:{payload['amount']}:{nonce}"
idem_key = base64.urlsafe_b64encode(
hmac.new(API_KEY.encode(), message.encode(), hashlib.sha256).digest()
).decode().rstrip("=")
headers = {
"Content-Type": "application/json",
"X-API-KEY": API_KEY,
"X-Idempotency-Key": idem_key,
}
resp = requests.post(url, headers=headers, json=payload)
detail = resp.json().get("detail", {})
if resp.status_code == 202 and detail.get("status") == "pending":
d = detail["data"]
print(f"Order ID: {d['orderId']}") # 用于状态端点 / webhook
print(f"Net to recipient: {d['net']} TRX (fee {d['fee']})")
else:
print(f"Code {detail.get('code')}: {detail.get('msg', detail)}")响应
已接受 — 提现已进入队列 (202 Accepted)
相应金额已从您的余额中预扣,打款已排队。轮询状态端点(或等待 webhook 通知),直到其变为 completed 或 failed。
{
"detail": {
"code": 10000,
"status": "pending",
"msg": "Withdrawal request accepted, processing within 5 minutes.",
"data": {
"orderId": "EXAMPLEorderId0000000000000000000000000000Aa",
"amount": 15.0,
"fee": 1.0,
"net": 14.0,
"address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}响应字段
| 字段 | 类型 | 描述 |
|---|---|---|
| detail.code | 整数 | 10000 表示已接受 |
| detail.status | 字符串 | pending |
| detail.data.orderId | 字符串 | 订单号 — 43 位 URL 安全字符串。用于状态端点,并用于在 webhook 载荷中标识订单。 |
| detail.data.amount | 浮点数 | 请求的总金额(TRX) |
| detail.data.fee | 浮点数 | 扣除的手续费(1 或 2 TRX) |
| detail.data.net | 浮点数 | 收款方实际收到的金额(amount − fee) |
| detail.data.address | 字符串 | 目标地址 |
状态端点
GET https://netts.io/apiv2/withdraw/status/{orderId}请求头:X-API-KEY(订单必须属于当前已认证的用户)。 orderId 是 URL 安全的 — 原样传递即可,无需 URL 编码。
| 订单状态 | HTTP | code | status |
|---|---|---|---|
| 已完成(TRX 已发送) | 200 | 10000 | completed(包含 processed_at) |
| 排队中 / 发送中 | 200 | 10001 | pending |
| 失败 | 200 | 5003 | failed(包含 error_message) |
| 未找到 / 不属于您 | 404 | -1 | — |
{
"detail": {
"code": 10000,
"status": "completed",
"data": {
"orderId": "EXAMPLEorderId0000000000000000000000000000Aa",
"amount": 15.0, "fee": 1.0, "net": 14.0,
"address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"processed_at": "2026-01-01 00:00:00+00:00"
}
}
}子账户
子账户提现的操作方式与常规用户完全相同 — 只需使用子账户自己的 API 密钥即可。 子账户使用其自身的密钥进行认证并调用相同的 POST /apiv2/withdraw 端点;提现金额将直接从该子账户自己的余额中扣除,并发送至请求中指定的 address。最低限额相同、手续费相同(1 TRX)、地址检查相同、流程相同。没有单独的子账户端点 — 每个账户(无论是主账户还是子账户)始终只能使用自己的密钥提取自己的余额。
Webhooks
无需进行轮询,您只需配置一次 webhook,当您的每笔提现达到最终状态(completed / failed)时,Netts 就会通过 POST 发送一条带签名的通知。Webhook 是按用户存储的,并适用于该账户的所有提现。如果未配置 webhook,只需轮询状态端点即可。
配置 / 查看 / 删除
POST https://netts.io/apiv2/withdraw/webhook # 创建或更新
GET https://netts.io/apiv2/withdraw/webhook # 查看当前配置(绝不会返回 secret)
DELETE https://netts.io/apiv2/withdraw/webhook # 退订请求头:X-API-KEY。
// POST 请求体
{
"callback_url": "https://your-server.example/netts/withdraw-hook",
"secret": "your_shared_secret_min_8_chars",
"enabled": true
}| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
| callback_url | 字符串 | 是 | 接收 POST 请求的 http(s) URL(≤ 2048 字符) |
| secret | 字符串 | 是 | 用于签名各载荷的共享密钥(8…256 字符) |
| enabled | 布尔值 | 否 | 在不删除配置的情况下启用/关闭推送。默认为 true |
GET 请求返回 { callback_url, enabled, secret_set, updated_at } — secret 本身绝不会被回显返回。
推送载荷
Netts 向您的 callback_url 发送 POST 请求,附带请求头 X-Netts-Signature: base64( HMAC-SHA256( secret, raw_body ) ) 以及如下 JSON 请求体:
{
"orderId": "EXAMPLEorderId0000000000000000000000000000Aa",
"status": "completed",
"amount": 15.0,
"fee": 1.0,
"net": 14.0,
"address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"processed_at": "2026-01-01 00:00:00+00:00",
"error_message": null
}status为completed或failed(当为failed时,将填充error_message)。
验证签名
签名是基于请求体的规范 JSON 计算得出的:键名已排序,无空格(separators=(",", ":"))。请使用相同的方式重新计算并进行比对。
import hmac, hashlib, base64, json
def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = base64.b64encode(
hmac.new(secret.encode(), raw_body, hashlib.sha256).digest()
).decode()
return hmac.compare_digest(expected, signature_header)
# Flask 示例:先针对接收到的原始字节(EXACT bytes)进行验证,然后再解析。
# if verify(request.get_data(), request.headers["X-Netts-Signature"], SECRET): ...请务必针对接收到的原始字节进行验证。如果您重新序列化已解析的 JSON,请还原其规范形式:
json.dumps(payload, ensure_ascii=False, separators=(",",":"), sort_keys=True)。
推送保证
- 请响应 HTTP 2xx 以进行确认。任何其他响应(或超时)均被视为推送失败。
- 每个订单最多尝试 3 次,时间窗口为自订单创建起 21 分钟内(重试退避间隔约为 5 分钟)。超出后将放弃推送 — 此时请回退到状态端点查询。
- 推送会进行去重:每个订单最多成功推送一次。
- 请使您的处理程序基于
orderId具备幂等性。
错误响应
认证错误 (401)
{ "detail": { "code": -1, "msg": "Invalid API key or IP not in whitelist" } }余额不足 (403)
{ "detail": { "code": 1004, "status": "failed", "msg": "Insufficient balance: 2.0 < 15 TRX" } }地址不可用 (403)
不接受向此目标地址提现。不会创建订单,也不会预扣任何资金。请使用其他地址或联系客服支持。
{ "detail": { "code": 1006, "status": "failed", "msg": "Withdrawals to this address are not available. Contact support." } }地址检查服务不可用 (503)
当前无法检查目标地址。不会创建订单,也不会预扣任何资金。请稍后重试 — 可以复用相同的 X-Idempotency-Key。
{ "detail": { "code": 1009, "status": "failed", "msg": "Address check is temporarily unavailable. No request was created. Retry shortly." } }存在未完成的提现 (409)
您在自己的余额上一次只能有一笔未完成的提现。请等待当前提现处理完毕。
{ "detail": { "code": 4090, "status": "failed", "msg": "You have a pending withdrawal. Wait until it is processed." } }验证错误 (400)
{ "detail": { "code": 5004, "status": "failed", "msg": "Minimum withdrawal is 3 TRX" } }错误代码参考
| 代码 | 描述 | HTTP 状态码 |
|---|---|---|
10000 | 已接受(提现已排队) / 已完成(状态端点) | 202 / 200 |
10001 | 处理中 — 排队中或发送中(状态端点) | 200 |
208 | 重复的已接受请求 — 返回缓存响应 | 208 |
- | 相同请求仍在处理中(请勿立即重试) | 409 |
4090 | 您已有一笔正在处理中的提现 | 409 |
-1 | 无效的 API 密钥 / IP 不在白名单中,或未找到订单 | 401 / 404 |
1004 | 余额不足 | 403 |
1006 | 无法向该地址提现 | 403 |
1009 | 地址检查服务暂时不可用 — 未创建订单,请稍后重试 | 503 |
5004 | 验证错误(金额 < 3、手续费 ≥ 金额、地址错误、幂等键格式错误) | 400 |
5003 | 提现失败 / 服务不可用 | 200(状态) / 503 |
5000 | 服务器内部错误 | 500 |
速率限制
按 API 密钥进行限制(请求头 X-API-KEY):
| 周期 | 限制 |
|---|---|
| 1 秒 | 5 次请求 |
| 1 分钟 | 150 次请求 |
超出速率限制 (429)
{ "message": "API rate limit exceeded" }幂等性
发送可选的 X-Idempotency-Key 请求头,以确保意外重复请求不会造成二次提现 — 原响应将以 HTTP 208 返回。如果您未发送此请求头,服务器将在短时间窗口内根据您的请求参数自动派生一个密钥。该密钥同时也是您的 orderId。
如何生成密钥
该密钥为 base64url( HMAC-SHA256( secret, message ) ) 并去除 = 填充符 — 一个 43 字符的 URL 安全字符串,其中:
- secret = 您的 API 密钥(
X-API-KEY); - message = 使用
:拼接的字段 —address:amount:nonce。
nonce 是任何对于同一逻辑订单的多次重试保持稳定、但在不同订单之间彼此各异的值 — 例如您为该订单维护的 UUID,或粗粒度的时间戳窗口。每个订单只生成一次密钥,并在每次重试时重新发送完全相同的值。
import hmac, hashlib, base64, time
def make_idempotency_key(api_key, address, amount, nonce=None):
if nonce is None:
nonce = str(int(time.time() // 2)) # 2 秒时间窗口;或使用您自己的订单 UUID
message = f"{address}:{amount}:{nonce}"
digest = hmac.new(api_key.encode(), message.encode(), hashlib.sha256).digest()
return base64.urlsafe_b64encode(digest).decode().rstrip("=") # 43 位 URL 安全字符格式校验。 提供的
X-Idempotency-Key必须由字符集A–Z a–z 0–9 + / = _ -内的 16–64 个字符组成。格式错误或超长的密钥将被拒绝并返回 HTTP 400(code 5004)。
| 状态码 | 含义 |
|---|---|
| 202 | 已接受(首次请求) |
| 208 | 已接受 — 返回缓存的响应(不会产生二次提现) |
| 409 | 相同的请求当前正在处理中 — 请等待,不要立即重试 |
失败后重试。 只有已接受的结果才会被缓存。如果前一次尝试失败(例如余额不足、参数验证失败),您可以安全地使用相同的密钥进行重试 — 系统会再次尝试该请求,而不是返回旧的错误信息。当一次尝试仍在处理中时,您会收到
409;请等待并稍后重试。
注意事项
- 异步打款。 初始响应始终为
pending确认;TRX 由后端守护进程发送,通常在约 5 分钟内完成。请使用状态端点或 webhook 获取结果。 - 订单被接受时立即预扣余额(而不是在最终发送 TRX 时)。
- 最低限额:3 TRX。手续费:1 TRX(或在启用
sub_and_robot_out时为 2 TRX),从总金额amount中扣除;收款方收到net = amount − fee。 - 自己的余额上一次只能有一笔处于 pending 状态的提现(
code 4090)。 - 目标地址检查。 每个请求在订单被接受前都会进行检查:
code 1006(HTTP 403)— 无法向该地址提现;code 1009(HTTP 503)— 检查服务暂时不可用,请稍后重试。在这两种情况下均不会创建订单。 - 子账户提现方式与常规用户完全相同 — 使用相同的
POST /apiv2/withdraw端点、相同的规则,但使用子账户自身的 API 密钥进行认证。子账户将其自身的余额提现至其指定的address。没有单独的子账户端点。 - orderId 为 43 字符的 URL 安全字符串;在状态 URL 中原样传递即可(无需进行编码)。
- Webhooks:按用户维度配置,使用
X-Netts-Signature进行签名;在 21 分钟窗口内最多尝试 3 次。通过POST /apiv2/withdraw/webhook进行配置。