Configurar webhooks para eventos de agentes
Recibe un POST firmado en tu propio endpoint https cuando el trabajo de un agente finaliza o falla, un agente espera aprobación o se publica un sitio de documentación. Eventos, payloads, verificación de firma, reintentos y desactivación automática.
Un webhook envía un POST firmado a tu propio endpoint en el momento en que ocurre algo, como cuando el trabajo de un agente finaliza o un agente espera aprobación, para que tus sistemas no tengan que realizar sondeos. Este artículo cubre cómo añadir un endpoint, los eventos, el payload, la verificación de la firma y qué sucede cuando las entregas fallan.
Antes de empezar
- Los webhooks están disponibles en los planes Pro, Business y Custom ("Los webhooks están disponibles en el plan Pro en adelante").
- Solo el propietario del espacio de trabajo y los administradores pueden gestionar los webhooks. Otros miembros verán "Solo los propietarios y administradores pueden gestionar los webhooks".
- Necesitas una dirección https pública que responda con un estado
2xxen menos de 10 segundos. - Un espacio de trabajo puede tener hasta 10 webhooks.
Añadir un webhook
Abre la pestaña Webhooks
Abre el menú del espacio de trabajo, elige AI agents & API, y luego abre Webhooks. Haz clic en Nuevo webhook.

Cada webhook muestra sus eventos, cuándo realizó el último envío y el final de su secreto. Introduce el endpoint
- URL del endpoint: "Una dirección HTTPS pública". Por ejemplo
https://example.com/clevera/webhooks. - Descripción: opcional, hasta 200 caracteres, por ejemplo "Relé de Slack".
- URL del endpoint: "Una dirección HTTPS pública". Por ejemplo
Elige los eventos
Marca los eventos que deseas recibir, o Todos los eventos, incluidos los nuevos. Los eventos marcados como Pronto se aceptan pero "Todavía no hay nada que envíe este evento".

Los eventos marcados como Pronto se aceptan pero aún no se envían. Créalo y guarda el secreto
Haz clic en Crear webhook. El Secreto de firma (que comienza con
whsec_) se muestra una sola vez: "Cópialo ahora: no volverás a verlo. Úsalo para comprobar la cabecera Clevera-Signature". Guárdalo como una contraseña y luego haz clic en Lo he guardado.Enviar un evento de prueba
Abre el menú ⋯ del webhook y elige Enviar evento de prueba. Clevera envía un evento
pingfirmado una vez (sin reintentos). "Evento de prueba enviado" significa que salió de Clevera; comprueba Entregas para ver la respuesta de tu endpoint.
Eventos
| Evento | Enviado cuando |
|---|---|
job.started |
Un trabajo que inició un agente comienza a ejecutarse. |
job.needs_input |
Un trabajo espera a una persona, por ejemplo una aprobación. Incluye el enlace y cuándo expira la solicitud. |
job.succeeded |
El trabajo finalizó. |
job.failed |
El trabajo falló. Incluye el código de error. |
job.canceled |
El trabajo fue cancelado, o su aprobación fue denegada o expiró. |
approval.requested |
Un agente solicitó aprobación en Solicitudes del agente. |
approval.decided |
Alguien aprobó o denegó la solicitud, o esta expiró. |
site.published |
Se publicó un sitio de documentación. |
site.build_failed |
La publicación de un sitio de documentación falló. |
ping |
Hiciste clic en Enviar evento de prueba. No necesita suscripción. |
Estos eventos aparecen como Pronto y aún no se envían: job.progress, video.ready, article.ready, research.completed, insights.weekly.
Los eventos de trabajo cubren los trabajos que inician los agentes (a través de un token de API o una aplicación conectada), no los videos que las personas inician en la aplicación.
Qué envía Clevera
Cada entrega es un POST con un cuerpo JSON y estas cabeceras:
| Cabecera | Valor |
|---|---|
Content-Type |
application/json |
User-Agent |
Clevera-Webhooks/1.0 |
Clevera-Event |
El nombre del evento, como job.succeeded |
Clevera-Delivery |
El ID único de la entrega. Una reentrega repite el mismo ID, así que úsalo para ignorar duplicados. |
Clevera-Signature |
t=<unix time>,v1=<signature> |
Los payloads contienen IDs, enlaces, estados y números, nunca el contenido en sí. Para leer un video o artículo finalizado, llama a la API con un token.
{
"id": "6f1c2d3e-4b5a-4c6d-8e9f-0a1b2c3d4e5f",
"event": "job.succeeded",
"created_at": "2026-10-20T14:03:11Z",
"data": {
"job": {
"id": "job_8k2m4n6p8r0t2v4x",
"kind": "video.create",
"status": "succeeded",
"error_code": null,
"subject": { "type": "video", "id": "vid_example" },
"credits_spent": 64
},
"needs_input": null,
"workspace": { "id": "tm_example" }
}
}
{
"id": "0b9a8c7d-6e5f-4a3b-9c2d-1e0f9a8b7c6d",
"event": "approval.requested",
"created_at": "2026-10-20T14:01:02Z",
"data": {
"approval": {
"id": "apr_example",
"kind": "spend",
"status": "pending",
"credits_high": 80,
"expires_at": "2026-10-21T14:01:02Z",
"decided_at": null,
"url": "https://app.clevera.ai/agents?tab=requests&team=tm_example"
},
"job": { "id": "job_8k2m4n6p8r0t2v4x", "kind": "video.create" },
"workspace": { "id": "tm_example" }
}
}
Verificar la firma
Comprueba cada entrega antes de confiar en ella. La firma es un HMAC-SHA256, cifrado con tu secreto de firma, del timestamp, un punto y el cuerpo de la solicitud sin procesar: <t>.<body>.
- Lee
tyv1de la cabeceraClevera-Signature. - Calcula
HMAC-SHA256(secret, t + "." + rawBody)como hexadecimal. - Compáralo con
v1en tiempo constante. - Rechaza las entregas cuyo
ttenga más de 5 minutos de antigüedad, para que una solicitud capturada no pueda ser reproducida.
import crypto from 'node:crypto';
export function verifyClevera(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries(header.split(',').map((p) => p.trim().split('=')));
const t = Number(parts.t);
if (!parts.v1 || !Number.isInteger(t)) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
import hashlib, hmac, time
def verify_clevera(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
parts = dict(p.strip().split("=", 1) for p in header.split(","))
t, v1 = parts.get("t", ""), parts.get("v1", "")
if not t.isdigit() or not v1 or abs(time.time() - int(t)) > tolerance:
return False
expected = hmac.new(secret.encode(), f"{t}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)
function verifyClevera(string $rawBody, string $header, string $secret, int $tolerance = 300): bool
{
parse_str(str_replace(',', '&', $header), $parts);
$t = $parts['t'] ?? '';
$v1 = $parts['v1'] ?? '';
if (! ctype_digit($t) || $v1 === '' || abs(time() - (int) $t) > $tolerance) {
return false;
}
return hash_equals(hash_hmac('sha256', $t.'.'.$rawBody, $secret), $v1);
}
Usa el cuerpo exactamente como se recibió. Analizar y volver a codificar el JSON primero cambia los bytes y la firma no coincidirá.
Reintentos y fallos
Una entrega cuenta como entregada cuando tu endpoint responde con un estado 2xx. Cualquier otra cosa hace que el intento falle:
- cualquier otro estado, incluidas las redirecciones (
3xxno se siguen); - ninguna respuesta en 10 segundos (5 segundos para conectar);
- una dirección que ya no se resuelve en la internet pública.
Clevera reintenta una entrega fallida 5 veces, después de 1 minuto, 5 minutos, 30 minutos, 2 horas y 6 horas. El cuerpo de respuesta de tu endpoint nunca se lee.
Cuando 15 entregas seguidas fallan en cada intento, el webhook se desactiva: "Desactivado tras 15 entregas fallidas seguidas. Corrige el endpoint y luego actívalo". El propietario y los administradores reciben una notificación "Webhook a host desactivado": "Sus entregas siguieron fallando. Corrige el endpoint y luego vuelve a activarlo". Los eventos que ocurren mientras está desactivado no se envían. Los eventos de prueba no cuentan para esto.
Gestionar un webhook
Haz clic en Entregas en un webhook para ver su registro, o abre su menú ⋯ para el resto:
| Opción | Qué hace |
|---|---|
| Editar | Cambia la URL, los eventos o la descripción. |
| Entregas | Las últimas 100 entregas con su estado (Entregado, Fallido, Enviando), Intentos y el código HTTP. Reenviar envía una de nuevo. Las entregas se guardan durante 30 días. |
| Enviar evento de prueba | Envía un ping firmado. |
| Regenerar secreto | "Las entregas se firman con el nuevo secreto de inmediato. Actualiza tu endpoint para comprobarlo". No hay solapamiento, así que actualiza tu endpoint inmediatamente después. |
| Desactivar / Activar | Pausa y reanuda las entregas. |
| Eliminar | "Dejamos de enviarle eventos y eliminamos su registro de entregas. Esto no se puede deshacer". |
Si el espacio de trabajo deja el plan Pro
Los webhooks permanecen listados y aún se pueden editar y eliminar, pero no se envía nada. Las entregas que esperan un reintento terminan como fallidas con plan_locked, y Enviar evento de prueba y Reenviar dicen "Los webhooks no están en tu plan". Volver a actualizar a Pro reanuda los envíos; no se elimina nada.
Solución de problemas
| Mensaje | Causa | Solución |
|---|---|---|
| "Los webhooks no están en tu plan." | El espacio de trabajo no está en Pro, Business o Custom. | Actualiza a Pro. |
| "Usa una dirección https://." | La URL comienza con http://. |
Sirve el endpoint a través de HTTPS. |
| "Usa un nombre de host, no una dirección IP." | La URL usa una dirección IP. | Usa un nombre de dominio. |
| "Esta dirección no es accesible en la internet pública." | El host se resuelve a una dirección privada o local. | Usa un endpoint público o un relé. |
| "Elimina el nombre de usuario y la contraseña de la dirección." | La URL tiene user:password@. |
Elimínalos; comprueba la firma en su lugar. |
| "Introduce una dirección web válida." | La URL no es válida. | Comprueba si hay errores tipográficos. |
| "Elige al menos un evento." | No hay ningún evento marcado. | Marca al menos un evento. |
| "Este espacio de trabajo ya tiene 10 webhooks." | El límite del espacio de trabajo. | Elimina uno que no uses. |
| "Activa primero el webhook." | Intentaste reenviar en un webhook que está desactivado. | Actívalo y luego reenvía. |
| "Esta entrega aún se está enviando." | La entrega sigue reintentándose. | Espera a que termine. |
| Las entregas fallan con un código HTTP | Tu endpoint respondió con un error. | Comprueba los registros de tu endpoint y asegúrate de que devuelva 2xx rápidamente; realiza el trabajo lento después de responder. |
| Las firmas nunca coinciden | El cuerpo fue cambiado antes de comprobarlo, o el secreto es antiguo. | Verifica contra el cuerpo sin procesar; regenera el secreto si no estás seguro. |