Saltar al contenido

← Documentación

Webhooks

Los eventos del servicio (una copia que falló, un servidor sin señal, una restauración terminada, el inventario del bucket) enviados por HTTPS a tu sistema, firmados y con reintentos.

Registrar un webhook

  1. En el panel, abre Configuración → Avisos. Solo los propietarios y administradores de la organización registran webhooks.
  2. Ponle un nombre, elige el formato (JSON firmado para tu sistema; Slack, Teams o Telegram para un mensaje listo) y pega la URL de destino. Tiene que ser https://, en una dirección pública de internet.
  3. Marca los eventos. Confirma con el código de la verificación en dos pasos: un webhook envía nombres de servidores y de planes fuera de Arkame.
  4. En el formato JSON, copia el secreto de firma (whsec_…): aparece una sola vez. Después, usa Enviar prueba para comprobar el destino.

La solicitud

Cada evento es un POST con Content-Type: application/json. Lo que varía por tipo está en data.details:

{
  "id": "0b6f0c1e-5f1a-4f6e-9d1e-2a7c3e9b8d41",
  "type": "backup.failed",
  "api_version": "2026-10-01",
  "created_at": "2026-10-13T12:00:00.000Z",
  "organization": { "id": "…", "name": "Acme" },
  "data": {
    "severity": "warning",
    "subject": { "type": "session", "id": "…" },
    "details": {
      "error_code": "storage_unreachable",
      "agent_id": "…", "agent_name": "fs-01",
      "plan_id": "…", "plan_name": "Nightly",
      "storage_id": "…", "storage_name": "Main bucket",
      "panel_url": "https://save.arkame.app/historico/backups/…"
    }
  }
}
  • Arkame-Signature: la marca de tiempo y la firma del cuerpo (ver abajo).
  • Arkame-Event-Id: igual al id del cuerpo, y el mismo en cada reintento y reenvío. Úsalo para no procesar el mismo evento dos veces.
  • Arkame-Event-Type y User-Agent: Arkame-Webhooks/1.

Dentro de api_version: 2026-10-01, solo hay cambios aditivos: puede aparecer un campo nuevo en details; ningún campo existente cambia de nombre, de tipo ni de sentido.

Tipos de evento

Eliges cuáles recibe cada webhook, y puedes cambiarlo después. Un tipo nuevo no llega solo a un webhook que ya existía: márcalo en la pantalla.

TipoCuándo se envía
backup.failedLa copia terminó con error. No se envía cuando el servicio del agente se detuvo o se reinició en medio de la copia.
backup.partialLa copia terminó incompleta: parte de los archivos no se guardó.
backup.completeddesmarcado al registrarLa copia terminó. Uno por copia, todo el día.
anomaly.detectedMuchos más archivos cambiados o borrados que lo normal del plan. severity: warning o critical.
anomaly.resolvedAlguien confirmó en el panel que el cambio era esperado.
purge.blockedLa limpieza de versiones antiguas de un almacenamiento quedó suspendida, por un cambio brusco en una copia o por una versión que no se restaura.
verification.completedLa prueba de restauración por muestra restauró y comprobó los archivos de un servidor, sin errores.
verification.failedLa prueba de restauración encontró un archivo que no se restaura (critical).
inventory.completeddesmarcado al registrarEl inventario del bucket comprobó todo, y no falta ninguna versión del catálogo.
inventory.mismatchEl inventario encontró una diferencia entre el catálogo y el bucket: critical cuando falta una versión; warning en lo que no impide restaurar.
backup.lateLa ejecución programada de un plan no empezó dos horas después de la hora prevista. Uno por atraso.
agent.offlineEl servidor lleva más de 60 minutos sin dar señal. Uno por episodio.
agent.onlineEl servidor que generó agent.offline volvió a responder.
restore.completedLa restauración terminó con todos los archivos restaurados. En la cuenta de Zimbra, se envía cuando la cuenta ya fue importada en Zimbra, no cuando los archivos llegan al servidor.
restore.partialLa restauración terminó con parte de los archivos sin restaurar.
restore.failedLa restauración terminó sin restaurar ningún archivo, o la importación de la cuenta de Zimbra falló.
report.readySe generó el informe mensual de la organización.
storage.config_changedAlguien cambió un almacenamiento o su política de retención, o entró en vigor un cambio programado. details.changed dice qué campos, nunca los valores; warning cuando cambia adónde va la copia o acorta la protección (retención menor, borrado definitivo, desactivar).
agent.update_failedLa actualización automática de un servidor falló, y sigue en la versión anterior. Una por intento.
storage.unreachableLa última prueba del bucket de un almacenamiento falló y ningún servidor en uso lo alcanza: el bucket fue borrado, el registro cambió, o la clave y la red fallan en todos. Uno por episodio, critical. details.cause es bucket, chave (clave), rede (red) u outra (otra); el error en bruto del proveedor queda solo en el panel.
storage.reachableEl bucket que generó storage.unreachable volvió a responder a un servidor.

También existe el ping, que solo se envía con el botón Enviar prueba.

Comprobar la firma

Arkame-Signature: t=SEGUNDOS,v1=HEX, donde v1 es el HMAC-SHA256 de t + "." + cuerpo, con el secreto entero (whsec_…) como clave. Calcúlalo sobre el cuerpo crudo, tal como llegó, y rechaza la solicitud con más de 5 minutos: sin eso, una solicitud capturada valdría para siempre.

Node.js

import { createHmac, timingSafeEqual } from 'node:crypto';

// rawBody: the text as received, before any JSON.parse.
export function isValidSignature(secret, header, rawBody) {
  const p = Object.fromEntries(header.split(',').map((x) => x.trim().split('=')));
  if (!/^\d+$/.test(p.t ?? '') || !p.v1) return false;
  if (Math.abs(Date.now() / 1000 - Number(p.t)) > 300) return false;
  const expected = createHmac('sha256', secret).update(`${p.t}.${rawBody}`).digest();
  const received = Buffer.from(p.v1, 'hex');
  return received.length === expected.length && timingSafeEqual(received, expected);
}

Python

import hashlib, hmac, time

# raw_body: the bytes as received, before any json.loads.
def is_valid_signature(secret: str, header: str, raw_body: bytes) -> bool:
    try:
        p = dict(x.strip().split("=", 1) for x in header.split(","))
        t = int(p["t"])
    except (KeyError, ValueError):
        return False
    if "v1" not in p or abs(time.time() - t) > 300:
        return False
    expected = hmac.new(secret.encode(), p["t"].encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, p["v1"])

PHP

// $rawBody: file_get_contents('php://input'), before json_decode.
function is_valid_signature(string $secret, string $header, string $rawBody): bool {
    parse_str(str_replace(',', '&', $header), $p);
    if (empty($p['t']) || empty($p['v1']) || abs(time() - (int) $p['t']) > 300) return false;
    return hash_equals(hash_hmac('sha256', $p['t'] . '.' . $rawBody, $secret), $p['v1']);
}

Guarda el secreto como una contraseña. Si se filtra, elimina el webhook y registra otro: el secreto nuevo aparece al registrarlo.

Entrega y reintentos

  • Éxito es cualquier respuesta 2xx en hasta 5 segundos: responde primero y procesa después. Una redirección (3xx) cuenta como error.
  • Si falla, volvemos a intentar después de 1 min, 5 min, 30 min, 2 h, 6 h y 24 h. El orden de llegada puede variar entre eventos: usa created_at y el Arkame-Event-Id.
  • 20 errores seguidos, o 3 días fallando, desactivan el webhook, y los propietarios y administradores reciben un correo. Se reactiva en la misma pantalla.
  • La pantalla muestra las últimas entregas y lo que respondió el destino. Una entrega terminada se puede reenviar: sale de nuevo con el mismo id de evento.
  • Solo https, en los puertos 443 u 8443, en una dirección pública. Se rechazan las direcciones de red interna, también un nombre público que apunte a una de ellas. Un n8n dentro de tu red necesita una dirección pública delante.

Slack, Teams y Telegram

El mismo evento, ya como mensaje, en el idioma elegido al registrarlo:

  • Slack: la URL de un Incoming Webhook del canal.
  • Teams: la URL de un flujo “Cuando se recibe una solicitud de webhook de Teams” (Workflows).
  • Telegram: la URL https://api.telegram.org/bot123456:ABC…/sendMessage de tu bot y el chat (id numérico o @canal). La URL se guarda cifrada.

n8n, Make y Zapier: usa el formato JSON con el disparador de webhook de cada uno, y comprueba la firma en un paso de código.

¿Listo para registrar uno?

Los webhooks están en Configuración → Avisos, junto a los correos que reciben los avisos.

Abrir los avisos en el panel →