Workflow Definition

A workflow is a JSON or YAML document. It lists the HTTP steps you want run, and how they connect.

Structure

{
  "name": "kebab-case-name",
  "version": 1,
  "schemas": {},
  "triggers": [{"type": "http", "schema": "InputSchema"}],
  "steps": {}
}
Field Required Description
name Yes Lowercase kebab-case identifier
version Yes Positive integer
schemas No Named JSON Schema definitions
triggers Yes Array of trigger configs
steps Yes Object of step definitions

Steps

"charge": {
  "after": ["validate"],
  "when": "steps.validate.response.status == 200",
  "request": {
    "method": "POST",
    "url": "https://api.stripe.com/v1/charges",
    "headers": {"authorization": "Bearer {{ secrets.STRIPE_KEY }}"},
    "body": {"amount": "{{ steps.validate.response.body.total }}"}
  },
  "retry": {"on": [500, 502, 503], "max": 3},
  "compensate": {
    "method": "POST",
    "url": "https://api.stripe.com/v1/refunds"
  }
}
Field Description
after Step IDs this step depends on
when Expression that must be true to execute
request HTTP request (method, url, headers, body)
response Response config (timeout, schema)
retry Retry on status codes with backoff
compensate Rollback request if a later step fails
wait Pause for an external signal
strategy Fan-out, batch, race, or scatter

Patterns

Linear chain. Steps run in sequence — chain them with after.

Parallel branches. Give two steps the same after and they’ll run concurrently.

Fan-out. Work through a list with bounded concurrency:

"strategy": {"type": "fan_out", "over": "steps.get.response.body.items", "concurrency": 5}

Wait for a signal. Pause the run until something external pings you:

"wait": {"signal": "approved", "timeout": "24h", "on_timeout": "reject"}

Conditional steps. Use when to skip a step:

"process": {
  "when": "trigger.body.type == \"order\"",
  "request": { ... }
}

If when evaluates to false, the step is skipped.

A bare when string dispatches by prefix:

  • Starts with return → Lua (use this when you need crypto, string ops, etc.)
  • Anything else → path expression (==, !=, in, and/or/not, dot-paths, null)

To pick a runtime explicitly, use the block form { "expr": "...", "lang": "lua" | "path" | "jsonpath" }.

Trigger guards. Triggers can have when too, to reject requests before a run even starts. Webhook signature checks need Lua for crypto.hmac, so prefix with return:

triggers:
  - type: http
    when: |
      return trigger.headers['x-signature'] ==
             crypto.hex(crypto.hmac('sha256', secrets.WEBHOOK_SECRET, trigger.raw_body))

Returns 403 if the condition is false. Handy for webhook signature validation.

Trigger validation. Point at a schema to validate the incoming payload:

"triggers": [{"type": "http", "schema": "OrderInput"}]

Webhook callbacks

When an external service needs to call back into your run (payment providers love this), it needs to authenticate. Every run gets its own short-lived session token, exposed in expressions as {{ run.session }}. It lasts 24 hours and carries the tenant’s encryption context.

Drop it into webhook URLs as a query parameter:

webhookUrl: "https://dispatched.work/workflows/my_webhook?dispatched_session={{ run.session }}"

The engine accepts ?dispatched_session= as an alternative to the Dispatched-Session header, so a service that can’t set custom headers can still authenticate.

Editor assistance

A JSON Schema for workflow files lives at /schemas/workflow.schema.json. Point your editor at it and you’ll get completion and validation as you write.

JSON — add $schema at the top:

{
  "$schema": "https://dispatched.work/schemas/workflow.schema.json",
  "name": "my-workflow",
  "version": 1
}

YAML — drop a yaml-language-server modeline on the first line (you’ll need the YAML extension in VS Code, or the equivalent elsewhere):

# yaml-language-server: $schema=https://dispatched.work/schemas/workflow.schema.json
name: my-workflow
version: 1

Project-wide in VS Code — match by filename in .vscode/settings.json:

{
  "json.schemas": [
    { "fileMatch": ["workflows/*.json"], "url": "https://dispatched.work/schemas/workflow.schema.json" }
  ],
  "yaml.schemas": {
    "https://dispatched.work/schemas/workflow.schema.json": ["workflows/*.yaml", "workflows/*.yml"]
  }
}

The schema covers structural rules — required fields, step id patterns, HTTP methods, strategy shapes, retry/backoff, wait durations, that kind of thing. Semantic checks (DAG cycle detection, $ref resolution, Lua syntax, cron field counts) stay server-side and run at publish time.