Skip to content

Manual Approval

Pauses workflow execution until a human approves or rejects. Sends the approval request via configured Slack/Email/Telegram connections, falls back to an in-app notification if no channel is configured.

How it works

  1. The Approval node generates a unique approval token.
  2. It dispatches the request in parallel to every channel you configured (Slack, Email, Telegram). Each delivery contains an Approve and a Reject link.
  3. If no channel is configured (or every dispatch fails), an in-app notification appears in the bell-icon panel with inline Approve/Reject buttons.
  4. A human clicks Approve or Reject — from chat, email, the dashboard, or any of them; first-to-respond wins.
  5. The workflow resumes on the matching output handle (approve or reject), and also on output, with decision data.

Configuration

FieldDescriptionNotes
Timeout (seconds)How long to wait for a decision before failing the nodeDefault 3600 (1 hour). Hard maximum 86400 (24 hours) in v1.
Approval MessageMessage shown to the approverOptional, templatable — e.g., Approve order #{{data.id}}?.
Slack ConnectionSlack workspace to notifyOptional. When set, Slack Channel becomes required.
Slack ChannelChannel name or IDe.g., #approvals or C0123456. Templatable.
Email ConnectionSMTP connection to send throughOptional. When set, Recipients becomes required.
RecipientsComma-separated email addressese.g., alice@example.com, bob@example.com. Templatable.
Telegram ConnectionTelegram bot connectionOptional. When set, Telegram Chat ID becomes required.
Telegram Chat IDNumeric chat ID or @channelnameTemplatable.

You can configure any combination — none, one, or all three. Multiple channels fire in parallel.

Output handles

Three outputs are emitted; downstream nodes can be wired to any of them.

HandleFires whenUse for
outputAlways (any decision)Default-wired downstream that needs the decision data without branching
approveDecision was approveBranch for the approved path
rejectDecision was rejectBranch for the rejected path

Each output item contains:

FieldDescription
decision"approve" or "reject".
commentOptional comment submitted via the JSON API.
decidedAtRFC 3339 timestamp of the decision.
tokenThe approval token.
approveUrlThe absolute approve URL.
rejectUrlThe absolute reject URL.
messageThe configured approval message (template-resolved).
dataPass-through of upstream input data.

TIP

For simple "do X if approved, otherwise Y" flows, wire the approve and reject handles directly. For more elaborate branching, wire the default output and use a Switch node downstream on the decision field.

Channel delivery formats

  • Slack — A message in the chosen channel with the approval message and <approve_url|✅ Approve> • <reject_url|❌ Reject> mrkdwn links.
  • Email — An HTML email to the listed recipients with styled Approve/Reject buttons; subject = the approval message.
  • Telegram — A message in the target chat with an inline keyboard: two URL buttons (Approve / Reject).

Per-channel delivery failures are logged but don't fail the node — as long as one channel reached a human (or the in-app fallback fired), the workflow keeps waiting.

In-app fallback

When no channel is configured, the approval appears as a notification in the bell icon with inline Approve/Reject buttons. Click either to record the decision and resume the workflow. The fallback is gated by the user's notification preferences (channel in_app, event type approval_request); the default is enabled.

WARNING

The in-app fallback fires only when zero channels are configured. If you set up Slack but the Slack workspace is offline, the workflow still waits — it doesn't fall back to in-app. Configure multiple channels for redundancy.

API endpoints

Approval URLs work for any HTTP client; they're not Slack-specific.

  • GET /api/approvals/{token}/{decision} — Renders an HTML confirmation page. Suitable for email/chat clicks.
  • POST /api/approvals/{token}/{decision} — JSON API. Body: {"comment": "optional comment"}. Returns {"decision": "...", "status": "recorded"}.

{decision} must be approve or reject. Tokens are single-use; a second submission returns 409 Conflict with already decided.

TIP

The token is the credential — keep approval URLs out of public logs. Tokens are 32 random bytes, URL-safe, and expire when the timeout elapses.

Timeout behaviour

If the timeout elapses with no decision, the node fails. Use an on-error edge to handle the timeout (e.g., notify a manager, auto-reject, escalate).

Limits

  • A waiting approval node holds one worker slot in the per-tenant concurrency limit. With the default 1-hour timeout this is fine; very long pending approvals (8–24h) tie up a slot.
  • v1 does not survive worker restarts — if the worker process restarts while waiting, the pending approval is orphaned and the workflow times out. Restart-safe suspension is on the v2 roadmap.
  • For Slack/Email/Telegram links to be clickable from external clients, the backend's WEBHOOK_BASE_URL env var must be set to the public URL of the API (e.g., https://api.example.com). Local dev defaults to http://localhost:8000.

Example

Order requires manager approval before shipping

Webhook trigger (order received)
  → Manual Approval
      timeout: 14400 (4 hours)
      message: "Approve order #{{data.id}} ({{data.total}})"
      slack: #approvals
      email: ops@example.com
    ├ approve → Shopify (mark fulfilled) → Email (shipping confirmation)
    └ reject  → Email (notify customer, refund)

When the workflow runs, the manager receives both a Slack message and an email with Approve/Reject buttons. Either click decides the approval; the workflow continues on the matching branch.