Triggering workflows from a chat button, end to end
How api_trigger workflows work: finding eligible ones, firing them from a button click or the CLI, passing payloads, and reading trigger.* variables in the workflow's own steps.
A workflow trigger button looks simple — click, workflow runs — but the eligibility rule, the payload contract, and the variable namespace the workflow reads from are all specific enough that getting one detail wrong (a non-api_trigger workflow, an array instead of an object payload) fails loudly rather than silently. This is the full contract.
Eligibility: only api_trigger workflows
A Dailybot workflow has an event type — schedule, form submission, check-in completion, or api_trigger (“When triggered via API or button”). Only api_trigger workflows can be fired from the outside — via the CLI, the REST endpoint, or a chat button’s callback_workflow. Anything else returns 400 workflow_not_triggerable.
Workflows are created and edited exclusively in the Dailybot web app — there is no CLI path to create or change one. Once an api_trigger workflow exists, resolve its UUID:
dailybot workflow list --filter api_trigger --json
--filter api_trigger is a client-side convenience over the standard workflow list — it returns only the workflows you can legally fire. Inspect one’s configuration with:
dailybot workflow get <workflow_uuid> --json
Treat the returned JSON as the source of truth for what the workflow actually does — don’t guess from its name.
Firing it directly (CLI / API)
# Fire it, no payload
dailybot workflow trigger <workflow_uuid> --json
# Fire it with a JSON payload the workflow can read
dailybot workflow trigger <workflow_uuid> \
--payload '{"version": "v2.5", "environment": "production"}' --json
HTTP equivalent:
curl -s -X POST \
-H "Authorization: Bearer $DAILYBOT_BEARER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"payload": {"version": "v2.5"}}' \
https://api.dailybot.com/v1/workflows/<workflow_uuid>/trigger/
A successful trigger returns HTTP 202 — the run is queued, not synchronous, and the response carries no run output; the workflow executes on the server asynchronously. --payload (or the request body’s payload) must be a JSON object — not an array, not a scalar — and is capped at 8 KiB; anything larger or malformed returns 400 workflow_trigger_payload_invalid. A frozen (disabled) workflow returns 403 workflow_frozen; a caller without execute permission gets 403 workflow_execute_not_allowed; an unknown UUID returns 404.
Confirm before triggering.
workflow triggeris side-effecting — it can start a deploy or any other automation. Restate the target workflow (name + UUID) and the payload (or “no payload”) and wait for an explicit yes before firing it from an agent context.
Firing it from a chat button
The same server-side trigger fires when a message button’s callback_workflow is clicked:
curl -sS -X POST 'https://api.dailybot.com/v1/send-message/' \
-H 'X-API-KEY: $DAILYBOT_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"message": "Ready to redeploy staging?",
"target_channels": ["C0123456789"],
"buttons": [
{"label": "Redeploy staging", "button_type": "interactive", "value": "redeploy_staging",
"callback_workflow": "$REDEPLOY_WORKFLOW_UUID"}
]
}'
CLI shorthand:
dailybot chat send -c C0123456789 -m "Ready to redeploy staging?" \
--workflow-button "Redeploy staging=$REDEPLOY_WORKFLOW_UUID"
callback_workflow takes the workflow UUID only — names and slugs are rejected. Unknown, inactive, or cross-organization UUIDs return 400 button_callback_workflow_not_found. No external HTTP call happens and no signing secret is involved: the trigger is entirely internal to Dailybot, attributed to the clicking user.
Button-triggered vs. API-triggered — same mechanism, one variable tells them apart
Both paths run through the identical trigger mechanism server-side. The only difference visible to the workflow’s own steps is {{trigger.source}}, which resolves to "api", "button_click", or "modal_submit" — so one workflow can branch its behavior (for example, skip a confirmation step when triggered by an already-confirmed button click, but ask again when triggered directly via the API).
Reading the click context inside the workflow
Steps in the triggered workflow reference the trigger’s context through the {{trigger.*}} namespace:
| Variable | Value |
|---|---|
{{trigger.source}} |
"api" | "button_click" | "modal_submit" |
{{trigger.button_value}} |
The clicked button’s value |
{{trigger.button_id}} |
The server-minted $btn/<uuid4> |
{{trigger.fields.<name>}} |
A modal_body input’s submitted value, if the button opened a modal first |
{{trigger.clicked_at}} |
Click timestamp |
{{trigger.body.*}} |
The raw --payload object, when triggered directly via API/CLI |
{{trigger.user.*}} |
uuid, full_name, first_name, email, role of the clicking/triggering user |
{{trigger.triggered_by_user_uuid}} |
Shortcut to the triggering user’s UUID |
One workflow, several buttons
Rather than creating near-duplicate workflows to vary a label, point several buttons at the same callback_workflow UUID with different values, and branch inside the workflow on {{trigger.button_value}}:
{"message": "Choose a rollback target",
"target_channels": ["C0123456789"],
"buttons": [
{"label": "Rollback to v2.3", "button_type": "interactive", "value": "v2.3", "callback_workflow": "$ROLLBACK_WORKFLOW_UUID"},
{"label": "Rollback to v2.2", "button_type": "interactive", "value": "v2.2", "callback_workflow": "$ROLLBACK_WORKFLOW_UUID"}
]}
Modal → workflow: collecting input before the trigger
modal_body composes with callback_workflow — the click opens the modal, and on submit the field values are delivered as {{trigger.fields.<input.name>}}, no external server involved:
{"label": "Report", "button_type": "interactive", "value": "report",
"callback_workflow": "$INCIDENT_WORKFLOW_UUID",
"modal_body": {
"title": "New incident",
"blocks": [
{"type": "input", "name": "summary", "label": "What happened?", "multiline": true, "required": true}
]
}}
The workflow can use {{trigger.fields.summary}} in any step — pre-fill a form answer, compose a follow-up message, or feed an AI prompt.
Error reference
| Error | When |
|---|---|
400 workflow_not_triggerable |
The workflow’s event type is not api_trigger |
400 workflow_trigger_payload_invalid |
Payload isn’t a JSON object, or exceeds 8 KiB |
400 button_callback_workflow_not_found |
callback_workflow doesn’t resolve to an active workflow in the caller’s org |
403 workflow_execute_not_allowed |
Caller lacks permission to execute workflows |
403 workflow_frozen |
The workflow is disabled |
403 plan_upgrade_required |
Workflows aren’t on the org’s plan |
404 |
Unknown workflow UUID |
Cross-references
- The send-message API, end to end for the full button contract
callback_workflowlives inside. /developers/api/workflowsfor the complete{{trigger.*}}reference./developers/cliforworkflow list/get/triggerflags.
Browse the Solutions hub for the product-facing tour of triggering automations straight from chat.
FAQ
- Which workflows can be triggered from a chat button or the API?
- Only workflows whose event type is api_trigger ("When triggered via API or button"). Workflows with other event types — schedule, form submission, etc. — return workflow_not_triggerable if you try to fire them via the API, workflow trigger CLI command, or a button's callback_workflow.
- How do I find which workflows are eligible to trigger from a button?
- Run dailybot workflow list --filter api_trigger --json. This is a client-side convenience filter over the standard list endpoint that returns only workflows with event type api_trigger.
- How do I pass data into a triggered workflow?
- Pass a JSON object payload — via --payload on dailybot workflow trigger, or the request body's payload field on POST /v1/workflows/<uuid>/trigger/. It must be a JSON object (not an array or scalar), capped at 8 KiB, and the workflow's steps can reference it as {{trigger.body.<key>}}.
- What's the difference between triggering a workflow via the API and via a button click?
- Both use the exact same server-side trigger mechanism and both return the workflow to run asynchronously. The difference is only in {{trigger.source}}, which reports "api", "button_click", or "modal_submit" so a workflow's steps can branch on how they were invoked.
- Can one workflow be triggered by several different buttons with different meanings?
- Yes — point several buttons at the same callback_workflow UUID with different value strings, and branch inside the workflow's steps on {{trigger.button_value}}. This avoids creating near-duplicate workflows just to vary the button label.