Guía de integración

Webhooks de clientes

Recibe un evento persistente y firmado de tipo verdict.changed cuando RealExploit cambie su veredicto para un CVE. La entrega se realiza al menos una vez y puede llegar fuera de orden: verifica los bytes originales, rechaza repeticiones y elimina duplicados de cada identificador de entrega antes de actuar.

Los webhooks de clientes aparecen en la consola únicamente cuando están habilitados en el entorno actual. Si la opción no aparece, el despliegue aún no está activo allí; no se realiza ninguna solicitud de gestión de endpoints.

Planes y titularidad

PlanEndpointsPropietario
7-Day Pass y Analyst0Puede eliminar un registro heredado revocado tras bajar de plan
Pro3Usuario individual
TeamSin límite comercial del planOrganización
EnterpriseSin límite comercial del planOrganización

«Sin límite comercial del plan» significa que no hay un límite comercial inferior para la cantidad de endpoints. Los controles de seguridad de uso razonable permiten hasta 1,000 endpoints activos o pausados por propietario, hasta tres destinos por nombre de host, 10 solicitudes de creación o rotación de secretos por usuario por minuto y 100 intentos de creación de endpoints por propietario en una ventana móvil de 24 horas. Los intentos de creación rechazados cuentan para el límite diario del propietario; las rotaciones de secretos no. Los propietarios y administradores de la organización gestionan los endpoints de Team y Enterprise.

Configurar y probar

  1. Abre Webhooks en la consola autenticada y añade un destino HTTPS público en el puerto 443. El nombre de host debe tener una dirección IPv4 pública; se rechazan las IP literales, redirecciones, fragmentos, credenciales incrustadas y cualquier respuesta DNS no pública.
  2. Copia el secreto de firma desde el diálogo de visualización única a tu gestor de secretos. Nunca se devuelve en las solicitudes de listado o detalle y no se puede recuperar después.
  3. Selecciona Probar. POST /v1/webhooks/{id}/test devuelve 202 con un identificador de entrega pendiente; no realiza una conexión de salida dentro de esa solicitud.
  4. Consulta el historial de entregas del endpoint hasta que ese identificador haya tenido éxito o alcance un estado terminal. Una respuesta 2xx de tu receptor indica éxito.
{
  "delivery_id": 981,
  "event_type": "webhook.test",
  "status": "pending"
}

Cabeceras y bytes firmados

Cada intento incluye estas cabeceras:

X-RealExploit-Timestamp: <Unix seconds>
X-RealExploit-Signature: v1=<lowercase HMAC-SHA256 hex>
X-RealExploit-Delivery-ID: <stable delivery id>
X-RealExploit-Event: verdict.changed | webhook.test
X-RealExploit-Event-Id: <stable event id>
X-RealExploit-Schema-Version: 1

El mensaje HMAC exacto es:

<timestamp>.<delivery_id>.<raw HTTP body bytes>

Verifica el cuerpo original de la solicitud antes de interpretar el JSON. No lo interpretes y vuelvas a serializar: incluso un JSON equivalente puede producir bytes distintos. Rechaza las marcas de tiempo fuera de tu ventana de protección contra repeticiones y compara la firma en tiempo constante.

Verificación en Python

import hashlib
import hmac
import time


def verify_webhook(secret: str, headers: dict[str, str], raw_body: bytes) -> bool:
    timestamp_text = headers.get("x-realexploit-timestamp", "")
    delivery_id = headers.get("x-realexploit-delivery-id", "")
    received = headers.get("x-realexploit-signature", "")
    if not isinstance(raw_body, bytes):
        return False
    if not timestamp_text.isascii() or not timestamp_text.isdigit():
        return False
    if not delivery_id.isascii() or not delivery_id.isdigit() or int(delivery_id) <= 0:
        return False
    timestamp = int(timestamp_text)
    if abs(int(time.time()) - timestamp) > 300:
        return False
    message = f"{timestamp_text}.{delivery_id}.".encode("utf-8") + raw_body
    digest = hmac.new(secret.encode("utf-8"), message, hashlib.sha256).hexdigest()
    signature_ok = hmac.compare_digest(f"v1={digest}", received)
    return signature_ok and headers.get("x-realexploit-schema-version") == "1"

Verificación en Node.js

Tu framework HTTP debe proporcionar a esta función el Bufferoriginal, no un objeto interpretado.

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

export function verifyWebhook(secret, headers, rawBody) {
  const timestamp = String(headers["x-realexploit-timestamp"] ?? "");
  const deliveryId = String(headers["x-realexploit-delivery-id"] ?? "");
  const receivedText = String(headers["x-realexploit-signature"] ?? "");
  if (!Buffer.isBuffer(rawBody)) return false;
  if (!/^\d+$/.test(timestamp)) return false;
  if (!/^[1-9]\d*$/.test(deliveryId)) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp)) > 300) return false;
  const message = Buffer.concat([
    Buffer.from(`${timestamp}.${deliveryId}.`, "utf8"),
    rawBody,
  ]);
  const expected = Buffer.from(
    `v1=${createHmac("sha256", secret).update(message).digest("hex")}`,
    "utf8",
  );
  const received = Buffer.from(receivedText, "utf8");
  const signatureOk = expected.length === received.length && timingSafeEqual(expected, received);
  return signatureOk && String(headers["x-realexploit-schema-version"] ?? "") === "1";
}

Protección contra repeticiones e idempotencia

Cuerpo del evento

{
  "data": {
    "cve_id": "CVE-2021-44228",
    "previous_verdict": "POC_AVAILABLE",
    "score": 95,
    "score_version": 1,
    "verdict": "ACTIVELY_EXPLOITED"
  },
  "event_id": 123,
  "occurred_at": "2026-08-10T02:00:00Z",
  "schema_version": 1,
  "type": "verdict.changed"
}

Los cuerpos son JSON UTF-8 compacto con claves ordenadas. El historial de entregas solo muestra la transición pública del CVE y un estado de entrega acotado; RealExploit no conserva cuerpos de respuesta ni textos arbitrarios de errores remotos.

La versión 1 no tiene un filtro de suscripción por CVE. Cada endpoint activo que cumpla los requisitos recibe todos los eventos capturados globalmente de tipo verdict.changed y sus propias pruebas manuales. Dimensiona tu receptor en consecuencia y filtra solo después de verificar la firma.

Reintentos y resultados terminales

RealExploit realiza hasta nueve intentos: la entrega inicial y después aproximadamente 1 minuto, 5 minutos, 25 minutos, 2 horas, 6 horas, 12 horas, 18 horas y 24 horas más tarde. Retry-After se respeta para 429/503 dentro de una ventana acotada de 1 minuto a 6 horas, con una variación aleatoria, pero nunca más allá del límite absoluto de 24 horas del ciclo de reintentos. Un reintento manual autorizado inicia un ciclo nuevo sin eliminar la auditoría de intentos previos.

¿Todo listo para integrar?

Consulta los precios actuales y después usa la consola autenticada cuando Webhooks esté visible en tu entorno.