Webhook Triggers
- Desktop, mobile and web
- All plans
What it does
A webhook trigger starts an agent (or a team) in your project when an outside service calls a URL, instead of at a set time. AgentsRoom gives each trigger a unique public URL and a regenerable signing secret; you paste them into GitHub, GitLab, Slack, Linear, Sentry, a CI job or anything that can POST JSON. Every call is verified against the secret before anything runs, an optional filter decides whether the event deserves a run, and an anti-burst window folds calls arriving close together into one run. The event's fields become prompt variables resolved at fire time. The agent runs on your own machine, as a real agent with its terminal and archived transcript. Nothing is consumed while nothing happens. A generic trigger also refuses replayed calls (signed timestamp and delivery id, on by default), and a Restricted run makes the agent CLI itself refuse the shell, the web and every AgentsRoom tool when the payload comes from someone you do not control.
Where to find it
- Inside a project, agents panel: the webhook shortcut ("Add an agent triggered by a webhook") or Scheduled agent open the same Triggers panel; the Webhooks filter, carrying the webhook icon, narrows the list and says No webhook agent yet when there is none.
- In the editor, Trigger offers five cards: Scheduled, Quota, Webhook, On ship, On demand. The list has an On ship filter too.
- The list shows each webhook's Last call, Replay the last call, and "No call received yet" until the first delivery.
How to use it
- New trigger, pick the Webhook card. AgentsRoom registers the endpoint and shows URL to paste into the service (Copy) and the Signing secret (Reveal the secret, Regenerate).
- Under Who calls this URL, pick Any service (JSON), GitHub, GitLab, Slack, Linear or Sentry. A shortcut verifies that service's own signature header and pre-maps its payload fields to variables; it is not an allow list.
- Paste the URL and secret into the service. Or use Example call > Copy as curl (macOS / Linux or Windows PowerShell variant) to send a real signed call from a terminal right away.
- Optionally set Only run when (a condition on the JSON body, e.g.
action == "opened",pull_request.title contains "fix", joined by&&/||) and Anti-burst (One run per call, or One run per N min at most). - Write the Prompt with Variables available in the prompt: click one to insert it (
{{event.title}},{{event.author}},{{event.url}},{{event.number}},{{event.branch}},{{payload}}for the raw JSON). Variables filled by the last call are highlighted. - Choose who runs it (one agent or a team), the Advanced block and Machines exactly as for a scheduled task, then Save and check Enabled.
- Or, under Who runs it, pick the third card Board action > Start next Todo ticket: on each accepted call the trigger starts no agent of its own and needs no prompt. It does what the board's Start next button does: the top Todo ticket that is not waiting on a prerequisite moves to In progress and starts with its own assigned agent or team (same path as
backlog_start_next, including its worktree). When Todo has nothing ready, the history says Nothing to start instead of a failure. - Look at Last call received, adjust the filter and prompt against that real payload, Replay the last call until the run is right.
On ship: start a monitoring agent when a ticket ships
The On ship card starts the trigger's agent (or team) when a backlog ticket of the project ENTERS a column, instead of on an outside call. Use it for a post-release pass: watch logs, errors and user feedback after a change ships.
- New trigger, pick On ship. No URL and no secret: the AgentsRoom server produces the event itself, and nothing outside AgentsRoom can send one.
- Starts when a ticket enters: Done (default) or In review (a QC step before the ticket is closed).
- Optionally Only run when, a condition on the ticket JSON:
ticket.type == "bug",ticket.tags contains "prod",fromStatus == "in_review". - Write the prompt with the ticket variables:
{{event.id}},{{event.title}},{{event.body}},{{event.type}},{{event.tags}},{{event.status}},{{event.from}},{{event.shipped}}(ISO time),{{event.branch}}(worktree branch),{{event.version}}(fixed-in version),{{event.agent}}/{{event.team}}(who did the work),{{event.author}}(reporter),{{event.url}}(external issue), or{{payload}}. Put the monitoring window and the success criteria in the prompt; Close after inactivity ends the run. - Every ticket that enters the column gets its own run, once: a double move or a retried request does not start a second one, and a ticket declined as won't fix never starts it. Only YOUR triggers of that project fire, whoever moved the ticket (kanban drag, MCP, phone).
- History shows one row per shipped ticket. Run now replays the last shipped ticket (the retry); the switch disables it.
Security: what protects a webhook trigger
A webhook URL is public and the payload is written by whoever triggers the event. Three controls, all enforced by the platform rather than by the prompt:
Signature and replay protection
- Every call is verified against the signing secret before anything else:
X-Hub-Signature-256for GitHub,X-Slack-Signaturefor Slack, theX-Gitlab-Tokenshared token for GitLab, an HMAC SHA-256 of the raw body for Linear and Sentry. An unsigned call answers 401 and starts nothing. - Any service (JSON) has Replay protection > Require a signed timestamp, ON for every new trigger, with an Acceptance window of 1, 5 (default), 15 or 60 minutes. The sender computes
HMAC-SHA256(secret, "v1:<timestamp>:<delivery id>:<raw body>")and sends three headers:X-AgentsRoom-Signature: v1=<hex>,X-AgentsRoom-Timestamp: <Unix time in seconds>,X-AgentsRoom-Delivery: <unique id>. Changing the timestamp or the id breaks the signature. A call outside the window (in the past or the future) answers 401 "Delivery timestamp outside the acceptance window", and a trigger with the protection on refuses the body-only signature outright (no fallback an attacker could pick). Example call > Copy as curl already signs this way, in both shells. - A trigger created before this protection keeps the body-only signature (
X-AgentsRoom-Signature: <hex of the body>) until you turn it on: then update the sender, or its calls start failing. The editor warns while it is off. - Slack signs its own timestamp: a Slack call more than 5 minutes old is refused.
- GitHub, GitLab, Linear and Sentry sign the body only. A delivery id they already sent never runs twice, but their signature cannot tell when a call was made.
- Native dedupe: every delivery id an endpoint accepts is recorded for 30 days, including the calls folded by Anti-burst. The same id again answers 200
duplicateand starts nothing, whether it is a provider retry or a replay. With replay protection on, only the SIGNEDX-AgentsRoom-Deliverycounts: an extra unsigned id header added to a captured call cannot get it through.
Restricted run (untrusted payloads)
For a trigger fed by input you do not control (pull request code, a public issue, a CI job running contributor code), turn on Restricted run under Who runs it. Without it the agent has your shell, your files and every tool, and only the prompt tells it what not to do, which a prompt injection in the payload can override. With it, the agent CLI itself enforces:
- no shell and no web access (unless Allowed commands lists some);
- project files read-only (Allow editing project files opens edits inside the project on Claude Code; Codex stays read-only whatever it says);
- no AgentsRoom tool at all (backlog, memory, SSH, databases, agent spawn, the embedded browser, your Chrome);
- only the trigger's own MCP servers (Advanced > MCP servers), and among them only those named in Allowed MCP tools, one per line:
serverfor every tool of it,server/toolfor one; - nothing ever waits for an approval: what is not allowed is refused and the run goes on.
It replaces the trigger's permission mode and CLI options. Per CLI:
- Claude Code:
--restricted(file tools confined to the project, the repository's own.claude/settings*.jsonand.mcp.jsonignored, bypass refused),--strict-mcp-configwith only the allowed servers,--permission-mode dontAsk, an explicit tool list (Read, Grep, Glob, TodoWrite, plus Edit/Write/NotebookEdit when edits are allowed, plus Bash only for Allowed commands, which becomeBash(<prefix>:*)rules). Reading or editing.env*,.mcp.json,.npmrc,.git-credentials,.agentsroom/and the config folder of every agent CLI (.claude/,.codex/,.cursor/,.gemini/,.agents/, ...) is denied, editing CI workflows too, WebFetch and WebSearch as well: a run allowed to edit must not plant a hook or an MCP server that the next, unrestricted agent would run. Skills and sub-agents are not offered (a file in the repository could otherwise widen the allowlist). AgentsRoom's status hooks still run. - Codex: the OS sandbox, always
--sandbox read-only(its sandbox cannot shield the agent config files, so Allow editing project files does not apply), network off,-a never, and a per-agent profile that switches every other MCP server off and filters the allowed ones withenabled_tools. Needs Codex 0.134 or later. Limit: a sandboxed command can still READ files outside the project, so prefer Claude Code for a run that holds a credential. Not available on Windows (the sandbox does not verifiably cut the network there). - Any other CLI, a team executor, or an SSH remote host: the trigger refuses to start, records a failed run with the reason, and consumes the webhook event. A restricted run never degrades into an unrestricted one.
The restriction stays on the run's agent for its whole life: a resume, a relaunch or a fork is restricted too; Ask on the side is not offered on it; a duplicate drops it (fresh conversation); relaunching it from the phone is refused ("relaunch it from the desktop app").
Allowed commands give the run a shell again: keep the list empty for an untrusted payload, and never allow a command that executes repository code (npm, make, pytest...). Claude Code's read-only commands also run once a shell exists; common exfiltration commands (curl, wget, env, cat, ...) are denied unless you listed them.
A credential for one trigger
- Advanced > Environment variables of the trigger (
KEY={{secret:NAME}}) reach that trigger's runs only, not the rest of the project. - Advanced > MCP servers of the trigger: a server's
envorheaderscan hold{{secret:NAME}}. For a restricted run this is the recommended place for a token (a Forgejo or GitHub token for a review bot): the MCP server holds it and the agent calls the tool without ever reading the token. List only the tools the run needs in Allowed MCP tools, and give the token the smallest scope your forge allows. {{secret:NAME}}is resolved from the secret vault of the machine that fires the trigger, at launch: pin Machines to a computer that holds the secret.
Settings
wakeForTriggers(global, Settings > Terminal; project and per-trigger overrides): wake the machine for the trigger's runs.autoLaunchAgentsAtStartup(global, Settings > Terminal): lets queued deliveries be replayed at launch.- Per trigger,
webhook.replayWindowSec(Triggers editor > Replay protection > Require a signed timestamp / Acceptance window, generic source): seconds, 60 to 3600. Default 300 on a new trigger; absent on older ones (body-only signature). - Per trigger,
webhook.source: "backlog"+webhook.shipStatus(Triggers editor > On ship > Starts when a ticket enters):done(default) orin_review. - Per trigger,
restriction(Triggers editor > Who runs it > Restricted run):allowedTools(Allowed MCP tools),allowedCommands(Allowed commands, Claude Code only),allowEdits(Allow editing project files). Absent = not restricted, the default.
Agent tools (MCP)
triggers_list,triggers_get,triggers_save,triggers_delete: manage triggers, including webhook mode.triggers_run: fire the trigger now (a webhook or On ship trigger replays its last event).triggers_save({ kind: "ship", shipStatus: "done" | "in_review" }): an On ship trigger;triggers_list({ kind: "ship" })lists them.triggers_save({ boardAction: "start-next-todo" }): make the trigger a board action (no prompt needed); an empty string goes back to an agent or team.triggers_get/triggers_listreportboardAction.triggers_save({ replayWindowSec })andtriggers_save({ restricted: true, allowedTools, allowedCommands, allowEdits }): an agent may turn these controls ON or TIGHTEN them, never loosen them. Turning replay protection off, lengthening the window of an existing trigger, turning a restricted run off or widening its lists is refused over MCP: that is the user's call, in the desktop editor. A new webhook trigger starts protected (300 s) and may be created with any window from 60 to 3600.triggers_getreturnsrestrictionandwebhook.replayWindowSec;triggers_listreturnsrestricted.
Providers
All providers: the spawned agent goes through the normal launch path with the role, provider and model set on the trigger.
Restricted run: Claude Code and Codex (not on Windows) only. Every other CLI refuses to start a restricted run instead of running it without its limits.
Mobile
Present. The phone edits the whole trigger, including the On ship mode and the webhook block (URL, secret, curl example, filter, anti-burst, replay protection, variables, last payload), Restricted run with its allowed tools and commands, machine scope and history. Execution stays on the desktop, which is also where a restricted run's agent can be relaunched.
Limits
- Inbound only: AgentsRoom receives events and never emits webhooks. An agent that must call a service does it with its own tools.
- The agent runs on your machine: an event arriving while AgentsRoom is closed is queued and replayed at the next launch, held for a week, then dropped.
- One event produces one run: with the project open on several machines, the first to pick the event locks it, the others skip it. Pin Machines to make one computer own a trigger.
- POST only. An unsigned call is rejected; knowing the URL is not enough.
- Regenerate invalidates the old secret at once: paste the new one into the service or the webhook breaks.
- Not a multi-step scenario builder: a trigger decides when an agent starts and hands it the event; the work is the agent's. The only built-in action it runs itself is Start next Todo ticket.
Common questions
- Is the public URL safe? Yes: GitHub is checked with X-Hub-Signature-256, Slack with X-Slack-Signature, GitLab with its shared token, Linear and Sentry with an HMAC of the raw body, and a generic trigger with a signature that also covers a timestamp and a delivery id. A call without a valid signature does nothing.
- Can someone replay a call they captured (a CI log, a proxy)? Not on a generic trigger with Replay protection on (the default for new ones): outside the acceptance window the call is refused, inside it the same delivery id never runs twice, and the id cannot be swapped without breaking the signature. Slack calls older than 5 minutes are refused too.
- My CI started getting 401 after I turned on replay protection. The sender still signs the body only. Sign
v1:<timestamp>:<delivery id>:<raw body>and sendX-AgentsRoom-Signature: v1=<hex>,X-AgentsRoom-Timestamp(seconds, not milliseconds) andX-AgentsRoom-Delivery: Copy as curl shows the exact commands. - My webhook agent reviews pull requests from strangers. How do I stop a prompt injection from using my shell or my servers? Turn on Restricted run: no shell, no web, read-only project files, no AgentsRoom tool, and only the MCP tools you list, enforced by Claude Code or Codex themselves. Put the forge token in the trigger's own MCP server so the agent never sees it.
- Can I give one trigger its own token without exposing it to the whole project? Yes: the trigger's Environment variables and MCP servers (under Advanced) take
{{secret:NAME}}references and apply to that trigger's runs only. - Why not have an agent poll the API? Every poll burns a full turn of context to answer "nothing new". A webhook costs nothing until the event happens, then reacts in seconds.
- Which services are supported? Anything that sends a signed POST with a JSON body: the five shortcuts, plus CI, monitoring, your own backend or a curl.
- How do I test without pushing commits? Copy as curl for a signed test call, then Replay the last call.
- Can an agent start by itself when a ticket ships, to watch production? Yes: the On ship mode. Pick Done or In review, write the monitoring brief with the ticket variables, and every shipped ticket gets one traceable monitoring run.
- Can a webhook start the next backlog ticket without an agent turn? Yes: Board action > Start next Todo ticket. Zero tokens for the trigger itself, no extra tab to close; the ticket then runs with the agent or team assigned to it. Same signature check, filter and anti-burst as the other modes.
Related
- Scheduled Tasks: the same panel, fired by the clock or the quota.
- Agent Teams: point a webhook at a pipeline.
- Backlog Task Board: scope a new issue as it is filed.
- Keep the Machine Awake: keeping the machine up while the run works.