Skip to content
Translated page. The English version is the source of truth.

POST /apiv2/withdraw ​

Retira TRX de tu saldo de Netts a cualquier dirección TRON. La solicitud devuelve un número de orden inmediatamente; el pago real on-chain es realizado de forma asíncrona por el backend (en ~5 minutos). Monitorea el resultado mediante sondeo al endpoint de estado o configurando un webhook.

ℹ️ Cómo funciona. Realizar un retiro reserva el monto de tu saldo de inmediato (el saldo se debita en el momento en que se acepta la orden). Luego, un demonio del backend envía el TRX y marca la orden como completed o failed. No hay resultado on-chain sincrónico en la respuesta inicial; siempre obtendrás primero una confirmación pending.

URL del Endpoint ​

POST https://netts.io/apiv2/withdraw

Encabezados de la Solicitud ​

EncabezadoRequeridoDescripción
Content-TypeSíapplication/json
X-API-KEYSíTu clave API del panel de Netts
X-Real-IPSíDirección IP de tu lista blanca
X-Idempotency-KeyNoClave opcional generada por el cliente (base64) para reintentar de forma segura sin un doble retiro. Si se omite, el servidor genera una automáticamente. Este valor se convierte en tu orderId.

Cuerpo de la Solicitud ​

json
{
    "amount": 15,
    "address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

Parámetros ​

ParámetroTipoRequeridoDescripción
amountnumberSíMonto bruto en TRX (mínimo 3). La tarifa se deduce de este monto; el destinatario recibe amount − fee (net).
addressstringSíDirección TRON de destino (T…, 34 caracteres, base58).
sub_and_robot_outbooleanNoModo de pago de robot/subcuenta: aplica la tarifa de 2 TRX en lugar de 1 TRX. Valor predeterminado false.

Tarifa. Se retiene una tarifa fija del amount bruto: 1 TRX normalmente, o 2 TRX cuando sub_and_robot_out = true. La orden se rechaza si amount − fee ≤ 0.

Solicitudes de Ejemplo ​

Los ejemplos a continuación también construyen y envían el X-Idempotency-Key para que una repetición accidental no cree un segundo retiro. Consulta Idempotencia para ver las reglas 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)}")

Respuesta ​

Aceptado — retiro en cola (202 Accepted) ​

El monto se reserva de tu saldo y se programa el pago. Consulta periódicamente el endpoint de estado (o espera al webhook) hasta que pase a 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 de Respuesta ​

CampoTipoDescripción
detail.codeinteger10000 aceptado
detail.statusstringpending
detail.data.orderIdstringNúmero de orden: una cadena de 43 caracteres segura para URL. Úsala para el endpoint de estado e identifica la orden en las cargas útiles de webhooks.
detail.data.amountnumberMonto bruto solicitado (TRX)
detail.data.feenumberTarifa retenida (1 o 2 TRX)
detail.data.netnumberMonto que recibe el destinatario (amount − fee)
detail.data.addressstringDirección de destino

Endpoint de Estado ​

GET https://netts.io/apiv2/withdraw/status/{orderId}

Encabezados: X-API-KEY + X-Real-IP (la orden debe pertenecer al usuario autenticado). orderId es seguro para URL: pásalo tal cual, no es necesaria la codificación URL.

Estado de la ordenHTTPcodestatus
Completada (TRX enviado)20010000completed (con processed_at)
En cola / enviando20010001pending
Fallida2005003failed (con error_message)
No encontrada / no te pertenece404-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"
        }
    }
}

Cuentas de subusuarios ​

Los retiros de subusuarios funcionan exactamente igual que para los usuarios regulares, solo que con la propia clave API del subusuario. Un subusuario llama a este mismo endpoint POST /apiv2/withdraw, autenticado con su propia clave; el retiro se debita del propio saldo de ese subusuario y se envía a cualquier address que especifique la solicitud. Mismo mínimo, misma tarifa (1 TRX), mismo flujo. No existe ningún endpoint separado para subusuarios: cada cuenta, principal o subusuario, solo retira su propio saldo con su propia clave.

Webhooks ​

En lugar de consultar periódicamente, configura un webhook una vez y Netts enviará por POST una notificación firmada cuando cada uno de tus retiros alcance un estado terminal (completed / failed). El webhook se almacena por usuario y se aplica a los retiros de esa cuenta. Si no hay ningún webhook configurado, simplemente consulta el endpoint de estado.

Configurar / ver / eliminar ​

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      # unsubscribe

Encabezados: 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ámetroTipoRequeridoDescripción
callback_urlstringSíURL http(s) (≤ 2048 caracteres) que recibe el POST
secretstringSíSecreto compartido (8…256 caracteres) utilizado para firmar cada carga útil
enabledbooleanNoActiva/desactiva la entrega sin eliminar la configuración. Valor predeterminado true

GET devuelve { callback_url, enabled, secret_set, updated_at }; el secreto en sí nunca se devuelve.

Carga útil de entrega ​

Netts envía un POST a tu callback_url con el encabezado X-Netts-Signature: base64( HMAC-SHA256( secret, raw_body ) ) y este cuerpo 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 es completed o failed (en failed, error_message se encuentra completado).

Verificación de la firma ​

La firma se calcula sobre el JSON canónico del cuerpo: claves ordenadas, sin espacios (separators=(",", ":")). Recalcúlala de la misma manera y compárala.

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): ...

Siempre verifica con los bytes sin procesar recibidos. Si vuelves a serializar el JSON analizado, reproduce la forma canónica: json.dumps(payload, ensure_ascii=False, separators=(",",":"), sort_keys=True).

Garantías de entrega ​

  • Responde con HTTP 2xx para confirmar la recepción. Cualquier otra respuesta (o un tiempo de espera agotado) se trata como un intento fallido.
  • Hasta 3 intentos por orden, dentro de un período de 21 minutos desde el momento en que se creó la orden (tiempo de espera entre reintentos ≈ 5 minutos). Después de eso, se desiste de la entrega; recurre al endpoint de estado.
  • Las entregas están desduplicadas: cada orden se entrega como máximo una vez con éxito.
  • Haz que tu controlador sea idempotente según el orderId.

Respuestas de Error ​

Error de Autenticación (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" } }

Retiro Pendiente Existente (409) ​

Puedes tener solo un retiro pendiente a la vez sobre tu propio saldo. Espera hasta que el actual sea procesado.

json
{ "detail": { "code": 4090, "status": "failed", "msg": "You have a pending withdrawal. Wait until it is processed." } }

Error de Validación (400) ​

json
{ "detail": { "code": 5004, "status": "failed", "msg": "Minimum withdrawal is 3 TRX" } }

Referencia de Códigos de Error ​

CódigoDescripciónEstado HTTP
10000Aceptado (retiro en cola) / Completado (endpoint de estado)202 / 200
10001Pendiente — en cola o enviando (endpoint de estado)200
208Duplicado de una solicitud ya aceptada — respuesta en caché208
-La misma solicitud aún se está procesando (no reintentar todavía)409
4090Ya tienes un retiro pendiente409
-1Clave API inválida / IP no en lista blanca, u orden no encontrada401 / 404
1004Saldo insuficiente403
5004Error de validación (monto < 3, tarifa ≥ monto, dirección incorrecta, clave de idempotencia incorrecta)400
5003Retiro fallido / servicio no disponible200 (estado) / 503
5000Error interno del servidor500

Límites de Tasa ​

Limitado por clave API (encabezado X-API-KEY):

PeríodoLímite
1 segundo5 solicitudes
1 minuto150 solicitudes

Límite de Tasa Excedido (429) ​

json
{ "message": "API rate limit exceeded" }

Idempotencia ​

Envía el encabezado opcional X-Idempotency-Key para que una repetición accidental no cree un segundo retiro; se devolverá la respuesta original con HTTP 208. Si no envías el encabezado, el servidor deduce una clave automáticamente a partir de los parámetros de tu solicitud dentro de una ventana de tiempo corta. La clave también es tu orderId.

Cómo formar la clave ​

La clave es base64url( HMAC-SHA256( secret, message ) ) sin el relleno =: una cadena de 43 caracteres segura para URL, donde:

  • secret = tu clave API (X-API-KEY);
  • message = los campos unidos con : — address:amount:nonce.

nonce es cualquier valor que sea estable entre reintentos de la misma orden lógica pero diferente entre órdenes distintas; p. ej., un UUID que guardes para esa orden o un bloque temporal aproximado. Genera la clave una vez por orden y vuelve a enviar exactamente el mismo valor en cada reintento.

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-safe

Validación. Un X-Idempotency-Key proporcionado debe tener entre 16 y 64 caracteres del conjunto A–Z a–z 0–9 + / = _ -. Una clave malformada o demasiado larga se rechaza con HTTP 400 (code 5004).

Código de EstadoSignificado
202Aceptado (primera solicitud)
208Ya aceptado — se devuelve la respuesta en caché (sin segundo retiro)
409La misma solicitud se está procesando actualmente — espera, no reintentes todavía

Reintentar tras un fallo. Solo se almacenan en caché los resultados aceptados. Si el intento anterior falló (p. ej., saldo insuficiente, validación), puedes reintentar de forma segura con la misma clave; la solicitud se intentará de nuevo en lugar de devolver el error anterior. Mientras un intento siga en curso obtendrás 409; espera y vuelve a intentarlo.

Notas ​

  • Pago asíncrono. La respuesta siempre es una confirmación pending; el TRX es enviado por un demonio del backend, normalmente en ~5 minutos. Usa el endpoint de estado o un webhook para ver el resultado.
  • El saldo se reserva inmediatamente cuando se acepta la orden (no cuando finalmente se envía el TRX).
  • Mínimo: 3 TRX. Tarifa: 1 TRX (o 2 TRX con sub_and_robot_out), retenida del amount bruto; el destinatario recibe net = amount − fee.
  • Uno pendiente a la vez sobre tu propio saldo (code 4090).
  • Los subusuarios retiran exactamente igual que los usuarios regulares: el mismo endpoint POST /apiv2/withdraw, mismas reglas, pero autenticados con la propia clave API del subusuario. Un subusuario retira su propio saldo a cualquier address que especifique. No hay ningún endpoint separado para subusuarios.
  • orderId es una cadena de 43 caracteres segura para URL; pásala tal cual en la URL de estado (no requiere codificación).
  • Webhooks: por usuario, firmados con X-Netts-Signature; hasta 3 intentos dentro de una ventana de 21 minutos. Se configuran mediante POST /apiv2/withdraw/webhook.