Skip to content
Browse documentation

Start here

How a webhook starts a Task

Task triggers match a source plus event type and optional filters across your workspace; the first matching entry starts a multi-step run alongside delivery.

Tasks are started by triggers, not by destinations. When Hooks accepts an inbound event, every enabled task in the workspace checks its trigger list; a match starts a multi-step run, in parallel with normal delivery.

A trigger is a list of entries. The first entry that matches the event starts the run:

trigger:
- source: github # provider name, or "*" for any source
event: issues.labeled # normalized <resource>.<action>, or "*" for any event
filter: # optional, AND semantics
- "{{event.label.name}} == 'agent'"
throttle: # optional per-key rate limit
key: "{{event.issue.number}}"
max: 1
window_minutes: 60
- source: stripe
event: invoice.paid
Field Meaning
source The provider the event came from: github, stripe, slack, linear, sentry, generic, or * (any).
event The normalized event type as <resource>.<action>, for example issues.labeled or invoice.paid, or * (any).
filter Optional expressions, all of which must pass. See the grammar below.
throttle Optional { key, max, window_minutes } rate limit applied after a match, per rendered key.

A task can also start on a cron schedule. Add a schedule block — a task needs an event trigger, a schedule, or both:

schedule:
cron: "0 9 * * mon-fri" # minute hour day-of-month month day-of-week
timezone: Asia/Tokyo # IANA zone, not UTC-only
  • Five fields only. 0/7 is Sunday; ranges (mon-fri), lists (1,15) and steps (*/15) work.
  • Times are wall-clock in the named zone. A spring-forward gap is skipped, and a repeated fall-back hour runs once, at the first occurrence.
  • Each tick starts one run, stamped with triggeredBy: schedule and the tick time. If the platform was down, the missed ticks collapse into one catch-up run instead of a burst.
  • Disabling the task stops new ticks; runs already in flight finish.

A trigger listens for a source + event type anywhere in the workspace, not to one endpoint or destination. If two endpoints both receive GitHub events, a github · push trigger can start a run from either one. Multiple tasks can match the same event, and each starts its own run. Several entries matching at once never start more than one run per task.

Filters are deliberately small so a typo fails at save time instead of silently never matching:

{{event.label.name}} == 'agent'
{{event.sender.is_collaborator}} == true
{{event.issue.pull_request}} != null
{{event.issue.draft}} # truthy
  • Only {{event.*}} paths resolve, against the source’s raw payload.
  • Operators are ==, !=, and bare truthiness; entries in a filter list are ANDed.
  • Anything more complex belongs in a step or a destination filter.

Step arguments, if conditions, throttle keys and wait.callback bodies all render {{...}} expressions. Each root resolves against a different object, and a path that does not exist fails the run with Missing template path <path> in the <scope>.

Root What it is Example
event The raw source payload — exactly what the sender posted. Not the Hooks envelope. {{event.issue.number}}
hooks Hooks metadata for the event that started the run. {{hooks.event_id}}
steps Outputs of earlier steps, keyed by step id. {{steps.create.machine_id}}
secret Named workspace secrets in scope for the step. {{secret.GITHUB_TOKEN}}
var Plaintext variables of the run’s environment. {{var.REGION}}
run Run state: id, token, status, error, log_tail, callback_url. {{run.id}}

{{hooks.event_id}} is the stored event id (evt_…). It is empty for runs that were not started by an event — scheduled runs, manual runs and reruns of a cron run.

Two traps worth knowing:

  • {{event.id}} is not the Hooks event id. It reads id from the sender’s payload and fails with Missing template path event.id in the raw source payload when the payload has no id — which is what most senders look like.
  • event.* cannot see normalized fields a destination would. If you need subject.title or an event type, use {{hooks.…}} plus the raw paths, or forward to a destination whose template already normalizes.

A dry run in the editor renders every step against the sample payload (or a stored event you pick), so a missing path shows up there before the task is saved.

How this differs from delivering to an agent

Section titled “How this differs from delivering to an agent”

Forwarding an event to an agent destination (or the /tools/payload-to-task helper) delivers the payload once — delivery templates shape the request body, and there is no run. A Task trigger starts a durable, multi-step run with steps, retries, timeouts, and {{steps.*}} state. Use a trigger when you want work to happen; use a destination when you only want a payload delivered.

  1. Open the console → Tasks → create a task, and fill the Trigger section: one or more source + event entries with optional filters and throttle.
  2. The editor saves the YAML and shows a trigger summary; use a dry run in the editor to validate steps without side effects.
  3. Real runs start from an inbound event that matches — send a test request with the trigger’s source and event type, then watch the run on the Runs page.
trigger:
- source: github
event: issues.labeled
filter:
- "{{event.label.name}} == 'agent'"
- "{{event.sender.is_collaborator}} == true"

A collaborator adding the agent label to an issue starts the task; the issue title, body, and URL are available to steps as {{event.subject.*}} and {{event.raw.*}}.