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

Webhooks — notificaciones de pedidos ​

Registre un endpoint HTTPS para recibir un webhook firmado en el momento exacto en que uno de sus pedidos sea completado y verificado en la cadena (on-chain). En lugar de hacer sondeos (polling), continúe con su flujo (p. ej., liberar USDT) tan pronto como llegue la notificación.

Se entregan tres eventos:

EventoEnviado cuando
delegation.confirmedUn alquiler de energy (1h / 5m) se confirma on-chain
bandwidth.delegatedSe completa un pedido de bandwidth
activation.confirmedSe ejecuta una activación de dirección on-chain

Esta página cubre la API de gestión (crear / listar / editar / rotar secreto / eliminar sus endpoints) y el formato de los webhooks que le enviamos.

ℹ️ Roles. Usted gestiona sus endpoints aquí. Netts realiza la entrega de forma asíncrona una vez que el pedido se verifica; no hay nada que sondear. Solo se envían eventos de éxito; los fallos y tiempos de espera agotados nunca se entregan.

🔒 Cada hash que enviamos se verifica primero on-chain. Un webhook se despacha únicamente después de que cada hash de transacción incluido en él se encuentre en un bloque. Si un hash aún no está en un bloque, la entrega se retiene y se vuelve a comprobar cada 30 segundos durante un máximo de 5 minutos; si nunca llega a confirmarse, no se envía nada para ese pedido. Nunca recibirá un hash que no exista on-chain.

URL base del endpoint ​

https://netts.io/apiv2/webhooks

Encabezados de solicitud ​

EncabezadoRequeridoDescripción
Content-TypeSí (para POST/PATCH)application/json
X-API-KEYSíSu clave API del panel de Netts
X-Real-IPSíDirección IP de su lista blanca

Su user_id se deriva de la clave API; nunca debe enviarlo. Puede ver y modificar únicamente sus propios endpoints.


Endpoint principal y de respaldo ​

Puede registrar como máximo dos endpoints, y cada uno tiene un role:

RolPropósito
primaryLa dirección a la que se entrega cada webhook.
backupAlternativa de respaldo. Se utiliza únicamente cuando la entrega al primary falla tras agotar sus reintentos.

Un único pedido confirmado genera un único webhook. No es una difusión en abanico (fan-out): el mismo evento nunca se envía a ambas direcciones a la vez. El endpoint backup existe por motivos de resiliencia: si su host principal es inaccesible o sigue respondiendo con estados distintos de 2xx, la entrega pasa al de respaldo en lugar de descartarse.

El primer endpoint que cree se convierte en primary; el segundo se convierte en backup. Puede especificar el role explícitamente, o intercambiarlos más tarde mediante PATCH.

¿Por qué no una URL independiente por cada tipo de operación? Porque el tipo de evento viaja dentro del cuerpo, en el campo event. Un solo manejador, una sola verificación de firma, y los nuevos tipos de eventos comenzarán a llegar sin necesidad de registrar nada nuevo.


Gestionar endpoints ​

Crear — POST /apiv2/webhooks ​

Registra un nuevo endpoint y devuelve un secret que se muestra solo una vez (guárdelo: firma cada webhook que recibe).

json
// request body — role is optional
{
    "url": "https://your-server.example/netts/delegation-hook",
    "role": "primary"
}

Si omite role, se asigna el primero disponible: primary, luego backup.

json
// response 201
{
    "detail": {
        "code": 10000,
        "status": "created",
        "data": {
            "id": 1,
            "url": "https://your-server.example/netts/delegation-hook",
            "secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "role": "primary",
            "is_active": true,
            "created_at": "2026-01-01T00:00:00"
        }
    }
}

Requisitos de la URL (validados al crear y en cada edición):

  • debe ser https;
  • debe resolverse en una dirección pública: las direcciones de bucle invertido (loopback), privadas (RFC1918), de enlace local (incl. 169.254.169.254) y otros rangos no enrutables son rechazados;
  • sin credenciales en la URL (user:pass@…);
  • longitud de hasta 2048 caracteres.

Una URL rechazada devuelve 400.

Puede tener dos endpoints: uno primary y uno backup. Un tercero devuelve 409 (4090). Solicitar un role que ya esté ocupado devuelve 409 (4091): intercambie los roles con PATCH o elimine primero el existente.

bash
curl -X POST https://netts.io/apiv2/webhooks \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: your_api_key" \
  -H "X-Real-IP: your_whitelisted_ip" \
  -d '{"url": "https://your-server.example/netts/delegation-hook", "role": "primary"}'

Listar — GET /apiv2/webhooks ​

Devuelve sus endpoints (el secret nunca se devuelve aquí).

json
{
    "detail": {
        "code": 10000,
        "status": "ok",
        "data": {
            "endpoints": [
                {
                    "id": 1,
                    "url": "https://your-server.example/netts/delegation-hook",
                    "role": "primary",
                    "is_active": true,
                    "created_at": "2026-01-01T00:00:00",
                    "updated_at": "2026-01-01T00:00:00"
                },
                {
                    "id": 2,
                    "url": "https://backup.example/netts/delegation-hook",
                    "role": "backup",
                    "is_active": true,
                    "created_at": "2026-01-01T00:00:00",
                    "updated_at": "2026-01-01T00:00:00"
                }
            ],
            "count": 2,
            "max_endpoints": 2,
            "roles": ["primary", "backup"]
        }
    }
}

Obtener uno — GET /apiv2/webhooks/{id} ​

Misma estructura que un elemento de la lista (sin secret). Un id ajeno o inexistente devuelve 404.

Editar — PATCH /apiv2/webhooks/{id} ​

Cambia la url, is_active y/o role. Envíe cualquier subconjunto; un cuerpo vacío devuelve 422. Si se cambia la url, se vuelve a validar (https / SSRF). Un id ajeno o inexistente devuelve 404.

json
// request body (any subset)
{ "url": "https://your-server.example/netts/new-hook", "is_active": false }
json
// response 200
{
    "detail": {
        "code": 10000,
        "status": "updated",
        "data": {
            "id": 1,
            "url": "https://your-server.example/netts/new-hook",
            "role": "primary",
            "is_active": false,
            "created_at": "2026-01-01T00:00:00",
            "updated_at": "2026-01-01T00:00:01"
        }
    }
}

Promover el respaldo. Enviar {"role": "primary"} a su endpoint de respaldo intercambia los dos roles en una única transacción: el antiguo principal pasa a ser el de respaldo. Nunca se quedará sin una dirección principal, y no se requiere ninguna llamada adicional para el otro endpoint.

bash
curl -X PATCH https://netts.io/apiv2/webhooks/2 \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: your_api_key" \
  -H "X-Real-IP: your_whitelisted_ip" \
  -d '{"role": "primary"}'

Establezca is_active: false para pausar la entrega sin eliminar el endpoint; true para reanudarla. Pausar su primary no promueve el de respaldo: la entrega seguirá intentándose hacia el principal. Intercambie los roles si desea que el de respaldo tome el control.

Rotar secreto — POST /apiv2/webhooks/{id}/rotate-secret ​

Genera un nuevo secret y lo devuelve una sola vez. El nuevo secreto entra en vigor de forma inmediata para las entregas posteriores, sin necesidad de acciones adicionales. Cada endpoint tiene su propio secreto: rotar el secreto del principal no modifica el del respaldo.

json
// response 200
{
    "detail": {
        "code": 10000,
        "status": "rotated",
        "data": {
            "id": 1,
            "secret": "whsec_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
        }
    }
}

Eliminar — DELETE /apiv2/webhooks/{id} ​

Elimina de forma permanente el endpoint y libera su rol. Devuelve 204 (sin cuerpo); un id ajeno o inexistente devuelve 404.

bash
curl -X DELETE https://netts.io/apiv2/webhooks/1 \
  -H "X-API-KEY: your_api_key" -H "X-Real-IP: your_whitelisted_ip"

Los webhooks que entregamos ​

Cuando se completa uno de sus pedidos, Netts envía una solicitud POST a su endpoint primary. Cada cuerpo es application/json (UTF-8); las direcciones y los hashes siempre se entregan como valores completos.

Campos comunes a todos los eventos:

CampoTipoDescripción
eventstringTipo de evento: clave de enrutamiento para su manejador
delivery_idintID de entrega: clave de desduplicación de su lado. También se envía en el encabezado X-Netts-Delivery.
order_idstringSu ID de pedido
order_typestring1h, 5m, bandwidth o activation
tx_hashesstring[]Todos los hashes de transacción de la operación, cada uno verificado on-chain
confirmed_atstringISO-8601 en formato UTC

delegation.confirmed — alquiler de energy ​

json
{
    "event": "delegation.confirmed",
    "delivery_id": 1,
    "order_id": "1Hxxxxxxxxxx",
    "order_type": "1h",
    "receive_address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "energy_amount": 65000,
    "tx_hash": "0000000000000000000000000000000000000000000000000000000000000000",
    "tx_hashes": ["0000000000000000000000000000000000000000000000000000000000000000"],
    "delegation_timestamp": 1700000000000,
    "confirmed_at": "2026-01-01T00:00:00Z"
}
CampoTipoDescripción
order_typestring1h o 5m
receive_addressstringDirección TRON que recibió la energy
energy_amountintCantidad de Energy delegada
tx_hashstringCampo heredado, conservado por compatibilidad: idéntico a tx_hashes[0]
delegation_timestampint?Opcional — presente solo cuando se confirma a través de la ruta Mongo

Se recomienda utilizar tx_hashes en nuevas integraciones: un pedido puede, en principio, completarse mediante más de una transacción. tx_hash seguirá funcionando.

bandwidth.delegated — pedido de bandwidth ​

json
{
    "event": "bandwidth.delegated",
    "delivery_id": 2,
    "order_id": "B1Hxxxxxxxxxxxxx",
    "order_type": "bandwidth",
    "rental_label": "1h",
    "rental_seconds": 3600,
    "receive_address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "bandwidth_amount": 400,
    "fulfillment": "delegated",
    "tx_hashes": ["0000000000000000000000000000000000000000000000000000000000000000"],
    "confirmed_at": "2026-01-01T00:00:00Z"
}
CampoTipoDescripción
rental_label / rental_secondsstring / intDuración del alquiler, p. ej., 1h / 3600
receive_addressstringDirección TRON que recibió el bandwidth
bandwidth_amountintUnidades de Bandwidth (netas)
fulfillmentstringCómo se completó el pedido — ver más abajo

Valores de fulfillment:

ValorSignificadotx_hashes
delegatedBandwidth delegado desde nuestro pool1+ hashes
trx_sendCompletado mediante el envío de TRX a la dirección en lugar de delegar1+ hashes
already_enoughLa dirección ya tenía suficiente bandwidth libre; no se envió nada on-chainvacío

already_enough es el único caso en el que tx_hashes está vacío: el pedido se cierra con éxito, pero no hay transacción porque no fue necesaria ninguna.

activation.confirmed — activación de dirección ​

json
{
    "event": "activation.confirmed",
    "delivery_id": 3,
    "order_id": "123456",
    "order_type": "activation",
    "address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "activation_type": "ACC_CREATE",
    "source": "telegram_bot",
    "tx_hashes": ["0000000000000000000000000000000000000000000000000000000000000000"],
    "confirmed_at": "2026-01-01T00:00:00Z"
}
CampoTipoDescripción
order_idstringID del pedido de activación (cadena numérica)
addressstringDirección TRON que fue activada
activation_typestringACC_CREATE (AccountCreateContract) o DIRECT (transferencia TRX)
sourcestringMarcador de origen. Ya sea una etiqueta de servicio o el ID del pedido de energy que requirió la activación

Solo se entregan activaciones reales. Si la dirección resultó estar ya activa y no se realizó ninguna transacción, no se envía ningún webhook.

Un pedido de energy que también haya requerido una activación genera dos webhooks: uno activation.confirmed y uno delegation.confirmed. Son eventos independientes con delivery_id distintos; enrútelos mediante el campo event.

Encabezados que enviamos:

EncabezadoValor
X-Netts-EventTipo de evento: delegation.confirmed, bandwidth.delegated o activation.confirmed
X-Netts-Deliverydelivery_id (desduplicación)
X-Netts-TimestampSegundos unix al momento del envío
X-Netts-Signaturesha256=<hex>, hex = HMAC_SHA256(secret, "<timestamp>." + raw_body)
User-Agentnetts-webhook/1.0

Verificar la firma ​

La firma sigue el esquema de Stripe (timestamp.body), calculado sobre los bytes en crudo que enviamos. Vuelva a calcularla con su secret, compárela en tiempo constante y rechácela si X-Netts-Timestamp se encuentra fuera de una ventana de ±5 minutos (protección contra ataques de repetición).

Firme con el secreto del endpoint que recibió la solicitud: el principal y el de respaldo tienen secretos independientes. Si ambas direcciones son atendidas por el mismo manejador, seleccione el secreto en función de la URL a la que llegó la solicitud.

python
import hmac, hashlib, time

def verify(raw_body: bytes, sig_header: str, ts_header: str, secret: str) -> bool:
    # freshness (anti-replay)
    if abs(time.time() - int(ts_header)) > 300:
        return False
    signed = f"{ts_header}.".encode() + raw_body
    expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig_header)

# Flask example:
# ok = verify(request.get_data(),
#             request.headers["X-Netts-Signature"],
#             request.headers["X-Netts-Timestamp"], SECRET)

Semántica de entrega (importante: al menos una vez / at-least-once) ​

La entrega es al menos una vez: una respuesta perdida puede provocar un reintento, por lo que podría recibir el mismo evento dos veces. Dado que la acción comercial (liberar USDT) involucra dinero sensible:

  1. La desduplicación es obligatoria: procese cada evento de forma idempotente según delivery_id (y/o order_id); una repetición no debe realizar ninguna operación (no-op).
  2. Verifique el HMAC antes de cualquier acción monetaria: no confíe en el cuerpo hasta que la firma coincida y X-Netts-Timestamp sea reciente.
  3. Devuelva 2xx solo después de haber almacenado el evento de forma duradera: de lo contrario, (correctamente) reintentaremos el envío.

Responda con 2xx para confirmar la recepción; cualquier código distinto de 2xx o tiempo de espera agotado activará un reintento.

Orden de los intentos:

  1. Los reintentos se dirigen a su endpoint primary. La ventana depende del tipo de pedido: los pedidos 5m reintentan durante ~1 minuto; todos los demás tipos durante ~10 minutos.
  2. Si la ventana se agota y usted registró un backup, la entrega se traslada allí y el cronograma de reintentos comienza de nuevo, firmado con el propio secreto del respaldo.
  3. Únicamente después de que el endpoint de respaldo también se agote, la entrega se marca como fallida definitivamente (dead).

Se utiliza el mismo delivery_id en todo momento, por lo que un mensaje que primero falló en el principal y luego tuvo éxito en el de respaldo sigue siendo un único evento para su lógica de desduplicación.


Referencia de códigos de error ​

CódigoDescripciónEstado HTTP
10000Éxito (created / ok / updated / rotated)200 / 201
-Eliminado (sin cuerpo)204
4000URL de webhook inválida o insegura (no es https, privada/loopback, credenciales, demasiado larga)400
-1Clave API inválida / IP fuera de la lista blanca401
-1Endpoint no encontrado (o no le pertenece)404
4090Límite de endpoints alcanzado (máx. 2: primary, backup)409
4091El rol solicitado ya está ocupado: intercámbielo con PATCH o elimine primero el endpoint existente409
4220Nada que actualizar (PATCH con cuerpo vacío)422
5003Error al crear el endpoint (inténtelo de nuevo)503

Límites de velocidad (Rate Limits) ​

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

PeríodoLímite
1 segundo5 solicitudes
1 minuto150 solicitudes

Límite de velocidad excedido (429) ​

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

Notas ​

  • El secreto se muestra una sola vez: al crear y al rotar. Nunca se devuelve mediante GET/LIST. ¿Lo perdió? Rótelo para obtener uno nuevo.
  • Dos endpoints, no difusión en abanico: uno primary y uno backup. Cada pedido confirmado genera un webhook, entregado al principal; el de respaldo se usa solo si el principal se agota.
  • Cambio de URL sin tiempo de inactividad: registre la nueva dirección como backup, verifíquela y luego aplique PATCH para convertirla en primary: el intercambio es atómico.
  • Pausar: PATCH … {"is_active": false} detiene la entrega sin perder el endpoint.
  • Solo eventos de éxito: delegation.confirmed, bandwidth.delegated, activation.confirmed. No existe ningún evento de fallo: un pedido fallido o con tiempo de espera agotado no genera ningún webhook.
  • Se pueden agregar nuevos tipos de eventos con el tiempo. Enrute mediante el campo event e ignore los tipos que aún no maneje: nunca necesitará registrar nada nuevo para comenzar a recibirlos.
  • Los hashes se verifican on-chain antes de la entrega (consulte la nota al principio): un webhook contiene hashes que ya están todos incluidos en un bloque, o no se envía en absoluto.
  • Las URL se validan para garantizar la seguridad frente a SSRF en el registro y en cada edición; el sistema de entrega vuelve a validarlas en el momento del envío.