Skip to content

Return Step

The return step terminates workflow execution immediately and optionally shapes the response sent back to the caller. All remaining steps after the return are skipped. The step editor adapts its form to the trigger type — webhook, form (post-submit), form (confirmation), form (prefill), or sub-workflow.

When a workflow is triggered by a webhook, the return step controls what the caller receives back as the HTTP response. Configure it in the Webhook Response tab of the return step editor:

Return step configured with webhook response fields including status code and body
FieldDescriptionExample
Status CodeHTTP status code to return (default 200)201
BodyResponse body — JSON object, string, or template expression{ "userId": "{{ create-user.id }}" }
Body Encodingjson (default), text (raw string / XML / HTML), or base64 (binary file)text
Content TypeMIME type for the Content-Type header (default application/json)text/csv
FilenameIf set, sets Content-Disposition so the caller’s browser triggers a download. Only meaningful with bodyEncoding: "base64".report.csv
HeadersOptional custom response headers as a flat key→value map. Values support templates.{ "X-Request-Id": "{{ start.id }}", "Cache-Control": "no-store" }

Reserved transport headers (Content-Length, Transfer-Encoding, Connection, Keep-Alive, Host) are ignored. Content-Type and Content-Disposition set via the Content Type / Filename fields take precedence over the same header set in Headers.

When triggered by a form, the return step controls what the submitter sees after submission. Configure it in the Form Response tab of the return step editor:

Return step configured with form response fields including message and type
FieldDescriptionExample
TypeVisual style: success (green), error (red), warning (amber), or info (blue)success
MessageMain heading shown to the user — supports templatesThanks, {{ initial.name }}!
DescriptionSubtitle below the headingYour request has been received.
Redirect URLIf set, redirects the user instead of showing the response screen — supports templateshttps://example.com/thank-you?ref={{ create-order.id }}
DataKey-value pairs displayed below the message as a structured detail table{ "Reference": "{{ create-order.id }}", "ETA": "2 days" }
ActionsArray of [{ label, url, style?, mode? }] for action buttons. See Form Triggers — Action Buttons[{ "label": "View order", "url": "..." }]
Behaviorredirect (default), stay, or reset — what happens after the user clicks Submit Anotherstay

A confirmation workflow runs after the user clicks Submit but before the main workflow runs, and returns a structured summary that QuickFlo shows in a confirmation dialog. The user has to explicitly approve before the main workflow executes.

The confirmation workflow’s return step writes a formConfirmation object:

FieldDescription
titleDialog heading (e.g. "Confirm your order")
summaryOptional subtitle below the title
itemsArray of detail rows: [{ label, value?, type? }]. type is 'info' (default), 'warning', or 'danger' and controls the row’s color.
warningMessageOptional amber warning message displayed below the items
{
"stepId": "review-summary",
"stepType": "core.return",
"input": {
"formConfirmation": {
"title": "Confirm your order",
"summary": "Please review before submitting.",
"items": [
{ "label": "Plan", "value": "Pro" },
{ "label": "Total", "value": "$99/month", "type": "info" },
{ "label": "Auto-renew", "value": "Enabled", "type": "warning" }
],
"warningMessage": "Cancellation is subject to our refund policy."
}
}
}

If the confirmation workflow fails or returns an empty formConfirmation, the dialog still appears with a generic prompt — the user can confirm or cancel either way.

A prefill workflow runs before the form renders and returns partial JSON Schema overrides for individual form fields — pre-populating defaults, restricting dropdown options based on the user’s session, hiding fields conditionally, and so on.

The prefill workflow’s return step writes a formPrefill object:

FieldDescription
fieldsMap of { fieldName: PrefillFieldOverrides } — each entry overrides a portion of the form’s JSON Schema for that field
meta.titleOptional — override the form’s title
meta.descriptionOptional — override the form’s description

Each PrefillFieldOverrides entry can carry any of the following — they’re merged into the field’s JSON Schema before the form renders:

OverridePurpose
defaultPre-populate the field’s value
enumRestrict dropdown / multi-select options to this list
enumLabelsMap of { enumValue: humanLabel } for friendly display names
enumCaptionsMap of { enumValue: longerCaption } for descriptions under each option
visibleSet to false to hide the field for this session
readonlySet to true to make the field read-only
propertiesFor table/multi-row fields, nested overrides per column

Example — pre-populate a contact form by looking up a customer from a query string token, restrict a region dropdown to the regions the user has access to, and hide the internal_notes field for non-admins:

{
"stepId": "prefill",
"stepType": "core.return",
"input": {
"formPrefill": {
"fields": {
"first_name": { "default": "{{ lookup-customer.body.firstName }}" },
"email": { "default": "{{ lookup-customer.body.email }}", "readonly": true },
"region": { "enum": "{{ lookup-customer.body.allowedRegions }}", "default": "{{ lookup-customer.body.defaultRegion }}" },
"internal_notes": { "visible": false }
},
"meta": {
"title": "Welcome back, {{ lookup-customer.body.firstName }}"
}
}
}
}

If the prefill workflow fails or times out, the form still renders with its static schema — a non-blocking warning is set on the response, and the user can fill out the form as normal.

When used inside a sub-workflow, the return step defines what the parent workflow receives as the step output. Add key-value pairs as flat top-level fields (no wrapper) for each value to return:

KeyValue
userId{{ user-check.id }}
statuscompleted

The parent workflow accesses these via the sub-workflow step ID:

{{ run-sub-workflow.userId }}
{{ run-sub-workflow.status }}

Each step has a Skip If condition in its settings. You can use this to make a return step conditional — for example, returning a 404 early if a lookup fails, while letting the workflow continue if it succeeds.

QuickFlo workflows can execute synchronously (wait for completion and return the result) or asynchronously (queue in the background and return immediately). The return step is what determines which mode is used.

The default mode is auto, which decides based on the trigger type and whether a return step exists:

TriggerHas Return Step with Response?Mode
WebhookYes (status code / body defined)Sync — caller waits, gets return step output
WebhookNoAsync — caller gets 202 Accepted with executionId
FormYes (form response defined)Sync — submitter sees the response
FormNoAsync — submitter sees a default success message
ScheduleN/AAsync always
Manual/APIN/AAsync by default

You can force sync or async execution regardless of auto-detection:

  • Query parameter: ?mode=sync or ?mode=async on the webhook/execution URL
  • Workflow settings: Set the execution mode to sync or async in the workflow settings dialog

Synchronous — The caller gets exactly what the return step defines, with the status code you set (default 200).

Asynchronous — The caller gets a 202 Accepted response with an executionId that can be used to check status later.