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
completedofailed. No hay un resultado on-chain sincrónico en la respuesta inicial — siempre obtienes primero un acuse de recibo en estadopending.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/withdrawEncabezados de solicitud
| Header | Required | Description |
|---|---|---|
| Content-Type | Sí | application/json |
| X-API-KEY | Sí | Tu clave API del panel de control de Netts |
| X-Idempotency-Key | No | Clave 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
{
"amount": 15,
"address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}Parámetros
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
| amount | number | Sí | Monto bruto en TRX (mínimo 3). La comisión se deduce de este monto — el destinatario recibe amount − fee (net). |
| address | string | Sí | Dirección TRON de destino (T…, 34 caracteres, base58). Se verifica antes de que se acepte la orden. |
| sub_and_robot_out | boolean | No | Modo 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
amountbruto: 1 TRX normalmente, o 2 TRX cuandosub_and_robot_out = true. La orden se rechaza siamount − fee ≤ 0.
Ejemplos de solicitudes
Los siguientes ejemplos también construyen y envían el
X-Idempotency-Keypara que una repetición accidental no cree un segundo retiro. Consulta Idempotencia para ver las reglas completas.
cURL
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
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.
{
"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
| Campo | Tipo | Descripción |
|---|---|---|
| detail.code | integer | 10000 aceptado |
| detail.status | string | pending |
| detail.data.orderId | string | Nú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.amount | number | Monto bruto solicitado (TRX) |
| detail.data.fee | number | Comisión retenida (1 o 2 TRX) |
| detail.data.net | number | Monto que recibe el destinatario (amount − fee) |
| detail.data.address | string | Direcció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 orden | HTTP | code | status |
|---|---|---|---|
| Completado (TRX enviado) | 200 | 10000 | completed (con processed_at) |
| En cola / enviando | 200 | 10001 | pending |
| Fallido | 200 | 5003 | failed (con error_message) |
| No encontrado / no es tuyo | 404 | -1 | — |
{
"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 # unsubscribeEncabezados: X-API-KEY.
// POST body
{
"callback_url": "https://your-server.example/netts/withdraw-hook",
"secret": "your_shared_secret_min_8_chars",
"enabled": true
}| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
| callback_url | string | Sí | URL http(s) (≤ 2048 caracteres) que recibe el POST |
| secret | string | Sí | Secreto compartido (8…256 caracteres) utilizado para firmar cada carga útil |
| enabled | boolean | No | Activa/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:
{
"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
}statusescompletedofailed(en caso defailed, se incluyeerror_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.
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)
{ "detail": { "code": -1, "msg": "Invalid API key or IP not in whitelist" } }Saldo insuficiente (403)
{ "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.
{ "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.
{ "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.
{ "detail": { "code": 4090, "status": "failed", "msg": "You have a pending withdrawal. Wait until it is processed." } }Error de validación (400)
{ "detail": { "code": 5004, "status": "failed", "msg": "Minimum withdrawal is 3 TRX" } }Referencia de códigos de error
| Código | Descripción | Estado HTTP |
|---|---|---|
10000 | Aceptado (retiro en cola) / Completado (endpoint de estado) | 202 / 200 |
10001 | Pendiente — en cola o enviando (endpoint de estado) | 200 |
208 | Duplicado de una solicitud ya aceptada — respuesta en caché | 208 |
- | La misma solicitud todavía se está procesando (no reintentar todavía) | 409 |
4090 | Ya tienes un retiro pendiente | 409 |
-1 | Clave API inválida / IP no presente en la lista blanca, u orden no encontrada | 401 / 404 |
1004 | Saldo insuficiente | 403 |
1006 | Los retiros a esta dirección no están disponibles | 403 |
1009 | Verificación de dirección temporalmente no disponible — ninguna orden creada, reintenta en breve | 503 |
5004 | Error de validación (monto < 3, comisión ≥ monto, dirección errónea, clave de idempotencia errónea) | 400 |
5003 | Retiro fallido / servicio no disponible | 200 (estado) / 503 |
5000 | Error interno del servidor | 500 |
Límites de tasa
Limitado por clave API (encabezado X-API-KEY):
| Período | Límite |
|---|---|
| 1 segundo | 5 solicitudes |
| 1 minuto | 150 solicitudes |
Límite de tasa excedido (429)
{ "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.
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-safeValidación. El
X-Idempotency-Keyprovisto debe tener entre 16 y 64 caracteres del conjuntoA–Z a–z 0–9 + / = _ -. Una clave malformada o excesivamente larga se rechaza con HTTP 400 (code 5004).
| Código de estado | Significado |
|---|---|
| 202 | Aceptado (primera solicitud) |
| 208 | Ya aceptado — se devuelve la respuesta en caché (sin segundo retiro) |
| 409 | La 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 delamountbruto; el destinatario recibenet = 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 laaddressque 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 dePOST /apiv2/withdraw/webhook.