HITL Approvals
Pause an ActionFlow at a human-in-the-loop node, deliver a review request, record a decision, and continue from the saved partial execution.
HITL Approvals
HITL (human in the loop) nodes pause a Flow until a person acts. The runner stores the completed prefix, creates a pending request, and marks the run waitingForHitl. It does not execute downstream nodes while the request is pending.
Current limitation: A HITL timeout expires the signed decision link; it does not automatically continue the graph with a timeout result. A valid, unexpired decision is required to queue the continuation run.
Nodes
Catalog category: Human in the loop.
| Node | Slug | Delivery |
|---|---|---|
| Human in the Loop Manual | hitlManual | In-product Approvals |
| Human in the Loop Slack | hitlSlack | Slack |
| Human in the Loop Discord | hitlDiscord | Discord |
| Human in the Loop Teams | hitlTeams | Microsoft Teams |
| Human in the Loop Telegram | hitlTelegram | Telegram |
| Human in the Loop WhatsApp | hitlWhatsApp | |
| Human in the Loop SMS | hitlSMS | SMS |
| Human in the Loop Email | hitlEmail | Platform email service (no organization SMTP) |
| Resend | hitlResend | Named Resend API key |
| SMTP | hitlSmtp | Named SMTP connection |
Typical shared inputs are message, review data, timeout in minutes, and approval type (approve, edit, or reject). Channel nodes add their destination fields. Advanced fields include the callback URL and callbackSecret. The node form still exposes callbackSecret, but the current runtime ignores that value. Callback links are signed and verified with the platform signing secret, not a caller-supplied per-node secret.
The current node builder defaults the timeout to 60 minutes and bounds the value to 1–1,440 minutes. The callback URL must target the ActionFlows app origin and be HTTPS, except for an allowed localhost URL in development.
Request and decision lifecycle
- The HITL function returns a pending output containing a request ID, message, review data, callback URL, and expiry.
- The runtime stores the pending request with the prefix run's executed node IDs, results, and Loop context. The prefix run is charged for usage accumulated before the pause and is marked
waitingForHitl. - The selected channel delivers the request. Reviewers can open the in-product Approvals inbox or the channel-specific decision link.
- The callback first validates its query fields:
hitlRequestId,hitlExpiresAt,decision, andhitlSignaturemust be present, the expiry must be valid and in the future, and the signature must match. Only a missing or invalid query field, invalid expiry, or signature mismatch returns401before decision resolution. ThePOSTbody is parsed afterward: a non-JSON request uses its raw text asresponse, while anapplication/jsonbody can provide{ "response": "..." }; thepayloadproperty is not used. A malformed or missing body alone does not return401; the request can proceed to a202response, and a non-empty text body supplies the human response. - A query-valid
POSTto/api/hitl/callbackrecords the decision attempt and returns202.resolved: falsecan mean that the request was already resolved with a continuation, was not found or was deleted (including deletion during resolution), required a missing comment, could not enqueue its continuation, or hit an expiry race after the initial query validation. An already-resolved request without a continuation can retry enqueueing, so it is not inherently aresolved: falseresult. A validGETshows confirmation; it does not apply the decision. - When the decision is resolved and the continuation enqueue succeeds, ActionFlows queues a new continuation run linked to the pending request. The continuation restores the saved partial results and Loop context and starts at the HITL node; the prefix is not rerun.
- Once the continuation is enqueued, the prefix history row is marked completed. The continuation has its own run-history row and can finish, fail, or pause again.
The decision output contains an approval status (approved, edited, or rejected), approval time, approved data, the human response, and requiresHumanInput: false. If the node requires a comment, a decision without a non-empty response is rejected.
A callback 202 is an accepted response, not proof that the decision was resolved or the continuation was enqueued. Check resolved in the response; if it is false, the callback passed query validation but the request was already resolved with a continuation, was not found or was deleted (including deletion during resolution), required a missing comment, could not enqueue its continuation, or hit an expiry race after initial validation. An already-resolved request without a continuation can retry enqueueing. Inspect the pending request and Run history for the prefix and any continuation row. The callback response does not expose a continuation ID or enqueue error.
Where reviewers act
Pending work appears in:
- Approvals in the workspace sidebar (
/approvals); - the ActionFlow Approvals tab (
/actionflows/{actionFlowId}/approvals); - Actionflow Studio when a run is paused on a HITL node; and
- Slack, Discord, Teams, Telegram, WhatsApp, SMS, or email, depending on the node.
If a callback returns 401, its query fields, expiry, or signature did not pass validation, so it does not create or resolve a continuation. Body parsing is separate; a malformed or missing body alone does not produce 401. Check the pending request and the run's waitingForHitl state rather than assuming a timeout decision was applied.
Related
Webhook
Receive external POST deliveries at a saved ActionFlow webhook URL, with token, idempotency, validation, passive-flow, and response-status behavior.
Run History
Inspect ActionFlow run states, filtered persisted results, settled credit usage, reruns, cancellation, and HITL continuations without assuming history is a raw request archive.