Skip to content
Academy Menu

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.

deep-diveDeveloperOps9 min read

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 trigger is 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_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

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.