Workflow Triggers

Workflows are started by triggers — events or actions that initiate a workflow run. Xema supports four trigger types, each designed for a different use case.

Trigger Types

TypeWhen to Use
workflow_dispatchManual start with explicit inputs
scheduleTime-based, recurring automation
webhookRespond to external system events (GitHub, Jira, Slack, etc.)
workflow_callCalled by another workflow as a reusable sub-workflow

A single workflow can declare multiple trigger types simultaneously:

yaml
1on:
2  workflow_dispatch:
3    inputs:
4      ...
5  schedule:
6    - cron: "0 9 * * MON"
7  webhook:
8    - event: scm.push

When triggered, each run carries a trigger payload that tells jobs how the workflow was started and what data it carries. You access this via the ${{ trigger.* }} context.


workflow_dispatch — Manual

The most straightforward trigger: a human or automated system manually starts the workflow with explicit input values.

Syntax

yaml
1on:
2  workflow_dispatch:
3    inputs:
4      project_name:
5        type: string
6        required: true
7        description: The name of the project
8      budget:
9        type: number
10        required: false
11        defaultValue: 100000
12      include_testing:
13        type: boolean
14        required: false
15        defaultValue: true
16      priority:
17        type: string
18        enum: [low, medium, high, critical]
19        required: true
20      tags:
21        type: array
22        required: false

Input Types

TypeDescriptionNotes
stringText valueCan use enum to restrict values
numberFloating-point number
integerWhole number
booleantrue or false
objectJSON object
arrayJSON arrayCan define items schema

Enum (Closed Sets)

Use enum to restrict a string input to a specific set of values:

yaml
1priority:
2  type: string
3  enum: [low, medium, high, critical]
4  required: true

This is validated at dispatch time. Any value outside the enum is rejected.

Dispatching via API

bash
1curl -X POST https://workflow-engine-api.xema.dev/workflows/{slug}/dispatch \
2  -H "Authorization: Bearer $TOKEN" \
3  -H "Content-Type: application/json" \
4  -d '{
5    "workflowSlug": "requirements-analysis",
6    "version": "1.0.0",
7    "inputs": {
8      "project_name": "Acme Platform",
9      "budget": 250000,
10      "include_testing": true,
11      "priority": "high"
12    }
13  }'

If version is omitted, the latest published version is used.

Accessing Inputs in Jobs

yaml
1jobs:
2  analyze:
3    uses: xema/agent
4    with:
5      project: ${{ inputs.project_name }}
6      budget: ${{ inputs.budget }}
7      priority: ${{ inputs.priority }}

Trigger Context

yaml
1${{ inputs.project_name }}   # Input value
2${{ trigger.kind }}          # "workflow_dispatch"
3${{ trigger.correlationId }} # Unique run correlation ID

schedule — Time-Based

Schedule workflows to run automatically at specific times using cron syntax.

Syntax

yaml
1on:
2  schedule:
3    - cron: "0 9 * * MON"
4      timezone: "America/New_York"
5      inputs:
6        report_type: weekly
7    - cron: "0 0 1 * *"
8      inputs:
9        report_type: monthly

Multiple schedules can be declared — each fires independently.

Cron Format

text
1┌───── minute (0–59)
2│ ┌───── hour (0–23)
3│ │ ┌───── day of month (1–31)
4│ │ │ ┌───── month (1–12)
5│ │ │ │ ┌───── day of week (0=Sunday, 1=Monday, ... 7=Sunday)
6│ │ │ │ │
7* * * * *

Common Cron Examples

ScheduleExpression
Every minute* * * * *
Every 15 minutes*/15 * * * *
Every hour0 * * * *
Daily at midnight (UTC)0 0 * * *
Daily at 9 AM0 9 * * *
Weekdays at 9 AM0 9 * * 1-5
Monday at 9 AM0 9 * * MON
First day of month0 0 1 * *
First Monday of month0 0 * * 1#1

Timezone

By default, cron times are evaluated in UTC. Specify a timezone to use local time:

yaml
1- cron: "0 9 * * MON"
2  timezone: "America/New_York"   # 9 AM New York time

Valid timezone names follow the IANA timezone database (e.g., Europe/London, Asia/Tokyo, America/Sao_Paulo).

Inputs on Schedules

Schedule inputs are validated against the workflow's workflow_dispatch.inputs schema. This means a workflow can be triggered both manually and on a schedule, sharing the same input declarations:

yaml
1on:
2  workflow_dispatch:
3    inputs:
4      report_type:
5        type: string
6        enum: [daily, weekly, monthly]
7        required: true
8  schedule:
9    - cron: "0 9 * * MON"
10      inputs:
11        report_type: weekly       # Pre-bound, validated against dispatch schema
12    - cron: "0 0 1 * *"
13      inputs:
14        report_type: monthly

Managing Schedules via API

bash
1# List schedules for a workflow
2curl "https://workflow-engine-api.xema.dev/schedules?workflowSlug=weekly-report" \
3  -H "Authorization: Bearer $TOKEN"
4
5# Create a new schedule
6curl -X POST "https://workflow-engine-api.xema.dev/schedules" \
7  -H "Authorization: Bearer $TOKEN" \
8  -d '{
9    "workflowSlug": "weekly-report",
10    "cronSpec": "0 9 * * FRI",
11    "timezone": "UTC",
12    "inputs": {"report_type": "weekly"}
13  }'
14
15# Pause a schedule
16curl -X PATCH "https://workflow-engine-api.xema.dev/schedules/{id}" \
17  -H "Authorization: Bearer $TOKEN" \
18  -d '{"state": "paused"}'
19
20# Resume a schedule
21curl -X PATCH "https://workflow-engine-api.xema.dev/schedules/{id}" \
22  -H "Authorization: Bearer $TOKEN" \
23  -d '{"state": "active"}'
24
25# Delete a schedule
26curl -X DELETE "https://workflow-engine-api.xema.dev/schedules/{id}" \
27  -H "Authorization: Bearer $TOKEN"

Trigger Context

yaml
1${{ trigger.kind }}        # "schedule"
2${{ trigger.cronSpec }}    # "0 9 * * MON" — the cron that fired
3${{ trigger.scheduleId }}  # Internal schedule ID
4${{ inputs.report_type }}  # Pre-bound input values

Example: Weekly Report with Multiple Schedules

yaml
1apiVersion: xema.dev/workflow/v1alpha1
2kind: Workflow
3metadata:
4  name: automated-reporting
5  version: 1.0.0
6
7on:
8  workflow_dispatch:
9    inputs:
10      report_type:
11        type: string
12        enum: [daily, weekly, monthly, quarterly]
13        required: true
14      recipients:
15        type: array
16        required: false
17  schedule:
18    - cron: "0 8 * * *"
19      timezone: "UTC"
20      inputs:
21        report_type: daily
22    - cron: "0 8 * * MON"
23      timezone: "UTC"
24      inputs:
25        report_type: weekly
26    - cron: "0 8 1 * *"
27      timezone: "UTC"
28      inputs:
29        report_type: monthly
30    - cron: "0 8 1 1,4,7,10 *"
31      inputs:
32        report_type: quarterly
33
34concurrency:
35  group: reporting-${{ inputs.report_type }}
36  mode: skip                # Skip if same report type already running
37
38jobs:
39  generate-report:
40    uses: xema/agent
41    with:
42      report_type: ${{ inputs.report_type }}
43      trigger_kind: ${{ trigger.kind }}
44      task: Generate ${{ inputs.report_type }} report
45    outputs:
46      report_id: ${{ result.artifact_id }}
47
48  send-report:
49    needs: generate-report
50    uses: xema/webhook
51    with:
52      url: https://api.acme.com/reports/send
53      payload:
54        report_id: ${{ needs.generate-report.outputs.report_id }}
55        report_type: ${{ inputs.report_type }}

webhook — Event-Driven

Trigger workflows in response to events from external systems — GitHub pushes, Jira issues, Slack messages, and more.

Syntax

yaml
1on:
2  webhook:
3    - event: scm.push
4      filters:
5        branch: main
6    - event: scm.pull_request
7      filters:
8        state: opened
9        targetBranch: main
10    - event: tracker.issue
11      filters:
12        type: feature
13        priority: [high, critical]

Multiple webhook subscriptions can be declared. Each fires independently.

How Webhooks Flow

text
1External Provider (GitHub, Jira, Slack, …)
2  → POST /webhooks to connector-gateway-api
3  → Adapter normalizes to canonical envelope
4  → Forward to workflow-engine-api
5  → Engine matches event type + filters
6  → Dispatch matching workflows

All external webhooks are normalized by the integration adapter before reaching your workflow. This means you work with consistent payload shapes regardless of provider.

Supported Events

SCM (Source Code Management)

EventWhen Fired
scm.pushCode pushed to a branch
scm.pull_requestPR opened, updated, or closed

Common filters: branch, targetBranch, state, repository.fullName

Tracker (Issue Tracking)

EventWhen Fired
tracker.issueIssue created or updated

Common filters: type, priority, status, assignee

Documentation

EventWhen Fired
doc.pageDocumentation page published or updated

Webhook Filters

Filters let you narrow which events trigger your workflow:

yaml
1on:
2  webhook:
3    - event: scm.push
4      filters:
5        branch: main                    # Exact string match
6    - event: tracker.issue
7      filters:
8        priority: [high, critical]      # Array = any of these values
9    - event: scm.pull_request
10      filters: {}                       # Empty = match all events of this type

Filter matching rules:

  • String value → exact match required
  • Array value → event field must match any of the array values
  • Empty filters {} → match all events of this type
  • Missing filters key → not checked (matches)

Trigger Context

The webhook payload is available via trigger.*:

yaml
1# Common trigger fields
2${{ trigger.kind }}                      # "webhook"
3${{ trigger.event }}                     # "scm.push"
4${{ trigger.provider }}                  # "github"
5${{ trigger.deliveryId }}               # Unique delivery ID (for idempotency)
6
7# Provider payload
8${{ trigger.payload.repository.fullName }}  # e.g., "acme/my-repo"
9${{ trigger.payload.pull_request.number }}  # PR number
10${{ trigger.payload.issue.key }}            # Jira issue key
11${{ trigger.payload.pull_request.title }}   # PR title
12${{ trigger.payload.branch }}               # Branch name
13${{ trigger.payload.commit.sha }}           # Commit SHA

Configuring Webhook Sources

External webhooks are configured per-connector in the Connector Gateway service. Each connector subscribes a specific provider and routes normalized events to the workflow engine.

See Integration Guide for provider setup instructions.

Example: GitHub PR Auto-Review

yaml
1apiVersion: xema.dev/workflow/v1alpha1
2kind: Workflow
3metadata:
4  name: pr-auto-review
5  version: 1.0.0
6
7on:
8  webhook:
9    - event: scm.pull_request
10      filters:
11        state: opened
12        targetBranch: main
13
14jobs:
15  review-pr:
16    uses: xema/agent
17    with:
18      task: Review pull request code quality
19      title: ${{ trigger.payload.pull_request.title }}
20      branch: ${{ trigger.payload.pull_request.branch }}
21      diff: ${{ trigger.payload.pull_request.diff }}
22    outputs:
23      review: ${{ result.review }}
24      approved: ${{ result.approved }}
25
26  post-review:
27    needs: review-pr
28    uses: xema/webhook
29    with:
30      url: ${{ trigger.payload.pull_request.comments_url }}
31      method: POST
32      body:
33        body: ${{ needs.review-pr.outputs.review }}

Example: Multi-Event Workflow

yaml
1on:
2  webhook:
3    - event: scm.push
4      filters:
5        branch: main
6    - event: scm.pull_request
7      filters:
8        state: [opened, synchronize]
9
10jobs:
11  handle-event:
12    uses: xema/agent
13    with:
14      event_type: ${{ trigger.event }}
15      payload: ${{ trigger.payload }}
16      task: Handle ${{ trigger.event }} event

workflow_call — Sub-Workflows

Mark a workflow as reusable so other workflows can call it as a job step. This enables composition and code reuse across workflows.

Declaring a Reusable Workflow

yaml
1apiVersion: xema.dev/workflow/v1alpha1
2kind: Workflow
3metadata:
4  name: requirements-sub-workflow
5  version: 1.0.0
6
7on:
8  workflow_call:
9    inputs:
10      scope:
11        type: string
12        enum: [simple, standard, enterprise]
13        required: true
14      project_name:
15        type: string
16        required: true
17    outputs:
18      spec_id:
19        value: ${{ needs.create-spec.outputs.spec_id }}
20      summary:
21        value: ${{ needs.analyze.outputs.summary }}
22
23jobs:
24  analyze:
25    uses: xema/agent
26    with:
27      scope: ${{ inputs.scope }}
28      task: Analyze requirements
29    outputs:
30      summary: ${{ result.summary }}
31
32  create-spec:
33    needs: analyze
34    uses: xema/emit-artifact
35    with:
36      content: ${{ needs.analyze.outputs.summary }}
37    outputs:
38      spec_id: ${{ result.artifact_id }}

Calling a Sub-Workflow

From a parent workflow, call a reusable workflow as a job:

yaml
1jobs:
2  call-requirements:
3    uses: my-org/requirements-sub-[email protected]
4    with:
5      scope: enterprise
6      project_name: ${{ inputs.project_name }}
7    outputs:
8      spec_id: ${{ result.spec_id }}
9      summary: ${{ result.summary }}
10
11  use-results:
12    needs: call-requirements
13    with:
14      spec_id: ${{ needs.call-requirements.outputs.spec_id }}

Sub-Workflow Outputs

Outputs from workflow_call are expressions referencing the sub-workflow's job outputs:

yaml
1on:
2  workflow_call:
3    outputs:
4      final_result:
5        value: ${{ needs.last-job.outputs.result }}   # Expression referencing an upstream job
6      item_count:
7        value: ${{ needs.enumerate.outputs.count }}

Trigger Context

yaml
1${{ trigger.kind }}             # "workflow_call"
2${{ trigger.parentRunId }}      # ID of the calling workflow run
3${{ trigger.parentWorkflowRef }}# e.g., "[email protected]"
4${{ inputs.scope }}             # Forwarded inputs from parent

Trigger Context Reference

All triggers share a common base context plus trigger-specific fields:

Common Fields

yaml
1${{ trigger.kind }}            # "workflow_dispatch" | "schedule" | "webhook" | "workflow_call"
2${{ trigger.correlationId }}   # Unique URN for this run (urn:xema:<uuid>)
3${{ trigger.orgId }}           # Organization ID
4${{ trigger.projectId }}       # Project ID
5${{ trigger.actorSubject }}    # User ID who triggered (null for schedule/webhook)
6${{ trigger.triggeredAt }}     # ISO 8601 UTC timestamp

By Trigger Type

Fielddispatchschedulewebhookworkflow_call
trigger.kind✓✓✓✓
trigger.actorSubject✓nullnullnull
trigger.cronSpec✗✓✗✗
trigger.scheduleId✗✓✗✗
trigger.event✗✗✓✗
trigger.provider✗✗✓✗
trigger.deliveryId✗✗✓✗
trigger.payload.*✗✗✓✗
trigger.parentRunId✗✗✗✓
trigger.parentWorkflowRef✗✗✗✓
inputs.*✓✓✗✓

Multi-Trigger Workflows

A workflow can be triggered by multiple trigger types. Use trigger.kind to handle them differently:

yaml
1on:
2  workflow_dispatch:
3    inputs:
4      report_type:
5        type: string
6        enum: [daily, weekly, monthly]
7        required: true
8  schedule:
9    - cron: "0 8 * * *"
10      inputs:
11        report_type: daily
12  webhook:
13    - event: tracker.issue
14      filters:
15        type: report_request
16
17jobs:
18  route-trigger:
19    uses: xema/agent
20    with:
21      triggered_by: ${{ trigger.kind }}
22      report_type: ${{ inputs.report_type }}
23      issue_key: ${{ trigger.payload.issue.key }}   # null for non-webhook
24      task: Generate report based on trigger type

Next: Concurrency & Execution Control
See Also: DSL Reference: Triggers