Approval (PbotApproval)

PbotApproval pauses workflow execution and waits for a person to decide. An approval resumes the workflow. A rejection ends the run and keeps the decision in its record.


Basic Usage

json
{
  "type": "PbotApproval",
  "properties": {
    "comment": "Please review this expense for ${{input.amount}}",
    "request_payload": {
      "expense_id": "@input.id",
      "amount": "@input.amount",
      "category": "@input.category",
      "submitted_by": "@input.employee_name"
    }
  }
}

When execution reaches this step, the run status changes to paused. The workflow stays paused until the approval is submitted.


Properties Reference

Property Type Required Description
comment string Yes Human-readable description of what needs review. Supports {{ }} templates
request_payload object No Structured data presented to the reviewer
approver_role string No Role expected to review the task
expiry_seconds integer No Time allowed for a decision
on_expiry string No "reject": settle an undecided approval as expired and fail the run once expiry_seconds pass. See Expiry

Approval Flow

sequenceDiagram participant Workflow participant Engine participant Reviewer Workflow->>Engine: PbotApproval step Engine->>Engine: Run status → "paused" Engine-->>Reviewer: Webhook: pbot_approval_requested Note over Reviewer: Reviews context,<br/>makes decision Reviewer->>Engine: POST /approvals/:runId/:stepId alt Approved Engine->>Engine: Run status becomes "waiting_on_wakeup" Engine->>Workflow: Resume at the next step else Rejected Engine->>Engine: Run status becomes "failed" end

1. Workflow pauses

The engine changes the run status to paused and emits a pbot_approval_requested webhook.

2. Reviewer decides

The reviewer sees the comment and request_payload. They submit their decision:

bash
curl -X POST https://your-hyphen.example.com/approvals/{runId}/0 \
  -H "X-Org-Id: acme-corp" \
  -H "Content-Type: application/json" \
  -d '{
    "approved": true,
    "comment": "Reviewed and approved - receipt verified"
  }'
POST/approvals/:runId/:stepId

Submit an approval decision for a paused workflow.

3. Hyphen applies the decision

An approved run resumes from the next step with the decision in context. A rejected run ends with a failed status. In both cases, the approval record keeps the reviewer, decision, comment, and timestamp.


Expiry

expiry_seconds sets how long the approval waits for a decision. on_expiry decides what happens when that time passes and nobody has decided.

json
{
  "type": "PbotApproval",
  "properties": {
    "comment": "Approve the payment batch {{input.batch_id}}",
    "expiry_seconds": 86400,
    "on_expiry": "reject"
  }
}

With on_expiry: "reject":

  • A background sweep, which runs every minute, settles the approval as expired. The run status becomes failed and no later step runs, and the workflow_run_failed webhook is sent with reason approval_expired.
  • The pbot_approval_decided webhook is sent with approved: false, expired: true, and reason: "expired". The run's evidence records an approval.decided event with expired: true.
  • A decision submitted after the expiry time is refused with 409: approval_expired, or approval_already_decided once the sweep has settled the approval. A late decision that arrives before the sweep settles the approval as expired in the same way.
  • A decision and the sweep that arrive at the same moment produce one outcome: the decision applies or the approval expires, never both.
  • In a workflow started per item by a loop with iteration: "child_run", every approval must declare expiry_seconds and on_expiry: "reject". The parent loop records an expired approval as that item's expired outcome.

Without on_expiry, behavior is unchanged: the approval stays pending and the run stays paused after expiry_seconds pass. A decision submitted after that time is refused with 409 (approval_expired), and the run stays paused.

on_expiry requires expiry_seconds; a workflow that declares one without the other is refused when it is validated.


Approval Response Format

Field Type Required Description
approved boolean Yes Whether the reviewer approved
comment string No Reviewer's comment

Context after approval

After an approval resumes the run, the following keys are available in context:

Context Key Value
@__approved Boolean value true
@__comment Comment text from the review decision

Use the context in the next step:

json
{
  "type": "process_expense",
  "properties": {
    "expense_id": "@input.id",
    "approval_comment": "@__comment"
  }
}

Conditional Approval

Combine with filter to only require approval under certain conditions:

json
{
  "type": "PbotApproval",
  "filter": {
    "condition": {
      "or": [
        { "greaterThan": ["@input.amount", 10000] },
        { "equal": ["@input.vendor_status", "new"] }
      ]
    }
  },
  "properties": {
    "comment": "High-value or new-vendor expense requires review",
    "request_payload": {
      "amount": "@input.amount",
      "vendor": "@input.vendor_name"
    }
  }
}

When the filter evaluates to false, the step is skipped and the workflow continues. The @__approved context key is not set in this case.


Listing Pending Approvals

GET/approvals/:runId

List all pending approval requests for a workflow run.


Webhook Notification

When a PbotApproval step pauses a workflow, a pbot_approval_requested webhook event is emitted. Use this to notify reviewers via your application, Slack, email, or any external system.

The webhook payload includes the run_id, step_id, comment, and request_payload so your notification system can provide full context.

→ Next: Form (PbotForm)