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

POST /apiv2/withdraw ​

Retira TRX de tu saldo de Netts a una dirección TRON. La solicitud devuelve un número de orden inmediatamente; el pago real en la cadena (on-chain) se realiza de forma asíncrona mediante el backend (en unos 5 minutos). Realiza el seguimiento del resultado mediante el sondeo del 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 en el backend envía el TRX y marca la orden como completed o failed. No hay un resultado on-chain sincrónico en la respuesta inicial — siempre obtienes primero un acuse de recibo en estado pending.

Antes de que se acepte la orden, se verifica la dirección de destino. Si los retiros hacia esa dirección no están disponibles, o la verificación no se puede completar en ese momento, la solicitud es rechazada: no se crea ninguna orden y no se reserva nada de tu saldo.

URL del endpoint ​

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

Encabezados de solicitud ​

HeaderRequiredDescription
Content-TypeSíapplication/json
X-API-KEYSíTu clave API del panel de control de Netts
X-Idempotency-KeyNoClave opcional generada por el cliente (base64) para reintentar de forma segura sin un doble retiro. Si se omite, el servidor deriva una automáticamente. Este valor se convierte en tu orderId.

Cuerpo de solicitud ​

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

Parámetros ​

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

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

Ejemplos de solicitudes ​

Los siguientes ejemplos 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-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-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. Sondea 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 segura para URL de 43 caracteres. Utilízalo para el endpoint de estado e identifica la orden en las cargas útiles de webhooks.
detail.data.amountnumberMonto bruto solicitado (TRX)
detail.data.feenumberComisión 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 (la orden debe pertenecer al usuario autenticado). orderId es seguro para URL — pásalo tal cual, sin necesidad de codificación URL.

Estado de la ordenHTTPcodestatus
Completado (TRX enviado)20010000completed (con processed_at)
En cola / enviando20010001pending
Fallido2005003failed (con error_message)
No encontrado / no es tuyo404-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 habituales — simplemente con la clave API propia del subusuario. Un subusuario llama a este mismo endpoint POST /apiv2/withdraw, autenticado con su propia clave; el retiro se debita del saldo propio de ese subusuario y se envía a la address que especifica la solicitud. Mismo mínimo, misma comisión (1 TRX), misma verificación de dirección, mismo flujo. No existe un endpoint separado para subusuarios — cada cuenta, principal o subusuario, solo retira su propio saldo con su propia clave.

Webhooks ​

En lugar de realizar sondeos, configura un webhook una sola 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 sondea 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.

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 caso de failed, se incluye error_message).

Verificación de la firma ​

La firma se calcula sobre el JSON canónico del cuerpo: claves ordenadas, sin espacios (separators=(",", ":")). Vuelve a calcularla de la misma manera y compara.

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 contra 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 acusar recibo. Cualquier otra respuesta (o un tiempo de espera agotado) se trata como un intento fallido.
  • Hasta 3 intentos por orden, dentro de una ventana 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 con éxito como máximo una vez.
  • Haz que tu manejador sea idempotente según 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" } }

Dirección no disponible (403) ​

Los retiros hacia esta dirección de destino no son aceptados. No se crea ninguna orden y no se reserva nada. Usa una dirección diferente o contacta a soporte.

json
{ "detail": { "code": 1006, "status": "failed", "msg": "Withdrawals to this address are not available. Contact support." } }

Verificación de dirección no disponible (503) ​

La dirección de destino no pudo verificarse en este momento. No se crea ninguna orden y no se reserva nada. Reintenta en breve — se puede reutilizar el mismo X-Idempotency-Key.

json
{ "detail": { "code": 1009, "status": "failed", "msg": "Address check is temporarily unavailable. No request was created. Retry shortly." } }

Existe un retiro pendiente (409) ​

Puedes tener solo un retiro pendiente a la vez en 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 todavía se está procesando (no reintentar todavía)409
4090Ya tienes un retiro pendiente409
-1Clave API inválida / IP no presente en la lista blanca, u orden no encontrada401 / 404
1004Saldo insuficiente403
1006Los retiros a esta dirección no están disponibles403
1009Verificación de dirección temporalmente no disponible — ninguna orden creada, reintenta en breve503
5004Error de validación (monto < 3, comisión ≥ monto, dirección errónea, clave de idempotencia errónea)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 — la respuesta original se devuelve con HTTP 208. Si no envías el encabezado, el servidor deriva una clave automáticamente a partir de los parámetros de tu solicitud dentro de una breve ventana de tiempo. La clave también es tu orderId.

Cómo formar la clave ​

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

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

nonce es cualquier valor que sea estable entre los reintentos de la misma orden lógica pero diferente entre órdenes distintas — por ejemplo, un UUID que conserves para esa orden, o un bloque aproximado de marca de tiempo. Genera la clave una sola vez por orden y reenvía 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. El X-Idempotency-Key provisto debe tener entre 16 y 64 caracteres del conjunto A–Z a–z 0–9 + / = _ -. Una clave malformada o excesivamente 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 los resultados aceptados se almacenan en caché. Si el intento anterior falló (por ejemplo, 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 reintenta.

Notas ​

  • Pago asíncrono. La respuesta siempre es un acuse de recibo en estado pending; el TRX es enviado por un demonio del backend, normalmente en un plazo aproximado de 5 minutos. Utiliza el endpoint de estado o un webhook para obtener el resultado.
  • El saldo se reserva inmediatamente cuando se acepta la orden (no cuando finalmente se envía el TRX).
  • Mínimo: 3 TRX. Comisión: 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 en tu propio saldo (code 4090).
  • Verificación de dirección de destino. Cada solicitud se verifica antes de que se acepte la orden: code 1006 (HTTP 403) — los retiros a esta dirección no están disponibles; code 1009 (HTTP 503) — la verificación no está disponible temporalmente, reintenta en breve. En ambos casos no se crea ninguna orden.
  • Los subusuarios retiran exactamente igual que los usuarios habituales — mismo endpoint POST /apiv2/withdraw, mismas reglas, pero autenticados con la clave API propia del subusuario. Un subusuario retira su propio saldo hacia la address que especifique. No hay un endpoint separado para subusuarios.
  • orderId es una cadena segura para URL de 43 caracteres; 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. Configurar a través de POST /apiv2/withdraw/webhook.