Build workflows

Triggers

Manual, schedule and webhook triggers, and triggers from GitHub, Slack, Linear, Stripe, Shopify, Zendesk and other connections — how each one fires and what data it gives your steps.

Every workflow has exactly one trigger. It decides when runs start and provides the trigger payload — the data your steps read as {{ trigger.* }}. Automatic triggers only fire while the workflow is enabled; a manual run is always possible.

Kind Fires when Setup
manual Someone presses Run, or an agent calls run_workflow None
schedule A cron schedule comes due None
webhook Any system POSTs to the workflow's URL Copy the URL into the sending system
github A matching GitHub event arrives A GitHub connection with a webhook
slack A matching Slack event arrives A Slack connection with Event Subscriptions
linear A matching Linear event arrives A Linear connection with a webhook
stripe, shopify, gitlab, … A matching event from that provider arrives A catalog connection with a webhook signing secret

Manual

trigger:
  kind: manual

Runs start from the Run button (which shows a form for the workflow's inputs), from the assistant, or from an MCP client. The payload contains the resolved inputs and who started the run. Manual runs work even when the trigger is disabled — every workflow can be run by hand, whatever its trigger kind.

Schedule

trigger:
  kind: schedule
  cron: "0 9 * * 1-5"        # 9:00 every weekday
  timezone: America/Toronto   # optional, defaults to UTC
  • cron is a standard five-field expression: minute, hour, day of month, month, day of week.
  • timezone is an IANA name such as Europe/Berlin or America/New_York.
  • Murmurator checks schedules every minute. After you enable a scheduled workflow, the first run happens at the next matching time — enabling doesn't trigger an immediate run.
  • If runs are missed (for example during maintenance), the workflow fires once when it catches up rather than once per missed slot.
  • Scheduled runs use each input's default value. The payload includes scheduled_at.
Cron Meaning
*/15 * * * * Every 15 minutes
0 * * * * Every hour, on the hour
30 8 * * 1 Mondays at 8:30
0 17 * * 5 Fridays at 17:00
0 0 1 * * Midnight on the first of each month

Webhook

trigger:
  kind: webhook

Each workflow gets its own unguessable URL, shown on the workflow page once its trigger is webhook. Send JSON to it from any system — monitoring tools, forms, other automations:

curl -X POST https://murmurator.rubyup.dev/hooks/w/<token> \
  -H "Content-Type: application/json" \
  -d '{"alert": {"title": "p95 latency > 2s", "service": "checkout"}}'

The payload has three parts:

Path Contains
trigger.body The parsed JSON body (or { raw } if it wasn't JSON)
trigger.query Query-string parameters
trigger.headers Content-Type, User-Agent and X-Request-Id

Responses: 202 with { "run_id": … } — the run number shown in the app, counted within your account — when a run starts, 404 when the workflow is disabled or isn't a webhook workflow, 422 when the definition is invalid, and 402 while the account's automation is paused.

Treat the URL like a password: anyone who has it can start runs while the trigger is enabled.

GitHub

trigger:
  kind: github
  connection: github             # your GitHub connection's slug
  events: [pull_request]         # optional; default is any event
  actions: [opened, ready_for_review]
  repos: [acme/api, acme/web]    # optional; default is any repository

Runs start when GitHub delivers an event that matches all of the filters you set. Filters you leave out match everything.

  • Setup: set a webhook secret on your GitHub connection, then add a webhook in the repository or organization settings using the request URL on the connection page, content type application/json, and the same secret. Choose the events your workflows need.
  • Supported events: pull_request, pull_request_review, pull_request_review_comment, issues, issue_comment, push, release, workflow_run, check_run.
  • Payload: the full GitHub webhook payload, plus trigger.event with the event name. Common paths: trigger.action, trigger.repository.full_name, trigger.pull_request.number, trigger.pull_request.title, trigger.issue.number, trigger.comment.body, trigger.sender.login.
  • Deliveries with a bad signature are rejected. One delivery can start several workflows if more than one matches.

Use conditions for finer filtering, such as skipping draft pull requests or matching a label.

Slack

trigger:
  kind: slack
  connection: slack        # your Slack connection's slug
  events: [app_mention]    # default; also message, reaction_added
  channels: [C02ANALYTICS] # optional; default is any channel
  include_bots: false      # default; ignore messages from bots
  • Setup: add the signing secret to your Slack connection, enable Event Subscriptions in your Slack app with the request URL on the connection page, and subscribe to the bot events you need (app_mention, message.channels, reaction_added).
  • Payload: the Slack event itself, plus trigger.team_id. Common paths: trigger.text, trigger.user, trigger.channel, trigger.ts and trigger.thread_ts. To reply in the same thread, pass thread_ts: "{{ trigger.ts }}" to slack.post_message.
  • Messages from bots — including your own workflow's replies — are ignored unless include_bots: true, which prevents loops. Edited and deleted messages (message subtypes) are ignored too.
  • Slack's automatic retries are acknowledged but don't start duplicate runs.

Linear

trigger:
  kind: linear
  connection: linear       # your Linear connection's slug
  events: [Issue]          # optional; also Comment, Project, Cycle, ...
  actions: [update]        # optional; create, update or remove
  teams: [ENG]             # optional; team keys
  states: [In Review]      # optional; the state an issue is in, or has just moved to
  include_self: false      # default; ignore what this connection's own user did
  • Setup: add a webhook in Linear under Settings → API → Webhooks with the request URL on your Linear connection, and paste its signing secret on the connection.
  • Payload: Linear's webhook body. Common paths: trigger.type, trigger.action, trigger.url, trigger.data.identifier, trigger.data.title, trigger.data.state.name, trigger.data.team.key, trigger.data.body (for comments), trigger.updatedFrom and trigger.actor.name.
  • states on an update only fires when the state itself changed, so editing the title of an issue that's already In Review doesn't run the workflow again.
  • teams needs the team in the event, as Issue events carry it; to filter comments by team, use a condition on a looked-up issue instead.
  • Anything the connection's own user did — including your workflow's own comments and updates — is ignored unless include_self: true, which prevents loops.
  • Deliveries more than a minute old are refused, so a captured request can't be replayed later.

Catalog triggers

Catalog connections with triggers, such as Stripe, Shopify, Zendesk, GitLab, Jira and PagerDuty, all share one shape. The trigger's kind is the connection's kind:

trigger:
  kind: stripe
  connection: stripe                    # your Stripe connection's slug
  events: [invoice.payment_failed]      # optional; the connection page lists them
  livemode: [true]                      # optional; each provider has its own filter keys
  • Setup: paste the webhook signing secret on the connection, then add a webhook in the provider with the request URL shown on the connection page. The connection page and the integrations page spell out where each provider keeps both.
  • Events: named exactly as the provider names them, such as invoice.payment_failed for Stripe, orders/create for Shopify or merge_request for GitLab. Leave events out to run on every event the webhook sends.
  • Filter keys: each provider adds its own, such as projects and actions for GitLab, or forms for Typeform. Each takes a list and matches when the payload's value, or any item of a list value, equals one of them, ignoring case. The connection page lists its trigger keys.
  • Payload: the provider's webhook JSON body, plus trigger.event for the event name.
  • Every delivery's signature is checked with the connection's secret, and deliveries with a stale timestamp are refused wherever the provider sends one.
  • A workflow that changes the same record its trigger watches, such as updating a Zendesk ticket from a ticket-updated trigger, can set itself off again. Use a filter or a condition to skip the changes it makes itself.

Triggers Murmurator subscribes for

Microsoft Outlook, Microsoft Teams, Google Calendar and Gmail don't let you paste a webhook URL into a settings page. Their connections make the subscription for you instead:

  1. Connect the account, tick what to watch (Watch new Inbox mail, Watch calendar event changes, Watch channel messages, Watch calendar changes or Watch the inbox) and save.
  2. Murmurator subscribes with the connection's own URL and a secret it generates, and the connection page shows each subscription and when it expires. Subscribe now retries straight away if one failed.
  3. Subscriptions expire after a few days at most, so Murmurator renews each one halfway through its life, replacing it if the provider won't renew it. When Microsoft reports a subscription removed or needing reauthorization, it's renewed at once. Unticking a box, or removing the connection, removes the subscription.
trigger:
  kind: microsoft_outlook
  connection: outlook
  events: [created]
  watch: [inbox]        # which watched resource the event came from
  • Outlook and Teams notifications say what changed, not its contents: trigger.resourceData.id is the message or event ID, and trigger.resource its Graph path. Read it with get_message or list_channel_messages. Teams needs the team and channel IDs set on the connection, and an admin's consent to the ChannelMessage.Read.All permission.
  • Google Calendar notifications only say that the calendar changed (exists), with the channel details in trigger.headers. Follow up with list_events and an updated-since time.
  • Gmail delivers through Google Cloud Pub/Sub: create a topic in the same Google Cloud project as the OAuth client, let gmail-api-push@system.gserviceaccount.com publish to it, and add a push subscription whose endpoint is the request URL on the connection page, token included. trigger.decoded.historyId and trigger.decoded.emailAddress say which mailbox changed; follow up with search_messages.
  • Every one of these needs Murmurator to be reachable over HTTPS at a public address, as the providers call it directly.

When triggers don't fire

  • The workflow is disabled, or it's still a draft.
  • The event doesn't match the trigger's events or filter keys (action, repository, channel, team, state and so on).
  • The webhook secret or signing secret on the connection doesn't match the one the provider signs with.
  • For Outlook, Teams, Google Calendar and Gmail, a subscription failed or lapsed: the connection page shows why, and Subscribe now retries.
  • The account's automation is paused because the subscription lapsed — see Billing & usage.