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
| Type | When to Use |
|---|---|
workflow_dispatch | Manual start with explicit inputs |
schedule | Time-based, recurring automation |
webhook | Respond to external system events (GitHub, Jira, Slack, etc.) |
workflow_call | Called by another workflow as a reusable sub-workflow |
A single workflow can declare multiple trigger types simultaneously:
1on:
2 workflow_dispatch:
3 inputs:
4 ...
5 schedule:
6 - cron: "0 9 * * MON"
7 webhook:
8 - event: scm.pushWhen 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
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: falseInput Types
| Type | Description | Notes |
|---|---|---|
string | Text value | Can use enum to restrict values |
number | Floating-point number | |
integer | Whole number | |
boolean | true or false | |
object | JSON object | |
array | JSON array | Can define items schema |
Enum (Closed Sets)
Use enum to restrict a string input to a specific set of values:
1priority:
2 type: string
3 enum: [low, medium, high, critical]
4 required: trueThis is validated at dispatch time. Any value outside the enum is rejected.
Dispatching via API
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
1jobs:
2 analyze:
3 uses: xema/agent
4 with:
5 project: ${{ inputs.project_name }}
6 budget: ${{ inputs.budget }}
7 priority: ${{ inputs.priority }}Trigger Context
1${{ inputs.project_name }} # Input value
2${{ trigger.kind }} # "workflow_dispatch"
3${{ trigger.correlationId }} # Unique run correlation IDschedule — Time-Based
Schedule workflows to run automatically at specific times using cron syntax.
Syntax
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: monthlyMultiple schedules can be declared — each fires independently.
Cron Format
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
| Schedule | Expression |
|---|---|
| Every minute | * * * * * |
| Every 15 minutes | */15 * * * * |
| Every hour | 0 * * * * |
| Daily at midnight (UTC) | 0 0 * * * |
| Daily at 9 AM | 0 9 * * * |
| Weekdays at 9 AM | 0 9 * * 1-5 |
| Monday at 9 AM | 0 9 * * MON |
| First day of month | 0 0 1 * * |
| First Monday of month | 0 0 * * 1#1 |
Timezone
By default, cron times are evaluated in UTC. Specify a timezone to use local time:
1- cron: "0 9 * * MON"
2 timezone: "America/New_York" # 9 AM New York timeValid 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:
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: monthlyManaging Schedules via API
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
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 valuesExample: Weekly Report with Multiple Schedules
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
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
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 workflowsAll 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)
| Event | When Fired |
|---|---|
scm.push | Code pushed to a branch |
scm.pull_request | PR opened, updated, or closed |
Common filters: branch, targetBranch, state, repository.fullName
Tracker (Issue Tracking)
| Event | When Fired |
|---|---|
tracker.issue | Issue created or updated |
Common filters: type, priority, status, assignee
Documentation
| Event | When Fired |
|---|---|
doc.page | Documentation page published or updated |
Webhook Filters
Filters let you narrow which events trigger your workflow:
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 typeFilter 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.*:
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 SHAConfiguring 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
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
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 }} eventworkflow_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
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:
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:
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
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 parentTrigger Context Reference
All triggers share a common base context plus trigger-specific fields:
Common Fields
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 timestampBy Trigger Type
| Field | dispatch | schedule | webhook | workflow_call |
|---|---|---|---|---|
trigger.kind | ✓ | ✓ | ✓ | ✓ |
trigger.actorSubject | ✓ | null | null | null |
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:
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 typeNext: Concurrency & Execution Control
See Also: DSL Reference: Triggers