Skip to main content

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.

NodeSlugDelivery
Human in the Loop ManualhitlManualIn-product Approvals
Human in the Loop SlackhitlSlackSlack
Human in the Loop DiscordhitlDiscordDiscord
Human in the Loop TeamshitlTeamsMicrosoft Teams
Human in the Loop TelegramhitlTelegramTelegram
Human in the Loop WhatsApphitlWhatsAppWhatsApp
Human in the Loop SMShitlSMSSMS
Human in the Loop EmailhitlEmailPlatform email service (no organization SMTP)
ResendhitlResendNamed Resend API key
SMTPhitlSmtpNamed 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

  1. The HITL function returns a pending output containing a request ID, message, review data, callback URL, and expiry.
  2. 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.
  3. The selected channel delivers the request. Reviewers can open the in-product Approvals inbox or the channel-specific decision link.
  4. The callback first validates its query fields: hitlRequestId, hitlExpiresAt, decision, and hitlSignature must 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 returns 401 before decision resolution. The POST body is parsed afterward: a non-JSON request uses its raw text as response, while an application/json body can provide { "response": "..." }; the payload property is not used. A malformed or missing body alone does not return 401; the request can proceed to a 202 response, and a non-empty text body supplies the human response.
  5. A query-valid POST to /api/hitl/callback records the decision attempt and returns 202. resolved: false can 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 a resolved: false result. A valid GET shows confirmation; it does not apply the decision.
  6. 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.
  7. 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.

On this page