Webhooks de SolumeCore: eventos, firma y reintentos
Recibe en tu servidor los eventos de tu cuenta de SolumeCore: lista de eventos, payload, firma HMAC-SHA256, reintentos y código en Node y Python.
Un webhook de SolumeCore envía a tu propio servidor un POST por HTTPS con un cuerpo JSON cada vez que ocurre algo en tu cuenta o en uno de tus servicios: se cae un servidor, falla un backup, se paga una factura, alguien inicia sesión desde un dispositivo nuevo. Cada petición va firmada con HMAC-SHA256, de modo que tu código puede comprobar que viene de SolumeCore, y las entregas fallidas se reintentan solas.
Gestionas los webhooks en el panel de cliente, en Cuenta → API y webhooks → Webhooks. Este documento enumera los eventos a los que puedes suscribirte, muestra el payload y las cabeceras exactas, explica los reintentos y trae código de verificación de la firma que funciona, en Node.js y Python. Si prefieres recibir un mensaje en Discord, Slack o Telegram en lugar de programar, mira Integraciones y notificaciones.
Crear un webhook
- Entra en Cuenta → API y webhooks y abre la pestaña Webhooks.
- Pulsa Nuevo webhook.
- Escribe Tu dirección (URL). Debe ser
https://, sin usuario ni contraseña, y accesible desde internet público: se rechazan las direcciones que apuntan a redes privadas o internas. - Si quieres, añade una Descripción (por ejemplo, «Mi bot de Discord»).
- En Eventos, activa Todos los eventos (incluye los que se añadan en el futuro) o marca los que te interesan.
- Pulsa Crear webhook y confirma que eres tú cuando el panel te lo pida. Una ventana muestra el Secreto de firma una sola vez; empieza por
whsec_. Cópialo, marca Ya lo guardé y pulsa Listo.
Pulsa Mandar prueba en el webhook para enviar un evento ping por el mismo camino que los reales. Ver entregas muestra cada intento con Lo que mandamos y Lo que respondió tu servidor, y Mandar de nuevo pone en cola una copia de cualquier entrega anterior.
Conviene saber:
- Hace falta confirmar tu identidad para crear un webhook o rotar su secreto, y ninguna de las dos cosas se puede hacer desde una sesión de soporte.
- Por defecto una cuenta puede tener hasta 10 webhooks. El panel te indica el límite que te corresponde.
- El interruptor de cada webhook lo pausa o lo reanuda. Editar cambia la URL, la descripción y los eventos, y Rotar secreto emite uno nuevo; el anterior deja de servir al instante, así que actualiza antes tu servidor o acepta un pequeño hueco.
- Tras la primera vez, el panel solo muestra una breve pista de cada secreto. Un secreto perdido no se recupera: rótalo.
Cómo es una entrega
SolumeCore envía un POST con Content-Type: application/json y estas cabeceras:
| Cabecera | Valor |
|---|---|
Solume-Signature |
t=<segundos unix>,v1=<HMAC-SHA256 en hexadecimal> |
Solume-Event |
El nombre del evento, por ejemplo servidor.crash |
Solume-Delivery |
El id de la entrega, igual que id en el cuerpo |
User-Agent |
SolumeCore-Webhooks/1.0 |
El cuerpo tiene cuatro campos en el nivel superior. Los nombres de campos y de eventos están en español y se mantienen así:
{
"id": "cm0entrega0ejemplo",
"evento": "servidor.crash",
"creado": "2026-01-15T09:30:00.000Z",
"datos": {
"version": 1,
"familia": "servidor",
"categoria": "energia",
"severidad": "critico",
"servicio": {
"id": "clx0ejemplo0servicio",
"codigo": "juego-1042",
"nombre": "Survival",
"tipo": "juego",
"url": "https://dashboard.solumecore.com/game-servers/juego-1042"
},
"actor": { "tipo": "sistema", "id": null, "nombre": null },
"detalle": "java.lang.OutOfMemoryError: Java heap space",
"codigoSalida": 1
}
}| Campo | Significado |
|---|---|
id |
Único por entrega. Los reintentos automáticos conservan el mismo id y el mismo cuerpo: úsalo para ignorar duplicados |
evento |
Nombre del evento |
creado |
Cuándo se creó la entrega (ISO 8601, UTC) |
datos.version |
Versión del esquema del payload, hoy 1. Pueden aparecer campos nuevos sin cambiar la versión |
datos.familia, categoria, severidad |
A qué área pertenece y su gravedad: info, aviso o critico |
datos.servicio |
El servicio afectado (codigo es el código público, como vps-3315; url lleva a él en el panel), o null en eventos de cuenta |
datos.actor |
Quién lo hizo: duenio (tú), subusuario, staff o sistema. Si es el equipo, no se incluye ni id ni nombre |
Otras claves de datos |
Los datos de ese evento: detalle, backup, factura, monto, jugador, ruta, etc. |
Este sobre se aplica a los eventos del catálogo (todas las filas de la tabla de eventos de más abajo salvo la última). Los seis eventos de notificación de la última fila usan en datos una forma más simple: tipo, categoria (la categoría del aviso, como alertas o servidores), evento, titulo, mensaje y url, más los campos extra que haya, sin version, familia, severidad, servicio ni actor.
Nunca incluimos contraseñas, tokens ni secretos en un payload. El evento de prueba es ping y trae un mensaje y el webhookId.
Verificar la firma
v1 es el HMAC-SHA256 en hexadecimal del texto <t>.<cuerpo crudo>, con tu secreto de firma como clave. Usa el secreto completo, incluido el prefijo whsec_. Calcúlalo siempre sobre los bytes crudos del cuerpo, antes de interpretar el JSON, compara en tiempo constante y rechaza las peticiones cuyo t se aleje más de cinco minutos de tu reloj. Los reintentos se firman de nuevo con un t fresco, así que la tolerancia nunca los rechaza.
import crypto from 'node:crypto';
import express from 'express';
const SECRET = process.env.SOLUME_WEBHOOK_SECRET; // whsec_...
const app = express();
function isValidSignature(header, rawBody, secret, toleranceSeconds = 300) {
if (!header) return false;
const parts = Object.fromEntries(
header.split(',').map((p) => {
const i = p.indexOf('=');
return [p.slice(0, i).trim(), p.slice(i + 1).trim()];
}),
);
const t = Number(parts.t);
if (!Number.isInteger(t) || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.`).update(rawBody).digest();
const received = Buffer.from(parts.v1, 'hex');
return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}
app.post('/solume', express.raw({ type: 'application/json' }), (req, res) => {
if (!isValidSignature(req.get('Solume-Signature'), req.body, SECRET)) {
return res.sendStatus(401);
}
const delivery = req.get('Solume-Delivery'); // guárdalo para ignorar duplicados
const event = JSON.parse(req.body.toString('utf8'));
console.log(delivery, event.evento, event.datos.servicio?.codigo);
res.sendStatus(200); // responde rápido, trabaja después
});
app.listen(3000);import hashlib
import hmac
import os
import time
from flask import Flask, request
SECRET = os.environ["SOLUME_WEBHOOK_SECRET"] # whsec_...
app = Flask(__name__)
def is_valid_signature(header: str | None, raw_body: bytes, secret: str, tolerance: int = 300) -> bool:
if not header:
return False
try:
parts = dict(p.strip().split("=", 1) for p in header.split(","))
t = int(parts["t"])
received = parts["v1"]
except (KeyError, ValueError):
return False
if abs(time.time() - t) > tolerance:
return False
message = f"{t}.".encode() + raw_body
expected = hmac.new(secret.encode(), message, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected.encode(), received.encode())
@app.post("/solume")
def solume_webhook():
if not is_valid_signature(request.headers.get("Solume-Signature"), request.get_data(), SECRET):
return "", 401
event = request.get_json()
print(request.headers.get("Solume-Delivery"), event["evento"])
return "", 200Entrega, reintentos y pausa automática
- Cualquier respuesta
2xxcuenta como entregada. Responde en menos de 10 segundos y deja el trabajo lento para segundo plano. - No se siguen las redirecciones: un
301o302cuenta como intento fallido. - Se reintentan los errores de red, los tiempos de espera agotados y las respuestas
408,429y5xx. Las demás respuestas4xx, cualquier redirección y los destinos no permitidos se consideran un problema de configuración: la entrega falla en el primer intento y no se reintenta. - Una entrega se intenta hasta 8 veces. La espera antes de cada reintento crece desde unos 30 segundos hasta 2 minutos, 8 minutos, 30 minutos, 1 hora, 2 horas y por último 4 horas, con algo de aleatoriedad. Las entregas de prueba nunca se reintentan.
- El panel muestra el estado de cada entrega: Entregada, Reintentando, Fallida o Pendiente.
- Si 20 entregas seguidas fallan definitivamente (se agotan los reintentos o la respuesta no se reintenta), SolumeCore pasa el webhook a En pausa. La fila muestra el número de entregas fallidas. Arregla tu endpoint y vuelve a activar el interruptor; el contador empieza de cero.
- No se garantiza el orden: si necesitas ordenar los eventos, usa
creado.
Eventos a los que puedes suscribirte
El selector ofrece 82 nombres de evento, agrupados por categoría. Elige Todos los eventos para recibirlos todos, incluidos los que añadamos más adelante. Qué eventos se disparan en la práctica depende de las funciones que usen tus servicios.
| Familia | Eventos |
|---|---|
| Servidores de juego y apps: energía | servidor.encendido, servidor.apagado, servidor.reiniciado, servidor.crash, servidor.auto_reinicio, servidor.hibernado, servidor.despertado |
| Servidores de juego y apps: actividad | servidor.comando, archivo.creado, archivo.editado, archivo.borrado, archivo.subido, archivo.renombrado, sftp.login, sftp.cambio, jugador.entro, jugador.salio |
| Servidores de juego y apps: copias y contenido | backup.creado, backup.restaurado, backup.fallido, base_datos.backup, zona.restaurada, contenido.instalado, contenido.actualizado, staging.listo, staging.aplicado, staging.fallido |
| Servidores de juego y apps: accesos, despliegues y salud | subusuario.agregado, subusuario.quitado, deploy.iniciado, deploy.exitoso, deploy.fallido, deploy.sin_caida, deploy.sin_caida_fallido, salud.caida, salud.recuperada, recurso.alto |
| VPS | vps.encendida, vps.apagada, vps.reiniciada, vps.reinstalada, vps.backup, vps.snapshot, vps.consola, vps.trafico_80, vps.ddos_mitigado |
| Hosting web | hosting.casilla_creada, hosting.ssl_emitido, hosting.ssl_por_vencer, hosting.dominio_agregado |
| Cuenta: facturación | factura.emitida, factura.pagada, factura.vencida, pago.fallido, servicio.por_suspender, servicio.suspendido, servicio.cancelado, servicio.pausado, servicio.reanudado, credito.sla, credito.rendimiento, uso.saldo_bajo, uso.detenido_por_saldo, pozo.aporte, boost.aplicado, boost.vencido |
| Cuenta: seguridad y soporte | cuenta.login_nuevo, cuenta.seguridad, ia.accion, ticket.respondido, importacion.completada, importacion.fallida |
| Estado de la plataforma | status.incidente, status.mantenimiento, servidor.caido, servidor.recuperado |
| Eventos de notificación | alerta.disparada, alerta.resuelta, factura.por_vencer, backup.listo, servicio.activo, notificacion |
Algunos merecen una mención:
servidor.crashyservidor.auto_reiniciodistinguen una caída real de un reinicio que alguien pidió.cuenta.seguridadincluye un campocambio(api_key_creadayapi_key_revocadapara las claves de API, una breve descripción para otros cambios de seguridad), que sirve de alarma para tus claves de API.ia.accioninforma de cada cambio hecho por un asistente de IA conectado a tu cuenta; mira API de IA y MCP.- Los eventos de notificación (
alerta.disparada,alerta.resuelta,factura.por_vencer,backup.listo,servicio.activoynotificacion) llevan el propio aviso.notificaciones el comodín de los avisos que no tienen un evento propio, y sutipote dice de qué se trataba. - Las alertas que configuras en Notificaciones → Alertas llegan como
alerta.disparadayalerta.resuelta; el recordatorio de factura por vencer llega comofactura.por_vencer.
Documentos relacionados
- Integraciones y notificaciones
- Claves de API de la cuenta
- Referencia de la API de cliente
- Qué API de SolumeCore usar
- Estado del servicio e incidencias
FAQ
¿Cómo verifico la firma de un webhook de SolumeCore?
Lee la cabecera Solume-Signature (t=...,v1=...), calcula el HMAC-SHA256 de t, un punto y el cuerpo crudo de la petición con tu secreto whsec_, compáralo con v1 en tiempo constante y rechaza todo donde t tenga más de cinco minutos.
¿Por qué mi endpoint recibe el mismo evento dos veces?
Puede que un intento llegara bien a tu servidor pero la respuesta se perdiera o tardara, y entonces SolumeCore reintenta con el mismo id. Guarda los ids que ya procesaste y salta los repetidos. Mandar de nuevo en el panel crea una entrega nueva con otro id.
¿Por qué mi webhook pasó a En pausa?
Veinte entregas seguidas fallaron definitivamente (se agotaron los reintentos o la respuesta no se reintenta), así que SolumeCore lo pausó para no seguir golpeando un endpoint que no responde. Arregla el endpoint, vuelve a activar el interruptor y usa Mandar prueba para comprobarlo.
¿Puedo usar http:// o una dirección local?
No. La URL debe ser https:// y resolver a una dirección pública. Para desarrollar en local, expón tu equipo con un túnel HTTPS.
¿Los nombres de los campos están disponibles en inglés?
Por ahora no. Los nombres de eventos y las claves del payload están en español y son estables; las etiquetas del panel sí están traducidas.
¿En qué se diferencian los webhooks de las integraciones?
Un webhook es un POST firmado sencillo con el evento en formato compacto, que se configura en la pestaña de la API. Una integración HTTP envía un sobre firmado más completo y filtrable, y además ofrece destinos como Discord, Slack, Telegram, Teams y correo; mira Integraciones y notificaciones.