Webhooks entrantes

orch webhook serve: un receptor local verificado por HMAC que convierte un POST firmado en una tarea, desde n8n/Zapier, GitHub o Slack.

Objetivo#

Dejar que un disparador externo — un flujo de n8n/Zapier, un issue o comentario de GitHub, un slash command de Slack — cree una tarea de Orchemax y, una vez aprobada, la despache. orch webhook serve es un servidor HTTP local pequeno, solo loopback por defecto, con tres rutas: /task, /github, /slack. Es la contraparte entrante de orch notify (ver "Notificaciones y aprobaciones"), que solo envia eventos hacia afuera.

Pasos#

  1. Configura un secreto compartido y las rutas que quieras en orch.yaml:

    webhook:
      secret_env: ORCH_WEBHOOK_SECRET
      allow: [task, github, slack] # omitir = las tres
      require_approval: true # default; false deja despachar de inmediato una peticion firmada
      templates:
        cursor:
          agent: cursor
          prompt: "Lee los PR abiertos y resume."
          timeout_sec: 300
        github: # usado por eventos "issue opened/labelled orch" de GitHub
          agent: billing
    

    ORCH_WEBHOOK_SECRET debe estar definido en el entorno donde corre orch webhook serve. Cada ruta verifica una firma calculada con ese mismo secreto, con su propio esquema (abajo).

  2. Arranca el servidor:

    orch webhook serve --addr 127.0.0.1:8790
    

    El listener rechaza una direccion que no sea loopback (0.0.0.0, un :8790 sin host, una IP de LAN) salvo que pases --allow-remote — ponlo detras de tu propio reverse proxy/tunel si necesita ser alcanzable desde los servidores de GitHub o Slack.

  3. Verifica el cableado con una peticion firmada de muestra:

    orch webhook test --route task --agent cursor --prompt "responde con ok" --dispatch --approve
    

POST /task#

Body (JSON): {"template": "cursor", "project": "...", "dispatch": true, "approve": false} (o agent + prompt en vez de template). Headers: X-Orch-Timestamp (segundos unix) y X-Orch-Signature — HMAC-SHA256 en hex de "<timestamp>.<body crudo>" bajo el secreto compartido.

Respuesta: {"task_id", "status", "dispatched", "session_id"}. La tarea siempre se crea; solo despacha cuando dispatch:true y ademas approve:true o webhook.require_approval: false — si no, sale un evento needs_approval por orch notify (webhooks/Telegram), y la tarea espera orch task run <id> o una futura accion de aprobacion.

POST /github#

Verifica X-Hub-Signature-256 (el esquema propio de GitHub, HMAC-SHA256 sobre el body crudo). Dos eventos:

  • issues (opened, o labeled con la etiqueta orch): crea una tarea a partir del titulo/body del issue usando webhook.templates.github (agent y project salen de ese template — configuralo, o el evento se ignora).
  • issue_comment (un comentario que empieza con /orch <template> <prompt extra>, en un issue o un PR): crea una tarea desde webhook.templates.<template>, con el resto de la linea reemplazando el prompt por defecto del template.

Ambos despachan bajo la misma regla de require_approval que /task — un evento crudo de GitHub no trae un campo approve, asi que solo corre de inmediato cuando webhook.require_approval: false.

POST /slack#

Verifica el esquema de firma v0 de Slack: X-Slack-Request-Timestamp + X-Slack-Signature: v0=<hex hmac(secret, "v0:"+timestamp+":"+body)>. El body es el payload propio de Slack para slash commands, application/x-www-form-urlencoded — el campo text es <template> <prompt>. Responde 200 de inmediato con un ack efimero ({"response_type":"ephemeral","text":"..."}) que nombra el id de la tarea y si despacho o quedo esperando aprobacion.

n8n#

Usa un nodo HTTP Request (no el trigger Webhook — ese es para recibir, aca estas llamando hacia Orchemax). Apuntalo a http://<host>:8790/task, metodo POST, body JSON, y agrega dos headers calculados en un nodo Function/Code antes: X-Orch-Timestamp = unix time actual, X-Orch-Signature = HMAC-SHA256 en hex de "<timestamp>.<body>" con tu ORCH_WEBHOOK_SECRET. La mayoria de los nodos de cripto/HMAC, o un Code node de una linea, cubren esto.

Zapier#

Usa la accion "Webhooks by Zapier", "Custom Request" (POST), misma URL y body JSON. Calcula los dos headers con un paso "Code by Zapier" (Node.js crypto.createHmac('sha256', secret)) justo antes del paso de webhook, ya que Zapier no trae una accion HMAC propia.

Lo que nunca sale de la maquina#

Cada respuesta es solo JSON de estado/resumen — nada de codigo, contenido de archivo ni diff. El limite de 256 KB por body, el margen de 5 minutos en el timestamp y una cache de replay de 10 minutos (por firma) aplican a las tres rutas.