POST /apiv2/withdraw
Rút TRX từ số dư Netts của bạn về một địa chỉ TRON. Yêu cầu trả về một mã đơn hàng ngay lập tức; việc chi trả on-chain thực tế được thực hiện bất đồng bộ bởi hệ thống backend (trong vòng ~5 phút). Theo dõi kết quả bằng cách gọi kiểm tra endpoint trạng thái hoặc thiết lập một webhook.
ℹ️ Cách thức hoạt động. Việc tạo lệnh rút tiền sẽ tạm giữ số tiền từ số dư của bạn ngay lập tức (số dư bị trừ ngay thời điểm đơn hàng được chấp nhận). Sau đó, một tiến trình chạy ngầm ở backend sẽ gửi TRX và đánh dấu đơn hàng là
completedhoặcfailed. Sẽ không có kết quả on-chain đồng bộ trong phản hồi ban đầu — bạn luôn nhận được xác nhậnpendingtrước.Trước khi đơn hàng được chấp nhận, địa chỉ đích sẽ được kiểm tra. Nếu việc rút tiền đến địa chỉ đó không khả dụng, hoặc quá trình kiểm tra hiện không thể hoàn tất, yêu cầu sẽ bị từ chối: không có đơn hàng nào được tạo và không có khoản tiền nào bị tạm giữ từ số dư của bạn.
URL của Endpoint
POST https://netts.io/apiv2/withdrawHeaders của yêu cầu
| Header | Bắt buộc | Mô tả |
|---|---|---|
| Content-Type | Có | application/json |
| X-API-KEY | Có | API key của bạn từ bảng điều khiển Netts |
| X-Idempotency-Key | Không | Khóa tùy chọn do máy khách tạo (base64) để thử lại an toàn mà không bị rút tiền trùng lặp. Nếu bỏ qua, máy chủ sẽ tự động tạo một khóa. Giá trị này trở thành orderId của bạn. |
Body của yêu cầu
{
"amount": 15,
"address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}Tham số
| Tham số | Loại | Bắt buộc | Mô tả |
|---|---|---|---|
| amount | number | Có | Số tiền tổng (gross) bằng TRX (tối thiểu 3). Phí sẽ được khấu trừ từ số tiền này — người nhận sẽ nhận được amount − fee (net). |
| address | string | Có | Địa chỉ TRON đích (T…, 34 ký tự, base58). Được kiểm tra trước khi đơn hàng được chấp nhận. |
| sub_and_robot_out | boolean | Không | Chế độ thanh toán robot/tài khoản phụ: áp dụng mức phí 2 TRX thay vì 1 TRX. Mặc định là false. |
Phí. Một khoản phí cố định sẽ được khấu trừ từ số tiền tổng
amount: thông thường là 1 TRX, hoặc 2 TRX khisub_and_robot_out = true. Đơn hàng sẽ bị từ chối nếuamount − fee ≤ 0.
Ví dụ yêu cầu
Các ví dụ bên dưới cũng tạo và gửi
X-Idempotency-Keyđể việc gửi lặp lại ngoài ý muốn không tạo ra lệnh rút tiền thứ hai. Xem Tính bất biến (Idempotency) để biết đầy đủ các quy tắc.
cURL
API_KEY="your_api_key"
ADDR="TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
AMOUNT=15
NONCE=$(( $(date +%s) / 2 )) # ổn định cho các lần thử lại trong khoảng thời gian 2 giây; hoặc UUID đơn hàng của riêng bạn
# 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" ) ), đã loại bỏ padding.
# Tạo MỘT LẦN cho mỗi đơn hàng và gửi lại cùng một giá trị trong mỗi lần thử lại.
nonce = str(int(time.time() // 2)) # khoảng thời gian 2 giây; hoặc UUID đơn hàng của riêng bạn
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']}") # sử dụng giá trị này cho endpoint trạng thái / webhook
print(f"Net to recipient: {d['net']} TRX (fee {d['fee']})")
else:
print(f"Code {detail.get('code')}: {detail.get('msg', detail)}")Phản hồi
Đã chấp nhận — lệnh rút tiền đã vào hàng đợi (202 Accepted)
Số tiền đã được tạm giữ từ số dư của bạn và việc chi trả đã được lên lịch. Hãy thăm dò endpoint trạng thái (hoặc chờ webhook) cho đến khi nó chuyển thành 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"
}
}
}Các trường phản hồi
| Trường | Loại | Mô tả |
|---|---|---|
| detail.code | integer | 10000 đã được chấp nhận |
| detail.status | string | pending |
| detail.data.orderId | string | Mã đơn hàng — một chuỗi an toàn cho URL dài 43 ký tự. Sử dụng cho endpoint trạng thái và nó định danh đơn hàng trong dữ liệu webhook. |
| detail.data.amount | number | Số tiền tổng được yêu cầu (TRX) |
| detail.data.fee | number | Phí được khấu trừ (1 hoặc 2 TRX) |
| detail.data.net | number | Số tiền người nhận nhận được (amount − fee) |
| detail.data.address | string | Địa chỉ đích |
Endpoint trạng thái
GET https://netts.io/apiv2/withdraw/status/{orderId}Headers: X-API-KEY (đơn hàng phải thuộc về người dùng đã xác thực). orderId là chuỗi an toàn cho URL — truyền nguyên bản, không cần mã hóa URL.
| Trạng thái đơn hàng | HTTP | code | status |
|---|---|---|---|
| Hoàn thành (đã gửi TRX) | 200 | 10000 | completed (kèm processed_at) |
| Đang chờ / đang gửi | 200 | 10001 | pending |
| Thất bại | 200 | 5003 | failed (kèm error_message) |
| Không tìm thấy / không thuộc về bạn | 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"
}
}
}Tài khoản người dùng phụ (Sub-user accounts)
Việc rút tiền của người dùng phụ hoạt động hoàn toàn giống như người dùng thông thường — chỉ khác là sử dụng API key của chính người dùng phụ đó. Một người dùng phụ gọi cùng endpoint POST /apiv2/withdraw này, được xác thực bằng khóa riêng của mình; khoản tiền rút sẽ được trừ từ chính số dư của người dùng phụ đó và được gửi đến address mà yêu cầu chỉ định. Cùng mức tối thiểu, cùng mức phí (1 TRX), cùng quy trình kiểm tra địa chỉ, cùng luồng xử lý. Hoàn toàn không có endpoint riêng cho người dùng phụ — mỗi tài khoản, tài khoản chính hay người dùng phụ, chỉ rút số dư của chính mình bằng khóa riêng của mình.
Webhooks
Thay vì phải liên tục gọi kiểm tra, hãy cấu hình webhook một lần và Netts sẽ gửi POST một thông báo đã ký khi mỗi lệnh rút tiền của bạn đạt đến trạng thái cuối cùng (completed / failed). Webhook được lưu theo từng người dùng và áp dụng cho các lệnh rút tiền của tài khoản đó. Nếu không có webhook nào được cấu hình, chỉ cần gọi kiểm tra endpoint trạng thái.
Cấu hình / xem / xóa
POST https://netts.io/apiv2/withdraw/webhook # tạo hoặc cập nhật
GET https://netts.io/apiv2/withdraw/webhook # xem cấu hình hiện tại (secret không bao giờ được trả về)
DELETE https://netts.io/apiv2/withdraw/webhook # hủy đăng kýHeaders: X-API-KEY.
// POST body
{
"callback_url": "https://your-server.example/netts/withdraw-hook",
"secret": "your_shared_secret_min_8_chars",
"enabled": true
}| Tham số | Loại | Bắt buộc | Mô tả |
|---|---|---|---|
| callback_url | string | Có | URL http(s) (≤ 2048 ký tự) nhận yêu cầu POST |
| secret | string | Có | Khóa bí mật chia sẻ (8…256 ký tự) được sử dụng để ký mỗi payload |
| enabled | boolean | Không | Bật/tắt việc phân phối mà không cần xóa cấu hình. Mặc định true |
GET trả về { callback_url, enabled, secret_set, updated_at } — bản thân chuỗi bí mật không bao giờ được trả lại.
Payload phân phối
Netts gửi một yêu cầu POST đến callback_url của bạn kèm theo header X-Netts-Signature: base64( HMAC-SHA256( secret, raw_body ) ) và JSON body này:
{
"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
}statuslàcompletedhoặcfailed(khifailed,error_messagesẽ có giá trị).
Xác thực chữ ký
Chữ ký được tính toán trên định dạng canonical JSON của body: các khóa được sắp xếp thứ tự, không có khoảng trắng (separators=(",", ":")). Hãy tính toán lại theo cùng một cách và so sánh.
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)
# Ví dụ Flask: xác thực dựa trên CHÍNH XÁC các byte nhận được, sau đó phân tích cú pháp.
# if verify(request.get_data(), request.headers["X-Netts-Signature"], SECRET): ...Luôn xác thực dựa trên chuỗi byte thô nhận được. Nếu bạn tuần tự hóa lại JSON đã phân tích cú pháp, hãy tái tạo dạng chuẩn hóa (canonical):
json.dumps(payload, ensure_ascii=False, separators=(",",":"), sort_keys=True).
Đảm bảo phân phối
- Phản hồi với mã HTTP 2xx để xác nhận. Bất kỳ phản hồi nào khác (hoặc hết thời gian chờ) đều được coi là một lần thử thất bại.
- Tối đa 3 lần thử cho mỗi đơn hàng, trong khoảng thời gian 21 phút kể từ thời điểm đơn hàng được tạo (khoảng lùi thời gian thử lại ≈ 5 phút). Sau thời gian đó, việc phân phối sẽ bị hủy bỏ — hãy chuyển sang dùng endpoint trạng thái.
- Các lượt phân phối được chống trùng lặp: mỗi đơn hàng chỉ được phân phối thành công tối đa một lần.
- Hãy thiết kế trình xử lý của bạn có tính bất biến (idempotent) dựa trên
orderId.
Phản hồi lỗi
Lỗi xác thực (401)
{ "detail": { "code": -1, "msg": "Invalid API key or IP not in whitelist" } }Không đủ số dư (403)
{ "detail": { "code": 1004, "status": "failed", "msg": "Insufficient balance: 2.0 < 15 TRX" } }Địa chỉ không khả dụng (403)
Việc rút tiền đến địa chỉ đích này không được chấp nhận. Không có đơn hàng nào được tạo và không có gì bị tạm giữ. Vui lòng sử dụng địa chỉ khác hoặc liên hệ bộ phận hỗ trợ.
{ "detail": { "code": 1006, "status": "failed", "msg": "Withdrawals to this address are not available. Contact support." } }Kiểm tra địa chỉ không khả dụng (503)
Hiện không thể kiểm tra địa chỉ đích. Không có đơn hàng nào được tạo và không có gì bị tạm giữ. Hãy thử lại trong chốc lát — có thể tái sử dụng cùng một X-Idempotency-Key.
{ "detail": { "code": 1009, "status": "failed", "msg": "Address check is temporarily unavailable. No request was created. Retry shortly." } }Đang có lệnh rút tiền chờ xử lý (409)
Bạn chỉ có thể có một lệnh rút tiền đang chờ xử lý tại một thời điểm trên số dư của mình. Vui lòng đợi cho đến khi lệnh hiện tại được xử lý xong.
{ "detail": { "code": 4090, "status": "failed", "msg": "You have a pending withdrawal. Wait until it is processed." } }Lỗi xác thực dữ liệu (400)
{ "detail": { "code": 5004, "status": "failed", "msg": "Minimum withdrawal is 3 TRX" } }Bảng tra cứu mã lỗi
| Mã | Mô tả | Trạng thái HTTP |
|---|---|---|
10000 | Đã chấp nhận (lệnh rút tiền đã vào hàng đợi) / Đã hoàn thành (endpoint trạng thái) | 202 / 200 |
10001 | Đang chờ — đã vào hàng đợi hoặc đang gửi (endpoint trạng thái) | 200 |
208 | Trùng lặp với một yêu cầu đã được chấp nhận — phản hồi từ bộ nhớ đệm | 208 |
- | Yêu cầu tương tự vẫn đang được xử lý (chưa nên thử lại) | 409 |
4090 | Bạn đã có một lệnh rút tiền đang chờ xử lý | 409 |
-1 | API key không hợp lệ / IP không nằm trong danh sách trắng, hoặc không tìm thấy đơn hàng | 401 / 404 |
1004 | Không đủ số dư | 403 |
1006 | Việc rút tiền đến địa chỉ này không khả dụng | 403 |
1009 | Việc kiểm tra địa chỉ tạm thời không khả dụng — không có đơn hàng nào được tạo, hãy thử lại trong chốc lát | 503 |
5004 | Lỗi xác thực dữ liệu (số tiền < 3, phí ≥ số tiền, địa chỉ sai, khóa tính bất biến sai) | 400 |
5003 | Rút tiền thất bại / dịch vụ không khả dụng | 200 (trạng thái) / 503 |
5000 | Lỗi máy chủ nội bộ | 500 |
Giới hạn tốc độ
Bị giới hạn theo từng API key (header X-API-KEY):
| Thời gian | Giới hạn |
|---|---|
| 1 giây | 5 yêu cầu |
| 1 phút | 150 yêu cầu |
Vượt quá giới hạn tần suất (429)
{ "message": "API rate limit exceeded" }Tính bất biến (Idempotency)
Gửi header X-Idempotency-Key tùy chọn để việc gửi lặp lại ngoài ý muốn không tạo ra lệnh rút tiền thứ hai — phản hồi ban đầu sẽ được trả về với HTTP 208. Nếu bạn không gửi header này, máy chủ sẽ tự động tạo một khóa từ các tham số yêu cầu của bạn trong một khoảng thời gian ngắn. Khóa này cũng chính là orderId của bạn.
Cách tạo khóa
Khóa là chuỗi base64url( HMAC-SHA256( secret, message ) ) đã loại bỏ ký tự đệm = — một chuỗi 43 ký tự an toàn cho URL, trong đó:
- secret = API key của bạn (
X-API-KEY); - message = các trường được nối với nhau bằng dấu
:—address:amount:nonce.
nonce là bất kỳ giá trị nào giữ nguyên qua các lần thử lại của cùng một đơn hàng hợp lý nhưng khác nhau giữa các đơn hàng riêng biệt — ví dụ: một UUID bạn lưu giữ cho đơn hàng đó, hoặc một chuỗi thời gian định kỳ. Hãy tạo khóa một lần cho mỗi đơn hàng và gửi lại chính xác cùng một giá trị đó trong mỗi lần thử lại.
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)) # khoảng thời gian 2 giây; hoặc UUID đơn hàng của riêng bạn
message = f"{address}:{amount}:{nonce}"
digest = hmac.new(api_key.encode(), message.encode(), hashlib.sha256).digest()
return base64.urlsafe_b64encode(digest).decode().rstrip("=") # 43 ký tự an toàn cho URLXác thực.
X-Idempotency-Keyđược cung cấp phải có độ dài 16–64 ký tự thuộc bộ ký tựA–Z a–z 0–9 + / = _ -. Khóa sai định dạng hoặc quá dài sẽ bị từ chối với mã HTTP 400 (code 5004).
| Mã trạng thái | Ý nghĩa |
|---|---|
| 202 | Đã chấp nhận (yêu cầu đầu tiên) |
| 208 | Đã được chấp nhận trước đó — trả về phản hồi từ bộ nhớ đệm (không tạo lệnh rút tiền thứ hai) |
| 409 | Yêu cầu tương tự hiện đang được xử lý — hãy đợi, chưa nên thử lại vội |
Thử lại sau khi thất bại. Chỉ các kết quả được chấp nhận mới được lưu vào bộ nhớ đệm. Nếu lần thử trước đó thất bại (ví dụ: không đủ số dư, lỗi xác thực), bạn có thể an toàn thử lại với cùng một khóa — yêu cầu sẽ được thực hiện lại thay vì trả về lỗi cũ. Khi một lần thử vẫn đang trong tiến trình xử lý, bạn sẽ nhận được
409; hãy đợi và thử lại sau.
Ghi chú
- Thanh toán bất đồng bộ. Phản hồi ban đầu luôn là một xác nhận
pending; TRX được gửi bởi một tiến trình chạy ngầm ở backend, thường trong vòng ~5 phút. Hãy sử dụng endpoint trạng thái hoặc webhook để nhận kết quả. - Số dư được tạm giữ ngay lập tức khi đơn hàng được chấp nhận (không phải khi TRX được gửi đi thực tế).
- Tối thiểu: 3 TRX. Phí: 1 TRX (hoặc 2 TRX nếu dùng
sub_and_robot_out), được khấu trừ từ số tiềnamounttổng; người nhận sẽ nhận đượcnet = amount − fee. - Chỉ một lệnh chờ xử lý tại một thời điểm trên số dư của chính bạn (
code 4090). - Kiểm tra địa chỉ đích. Mọi yêu cầu đều được kiểm tra trước khi đơn hàng được chấp nhận:
code 1006(HTTP 403) — việc rút tiền đến địa chỉ này không khả dụng;code 1009(HTTP 503) — việc kiểm tra tạm thời không khả dụng, hãy thử lại trong chốc lát. Trong cả hai trường hợp đều không có đơn hàng nào được tạo. - Người dùng phụ rút tiền hoàn toàn giống người dùng thông thường — cùng endpoint
POST /apiv2/withdraw, cùng quy tắc, nhưng được xác thực bằng API key của chính người dùng phụ. Người dùng phụ rút số dư của chính mình vềaddressmà họ chỉ định. Không có endpoint riêng biệt cho người dùng phụ. - orderId là một chuỗi 43 ký tự an toàn cho URL; hãy truyền nguyên bản vào URL trạng thái (không cần mã hóa).
- Webhooks: theo từng người dùng, được ký bằng
X-Netts-Signature; tối đa 3 lần thử trong khoảng thời gian 21 phút. Cấu hình quaPOST /apiv2/withdraw/webhook.