SWIRLS_
Workflows

Resources

Define forms, webhooks, and schedules as workflow entry points.

What it is. The ways the outside world hands input to your system: forms collect it from people, webhooks accept it from programs, and schedules generate it on a clock.

Use it when you have forms today (Google Forms, Typeform, your website), apps that can call a webhook, or a procedure you run on a schedule.

Works with triggers (the binding that starts the workflow) and credential profiles (type: basic for public form gates).

Resources are the entry points for your workflows. Each resource type generates a trigger that starts a workflow run. Connect resources to workflows using triggers.

Forms

A form defines an input interface with a JSON Schema for its payload. Swirls server-renders the form from that schema (each property's "title" becomes the field label) and hosts it at a public endpoint; submissions are CSRF-protected. You can inline the schema with @json { … } or define a reusable top-level schema block and set schema: my_schema_name on the form (and on workflow nodes) so one definition is shared across triggers and steps.

FieldTypeRequiredDescription
labelstringYesDisplay name
descriptionstringNoDescription shown in the Portal
visibilitystringNopublic or internal. Defaults to internal.
enabledbooleanNoWhether the form is active
authidentifierNoName of a top-level auth block with type: basic for HTTP Basic auth gating
schemajson block or nameNoJSON Schema for the form payload, or a reference to a schema resource

Visibility. public forms are served at /triggers/forms/:projectId/:formName. internal forms return 404 from the hosted endpoint but remain usable from the Cloud dashboard, where submitting still fires the trigger. The default is internal.

HTTP Basic auth. Set auth: <block_name> where the referenced auth block is a credential profile with type: basic. A valid Authorization: Basic header is then required before the hosted form renders or accepts a submission. This only applies to public forms: an internal form 404s on the hosted endpoint before any auth check, so auth: on it has no effect.

schema contact_payload {
  label: "Contact payload"
  schema: @json {
    {
      "type": "object",
      "required": ["name", "email", "message"],
      "properties": {
        "name": { "type": "string", "title": "Name" },
        "email": { "type": "string", "title": "Email" },
        "message": { "type": "string", "title": "Message" }
      },
      "additionalProperties": false
    }
  }
}

form contact_form {
  label: "Contact Form"
  description: "Collect contact information"
  visibility: public
  enabled: true
  schema: contact_payload
}

A password-protected public form:

secret portal_auth {
  vars: [PORTAL_USER, PORTAL_PASS]
}

auth portal_basic {
  type: basic
  secrets: portal_auth
  username: PORTAL_USER
  password: PORTAL_PASS
}

form partner_intake {
  label: "Partner Intake"
  visibility: public
  auth: portal_basic
  enabled: true
  schema: contact_payload
}

Webhooks

A webhook defines a hosted HTTP POST endpoint at /triggers/webhooks/:projectId/:webhookName.

FieldTypeRequiredDescription
labelstringYesDisplay name
descriptionstringNoDescription
enabledbooleanNoWhether the webhook is active
schemajson block or nameNoJSON Schema for the expected payload
secret<block>.<var>NoReference to a secret var for shared-secret verification
headerstringNoHeader name that carries the shared secret (e.g. "X-Webhook-Secret")

Shared-secret auth. The sender puts the secret value in the configured header; Swirls compares it against your stored secret in constant time. A missing or wrong secret gets a uniform 401. Rules:

  • secret and header go together: setting one without the other is a validator error.
  • Without both, the webhook accepts any POST. That is allowed, but the validator warns so the choice is auditable.
  • The header name must be a custom header. Standard headers that proxies or clients control (Cookie, Host, Content-Type, X-Forwarded-*, User-Agent, and similar) are rejected; Authorization is allowed.

Payload handling. An empty body is treated as {}. Invalid JSON gets 400. When a schema is declared, the payload is validated against it; a mismatch gets 422 with field-level errors. An accepted request gets 202 with the started execution ids. A disabled webhook returns 200 with an explanatory body so senders do not retry forever.

secret partner_creds {
  vars: [PARTNER_WEBHOOK_SECRET]
}

webhook partner_events {
  label: "Partner Events"
  enabled: true
  secret: partner_creds.PARTNER_WEBHOOK_SECRET
  header: "X-Webhook-Secret"
  schema: @json {
    {
      "type": "object",
      "required": ["type", "data"],
      "properties": {
        "type": { "type": "string" },
        "data": { "type": "object" }
      },
      "additionalProperties": false
    }
  }
}

Schedules

A schedule triggers a workflow on a cron expression.

FieldTypeRequiredDescription
labelstringYesDisplay name
cronstringYesStandard 5-field cron expression
timezonestringNoIANA timezone (e.g. "America/New_York")
enabledbooleanNoWhether the schedule is active

Semantics:

  • Schedules fire with one-minute granularity. The cron expression is evaluated in the schedule's timezone.
  • The payload is always {}. A scheduled workflow's root should not expect input fields.
  • A schedule never overlaps itself: a run that is still going blocks the next occurrence, and the next run time is computed before firing, so a slow run does not delay the occurrence after it.
schedule daily_report {
  label: "Daily Report"
  cron: "0 9 * * *"
  timezone: "America/New_York"
  enabled: true
}

Connecting resources to workflows

Resources do not run workflows on their own. You connect a resource to a workflow using a trigger. See Triggers for details.

On this page