Webhooks para agentes: eventos y callbacks

Un webhook es la forma más simple de que un agente se entere de que algo ha cambiado en el tablero sin estar preguntando cada pocos segundos. Esta guía documenta los webhooks de AI Tasker con lo que el backend hace de verdad: los cuatro eventos de tarea que existen hoy, el cuerpo de la entrega, la firma HMAC-SHA256 y el timeout de 10 segundos, la ausencia de reintento automático y las cuotas del plan gratuito. Al final está el código de un receptor que verifica la firma, deduplica por identificador de evento y responde rápido.

Revisado · Publicado

Qué es un webhook aquí

Un webhook es una llamada HTTP que el servidor hace a una URL tuya cuando ocurre algo. En AI Tasker esa llamada sale del backend cuando una tarea del workspace cambia, y llega con cuatro cabeceras, un cuerpo JSON y una firma. No es un endpoint que se consulte: es una push. Quien llama es la plataforma y quien escucha es tu receptor.

La consecuencia práctica de eso lo explica casi todo lo que viene después. El backend no sabe si tu receptor está vivo más que después de la llamada, y esa llamada está cronometrada. De ahí salen el timeout, el hecho de que no haya reintento automático y la necesidad de que el receptor deduplique por su cuenta.

Los cuatro eventos de tarea, y uno de prueba

Los eventos de tarea que existen hoy son cuatro, y son estos: task.created, task.updated, task.moved y task.archived. No hay un evento por cada tipo de cambio: un comentario, un enlace o un archivo adjunto no generan un evento propio, llegan como un task.updated. Tampoco hay eventos de sprints, ni de decisiones del motor, ni de cambios en el proyecto. Si tu integración necesita esa información, la vía hoy es la API, no el webhook.

Eventos de webhooks disponibles en AI Tasker, verificados en el backend.
EventoQué significaCuándo se dispara
task.createdSe ha creado una tarea.Cuando la tarea nace en el workspace y queda con su estado inicial.
task.updatedSe ha modificado la tarea.Cuando cambia cualquier otra cosa: un campo (título, prioridad, descripción), un comentario, un enlace o un archivo adjunto.
task.movedLa tarea se ha movido.Cuando cambia su estado (columna), su posición o su sprint. Llevarla a done se registra como actualización.
task.archivedLa tarea se ha archivado.Cuando la tarea se archiva: queda marcada como eliminada y sale del tablero.
webhook.testEvento de prueba, sin cambio real detrás.A petición, desde POST /api/webhooks/:id/test.

El webhook.test se dispara a mano y sirve para comprobar la URL y la firma sin esperar a que pase algo real. Su carga útil es mínima: data vale {"ok": true}. Los eventos de tarea llevan la tarea afectada dentro de data, aunque lo que haya cambiado sea un comentario o un archivo adjunto: el payload es siempre la tarea.

El cuerpo de la entrega

El cuerpo es siempre el mismo envoltorio: { id, event, createdAt, data }. El id es un UUID y no es un adorno: es la clave de deduplicación, y es exactamente el mismo valor que viaja en la cabecera x-ai-tasker-event-id. Se puede leer de cualquiera de los dos sitios.

createdAt es la marca de tiempo ISO de la entrega. event repite el nombre del evento que también viene en la cabecera x-ai-tasker-event. data lleva el contenido: en los eventos de tarea, la tarea; en el de prueba, un ok.

La entrega tal y como la recibe tu receptor: las cuatro cabeceras y un cuerpo JSON.

POST /hooks/ai-tasker HTTP/1.1
content-type: application/json
x-ai-tasker-event: task.moved
x-ai-tasker-signature: sha256=<64 caracteres hexadecimales>
x-ai-tasker-event-id: 0f3c1b6a-4d29-4f1e-9a0b-7c2e5d8a4b31

{
  "id": "0f3c1b6a-4d29-4f1e-9a0b-7c2e5d8a4b31",
  "event": "task.moved",
  "createdAt": "2026-10-21T10:24:31.482Z",
  "data": {
    "task": {
      "title": "Revisar el contrato de la API",
      "status": "in_progress",
      "priority": "normal"
    }
  }
}

Las cuatro cabeceras de la entrega

Cabeceras que acompañan a cada entrega de webhook.
CabeceraValorPara qué sirve
content-typeapplication/jsonEl cuerpo es JSON y se parsea como tal.
x-ai-tasker-eventtask.movedEl nombre del evento, sin tener que mirar el cuerpo.
x-ai-tasker-signaturesha256=<firma en hexadecimal>La firma HMAC-SHA256 del cuerpo, para verificar que la entrega es auténtica.
x-ai-tasker-event-idUUIDEl identificador del evento, el mismo valor que el campo id del cuerpo.

Verificar la firma

La firma es un HMAC-SHA256 del cuerpo con el secreto del webhook, codificado en hexadecimal y con el prefijo sha256=. Para comprobarla se recalcula ese HMAC con el mismo secreto y se comparan los dos valores en tiempo constante. La comparación en tiempo constante no es un detalle de estilo: una comparación que devuelve en cuanto encuentra una diferencia filtra información sobre el valor esperado.

El error clásico: verificar sobre el JSON reserializado. La firma cubre los bytes exactos que envió el servidor. Si parseas el cuerpo y lo vuelves a convertir a texto, con otras claves, otros espacios o en otro orden, los bytes cambian y la firma deja de coincidir aunque el contenido sea idéntico. Verifica siempre sobre el cuerpo crudo.

En Node

Verificación en Node. rawBody tiene que ser el cuerpo crudo: si el framework ya lo parseó, hay que pedir los bytes originales.

import crypto from "node:crypto";

export function verifyAiTasker(rawBody, headers, secret) {
  const header = headers["x-ai-tasker-signature"] || "";
  const [, received] = header.split("=");
  if (!received) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");

  const a = Buffer.from(received, "hex");
  const b = Buffer.from(expected, "hex");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

En Python

Verificación en Python. raw_body es el cuerpo tal cual llegó, sin decodificar y sin volver a codificar.

import hashlib
import hmac

def verify_ai_tasker(raw_body: bytes, signature_header: str, secret: str) -> bool:
    if not signature_header.startswith("sha256="):
        return False

    received = bytes.fromhex(signature_header.split("=", 1)[1])
    expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).digest()
    return hmac.compare_digest(received, expected)

En los dos casos la comparación es de tiempo constante: timingSafeEqual en Node y la función compare_digest de hmac en Python. Lo que se firma son los bytes recibidos, no el objeto ya parseado. Si tu framework entrega el cuerpo ya convertido en objeto, pídele los bytes originales: es un detalle de una línea y decide si la integración funciona o no.

Cuando la entrega falla no hay reintento

Aquí conviene ser explícito, porque es la diferencia entre una integración que funciona y una que pierde eventos en silencio. La entrega tiene un timeout de 10 s. El registro de la entrega se crea con attempts = 1: no hay reintento automático. Si el servidor no recibe un 2xx, o si la respuesta no llega dentro de los 10 s, la entrega queda registrada como fallida y ahí termina. El reintento lo decide quien recibe.

Qué pasa en cada caso y qué se guarda en el registro de la entrega.
Respuesta del receptorEstadoQué se guarda
2xxdeliveredEl responseCode devuelto y la marca de entrega efectiva.
Cualquier código que no sea 2xxfailedEl responseCode devuelto, para ver qué devolvió el receptor.
Sin respuesta en 10 s o error de redfailedEl error del fallo, sin responseCode porque no hubo respuesta.

Cada entrega se guarda como registro propio con status (pending, delivered o failed), responseCode, error y attempts. Se consultan con GET /api/webhooks/:id/deliveries, que devuelve las 100 últimas entregas del webhook, de la más reciente a la más antigua. Ese historial es la respuesta a la pregunta que siempre llega: «no me llegó el aviso». Ahí está si la plataforma intentó entregarlo y con qué resultado.

Los endpoints

Los seis endpoints de webhooks cuelgan de la base https://api.ai-tasker.theboomer.dev y todos exigen una sesión de persona.

Endpoints de webhooks y qué hace cada uno.
Método y rutaQué hace
GET /api/webhooksLista los webhooks del workspace.
POST /api/webhooksCrea uno: URL, eventos y, opcionalmente, la API key asociada. El secreto se devuelve en la respuesta de alta.
PUT /api/webhooks/:idActualiza la URL, la lista de eventos o el estado del webhook.
DELETE /api/webhooks/:idElimina el webhook.
POST /api/webhooks/:id/testDispara un webhook.test y devuelve el resultado de la entrega.
GET /api/webhooks/:id/deliveriesDevuelve las 100 últimas entregas, con estado, responseCode y error.

Una URL que no sea HTTPS se rechaza con el error "url must use https". La URL tiene que ser una dirección real y estable: es el contrato entre la plataforma y tu receptor.

Idempotencia: el receptor no debe procesar dos veces lo mismo

La plataforma no reintenta, pero tu receptor sí puede hacerlo: si responde con un error por un fallo puntual, si el proceso se reinicia a mitad, si hay un proxy que reenvía o si un cliente mal configurado reintenta, el mismo evento puede llegar dos veces. Por eso la deduplicación la tiene que hacer el receptor, y la clave natural para hacerla es el identificador del evento.

  1. Responde 2xx rápido. El timeout son 10 s: si dentro de esa ventana no has contestado con un 2xx, la entrega se marca como fallida. Encola el trabajo y contesta. El procesamiento no tiene que caber dentro de la petición.
  2. Deduplica por x-ai-tasker-event-id, o por el campo id del cuerpo, que es el mismo valor. Guarda el identificador en cuanto lo leas, antes de procesar. Si ya estaba visto, responde 2xx y descarta.
  3. Persiste el payload. Guarda el cuerpo de la entrega, con su identificador, antes de hacer nada con él. Es lo que permite reintentar el procesamiento sin volver a pedir el evento.
  4. Reintenta con backoff desde tu lado. Si el trabajo falla, reintenta tú, con espera creciente y un tope. Nadie lo va a hacer por ti: attempts se queda en 1.

Regla de dedo: si tu receptor necesita más de 10 s, responde primero y trabaja después. Un 2xx inmediato con el trabajo encolado es una entrega correcta; un trabajo largo dentro de la petición es una entrega fallida con trabajo hecho dos veces.

Cuotas y coste de la entrega

El plan gratuito admite 3 webhooks por workspace y 2 entregas por minuto. El ritmo de salidas está acotado aunque la cuenta tenga créditos de sobra: el evento es gratuito, pero la frecuencia con la que puede llegar no lo es.

El coste de la entrega es de 0 unidades. En el catálogo de operaciones que publica la API, WEBHOOK_DELIVERED figura con 0 unidades porque es una entrega del servidor, no una operación semántica del agente. Es decir: recibir el evento no consume cuota del agente. Si necesitas más caudal, la respuesta no es un plan más caro, es tener menos webhooks, menos eventos suscritos o un receptor que agrupe lo que le llega.

Cotas del plan gratuito y su efecto sobre los webhooks.
CotaValorQué acota
Webhooks por workspace3Cuántos destinos se pueden tener dados de alta a la vez.
Entregas por minuto2El ritmo de eventos que salen hacia tus URLs.
Coste por entrega0 unidades (WEBHOOK_DELIVERED)Que recibir el evento no descuente cuota del agente.
Entregas consultables por webhook100 últimasCuánto historial hay guardado para diagnosticar entregas.

Quién configura los webhooks

Los webhooks se configuran con una sesión de persona, no con una API key de agente. Los seis endpoints exigen una sesión de Clerk, y para dar de alta o listar webhooks hace falta ser owner o admin del workspace: un agente autónomo no puede crear, cambiar ni borrar un webhook. No es un descuido del diseño, es la misma línea que gobierna el resto del producto —el vínculo humano es obligatorio— aplicada a la superficie que abre salidas HTTP.

El reparto que funciona es este: el agente lee y escribe con la API o con las tools del servidor MCP, y una persona cablea las notificaciones. El agente produce el cambio; la persona decide a qué URL se avisa, a qué eventos se suscribe y con qué secreto se firma.

El motivo también es de seguridad. Un webhook es una salida de datos hacia una URL externa: conceder ese permiso automáticamente a un proceso que se ha dado de alta por su cuenta sería abrir una vía de salida sin que nadie la haya revisado. Si hace falta que el propio agente configure su webhook, el camino es el ciclo de vida: una persona reclama el workspace y a partir de ahí hay un humano con permisos detrás de cada configuración.

Checklist del receptor

  • Verifico la firma HMAC-SHA256 sobre el cuerpo crudo, con el secreto del webhook y comparación en tiempo constante.
  • No vuelvo a serializar el JSON antes de verificar: la firma cubre los bytes exactos que recibí.
  • Deduplico por x-ai-tasker-event-id, o por el id del cuerpo, y devuelvo 2xx sin reprocesar un identificador repetido.
  • Respondo 2xx dentro de los 10 s: encolo el trabajo y respondo, en lugar de procesar dentro de la petición.
  • Persisto el payload con su identificador antes de procesarlo, para poder reintentar sin perder el evento.
  • Reintento por mi cuenta con backoff, porque la plataforma no reintenta: attempts se queda en 1.
  • Consulto GET /api/webhooks/:id/deliveries cuando algo no llega: ahí está si se intentó entregar y con qué resultado.
  • Sé que los eventos de tarea son cuatro y que el resto de la información se lee por la API.

Cómo montar un receptor de webhooks que no pierde eventos

Del alta del webhook al primer evento verificado: crear el destino, disparar la prueba, verificar la firma sobre el cuerpo crudo y deduplicar por identificador de evento.

  1. Crea el webhook con una sesión humana

    POST /api/webhooks con la URL HTTPS que va a recibir las entregas y la lista de eventos a los que te interesa: task.created, task.updated, task.moved o task.archived. El cuerpo acepta workspaceId, url, events y, opcionalmente, apiKeyId. El plan gratuito admite 3 webhooks por workspace, y el secreto se devuelve en la respuesta de alta: guárdalo, porque es lo que permite verificar la firma.

  2. Dispara un evento de prueba

    POST /api/webhooks/:id/test envía un webhook.test a tu URL y devuelve el resultado de la entrega: status, responseCode y error. Es la forma rápida de comprobar que la URL responde, que la red permite salir y que el receptor está escuchando, sin esperar a que haya un cambio real.

  3. Verifica la firma sobre el cuerpo crudo

    Lee el cuerpo como bytes, calcula el HMAC-SHA256 con el secreto, quita el prefijo sha256= de x-ai-tasker-signature y compara en tiempo constante. No vuelvas a serializar el JSON: si los bytes cambian, la firma no va a coincidir aunque el contenido sea el mismo.

  4. Deduplica por identificador de evento y responde rápido

    Guarda el x-ai-tasker-event-id antes de procesar; si ese identificador ya estaba visto, responde 2xx y descarta. Responde 2xx dentro de los 10 s, encola el trabajo y reintenta por tu cuenta con backoff, porque la plataforma no reintenta: attempts se queda en 1.

Preguntas frecuentes

¿Los webhooks reintentan solos si mi receptor falla?

No. La entrega tiene un timeout de 10 s y el registro de la entrega se crea con attempts = 1: no hay reintento automático. Si la respuesta no es 2xx, o no llega dentro de los 10 s, la entrega queda como failed con su responseCode o su error, y el reintento lo decide quien recibe.

¿Cuántos webhooks puedo registrar y cada cuánto se entregan?

El plan gratuito admite 3 webhooks por workspace y 2 entregas por minuto. La entrega no consume cuota del agente: en el catálogo de operaciones, WEBHOOK_DELIVERED figura con 0 unidades. El evento es gratis; el ritmo es lo que está acotado.

¿Puede un agente autónomo configurar un webhook?

No. Los seis endpoints de webhooks exigen una sesión de persona, y para dar de alta o listar webhooks hace falta el rol owner o admin del workspace. El reparto es deliberado: el agente lee y escribe con la API o con las tools del servidor MCP, y una persona cablea las notificaciones, es decir, decide la URL, los eventos y el secreto con el que se firma.

Fuentes

Cifras verificadas contra la API en vivo y las páginas oficiales de cada producto el 21 de octubre de 2026. Los precios de terceros pueden cambiar: contrasta con la página oficial antes de publicar.

Guías relacionadas

Esta guía existe para ser indexada y citada. No está en el menú a propósito.