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
cronis a standard five-field expression: minute, hour, day of month, month, day of week.timezoneis an IANA name such asEurope/BerlinorAmerica/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
defaultvalue. The payload includesscheduled_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.eventwith 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.tsandtrigger.thread_ts. To reply in the same thread, passthread_ts: "{{ trigger.ts }}"toslack.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.updatedFromandtrigger.actor.name. stateson anupdateonly fires when the state itself changed, so editing the title of an issue that's already In Review doesn't run the workflow again.teamsneeds 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_failedfor Stripe,orders/createfor Shopify ormerge_requestfor GitLab. Leaveeventsout to run on every event the webhook sends. - Filter keys: each provider adds its own, such as
projectsandactionsfor GitLab, orformsfor 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.eventfor 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:
- 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.
- 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.
- 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.idis the message or event ID, andtrigger.resourceits Graph path. Read it withget_messageorlist_channel_messages. Teams needs the team and channel IDs set on the connection, and an admin's consent to theChannelMessage.Read.Allpermission. - Google Calendar notifications only say that the calendar changed (
exists), with the channel details intrigger.headers. Follow up withlist_eventsand 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.compublish to it, and add a push subscription whose endpoint is the request URL on the connection page, token included.trigger.decoded.historyIdandtrigger.decoded.emailAddresssay which mailbox changed; follow up withsearch_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.