Sign inCreate server
API

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.

10 min read#webhooks#hmac signature#webhook events#webhook retries#signature verification#server crash alert#event payload#node.js webhook

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

  1. Go to Account → API & webhooks and open the Webhooks tab.
  2. Click New webhook.
  3. 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.
  4. Optionally add a Description (for example "My Discord bot").
  5. Under Events, switch on All events (which includes events added in the future) or tick the ones you want.
  6. 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:

json
{
  "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.

js
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);
python
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 "", 200

Delivery, retries and automatic pause

  • Any 2xx answer counts as delivered. Answer within 10 seconds and do slow work in the background.
  • Redirects are not followed: a 301 or 302 counts as a failed attempt.
  • Network errors, timeouts, 408, 429 and 5xx answers are retried. Other 4xx answers, 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 creado if 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.crash and servidor.auto_reinicio separate a real crash from a restart someone requested.
  • cuenta.seguridad has a cambio field (api_key_creada and api_key_revocada for API keys, a short description for other security changes), which is a handy tripwire for API keys.
  • ia.accion reports 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.activo and notificacion) carry the notice itself. notificacion is the catch-all for notices that have no event of their own, and its tipo tells you what it was.
  • Alerts you configure in Notifications → Alerts arrive as alerta.disparada and alerta.resuelta; the invoice-due reminder arrives as factura.por_vencer.

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.

Was this article helpful?
SolumeCore Webhooks: Events, Signature and Retries