Appearance
POST /apiv2/withdraw
Faça o saque de TRX do seu saldo na Netts para qualquer endereço TRON. A requisição retorna um número de pedido imediatamente; o pagamento on-chain real é realizado de forma assíncrona pelo backend (em cerca de 5 minutos). Acompanhe o resultado fazendo polling no endpoint de status ou configurando um webhook.
ℹ️ Como funciona. Fazer um saque reserva o valor do seu saldo imediatamente (o saldo é debitado no momento em que o pedido é aceito). Um daemon de backend então envia o TRX e marca o pedido como
completedoufailed. Não há nenhum resultado on-chain síncrono na resposta inicial — você sempre recebe primeiro uma confirmaçãopending.
URL do Endpoint
POST https://netts.io/apiv2/withdrawCabeçalhos da Requisição
| Cabeçalho | Obrigatório | Descrição |
|---|---|---|
| Content-Type | Sim | application/json |
| X-API-KEY | Sim | Sua chave de API do painel da Netts |
| X-Real-IP | Sim | Endereço IP da sua whitelist |
| X-Idempotency-Key | Não | Chave opcional gerada pelo cliente (base64) para tentar novamente com segurança sem duplicar o saque. Se omitida, o servidor gera uma automaticamente. Este valor se torna o seu orderId. |
Corpo da Requisição
json
{
"amount": 15,
"address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}Parâmetros
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| amount | number | Sim | Valor bruto em TRX (mínimo 3). A taxa é deduzida deste valor — o destinatário recebe amount − fee (net). |
| address | string | Sim | Endereço TRON de destino (T…, 34 caracteres, base58). |
| sub_and_robot_out | boolean | Não | Modo de pagamento por robô/subconta: aplica a taxa de 2 TRX em vez de 1 TRX. Padrão false. |
Taxa. Uma taxa fixa é retida do
amountbruto: 1 TRX normalmente, ou 2 TRX quandosub_and_robot_out = true. O pedido é rejeitado seamount − fee ≤ 0.
Exemplos de Requisição
Os exemplos abaixo também geram e enviam o
X-Idempotency-Keypara que uma repetição acidental não crie um segundo saque. Consulte Idempotência para ver as regras completas.
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)}")Resposta
Aceito — saque enfileirado (202 Accepted)
O valor é reservado do seu saldo e o pagamento é agendado. Faça polling no endpoint de status (ou aguarde o webhook) até que ele mude para 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"
}
}
}Campos da Resposta
| Campo | Tipo | Descrição |
|---|---|---|
| detail.code | integer | 10000 aceito |
| detail.status | string | pending |
| detail.data.orderId | string | Número do pedido — uma string URL-safe de 43 caracteres. Use-o para o endpoint de status; ele também identifica o pedido nos payloads de webhook. |
| detail.data.amount | number | Valor bruto solicitado (TRX) |
| detail.data.fee | number | Taxa retida (1 ou 2 TRX) |
| detail.data.net | number | Valor que o destinatário recebe (amount − fee) |
| detail.data.address | string | Endereço de destino |
Endpoint de Status
GET https://netts.io/apiv2/withdraw/status/{orderId}Cabeçalhos: X-API-KEY + X-Real-IP (o pedido deve pertencer ao usuário autenticado). orderId é URL-safe — passe-o como está, sem necessidade de codificação de URL.
| Estado do pedido | HTTP | code | status |
|---|---|---|---|
| Concluído (TRX enviado) | 200 | 10000 | completed (com processed_at) |
| Enfileirado / enviando | 200 | 10001 | pending |
| Falhou | 200 | 5003 | failed (com error_message) |
| Não encontrado / não é seu | 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"
}
}
}Contas de Subusuários
Os saques de subusuários funcionam exatamente da mesma forma que para usuários regulares — apenas com a própria chave de API do subusuário. Um subusuário chama este mesmo endpoint POST /apiv2/withdraw, autenticado com sua própria chave; o saque é debitado do próprio saldo desse subusuário e enviado para o address especificado na requisição. Mesmo valor mínimo, mesma taxa (1 TRX), mesmo fluxo. Não há nenhum endpoint separado para subusuários — cada conta, principal ou subusuário, sempre saca apenas do seu próprio saldo com sua própria chave.
Webhooks
Em vez de fazer polling, configure um webhook uma vez e a Netts enviará um POST com uma notificação assinada quando cada um dos seus saques atingir um estado terminal (completed / failed). O webhook é armazenado por usuário e se aplica aos saques dessa conta. Se nenhum webhook estiver configurado, basta fazer polling no endpoint de status.
Configurar / visualizar / remover
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 # unsubscribeCabeçalhos: 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
}| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
| callback_url | string | Sim | URL http(s) (≤ 2048 caracteres) que recebe o POST |
| secret | string | Sim | Segredo compartilhado (8…256 caracteres) usado para assinar cada payload |
| enabled | boolean | Não | Ativa/desativa o envio sem excluir a configuração. Padrão true |
O GET retorna { callback_url, enabled, secret_set, updated_at } — o segredo em si nunca é retornado.
Payload de entrega
A Netts envia um POST para o seu callback_url com o cabeçalho X-Netts-Signature: base64( HMAC-SHA256( secret, raw_body ) ) e este corpo JSON:
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écompletedoufailed(em caso defailed,error_messageé preenchido).
Verificando a assinatura
A assinatura é calculada sobre o JSON canônico do corpo: chaves ordenadas, sem espaços (separators=(",", ":")). Recalcule da mesma forma e compare.
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): ...Sempre faça a verificação contra os bytes brutos recebidos. Se você serializar novamente o JSON analisado, reproduza a forma canônica:
json.dumps(payload, ensure_ascii=False, separators=(",",":"), sort_keys=True).
Garantias de entrega
- Responda com HTTP 2xx para confirmar o recebimento. Qualquer outra resposta (ou timeout) é tratada como uma tentativa com falha.
- Até 3 tentativas por pedido, dentro de uma janela de 21 minutos a partir do momento em que o pedido foi criado (intervalo entre tentativas ≈ 5 minutos). Depois disso, o envio é cancelado — utilize o endpoint de status como alternativa.
- As entregas são desduplicadas: cada pedido é entregue com sucesso no máximo uma vez.
- Torne o seu handler idempotente com base no
orderId.
Respostas de Erro
Erro de Autenticação (401)
json
{ "detail": { "code": -1, "msg": "Invalid API key or IP not in whitelist" } }Saldo Insuficiente (403)
json
{ "detail": { "code": 1004, "status": "failed", "msg": "Insufficient balance: 2.0 < 15 TRX" } }Saque Pendente Existente (409)
Você pode ter apenas um saque pendente por vez no seu próprio saldo. Aguarde até que o atual seja processado.
json
{ "detail": { "code": 4090, "status": "failed", "msg": "You have a pending withdrawal. Wait until it is processed." } }Erro de Validação (400)
json
{ "detail": { "code": 5004, "status": "failed", "msg": "Minimum withdrawal is 3 TRX" } }Referência de Códigos de Erro
| Código | Descrição | Status HTTP |
|---|---|---|
10000 | Aceito (saque enfileirado) / Concluído (endpoint de status) | 202 / 200 |
10001 | Pendente — enfileirado ou enviando (endpoint de status) | 200 |
208 | Duplicata de uma requisição já aceita — resposta em cache | 208 |
- | A mesma requisição ainda está sendo processada (não tente novamente ainda) | 409 |
4090 | Você já tem um saque pendente | 409 |
-1 | Chave de API inválida / IP fora da whitelist, ou pedido não encontrado | 401 / 404 |
1004 | Saldo insuficiente | 403 |
5004 | Erro de validação (amount < 3, taxa ≥ amount, endereço inválido, chave de idempotência inválida) | 400 |
5003 | Falha no saque / serviço indisponível | 200 (status) / 503 |
5000 | Erro interno do servidor | 500 |
Limites de Taxa
Limitado por chave de API (cabeçalho X-API-KEY):
| Período | Limite |
|---|---|
| 1 segundo | 5 requisições |
| 1 minuto | 150 requisições |
Limite de Taxa Excedido (429)
json
{ "message": "API rate limit exceeded" }Idempotência
Envie o cabeçalho opcional X-Idempotency-Key para que uma repetição acidental não crie um segundo saque — a resposta original é retornada com HTTP 208. Se você não enviar o cabeçalho, o servidor deriva uma chave automaticamente a partir dos parâmetros da requisição dentro de uma janela de tempo curta. A chave também é o seu orderId.
Como formar a chave
A chave é base64url( HMAC-SHA256( secret, message ) ) sem o preenchimento = — uma string URL-safe de 43 caracteres, onde:
- secret = sua chave de API (
X-API-KEY); - message = os campos unidos com
:—address:amount:nonce.
nonce é qualquer valor que seja estável entre novas tentativas do mesmo pedido lógico, mas diferente entre pedidos distintos — por exemplo, um UUID mantido para esse pedido ou um bucket de timestamp aproximado. Gere a chave uma vez por pedido e reenvie exatamente o mesmo valor a cada nova tentativa.
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-safeValidação. Um
X-Idempotency-Keyfornecido deve ter entre 16 e 64 caracteres pertencentes ao conjuntoA–Z a–z 0–9 + / = _ -. Uma chave malformada ou longa demais é rejeitada com HTTP 400 (code 5004).
| Código de Status | Significado |
|---|---|
| 202 | Aceito (primeira requisição) |
| 208 | Já aceito — resposta em cache retornada (nenhum segundo saque) |
| 409 | A mesma requisição está sendo processada no momento — aguarde, não tente novamente ainda |
Tentando novamente após uma falha. Apenas resultados aceitos são armazenados em cache. Se a tentativa anterior falhou (por exemplo, saldo insuficiente, validação), você pode com segurança tentar novamente com a mesma chave — a requisição será tentada novamente em vez de retornar o erro antigo. Enquanto uma tentativa ainda estiver em andamento, você receberá
409; aguarde e tente novamente.
Observações
- Pagamento assíncrono. A resposta é sempre uma confirmação
pending; o TRX é enviado por um daemon de backend, normalmente dentro de ~5 minutos. Use o endpoint de status ou um webhook para obter o resultado. - O saldo é reservado imediatamente quando o pedido é aceito (não quando o TRX é finalmente enviado).
- Mínimo: 3 TRX. Taxa: 1 TRX (ou 2 TRX com
sub_and_robot_out), retida doamountbruto; o destinatário recebenet = amount − fee. - Apenas um pendente por vez no seu próprio saldo (
code 4090). - Subusuários realizam saques exatamente como usuários regulares — mesmo endpoint
POST /apiv2/withdraw, mesmas regras, mas autenticados com a própria chave de API do subusuário. Um subusuário saca seu próprio saldo para qualqueraddressespecificado. Não há nenhum endpoint separado para subusuários. - orderId é uma string URL-safe de 43 caracteres; passe-a como está na URL de status (nenhuma codificação é necessária).
- Webhooks: por usuário, assinados com
X-Netts-Signature; até 3 tentativas dentro de uma janela de 21 minutos. Configure viaPOST /apiv2/withdraw/webhook.