Appearance
POST /apiv2/withdraw
Tarik TRX dari saldo Netts Anda ke alamat TRON. Permintaan akan langsung mengembalikan nomor pesanan; pembayaran on-chain sebenarnya dilakukan secara asinkron oleh backend (dalam waktu ~5 menit). Lacak hasilnya dengan melakukan polling pada endpoint status atau dengan mengonfigurasi webhook.
ℹ️ Cara kerjanya. Mengajukan penarikan akan langsung mencadangkan jumlah tersebut dari saldo Anda (saldo didebit 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 pemberitahuanpendingterlebih dahulu.Sebelum pesanan diterima, alamat tujuan akan diperiksa. Jika penarikan ke alamat tersebut tidak tersedia, atau pemeriksaan tidak dapat diselesaikan saat ini, permintaan akan ditolak: tidak ada pesanan yang dibuat dan tidak ada yang dicadangkan dari saldo Anda.
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-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. |
Isi 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). Diperiksa sebelum pesanan diterima. |
| 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
amountkotor: 1 TRX secara normal, atau 2 TRX saatsub_and_robot_out = true. Pesanan ditolak jikaamount − fee ≤ 0.
Contoh Permintaan
Contoh di bawah ini juga membuat dan mengirimkan
X-Idempotency-Keysehingga 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 )) # stabil untuk percobaan ulang dalam jendela 2 detik; atau UUID pesanan Anda sendiri
# 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
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 dihapus.
# Buat SEKALI per pesanan dan kirim ulang nilai yang sama persis pada setiap percobaan ulang.
nonce = str(int(time.time() // 2)) # bucket 2 detik; atau UUID pesanan Anda sendiri
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']}") # gunakan ini untuk endpoint status / 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 dicadangkan dari saldo Anda dan pembayaran 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 payload 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 (pesanan harus milik 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 dikirim | 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 milik sub-pengguna tersebut dan dikirim ke address yang ditentukan dalam permintaan. Batas minimum yang sama, biaya yang sama (1 TRX), pemeriksaan alamat yang sama, 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 yang ditandatangani saat 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.
Mengonfigurasi / melihat / menghapus
POST https://netts.io/apiv2/withdraw/webhook # buat atau perbarui
GET https://netts.io/apiv2/withdraw/webhook # lihat konfigurasi saat ini (secret tidak pernah dikembalikan)
DELETE https://netts.io/apiv2/withdraw/webhook # berhenti berlanggananHeader: X-API-KEY.
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 payload |
| 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.
Payload pengiriman
Netts mengirimkan POST ke callback_url Anda dengan header X-Netts-Signature: base64( HMAC-SHA256( secret, raw_body ) ) dan JSON body 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 diisi).
Memverifikasi tanda tangan
Tanda tangan dihitung melalui canonical JSON dari body: 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)
# Contoh Flask: verifikasi terhadap byte TEPAT yang diterima, lalu lakukan parse.
# if verify(request.get_data(), request.headers["X-Netts-Signature"], SECRET): ...Selalu verifikasi terhadap byte mentah yang diterima. Jika Anda melakukan serialisasi ulang terhadap JSON yang telah di-parse, buat ulang 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 batas waktu habis) akan diperlakukan sebagai upaya yang gagal.
- Hingga 3 kali percobaan per pesanan, dalam jendela waktu 21 menit dari saat pesanan dibuat (jeda percobaan ulang ≈ 5 menit). Setelah itu, pengiriman dihentikan — beralihlah ke endpoint status.
- Pengiriman bersifat de-duplikasi: setiap pesanan berhasil dikirimkan paling banyak satu kali.
- Buat penangan Anda bersifat idempoten pada
orderId.
Respons Kesalahan
Kesalahan Autentikasi (401)
json
{ "detail": { "code": -1, "msg": "Invalid API key or IP not in whitelist" } }Saldo Tidak Cukup (403)
json
{ "detail": { "code": 1004, "status": "failed", "msg": "Insufficient balance: 2.0 < 15 TRX" } }Alamat Tidak Tersedia (403)
Penarikan ke alamat tujuan ini tidak diterima. Tidak ada pesanan yang dibuat dan tidak ada yang dicadangkan. Gunakan alamat yang berbeda atau hubungi dukungan.
json
{ "detail": { "code": 1006, "status": "failed", "msg": "Withdrawals to this address are not available. Contact support." } }Pemeriksaan Alamat Tidak Tersedia (503)
Alamat tujuan tidak dapat diperiksa saat ini. Tidak ada pesanan yang dibuat dan tidak ada yang dicadangkan. Coba lagi segera — X-Idempotency-Key yang sama dapat digunakan kembali.
json
{ "detail": { "code": 1009, "status": "failed", "msg": "Address check is temporarily unavailable. No request was created. Retry shortly." } }Penarikan Tertunda Sudah 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 diantrekan) / Selesai (endpoint status) | 202 / 200 |
10001 | Tertunda — dalam antrean atau sedang dikirim (endpoint status) | 200 |
208 | Duplikat dari permintaan yang sudah diterima — respons dalam cache | 208 |
- | Permintaan yang sama masih diproses (jangan coba lagi sekarang) | 409 |
4090 | Anda sudah memiliki penarikan tertunda | 409 |
-1 | Kunci API tidak valid / IP tidak ada di whitelist, atau pesanan tidak ditemukan | 401 / 404 |
1004 | Saldo tidak cukup | 403 |
1006 | Penarikan ke alamat ini tidak tersedia | 403 |
1009 | Pemeriksaan alamat sementara tidak tersedia — tidak ada pesanan yang dibuat, coba lagi segera | 503 |
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 Kecepatan
Dibatasi per kunci API (header X-API-KEY):
| Periode | Batas |
|---|---|
| 1 detik | 5 permintaan |
| 1 menit | 150 permintaan |
Batas Kecepatan Terlampaui (429)
json
{ "message": "API rate limit exceeded" }Idempotensi
Kirim header opsional X-Idempotency-Key sehingga pengulangan yang tidak disengaja tidak membuat penarikan kedua — respons asli dikembalikan dengan HTTP 208. Jika Anda tidak mengirim header tersebut, server akan menurunkan kunci secara otomatis dari parameter permintaan Anda dalam jendela waktu yang singkat. Kunci ini juga merupakan orderId Anda.
Cara menyusun kunci
Kunci adalah base64url( HMAC-SHA256( secret, message ) ) dengan padding = dihapus — string URL-safe 43 karakter, dengan ketentuan:
- secret = kunci API Anda (
X-API-KEY); - message = 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. Buat kunci sekali per pesanan dan kirim ulang nilai yang sama persis pada setiap percobaan ulang.
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)) # bucket 2 detik; atau UUID pesanan Anda sendiri
message = f"{address}:{amount}:{nonce}"
digest = hmac.new(api_key.encode(), message.encode(), hashlib.sha256).digest()
return base64.urlsafe_b64encode(digest).decode().rstrip("=") # URL-safe 43 karakterValidasi.
X-Idempotency-Keyyang disediakan harus berupa 16–64 karakter dari set 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 dalam cache dikembalikan (tidak ada penarikan kedua) |
| 409 | Permintaan yang sama sedang diproses — tunggu, jangan coba lagi sekarang |
Mencoba kembali setelah kegagalan. Hanya hasil yang diterima yang disimpan dalam cache. Jika percobaan sebelumnya gagal (mis. saldo tidak cukup, validasi), Anda dapat dengan aman mencoba kembali dengan kunci yang sama — permintaan akan dicoba lagi daripada mengembalikan kesalahan lama. Saat upaya masih berlangsung, Anda akan mendapatkan
409; tunggu dan coba lagi.
Catatan
- Pembayaran asinkron. Responsnya selalu berupa konfirmasi penerimaan
pending; TRX dikirim oleh daemon backend, biasanya dalam waktu ~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 dariamountkotor; penerima menerimanet = amount − fee. - Hanya satu yang tertunda dalam satu waktu pada saldo Anda sendiri (
code 4090). - Pemeriksaan alamat tujuan. Setiap permintaan diperiksa sebelum pesanan diterima:
code 1006(HTTP 403) — penarikan ke alamat ini tidak tersedia;code 1009(HTTP 503) — pemeriksaan sementara tidak tersedia, coba lagi segera. Dalam kedua kasus, tidak ada pesanan yang dibuat. - 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 keaddressyang ditentukannya. Tidak ada endpoint sub-pengguna yang terpisah. - orderId adalah string URL-safe 43 karakter; teruskan apa adanya dalam 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.