Planes y titularidad
| Plan | Endpoints | Propietario |
|---|---|---|
| 7-Day Pass y Analyst | 0 | Puede eliminar un registro heredado revocado tras bajar de plan |
| Pro | 3 | Usuario individual |
| Team | Sin límite comercial del plan | Organización |
| Enterprise | Sin límite comercial del plan | Organizació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
- 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.
- 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.
- Selecciona Probar.
POST /v1/webhooks/{id}/testdevuelve202con un identificador de entrega pendiente; no realiza una conexión de salida dentro de esa solicitud. - Consulta el historial de entregas del endpoint hasta que ese identificador haya tenido éxito o alcance un estado terminal. Una respuesta
2xxde 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
- Usa una ventana corta de protección contra repeticiones; cinco minutos es un valor predeterminado razonable.
- Guarda
X-RealExploit-Delivery-IDen una tabla con restricción de unicidad antes de aplicar efectos secundarios. - Compara
occurred_atyevent_idcon el último estado aplicado; las entregas simultáneas de un endpoint no están ordenadas. - Devuelve
2xxsolo después de aceptar de forma persistente la solicitud. Si tu proceso la acepta y falla antes de responder, RealExploit puede entregarla de nuevo. - Considera no confiables las cabeceras de evento y esquema hasta que la firma se valide. Después interpreta el cuerpo firmado y exige que su tipo y versión coincidan con las cabeceras admitidas.
- Mantén el procesamiento de webhooks separado de la latencia de las solicitudes de los usuarios; usa una cola local cuando sea posible.
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.
2xx: éxito.408,409,425,429, y5xx: se puede reintentar.- Redirecciones, otros estados
4xx, TLS inválido y destinos inseguros: fallo permanente. - Tras el último intento fallido, la entrega queda en estado
dead. Rota el secreto, prueba o corrige el endpoint; no asumas que un reintento manual omitirá las comprobaciones de elegibilidad. - Pausar, eliminar, rotar el secreto, sustituir el destino o perder los derechos de acceso cancela las entregas ejecutables de la configuración anterior. Permanecen en estado
cancelleden el historial y no se vuelven a entregar al reanudar o recrear el endpoint.
¿Todo listo para integrar?
Consulta los precios actuales y después usa la consola autenticada cuando Webhooks esté visible en tu entorno.