Saltar a contenido

Configurar un trigger de evento

Un trigger de evento dispara un agent cuando pasa algo en otro sitio.

Hay dos maneras de que eso nos llegue, y cuál usa una fuente es asunto de la fuente y no tuyo:

  • Por envío (push). Un provider hace POST de una carga firmada — una issue de GitHub, o cualquier cosa que pueda enviar JSON firmado (la fuente API).
  • Por sondeo (polling). La plataforma lee una cuenta conectada con una cadencia. Gmail es así: no se nos envía nada, así que no hay URL que configurar ni secreto que guardar. Conectas el buzón y ese es todo el montaje.

Conceptos cuenta qué es un trigger y cómo se comporta un run disparado; Gobernanza cuenta lo que gasta y cómo se trata un rechazo.

Esta página es lo que haces después: cómo apuntar un provider real al webhook, qué tiene que contener la entrega, y cómo probarlo todo desde un portátil.

Si solo necesitas el reloj, lo que quieres es un horario

Sin webhook, sin secreto, sin provider que configurar.

Cualquiera de los dos tipos puede partir de una plantilla sembrada (GET /trigger-templates). Una plantilla de horario — «resume mis pull requests abiertas cada mañana de entre semana» — rellena de antemano el prompt y una cadencia sensata. Una plantilla de evento — «clasifica la nueva issue», «redacta una respuesta al correo» — rellena de antemano el prompt en el paso de mensaje de su propia fuente. Ninguna de las dos parte de una caja en blanco.

Una programación cuyo último run sigue en marcha

Una programación puede vencer mientras el run que disparó la vez anterior sigue en marcha: una sincronización nocturna que dura más que una noche, o un run aparcado en una aprobación que nadie ha decidido. If the previous run is still going en el formulario de la programación, overlap en la API, decide qué pasa:

Opción overlap Qué hace
Skip this run skip (por defecto) La ocurrencia se pierde y la programación espera a su siguiente hora. Un agent lento se ejecuta tan a menudo como alcanza a terminar
Run when it finishes queue Dispara una vez cuando termina el run anterior, por muchas ocurrencias que hayan pasado
Run anyway allow Dispara a su hora, en el mismo registro de runs, así que dos runs se solapan y se pagan los dos

Un trigger de evento no tiene este ajuste: cada entrega es su propio disparo.

Todo lo de abajo es para el caso de los eventos.

Dónde viven en el producto, y cómo llamarlos

Routines es el paraguas - la navegación, la página, el panel propio del agent, la barra lateral del chat y la tarjeta del dashboard usan esa única palabra, así que una persona se encuentra el mismo nombre llegue por donde llegue. Las dos familias que hay debajo siguen siendo distintas porque se comportan de forma distinta: un horario dispara con el reloj, un trigger dispara con una llegada. Lo que no podía diferir era el paraguas, que es como la navegación acabó diciendo «Routines» sobre un panel titulado «Schedules & triggers» (#594).

Cuatro superficies, una lista:

Dónde Para qué sirve
Routines (/routines) Todas las rutinas de la organización, y las dos maneras de empezar una
La pestaña Availability de un agent Solo las de ese agent, al lado de donde se configura su exposición
La sección Routines de la barra lateral del chat Lo que hace por su cuenta el agent con el que estás hablando
La tarjeta Routines del dashboard Lo más próximo primero, con cómo fue el último disparo - una rutina que falla cada hora es invisible en cualquier otro sitio de esa página

La tarjeta del dashboard se añade desde Customize y está en la disposición por defecto bajo Needs attention. Lee esa misma lista de toda la organización, así que quien puede ver los agents ve sus rutinas; el resultado y el coste de cada fila necesitan runs:view y sin ese permiso sencillamente no aparecen.

El mecanismo, una vez

Un trigger de evento te entrega dos cosas: una URL de webhook y un secreto de firma. Un provider hace POST de su carga a la URL y firma la petición; la plataforma recalcula la firma y dispara el agent solo si las dos coinciden.

flowchart TD
    P[A provider, or your own script] -->|POST + signature header| W["/api/v1/webhooks/triggers/{source}/{id}"]
    W --> V{signature verifies?}
    V -->|no| R403["403 - refused before the runner"]
    V -->|yes| J{a JSON object?}
    J -->|no| R400["400"]
    J -->|yes| F{trigger active,<br/>filter matches?}
    F -->|no| R202["202 - nothing to do"]
    F -->|yes| SUB["queue a fire-trigger job"]
    SUB --> R202b["202 - accepted, not finished"]
    SUB -.->|later, in the worker| RUN[the agent runs, spending the org's budget]
  • La URL se construye sobre la única dirección pública del despliegue (PUBLIC_BASE_URL), no sobre el origen del dashboard - el webhook lo sirve el host de la API, que suele ser un origen distinto al de la interfaz. Su forma es:
{PUBLIC_BASE_URL}/api/v1/webhooks/triggers/{source}/{trigger_id}

source es github o webhook (el nombre de cable de la fuente API); trigger_id es un UUID imposible de adivinar. El diálogo la rellena por ti - cópiala, no la construyas a mano. gmail no tiene URL: una fuente sondeada no tiene puerta de entrada, y un POST que nombre una se responde como cualquier entrega que no tiene nada que hacer.

  • La firma es HMAC-SHA256 sobre los bytes crudos exactos de la petición, con el secreto de firma como clave, codificada en hexadecimal y con el prefijo sha256=. Viaja en una cabecera que depende de la fuente:
Fuente Cabecera
github X-Hub-Signature-256
webhook X-Signature-256

GitHub firma sus entregas de forma nativa bajo su propia cabecera X-Hub-Signature-256, así que le das el secreto a GitHub y él firma. La fuente API reutiliza el esquema idéntico bajo X-Signature-256, que tiene que poner por su cuenta lo que sea que apuntes a la URL. Una fuente sondeada no firma nada y no guarda ningún secreto: no se le envió nada, se la leyó, y el propio grant de OAuth de la cuenta es lo que autorizó esa lectura.

La firma no es un adorno

Sin ella, la URL es lo único que hay entre un desconocido y el budget de modelo de tu organización - y las URL se filtran: a los logs, al historial de entregas de un provider, a una captura de pantalla en un ticket de soporte. Cualquiera que tenga la URL podría disparar el agent a voluntad y gastar contra tus topes. El secreto es lo que hace que una entrega sea auténtica y no solo esté bien dirigida.

Una petición cuya firma no se verifica se rechaza con un 403 antes de llegar siquiera al runner; el secreto está sellado en el vault y nunca aparece en una lectura, en un listado ni en la URL.

El 202 significa aceptado, no terminado

Una entrega que coincide se encola como un job fire-trigger en la propia transacción de la petición y el agent se ejecuta en el worker, así que el provider recibe su respuesta tras un insert en vez de esperar a un modelo. No leas un 202 como «el agent ya ha respondido» - para eso lee el run en Activity.

Una entrega verificada que no tiene nada que hacer - un trigger inactivo, o una carga que el filtro no hace coincidir - responde 202 exactamente igual que una que dispara, así que tener el secreto no te dice nada sobre qué triggers existen. Un cuerpo que no es un objeto JSON es un 400.

Rotar el secreto y editar el filtro

La URL es la identidad del trigger y nunca cambia. El secreto es una credencial, y como cualquier otra clave de este producto se puede rotar — un resellado y un texto plano nuevo que se muestra exactamente una vez.

POST /agents/{agent_id}/triggers/{trigger_id}/rotate-secret

Acuña un secreto nuevo, lo sella y devuelve el trigger con reveal_secret puesto una vez — el mismo campo que usa la creación. Rota en cuanto un secreto pueda haberse filtrado; el antiguo deja de verificar inmediatamente.

Para un hook que registró la propia plataforma (auto_webhook), la rotación lo vuelve a registrar con el secreto nuevo, así que sus entregas siguen verificando y no hay nada que revelar. Salvo que la cuenta ya no pueda registrarlo, en cuyo caso el trigger cae a manual y el secreto revelado es lo que vuelves a pegar.

Un horario no tiene secreto, así que rotar uno se rechaza.

Qué acciones de una issue disparan es un filtro, no un trigger distinto, así que se edita en el sitio. Haz PATCH del trigger con un event_config nuevo y se vuelve a validar contra las reglas de la fuente exactamente igual que lo valida la creación — una clave desconocida se rechaza en vez de guardarse para no coincidir con nada.

La fuente y el secreto no se editan así. Reapuntar un trigger de evento a otra fuente es un trigger nuevo: borra este, crea aquel.

Gmail (~1 minuto, y sin ningún secreto)

Gmail se sondea, así que el montaje es una pantalla de consentimiento y nada más.

  1. Conecta la cuenta. Routines → New event trigger → Gmail → Connect account. Eso necesita mcp:manage, el mismo permiso que necesita cualquier otra cuenta conectada.
  2. Elige qué lo dispara: cualquier mensaje nuevo, solo la bandeja de entrada, o marcado como importante. Afina más con un remitente o un fragmento del asunto, o con una etiqueta de Gmail.
  3. Escribe el prompt, o parte de la plantilla «redacta una respuesta».

No hay URL que pegar ni secreto que guardar, porque nadie nos envía nada. Lo que conviene saber sobre cómo lee:

  • Una vez por minuto. El latido le pregunta a Gmail qué ha llegado desde que miró por última vez, así que la latencia en el peor caso es un minuto. Es deliberado: la alternativa - users.watch hacia un topic de Google Cloud Pub/Sub - es en tiempo real y cuesta un topic y una suscripción como requisitos del despliegue, más un registro que caduca cada siete días y necesita algo que lo renueve.
  • Conectar no dispara nada - y no pierde nada. La posición del buzón se toma en el momento en que se completa el consentimiento, así que conectar no dispara el agent una vez por cada mensaje que ya estuviera ahí, y el correo que llegue entre el consentimiento y el primer latido aterriza igualmente después de esa posición y dispara.
  • Una ráfaga está acotada. Un tic lee como mucho 25 mensajes nuevos enteros. El volcado de una lista de correo no se convierte en 400 runs de agent; la posición avanza igualmente, así que el atasco no se relee para siempre.
  • Un mensaje puede disparar varios triggers. A diferencia de un webhook, cuya URL nombra exactamente uno - «cualquier mensaje» y «marcado como importante» sobre el mismo buzón disparan los dos.
  • Una semana perdida se repara sola. Google guarda alrededor de una semana de historial. Un cursor más antiguo que eso se resincroniza con el ahora en vez de dejar el buzón aparcado para siempre.

El despliegue necesita un cliente OAuth de Google (GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET - el mismo par que usa el inicio de sesión con Google) con la API de Gmail habilitada. Sin él la tarjeta lo dice en vez de ofrecer un botón Connect que solo podría fallar. A diferencia de GitHub, el cliente es del despliegue y no de cada organización: la pantalla de consentimiento de Google para un scope de buzón necesita un proyecto verificado, que un operador registra una vez y que ninguno de sus inquilinos puede registrar en absoluto.

Dos maneras de conectar GitHub, y cómo saber cuál tienes

Hay dos, y un despliegue puede ofrecer una o ambas. El selector muestra GitHub y GitHub (App) como fuentes distintas, y cuál usa un trigger se decide al crearlo (#1072).

GitHub (OAuth App) GitHub (App)
Qué guarda Un token de acceso de la persona que dio el consentimiento Un id de instalación más la clave privada de la App en el vault
Qué alcanza Todo repositorio que esa cuenta pueda administrar —repo más admin:repo_hook, lectura y escritura Los repositorios en los que se instaló la App, con los permisos que la App declara
Cuánto dura Para siempre. Un token clásico de OAuth App no tiene refresco ni caducidad, así que uno filtrado vale hasta que alguien lo revoque a mano Una hora. Acuñado a demanda desde la clave, que nunca sale del vault
Hooks Uno por repositorio, creado con el trigger y borrado con él Ninguno. La App ya está entregando
Límite de peticiones Los 5000/hora de la persona, compartidos con todo lo demás que esa cuenta autorizó El propio de la App
Dónde entrega /api/v1/webhooks/triggers/github/<id del trigger> —una URL por trigger /api/v1/webhooks/github-app —una URL para todas las instalaciones

Cómo saber en cuál está un trigger: por su fuente. Un trigger creado bajo GitHub (App) no tiene webhook que encontrar en los ajustes del repositorio, porque no existe; uno bajo GitHub tiene exactamente uno, añadido por la plataforma al crearlo.

Configurar la App (una vez por organización)

  1. Registra la App en github.com/settings/apps (o el equivalente de tu organización).
  2. Webhook URL: https://<tu despliegue>/api/v1/webhooks/github-app. Una URL para todas las instalaciones, y por eso la dirección pública del despliegue es aquí un requisito de configuración y no un valor de ejecución.
  3. Webhook secret: genéralo. Firma cada entrega de cada instalación de esta App.
  4. Permisos: Issues: Read-only y Metadata: Read-only. No se lee nada más, y cualquier cosa por encima es acceso que nadie necesita.
  5. Suscríbete a eventos: Issues.
  6. Genera una clave privada en la misma página y descarga el PEM.
  7. Guarda las tres en el vault como un secreto GitHub App: el App ID, la clave privada y el webhook secret. Uno por organización y visible para la organización: la ruta de entrega lo lee por organización y se niega a adivinar entre dos.
  8. Instala la App en los repositorios que quieras, desde la pestaña Install App. Eso, y solo eso, es lo que el despliegue puede alcanzar.
  9. Conéctala — con el botón Connect del portal, que pide el id de instalación. GitHub lo pone al final de la URL de la página de ajustes de la propia instalación (…/settings/installations/<id de instalación>). No es un secreto: viaja en cada entrega y es lo que le dice a la plataforma a qué grant pertenece una entrega.

El paso 5 no tiene equivalente en la ruta OAuth, porque una App no tiene ningún flujo de consentimiento que arrancar. Conectarla acuña de inmediato un token de instalación —no para guardarlo, sino para demostrar que el App ID, la clave privada y el id de instalación concuerdan. Un id mal tecleado, o un PEM que perdió sus saltos de línea camino del formulario, se rechaza ahí en lugar de descubrirse más tarde como entregas que en silencio no encajan con nada. Es además el único momento en que la clave puede comprobarse: el vault no vuelve a mostrar un secreto guardado.

Crear un trigger elige entonces un repositorio de la instalación y no registra nada.

Apagarlo

Desactivar la conexión hace que las entregas dejen de emparejarse del todo — los grants desactivados no son candidatos. Ese es el interruptor de toda la instalación, sin tocar GitHub.

Cuando llega una entrega

Una URL, así que la ruta no nombra nada. El id de instalación del payload selecciona los grants a los que podría pertenecer, el webhook secret de la App para esa organización verifica la firma, y el repositorio y el evento eligen los triggers —todos, no uno—. Dos triggers sobre el mismo repositorio disparan los dos, que es lo que los presets invitan y lo que una URL por trigger no puede hacer por construcción.

Una entrega que no coincide con nada responde 202 igual que una que lo disparó todo. Una firma que no verifica contra ningún candidato es un 403, así que Recent Deliveries de GitHub le muestra a quien configuró mal la App qué falla.

Cuál elegir

La App, salvo que no puedas registrar una. La vía de OAuth App es el recurso para un despliegue cuya organización de GitHub no deja a nadie crear una App, y no se va a ninguna parte —pero un token que puede escribir en todos los repositorios que alcance una administradora, y que nunca caduca, es una credencial muy grande para leer issues.

Una receta con GitHub (~5 minutos)

GitHub firma sus propias entregas, así que es la fuente más rápida de conectar. Crea primero el trigger con la fuente GitHub, copia su URL de webhook y su secreto de firma, y después:

  1. En el repositorio que quieras vigilar, ve a Settings → Webhooks → Add webhook.
  2. Payload URL - pega la URL de webhook del diálogo del trigger.
  3. Content type - elige application/json. No application/x-www-form-urlencoded: la firma cubre los bytes exactos que envía GitHub, y la codificación de formulario los cambia, así que una entrega codificada como formulario no verifica contra nada y vuelve como 403.
  4. Secret - pega el secreto de firma.
  5. Which events? - elige Let me select individual events, marca Issues y desmarca todo lo demás. Solo los webhooks de issues llegan siquiera al camino del disparo (el tipo de evento se lee de la cabecera X-GitHub-Event); cualquier otra cosa se descarta. Afina qué acciones de una issue disparan con el filtro del trigger - por defecto es la creación de una issue (opened).
  6. Add webhook. GitHub envía un ping, que no es un evento issues, así que no disparará el agent - eso es lo esperado.

Cuando una entrega se rechaza, diagnostícala en la pestaña Recent Deliveries del webhook en GitHub: muestra la petición exacta y la respuesta. Un 403 ahí es una firma que no cuadra - casi siempre el secreto está mal o el content type no es application/json.

El contrato de la carga para las fuentes entregadas por relé

GitHub es el dueño de la forma de su carga, y la de una fuente sondeada la lee el adaptador que la lee - los filtros de un trigger de Gmail se contrastan con el propio mensaje, así que no hay contrato que tú tengas que cumplir.

El que sí es tuyo es la fuente comodín webhook - API en los diálogos. No tiene filtro: una entrega verificada dispara, y todo el cuerpo JSON se añade al prompt. Úsala para cualquier cosa que ningún portal cubra - vigilar un feed para el que ningún provider expone una API (una página de LinkedIn, un anuncio de un marketplace), o cualquier herramienta que sepa hacer POST - siendo el relé que tú escribas el que vigila.

Aquí había antes una fuente email, y era esta misma con otro nombre: renombraba dos campos del filtro y te pedía ejecutar un relé - un code step de Zapier o de Make, un script pequeño - que firmara y nos enviara JSON, porque nada en este producto podía recibir correo. Se quitó por la misma razón que linkedin: una entrada de un desplegable cuyo nombre promete una integración que no existe. Gmail la sustituyó como cuenta conectada de verdad (más arriba), y un buzón alimentado por un relé es la fuente API con un ejemplo documentado.

Un correo alimentado por un relé, como fuente API:

{ "from": "billing@acme.com", "subject": "Invoice #4021", "body": "…" }

Ya nada filtra por esos nombres, así que todo el cuerpo llega al prompt y el agent lo lee. Si lo que quieres es el filtrado, conecta el buzón en su lugar.

Firmar una entrega tú mismo

Para la fuente genérica webhook (y para probar a mano cualquier fuente), la petición la firmas tú. Dos trampas deciden si la firma verifica, porque las dos cambian los bytes:

  • Firma los bytes que envías, y solo esos. echo añade un salto de línea final que se firma pero puede no enviarse, o enviarse sin haberse firmado; usa printf '%s' y pasa el cuerpo con curl --data-raw para que no se añada ni se interprete nada.
  • No vuelvas a serializar. Firmar un dict y dejar después que tu cliente HTTP lo recodifique produce bytes distintos (claves reordenadas, espaciado distinto). Firma una cadena y envía esa misma cadena.
SECRET='your-signing-secret'
URL='https://api.example.com/api/v1/webhooks/triggers/webhook/<trigger_id>'
BODY='{"hello":"world"}'

SIG="sha256=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')"

curl -sS -X POST "$URL" \
  -H 'Content-Type: application/json' \
  -H "X-Signature-256: $SIG" \
  --data-raw "$BODY"
import hashlib
import hmac

import httpx

secret = b"your-signing-secret"
url = "https://api.example.com/api/v1/webhooks/triggers/webhook/<trigger_id>"
body = b'{"hello":"world"}'

signature = "sha256=" + hmac.new(secret, body, hashlib.sha256).hexdigest()

# content=body sends these exact bytes. json=... would re-serialize and sign nothing.
httpx.post(
    url,
    content=body,
    headers={"Content-Type": "application/json", "X-Signature-256": signature},
)

Para la fuente github el algoritmo es idéntico; solo cambia el nombre de la cabecera, que pasa a ser X-Hub-Signature-256.

Zapier y Make no pueden hacer esto sin un code step

Ninguno de los dos tiene una acción HMAC

Sus pasos estándar de «POST a un webhook» envían el cuerpo pero no pueden firmarlo, así que cada entrega llega sin firmar y se rechaza con 403. Cuenta con una hora con un code step, no con cinco minutos de clics.

Es tentador echar mano de una acción de webhook sin código en Zapier o en Make. Tienes que añadir su code step (el Code by Zapier de Zapier, el módulo Custom JS / functions de Make), calcular el HMAC sha256=<hex> sobre el cuerpo exacto que vas a enviar, y poner con él la cabecera X-Signature-256.

Eso funciona, pero sé honesto sobre el coste: es más o menos una hora con un code step, no cinco minutos de clics. Si lo único que quieres es comprobar el trigger de principio a fin, firma antes una petición a mano con el fragmento de arriba.

Probar en local

Prueba Run now antes de configurar un provider

Dispara cualquiera de los dos tipos de trigger una vez, bajo demanda, sin firma y sin webhook de por medio - la forma más rápida de confirmar que el agent, su prompt y su budget se comportan.

En un portátil PUBLIC_BASE_URL vale por defecto http://localhost:8000, así que la URL que te entrega el diálogo es inalcanzable desde GitHub o desde cualquier relé alojado - no pueden ver tu máquina. Dos maneras de salvarlo:

  • Usa simplemente Run now. Run now dispara cualquiera de los dos tipos de trigger una vez y bajo demanda - un horario dispara una vez de más con su cadencia intacta, y un trigger de evento dispara también, como disparo manual de prueba: el agent ejecuta su prompt base sin contexto de entrega, sin firma y sin webhook de por medio. Es la forma más rápida de confirmar que el agent, su prompt y su budget se comportan sin ningún provider configurado. Un trigger inactivo (en pausa) se respeta - Run now no le hace nada. Su única laguna es que no recorre el camino de la firma ni una carga real, así que no pillará un secreto equivocado ni un campo mal nombrado.

  • Expón el puerto con un túnel cuando sí quieras probar el camino real del webhook. Apunta un túnel a la API, pon PUBLIC_BASE_URL a la dirección pública del túnel, y crea el trigger después de eso - la URL se construye a partir de PUBLIC_BASE_URL en el momento de leerla, así que un trigger creado antes del cambio seguiría entregando una URL de localhost.

cloudflared tunnel --url http://localhost:8000
# then set PUBLIC_BASE_URL to the printed https URL, restart the API,
# and create the trigger

Apunta el provider (o tu script de firma) a la URL del túnel y la entrega llega a tu máquina como cualquiera alojada.

Resumen

  • Un trigger te entrega una URL y un secreto de firma. La URL es su identidad y nunca cambia; el secreto es una credencial y se puede rotar.
  • La firma es HMAC-SHA256 sobre los bytes crudos exactos, y es lo que hace que una entrega sea auténtica y no solo esté bien dirigida.
  • Un 202 significa aceptado, no terminado. Lee el run en Activity.
  • Gmail se sondea, así que no tiene URL ni secreto alguno — conecta el buzón y ese es todo el montaje.
  • En un portátil, echa mano de Run now antes que de un túnel.