Appearance
POST /apiv2/withdraw
Tarik TRX dari saldo Netts Anda ke alamat TRON mana pun. Permintaan ini langsung mengembalikan nomor pesanan; pembayaran on-chain yang sebenarnya dilakukan secara asinkron oleh backend (dalam ~5 menit). Lacak hasilnya dengan melakukan polling pada endpoint status atau dengan mengonfigurasi webhook.
ℹ️ Cara kerjanya. Mengajukan penarikan langsung mencadangkan jumlah tersebut dari saldo Anda (saldo didebit pada saat pesanan diterima). Daemon backend kemudian mengirimkan TRX dan menandai pesanan sebagai
completedataufailed. Tidak ada hasil on-chain sinkron dalam respons awal — Anda selalu mendapatkan konfirmasipendingterlebih dahulu.
URL Endpoint
POST https://netts.io/apiv2/withdrawHeader Permintaan
| Header | Wajib | Deskripsi |
|---|---|---|
| Content-Type | Ya | application/json |
| X-API-KEY | Ya | Kunci API Anda dari dasbor Netts |
| X-Real-IP | Ya | Alamat IP dari whitelist Anda |
| X-Idempotency-Key | Tidak | Kunci opsional yang dibuat oleh klien (base64) untuk mencoba kembali dengan aman tanpa penarikan ganda. Jika dihilangkan, server akan menurunkannya secara otomatis. Nilai ini menjadi orderId Anda. |
Badan Permintaan
json
{
"amount": 15,
"address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}Parameter
| Parameter | Tipe | Wajib | Deskripsi |
|---|---|---|---|
| amount | number | Ya | Jumlah kotor dalam TRX (minimum 3). Biaya dipotong dari jumlah ini — penerima mendapatkan amount − fee (net). |
| address | string | Ya | Alamat TRON tujuan (T…, 34 karakter, base58). |
| sub_and_robot_out | boolean | Tidak | Mode pembayaran robot/sub: mengenakan biaya 2 TRX alih-alih 1 TRX. Default false. |
Biaya. Biaya tetap dipotong dari jumlah kotor
amount: 1 TRX pada kondisi normal, atau 2 TRX saatsub_and_robot_out = true. Pesanan ditolak jikaamount − fee ≤ 0.
Contoh Permintaan
Contoh di bawah ini juga membangun dan mengirimkan
X-Idempotency-Keyagar pengulangan yang tidak disengaja tidak membuat penarikan kedua. Lihat Idempotensi untuk aturan lengkapnya.
cURL
bash
API_KEY="your_api_key"
ADDR="TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
AMOUNT=15
NONCE=$(( $(date +%s) / 2 )) # stable for retries within a 2s window; or your own order 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-Real-IP: your_whitelisted_ip" \
-H "X-Idempotency-Key: $IDEMP" \
-d "{\"amount\": $AMOUNT, \"address\": \"$ADDR\"}"Python
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" ) ), padding stripped.
# Generate ONCE per order and resend the same value on every retry.
nonce = str(int(time.time() // 2)) # 2s bucket; or your own order 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-Real-IP": "your_whitelisted_ip",
"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']}") # use it for the status endpoint / webhook
print(f"Net to recipient: {d['net']} TRX (fee {d['fee']})")
else:
print(f"Code {detail.get('code')}: {detail.get('msg', detail)}")Respons
Diterima — penarikan dimasukkan ke dalam antrean (202 Accepted)
Jumlah tersebut dicadangkan dari saldo Anda dan pembayaran telah dijadwalkan. Lakukan polling pada endpoint status (atau tunggu webhook) hingga statusnya menjadi completed / failed.
json
{
"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"
}
}
}Bidang Respons
| Bidang | Tipe | Deskripsi |
|---|---|---|
| detail.code | integer | 10000 diterima |
| detail.status | string | pending |
| detail.data.orderId | string | Nomor pesanan — string URL-safe 43 karakter. Gunakan untuk endpoint status dan ini mengidentifikasi pesanan dalam muatan webhook. |
| detail.data.amount | number | Jumlah kotor yang diminta (TRX) |
| detail.data.fee | number | Biaya yang dipotong (1 atau 2 TRX) |
| detail.data.net | number | Jumlah yang diterima penerima (amount − fee) |
| detail.data.address | string | Alamat tujuan |
Endpoint Status
GET https://netts.io/apiv2/withdraw/status/{orderId}Header: X-API-KEY + X-Real-IP (pesanan harus dimiliki oleh pengguna yang diautentikasi). orderId bersifat URL-safe — teruskan apa adanya, tidak perlu URL-encoding.
| Status pesanan | HTTP | code | status |
|---|---|---|---|
| Selesai (TRX terkirim) | 200 | 10000 | completed (dengan processed_at) |
| Dalam antrean / sedang mengirim | 200 | 10001 | pending |
| Gagal | 200 | 5003 | failed (dengan error_message) |
| Tidak ditemukan / bukan milik Anda | 404 | -1 | — |
json
{
"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"
}
}
}Akun sub-pengguna
Penarikan sub-pengguna berfungsi persis sama seperti untuk pengguna reguler — hanya dengan kunci API milik sub-pengguna itu sendiri. Sub-pengguna memanggil endpoint POST /apiv2/withdraw yang sama ini, diautentikasi dengan kuncinya sendiri; penarikan didebit dari saldo sub-pengguna itu sendiri dan dikirim ke address mana pun yang ditentukan dalam permintaan. Minimum yang sama, biaya yang sama (1 TRX), alur yang sama. Tidak ada endpoint sub-pengguna yang terpisah — setiap akun, induk maupun sub-pengguna, hanya pernah menarik saldonya sendiri dengan kuncinya sendiri.
Webhook
Alih-alih polling, konfigurasikan webhook sekali dan Netts akan mengirimkan POST notifikasi bertanda tangan ketika setiap penarikan Anda mencapai status akhir (completed / failed). Webhook disimpan per pengguna dan berlaku untuk penarikan akun tersebut. Jika tidak ada webhook yang dikonfigurasi, cukup lakukan polling pada endpoint status.
Konfigurasi / lihat / hapus
POST https://netts.io/apiv2/withdraw/webhook # create or update
GET https://netts.io/apiv2/withdraw/webhook # view current config (secret is never returned)
DELETE https://netts.io/apiv2/withdraw/webhook # unsubscribeHeader: X-API-KEY + X-Real-IP.
json
// POST body
{
"callback_url": "https://your-server.example/netts/withdraw-hook",
"secret": "your_shared_secret_min_8_chars",
"enabled": true
}| Parameter | Tipe | Wajib | Deskripsi |
|---|---|---|---|
| callback_url | string | Ya | URL http(s) (≤ 2048 karakter) yang menerima POST |
| secret | string | Ya | Rahasia bersama (8…256 karakter) yang digunakan untuk menandatangani setiap muatan |
| enabled | boolean | Tidak | Mengaktifkan/menonaktifkan pengiriman tanpa menghapus konfigurasi. Default true |
GET mengembalikan { callback_url, enabled, secret_set, updated_at } — rahasia itu sendiri tidak pernah dikembalikan.
Muatan pengiriman
Netts mengirimkan POST ke callback_url Anda dengan header X-Netts-Signature: base64( HMAC-SHA256( secret, raw_body ) ) dan badan JSON ini:
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
}statusadalahcompletedataufailed(padafailed,error_messageakan terisi).
Memverifikasi tanda tangan
Tanda tangan dihitung di atas JSON kanonikal dari badan pesan: kunci diurutkan, tanpa spasi (separators=(",", ":")). Hitung ulang dengan cara yang sama dan bandingkan.
python
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 example: verify against the EXACT bytes received, then parse.
# if verify(request.get_data(), request.headers["X-Netts-Signature"], SECRET): ...Selalu verifikasi terhadap byte mentah yang diterima. Jika Anda membuat ulang serialisasi JSON yang telah diurai, reproduksi bentuk kanonikalnya:
json.dumps(payload, ensure_ascii=False, separators=(",",":"), sort_keys=True).
Jaminan pengiriman
- Berikan respons dengan HTTP 2xx untuk mengonfirmasi penerimaan. Respons lain apa pun (atau waktu habis) dianggap sebagai upaya yang gagal.
- Hingga 3 kali percobaan per pesanan, dalam jendela waktu 21 menit sejak saat pesanan dibuat (backoff percobaan ulang ≈ 5 menit). Setelah itu, pengiriman dihentikan — beralihlah ke endpoint status.
- Pengiriman dide-duplikasi: setiap pesanan berhasil dikirimkan paling banyak satu kali.
- Buat penangan Anda bersifat idempoten berdasarkan
orderId.
Respons Kesalahan
Kesalahan Autentikasi (401)
json
{ "detail": { "code": -1, "msg": "Invalid API key or IP not in whitelist" } }Saldo Tidak Mencukupi (403)
json
{ "detail": { "code": 1004, "status": "failed", "msg": "Insufficient balance: 2.0 < 15 TRX" } }Penarikan yang Tertunda Masih Ada (409)
Anda hanya boleh memiliki satu penarikan tertunda dalam satu waktu pada saldo Anda sendiri. Tunggu hingga penarikan saat ini selesai diproses.
json
{ "detail": { "code": 4090, "status": "failed", "msg": "You have a pending withdrawal. Wait until it is processed." } }Kesalahan Validasi (400)
json
{ "detail": { "code": 5004, "status": "failed", "msg": "Minimum withdrawal is 3 TRX" } }Referensi Kode Kesalahan
| Kode | Deskripsi | Status HTTP |
|---|---|---|
10000 | Diterima (penarikan dimasukkan ke antrean) / Selesai (endpoint status) | 202 / 200 |
10001 | Tertunda — dalam antrean atau sedang mengirim (endpoint status) | 200 |
208 | Duplikat dari permintaan yang sudah diterima — respons yang di-cache | 208 |
- | Permintaan yang sama masih diproses (jangan coba lagi sekarang) | 409 |
4090 | Anda sudah memiliki penarikan yang tertunda | 409 |
-1 | Kunci API tidak valid / IP tidak ada dalam whitelist, atau pesanan tidak ditemukan | 401 / 404 |
1004 | Saldo tidak mencukupi | 403 |
5004 | Kesalahan validasi (jumlah < 3, biaya ≥ jumlah, alamat salah, kunci idempotensi salah) | 400 |
5003 | Penarikan gagal / layanan tidak tersedia | 200 (status) / 503 |
5000 | Kesalahan server internal | 500 |
Batas Laju
Dibatasi per kunci API (header X-API-KEY):
| Periode | Batas |
|---|---|
| 1 detik | 5 permintaan |
| 1 menit | 150 permintaan |
Batas Laju Terlampaui (429)
json
{ "message": "API rate limit exceeded" }Idempotensi
Kirimkan header opsional X-Idempotency-Key agar pengulangan yang tidak disengaja tidak membuat penarikan kedua — respons asli akan dikembalikan dengan HTTP 208. Jika Anda tidak mengirimkan header tersebut, server akan menurunkan kunci secara otomatis dari parameter permintaan Anda dalam jendela waktu singkat. Kunci tersebut juga merupakan orderId Anda.
Cara membentuk kunci
Kunci tersebut adalah base64url( HMAC-SHA256( secret, message ) ) dengan padding = dihapus — string URL-safe 43 karakter, di mana:
- secret = kunci API Anda (
X-API-KEY); - message = bidang-bidang yang digabungkan dengan
:—address:amount:nonce.
nonce adalah nilai apa pun yang stabil di seluruh percobaan ulang pesanan logis yang sama tetapi berbeda antara pesanan yang berbeda — mis. UUID yang Anda simpan untuk pesanan tersebut, atau bucket stempel waktu kasar. Hasilkan kunci sekali per pesanan dan kirim ulang nilai yang sama persis pada setiap percobaan.
python
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-second bucket; or your own order 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-char URL-safeValidasi. Nilai
X-Idempotency-Keyyang disediakan harus berupa 16–64 karakter dari kumpulan karakterA–Z a–z 0–9 + / = _ -. Kunci yang salah format atau terlalu panjang akan ditolak dengan HTTP 400 (code 5004).
| Kode Status | Arti |
|---|---|
| 202 | Diterima (permintaan pertama) |
| 208 | Sudah diterima — respons yang di-cache dikembalikan (tidak ada penarikan kedua) |
| 409 | Permintaan yang sama saat ini sedang diproses — tunggu, jangan coba lagi sekarang |
Mencoba lagi setelah kegagalan. Hanya hasil yang diterima yang di-cache. Jika percobaan sebelumnya gagal (mis. saldo tidak mencukupi, validasi), Anda dapat dengan aman mencoba lagi dengan kunci yang sama — permintaan akan dicoba kembali alih-alih mengembalikan kesalahan lama. Saat percobaan masih berlangsung Anda mendapatkan
409; tunggu dan coba lagi.
Catatan
- Pembayaran asinkron. Respons selalu berupa konfirmasi
pending; TRX dikirim oleh daemon backend, biasanya dalam ~5 menit. Gunakan endpoint status atau webhook untuk melihat hasilnya. - Saldo langsung dicadangkan saat pesanan diterima (bukan saat TRX akhirnya dikirim).
- Minimum: 3 TRX. Biaya: 1 TRX (atau 2 TRX dengan
sub_and_robot_out), dipotong dari jumlah kotoramount; penerima menerimanet = amount − fee. - Satu penarikan tertunda dalam satu waktu pada saldo Anda sendiri (
code 4090). - Sub-pengguna melakukan penarikan persis seperti pengguna reguler — endpoint
POST /apiv2/withdrawyang sama, aturan yang sama, tetapi diautentikasi dengan kunci API milik sub-pengguna itu sendiri. Sub-pengguna menarik saldonya sendiri keaddressmana pun yang ditentukannya. Tidak ada endpoint sub-pengguna yang terpisah. - orderId adalah string URL-safe 43 karakter; teruskan apa adanya di URL status (tidak memerlukan encoding).
- Webhook: per pengguna, ditandatangani dengan
X-Netts-Signature; hingga 3 kali percobaan dalam jendela waktu 21 menit. Konfigurasikan melaluiPOST /apiv2/withdraw/webhook.