Inbound webhooks

orch webhook serve: a local HMAC-verified receiver that turns a signed POST into a task, from n8n/Zapier, GitHub or Slack.

Goal#

Let an outside trigger — an n8n/Zapier flow, a GitHub issue or comment, a Slack slash command — create an Orchemax task and, once approved, dispatch it. orch webhook serve is a small local HTTP server, loopback-only by default, with three routes: /task, /github, /slack. This is the inbound counterpart of orch notify (see "Notifications and approvals"), which only sends events out.

Steps#

  1. Configure a shared secret and the routes you want in orch.yaml:

    webhook:
      secret_env: ORCH_WEBHOOK_SECRET
      allow: [task, github, slack] # omit = all three
      require_approval: true # default; false lets a signed request dispatch immediately
      templates:
        cursor:
          agent: cursor
          prompt: "Read the open PRs and summarize."
          timeout_sec: 300
        github: # used by GitHub "issue opened/labelled orch" events
          agent: billing
    

    ORCH_WEBHOOK_SECRET must be set in the environment orch webhook serve runs under. Every route verifies a signature computed from that same secret, in its own scheme (below).

  2. Start the server:

    orch webhook serve --addr 127.0.0.1:8790
    

    The listener refuses a non-loopback address (0.0.0.0, a bare :8790, a LAN IP) unless you pass --allow-remote — put it behind your own reverse proxy/tunnel if it needs to be reachable from GitHub or Slack's servers.

  3. Check the wiring with a signed sample request:

    orch webhook test --route task --agent cursor --prompt "reply with ok" --dispatch --approve
    

POST /task#

Body (JSON): {"template": "cursor", "project": "...", "dispatch": true, "approve": false} (or agent + prompt instead of template). Headers: X-Orch-Timestamp (unix seconds) and X-Orch-Signature — hex HMAC-SHA256 of "<timestamp>.<raw body>" under the shared secret.

Response: {"task_id", "status", "dispatched", "session_id"}. The task is always created; it only dispatches when dispatch:true and either approve:true or webhook.require_approval: false — otherwise a needs_approval event goes out through orch notify (webhooks/Telegram), and the task waits for orch task run <id> or a future approval action.

POST /github#

Verifies X-Hub-Signature-256 (GitHub's own HMAC-SHA256-over-raw-body scheme). Two events:

  • issues (opened, or labeled with the orch label): creates a task from the issue title/body using webhook.templates.github (agent/project come from that template — configure it, or the event is ignored).
  • issue_comment (a comment starting with /orch <template> <extra prompt>, on an issue or a PR): creates a task from webhook.templates.<template>, the rest of the line replacing the template's default prompt.

Both dispatch under the same require_approval rule as /task — a raw GitHub event carries no approve field, so it only runs immediately when webhook.require_approval: false.

POST /slack#

Verifies Slack's v0 signing scheme: X-Slack-Request-Timestamp + X-Slack-Signature: v0=<hex hmac(secret, "v0:"+timestamp+":"+body)>. Body is Slack's own application/x-www-form-urlencoded slash-command payload — the text field is <template> <prompt>. Replies 200 immediately with an ephemeral ack ({"response_type":"ephemeral","text":"..."}) naming the task id and whether it dispatched or is waiting for approval.

n8n#

Use an HTTP Request node (not the Webhook trigger — that's for receiving, you are calling out to Orchemax). Point it at http://<host>:8790/task, method POST, JSON body, and add two headers computed in a Function/Code node before it: X-Orch-Timestamp = current unix time, X-Orch-Signature = hex HMAC-SHA256 of "<timestamp>.<body>" with your ORCH_WEBHOOK_SECRET. Most crypto/HMAC nodes or a one-line Code node cover this.

Zapier#

Use the "Webhooks by Zapier" action, "Custom Request" (POST), same URL and JSON body. Compute the two headers with a "Code by Zapier" step (Node.js crypto.createHmac('sha256', secret)) immediately before the webhook step, since Zapier has no built-in HMAC action.

What never leaves the machine#

Every response is status/summary JSON only — no code, no file contents, no diff. The 256 KB body cap, 5-minute timestamp skew and a 10-minute replay cache (by signature) apply to every route.