Human Review
The Human Review step suspends a workflow and emails somebody for a decision. The run stays paused until they answer, then continues down the branch bound to the outcome they picked.
Use it as a checkpoint before anything irreversible, and before acting on AI output nobody has reviewed yet.
When to Use It
- Financial actions: approving a refund or a large payment before it processes.
- Publishing: reviewing AI-drafted posts or replies before they go out.
- Destructive operations: confirming deletion of records flagged automatically.
Setting it up
Add the step from Tools on the canvas dock. Only the first field is required.
| Field | Required | Description |
|---|---|---|
| Approver Email | Yes | Who is emailed. One address; a list separated by commas is rejected. |
| Email Subject | No | Subject line. Defaults to “Action required: approve workflow”. |
| Message | No | Instructions the reviewer reads. Takes references to earlier steps. |
| Context Data | No | Key and value pairs shown in the email and on the review page. |
| Decisions | No | The outcomes the reviewer can pick. Defaults to Approve and Deny. |
| Expire after (seconds) | No | Take the Expired branch if nobody answers in time. Blank or 0 waits indefinitely. |
| Form Title | No | Heading above the reviewer’s form. |
| Form Description | No | Helper text under the heading. |
| Form Fields | No | Fields the reviewer fills in alongside their decision. |
Notifying somebody is not the same as authorising them. The email goes to whatever address you enter, but responding requires a signed-in member of this workflow’s team. To collect a decision from a customer or another outside party, use the Wait step: its form and webhook modes accept an answer from outside Glow.
Giving the reviewer what they need
The Message is where the decision gets made, so put the values it rests on into it rather than sending a bare “please approve”:
Refund request from {{ 1.customer_email }}
Amount: {{ 1.amount }}
Reason given: {{ 2.result }}Context Data does the same job in a tidier shape. Each row is a label and a value, and the pairs are listed in the email and on the review page as a small table. A row named Deal with the value {{ 2.dealName }} reads better than the same reference buried in a paragraph.
Both are resolved when the step runs, so the reviewer sees the values as they were at that moment.
Naming the outcomes
Decisions is where you set what the reviewer can choose. Leave it empty and they get the default pair, Approve and Deny.
Each row you add takes a Value, which is how the choice is recorded, and a Button label, which is what the reviewer clicks. One row can be flagged as the Expired branch. Every decision gets its own button in the email and its own output on the canvas. “Approve”, “Send back for edits” and “Reject” become three branches you wire separately.
Connect each output to the steps that should follow that choice. A decision left unwired continues down the step’s normal output, and a Deny-style outcome settles the run through its error path.
Asking for a value alongside the decision
Form Fields puts a form on the review page. A reviewer can then supply a rejection reason or a corrected amount alongside their decision.
Each field takes a Field name, which is the key later steps read it by, plus a Label, a Type, whether it is Required, and optional Help text. The choice types take a comma-separated list of Choices. The types are short text, long text, number, date, dropdown, multi-select, checkbox, radio and file.
Answers arrive on the step as formData, keyed by the field names you chose. A field named rejectionReason is read by a later step as {{ 5.formData.rejectionReason }}.
Setting a deadline
Expire after (seconds) decides how long the request stays answerable. Leave it blank or at 0 and the request waits indefinitely.
Set it, and mark one of your Decisions rows as the Expired branch, and an unanswered request continues down that branch. Set it without marking one and the timeout settles the run through the step’s error path rather than being mistaken for approval.
What the Approver Sees
An email with your subject, your message, any Context Data you supplied, and a button per decision. Each link belongs to that one run: once a decision is submitted, the others stop working and the workflow resumes. Approvers away from a desk can answer from a phone browser too, through Mobile View.
Where you configured a form, the button opens the review page with the form on it, and the decision is submitted together with the answers.
What it passes on
While the workflow is paused, the step’s output is:
{
"status": "pending",
"approverEmail": "[email protected]",
"subject": "Action required: approve workflow",
"message": "Refund request from …",
"reviewUrl": "https://…",
"context": [{ "key": "Deal", "value": "Acme renewal" }],
"decisions": [{ "id": "approved", "label": "Approve" }],
"expiresAt": "2026-08-10T09:00:00.000Z"
}Glow emails the approver automatically. reviewUrl is also published so you can route it elsewhere. Post it to a Slack channel, for example, so whoever is on shift can act.
Once somebody answers, the step’s own result carries the outcome:
| Reference | Contains |
|---|---|
{{ N.decision }} | The Value of the decision that was picked |
{{ N.formData.<name> }} | What the reviewer filled in, where you set a form |
{{ N.answeredAt }} | When the answer arrived |
Where several outcomes must do different things, wiring each decision’s own output is the direct route. Reading {{ N.decision }} in a Condition after the step does the same job in one branch.
Limits
An expiry runs to a maximum of 30 days. The field refuses a larger number when you type it, so a run cannot end up waiting for a period the step never accepted.
Where a decision has to arrive sooner than anyone is likely to check email, set a short expiry and wire the Expired branch to a chaser. Posting the review link into a channel somebody is watching works too.
Troubleshooting
A workflow waiting for approval shows as Running in the execution log. That is expected. It is suspended, not stuck.
The approver did not get the email. Check the address in Approver Email for typos, and check spam. As a fallback, send reviewUrl yourself in a Slack or email step placed after the approval step.
The email was rejected before it went out. The field takes exactly one well-formed address. Two addresses separated by a comma fail the step rather than emailing both.
The link no longer works. Each link is valid for a single run, and only until the decision is made. Reopening it afterwards shows that it has already been answered.
Nobody ever responded. Set Expire after (seconds) and wire the Expired branch, so an unanswered request routes somewhere deliberate. A run that is no longer wanted can be cancelled from the Executions tab.
Two people answered at once. Only the first answer counts. The second is recorded as already answered and changes nothing.
What’s Next?
- Pause for a time, or until an outside system calls back, with Wait.
- Branch on the outcome with Conditions.
- Halt a run deliberately with Stop and Error.