POST /apiv2/reports/webhooks
Registre una URL y NETTS la llamará cuando un informe esté listo, en lugar de que usted tenga que sondear el estado.
Estos endpoints son independientes de los webhooks de órdenes. Registrarse allí no le suscribe a las notificaciones de informes, y viceversa. El formato de transmisión —firma, encabezados, comportamiento de reintentos— es idéntico, por lo que un controlador escrito para uno funciona para el otro.
URL base del endpoint
https://netts.io/apiv2/reports/webhooksEncabezados de solicitud
| Encabezado | Requerido | Descripción |
|---|---|---|
X-API-KEY | sí | Clave API del panel de control |
X-Real-IP | sí | Una dirección de la lista blanca de la clave |
Principal y de respaldo
Hasta dos endpoints por cuenta. primary recibe todo. backup se utiliza únicamente después de que la entrega al principal haya agotado los intentos — y está firmado con su propio secreto, no con el del principal.
Registrar
curl -s -X POST 'https://netts.io/apiv2/reports/webhooks' \
-H 'X-API-KEY: your-api-key' \
-H 'X-Real-IP: 203.0.113.10' \
-H 'Content-Type: application/json' \
-d '{"url": "https://example.com/netts/reports", "role": "primary"}'{
"status": "success",
"code": 10000,
"data": {
"id": 1,
"url": "https://example.com/netts/reports",
"role": "primary",
"is_active": true,
"created_at": "2026-09-06 17:05:12+00:00",
"updated_at": "2026-09-06 17:05:12+00:00",
"secret": "whsec_<64 hex characters>"
}
}El secreto se muestra una sola vez, aquí. Nunca se vuelve a devolver — ni mediante la lista, ni mediante el endpoint de lectura. Guárdelo cuando lo reciba. Si se pierde, emita uno nuevo:
curl -s -X POST 'https://netts.io/apiv2/reports/webhooks/1/rotate-secret' \
-H 'X-API-KEY: your-api-key' -H 'X-Real-IP: 203.0.113.10'La rotación surte efecto inmediatamente y el secreto antiguo deja de verificar, por lo que debe implementar el nuevo valor primero si no puede tolerar una interrupción.
Administrar
| Método | Ruta | Acción |
|---|---|---|
GET | /apiv2/reports/webhooks | listar los suyos, sin secretos |
GET | /apiv2/reports/webhooks/{id} | leer uno |
PATCH | /apiv2/reports/webhooks/{id} | cambiar url, o pausar con is_active: false |
DELETE | /apiv2/reports/webhooks/{id} | eliminarlo |
La URL debe ser HTTPS pública. Se rechazan las direcciones de bucle invertido (loopback), privadas y de enlace local, al igual que las credenciales dentro de la URL. Todo lo que sea rechazado se devuelve como 422 con el motivo. La comprobación se ejecuta nuevamente justo antes de cada entrega, por lo que un endpoint que posteriormente resuelva a una dirección privada dejará de recibir datos.
Qué enviamos
{
"event": "report.ready",
"delivery_id": 4,
"order_id": "REPxxxxxxxxxxxx",
"order_type": "statement",
"client_request_id": "stmt-2026-09-usdt",
"status": "done",
"format": "csv",
"download_url": "/apiv2/reports/REPxxxxxxxxxxxx/download",
"expires_at": "2026-10-06 15:48:04+00:00",
"artifact": { "sha256": "…", "size_bytes": 696 },
"confirmed_at": "2026-09-06T15:48:04Z"
}| Campo | Descripción |
|---|---|
event | report.ready — clave de enrutamiento para su controlador |
delivery_id | Clave de deduplicación. También se envía como el encabezado X-Netts-Delivery |
order_id | El número de orden que se le proporcionó cuando puso el informe en cola |
order_type | statement o balance_at_date |
download_url | Ruta para obtener el archivo, relativa a https://netts.io |
artifact.sha256 | Suma de comprobación, para que pueda verificar lo que descargó |
confirmed_at | UTC |
Todas las marcas de tiempo están en UTC.
Verificación de la firma
X-Netts-Signature: sha256=<hex>
X-Netts-Timestamp: <unix seconds>
X-Netts-Delivery: <delivery_id>La firma es un HMAC-SHA256 sobre "<timestamp>." + raw body, calculado con el secreto del endpoint que recibió la solicitud. Compare en tiempo constante y rechace cualquier solicitud cuya marca de tiempo quede fuera de una ventana de ±5 minutos.
import hmac, hashlib, time
def verify(raw_body: bytes, sig_header: str, ts_header: str, secret: str) -> bool:
if abs(time.time() - int(ts_header)) > 300: # anti-replay
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)Firme con el secreto de la URL en la que se recibió la solicitud: el principal y el de respaldo tienen secretos diferentes.
La entrega es al menos una vez
Una respuesta perdida provoca un reintento, por lo que el mismo evento puede llegar dos veces.
- Deduplique mediante
delivery_id. Una repetición debe ser una operación nula (no-op) de su lado. - Verifique la firma antes de procesar, no después.
- Responda
2xxsolo después de haber almacenado el evento. Cualquier otra cosa, o un tiempo de espera agotado, se trata como un fallo y se reintenta.
Los reintentos a un endpoint se realizan a 1 minuto, 5 minutos, 15 minutos, 1 hora, 6 horas y 24 horas — seis intentos en total, abarcando un poco más de 31 horas. Cuando se agotan y ha registrado un backup, la entrega se traslada allí y el cronograma comienza de nuevo con el secreto propio del respaldo. El delivery_id se mantiene igual en todo momento, por lo que un evento que falló en el principal y tuvo éxito en el de respaldo sigue siendo un único evento.
No se siguen las redirecciones.
Límites de tasa
10 solicitudes por segundo por endpoint, compartidas entre todos los clientes.
Errores
El registro responde 201, la eliminación responde 204 sin cuerpo, todo lo demás 200.
| HTTP | Significado |
|---|---|
401 | clave faltante o no válida, o la IP de origen no está en la lista blanca |
404 | no existe dicho endpoint en su cuenta |
409 | el rol solicitado ya está ocupado — role primary is already taken |
422 | la URL fue rechazada, o un cuerpo de PATCH no contenía nada que cambiar |
429 | límite de tasa excedido |
Una URL rechazada se devuelve como 422 con el motivo detallado, para que pueda mostrárselo a quien lo haya escrito:
{"status": "error", "code": -4, "msg": "url: Value error, invalid webhook_url: resolved address 127.0.0.1 is not public"}Las formulaciones son only https:// URLs are allowed, credentials in URL are not allowed y resolved address <ip> is not public. La última se resuelve en el momento del registro y nuevamente justo antes de cada entrega, por lo que un nombre de host que posteriormente apunte a una dirección privada dejará de recibir datos.
Relacionado
- Archivos de extractos — ordenar el informe que activa esta notificación
- Webhooks de órdenes — el registro independiente para eventos de energía, ancho de banda y activación