SolumeCore Webhooks: Events, Signature and Retries
Get SolumeCore account events on your own server: event list, JSON payload format, HMAC-SHA256 signature, retries, and Node.js and Python examples.
A SolumeCore webhook sends your own server an HTTPS POST with a JSON body whenever something happens in your account or on one of your services: a server crashes, a backup fails, an invoice is paid, a sign-in comes from a new device. Every request is signed with HMAC-SHA256, so your code can check it really came from SolumeCore, and failed deliveries are retried automatically.
You manage webhooks in the client panel under Account → API & webhooks → Webhooks. This document lists the events you can subscribe to, shows the exact payload and headers, explains retries and gives working signature-verification code for Node.js and Python. If you would rather get a message in Discord, Slack or Telegram than write code, see Integrations and notifications.
Create a webhook
- Go to Account → API & webhooks and open the Webhooks tab.
- Click New webhook.
- Enter Your address (URL). It must be
https://, with no user name or password in it, and it must be reachable from the public internet: addresses that point at private or internal networks are rejected. - Optionally add a Description (for example "My Discord bot").
- Under Events, switch on All events (which includes events added in the future) or tick the ones you want.
- Click Create webhook and confirm it is you when the panel asks. A window shows the Signing secret once, starting with
whsec_. Copy it, tick I saved it and click Done.
Click Send test on the webhook to send a ping event through the same path as real ones. See deliveries shows every attempt with What we sent and What your server answered, and Send again queues a copy of any past delivery.
Good to know:
- A confirmation of identity is required to create a webhook or to rotate its secret, and neither can be done from a support session.
- By default an account can have up to 10 webhooks. The panel tells you the limit that applies to you.
- The toggle on each webhook pauses or resumes it. Edit changes the URL, description and events, and Rotate secret issues a new secret; the old one stops working immediately, so update your server first or accept a short gap.
- After the first view, the panel shows only a short hint of each secret. A lost secret cannot be recovered; rotate it.
What a delivery looks like
SolumeCore sends POST with Content-Type: application/json and these headers:
| Header | Value |
|---|---|
Solume-Signature |
t=<unix seconds>,v1=<hex HMAC-SHA256> |
Solume-Event |
The event name, for example servidor.crash |
Solume-Delivery |
The delivery id, identical to id in the body |
User-Agent |
SolumeCore-Webhooks/1.0 |
The body has four top-level fields. Field names and event names are in Spanish and will stay that way:
{
"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
}
}| Field | Meaning |
|---|---|
id |
Unique per delivery. Automatic retries reuse the same id and the same body, so use it to ignore duplicates |
evento |
Event name |
creado |
When the delivery was created (ISO 8601, UTC) |
datos.version |
Payload schema version, currently 1. New fields can appear without a version change |
datos.familia, categoria, severidad |
Where the event belongs and how serious it is: info, aviso or critico |
datos.servicio |
The service concerned (codigo is the public code such as vps-3315, url links to it in the panel), or null for account events |
datos.actor |
Who did it: duenio (you), subusuario, staff or sistema. For staff, no id or name is included |
Other datos keys |
The data of that event: detalle, backup, factura, monto, jugador, ruta and so on |
This envelope applies to the events of the catalog (every row of the event table below except the last one). The six notification events in the last row use a simpler shape in datos: tipo, categoria (the notice category, such as alertas or servidores), evento, titulo, mensaje and url, plus any extra fields, without version, familia, severidad, servicio or actor.
We never put passwords, tokens or secrets in a payload. The test event is ping and carries a mensaje and the webhookId.
Verify the signature
v1 is the hex HMAC-SHA256 of the text <t>.<raw body>, keyed with your signing secret. Use the whole secret string, including the whsec_ prefix. Always compute it over the raw bytes of the body, before any JSON parsing, compare in constant time, and reject requests whose t is more than five minutes from your clock. Retries are signed again with a fresh t, so the tolerance never rejects them.
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'); // store it to ignore duplicates
const event = JSON.parse(req.body.toString('utf8'));
console.log(delivery, event.evento, event.datos.servicio?.codigo);
res.sendStatus(200); // answer fast, work later
});
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 "", 200Delivery, retries and automatic pause
- Any
2xxanswer counts as delivered. Answer within 10 seconds and do slow work in the background. - Redirects are not followed: a
301or302counts as a failed attempt. - Network errors, timeouts,
408,429and5xxanswers are retried. Other4xxanswers, any redirect and destinations that are not allowed are treated as a configuration problem: the delivery fails on its first attempt and is not retried. - A delivery is attempted up to 8 times. The wait before each retry grows from about 30 seconds to 2 minutes, 8 minutes, 30 minutes, 1 hour, 2 hours and finally 4 hours, with a little randomness. Test deliveries are never retried.
- The panel shows the state of each delivery: Delivered, Retrying, Failed or Pending.
- If 20 deliveries in a row fail for good (retries used up, or an answer that is not retried), SolumeCore switches the webhook to Paused. The row shows the number of failed deliveries. Fix your endpoint and turn the toggle back on; the counter starts again from zero.
- Order is not guaranteed: use
creadoif you need to sort events.
Events you can subscribe to
The picker lists 82 event names, grouped by category. Choose All events to receive every one of them, including new ones we add later. Which events actually fire depends on the features your services use.
| Family | Events |
|---|---|
| Game servers and apps: power | servidor.encendido, servidor.apagado, servidor.reiniciado, servidor.crash, servidor.auto_reinicio, servidor.hibernado, servidor.despertado |
| Game servers and apps: activity | servidor.comando, archivo.creado, archivo.editado, archivo.borrado, archivo.subido, archivo.renombrado, sftp.login, sftp.cambio, jugador.entro, jugador.salio |
| Game servers and apps: backups and content | backup.creado, backup.restaurado, backup.fallido, base_datos.backup, zona.restaurada, contenido.instalado, contenido.actualizado, staging.listo, staging.aplicado, staging.fallido |
| Game servers and apps: access, deploys, health | 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 |
| Web hosting | hosting.casilla_creada, hosting.ssl_emitido, hosting.ssl_por_vencer, hosting.dominio_agregado |
| Account: billing | 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 |
| Account: security and support | cuenta.login_nuevo, cuenta.seguridad, ia.accion, ticket.respondido, importacion.completada, importacion.fallida |
| Platform status | status.incidente, status.mantenimiento, servidor.caido, servidor.recuperado |
| Notification events | alerta.disparada, alerta.resuelta, factura.por_vencer, backup.listo, servicio.activo, notificacion |
A few of them are worth knowing about:
servidor.crashandservidor.auto_reinicioseparate a real crash from a restart someone requested.cuenta.seguridadhas acambiofield (api_key_creadaandapi_key_revocadafor API keys, a short description for other security changes), which is a handy tripwire for API keys.ia.accionreports each change made by an AI assistant connected to your account; see AI and MCP API.- The notification events (
alerta.disparada,alerta.resuelta,factura.por_vencer,backup.listo,servicio.activoandnotificacion) carry the notice itself.notificacionis the catch-all for notices that have no event of their own, and itstipotells you what it was. - Alerts you configure in Notifications → Alerts arrive as
alerta.disparadaandalerta.resuelta; the invoice-due reminder arrives asfactura.por_vencer.
Related documents
- Integrations and notifications
- Account API keys
- Client API reference
- Which SolumeCore API to use
- Service status and incidents
FAQ
How do I verify a SolumeCore webhook signature?
Read the Solume-Signature header (t=...,v1=...), compute HMAC-SHA256 of t, a dot and the raw request body using your whsec_ secret, compare it with v1 in constant time and reject anything where t is more than five minutes old.
Why does my endpoint receive the same event twice?
An attempt may have succeeded on your side but timed out or failed on the way back, so SolumeCore tries again with the same id. Store the ids you have processed and skip repeats. Send again in the panel creates a new delivery with a new id.
Why did my webhook turn to Paused?
Twenty deliveries in a row failed for good (retries used up, or an answer that is not retried), so SolumeCore paused it to stop hitting an endpoint that was not answering. Fix the endpoint, turn the toggle back on and use Send test to check it.
Can I use http:// or a local address?
No. The URL must be https:// and must resolve to a public address. For local development, expose your machine through an HTTPS tunnel.
Are the field names available in English?
Not at the moment. Event names and payload keys are in Spanish and stable; the panel labels are translated.
Is there a difference between webhooks and integrations?
A webhook is a plain signed POST of the event in a compact format, set up in the API tab. An HTTP integration sends a richer, filterable, signed envelope and adds Discord, Slack, Telegram, Teams and email targets; see Integrations and notifications.