Skip to main content

Overview

When a workflow reaches a human_approval step, Station can notify external systems via webhooks. This enables human-in-the-loop automation with:
  • Slack notifications with approve/reject buttons
  • ntfy.sh alerts for mobile push notifications
  • PagerDuty escalations for critical decisions
  • Custom dashboards showing pending approvals
┌─────────────────────────────────────────────────────────────────────────────┐
│                        APPROVAL WEBHOOK FLOW                                 │
└─────────────────────────────────────────────────────────────────────────────┘

     ┌──────────────┐
     │   Workflow   │
     │   Running    │
     └──────┬───────┘


┌───────────────────────┐
│   human_approval      │
│       step            │
└───────────┬───────────┘


┌───────────────────────┐     ┌─────────────────────────────────────┐
│  Create Approval      │     │         WEBHOOK NOTIFICATION        │
│  Record in DB         │     │                                     │
└───────────┬───────────┘     │  POST https://ntfy.sh/your-topic    │
            │                 │                                     │
            ├────────────────▶│  {                                  │
            │                 │    "event": "approval.requested",   │
            │                 │    "approval_id": "appr-abc123",    │
            │                 │    "message": "Approve deploy?",    │
            │                 │    "approve_url": "http://...",     │
            │                 │    "reject_url": "http://..."       │
            │                 │  }                                  │
            │                 └─────────────────────────────────────┘


┌───────────────────────┐
│   WORKFLOW PAUSED     │◄───────────────────────────────┐
│   (waiting_approval)  │                                │
└───────────┬───────────┘                                │
            │                                            │
            │         Human reviews notification         │
            │                                            │
            ▼                                            │
     ┌──────────────┐                                    │
     │   Decision   │                                    │
     └──────┬───────┘                                    │
            │                                            │
    ┌───────┴───────┐                                    │
    │               │                                    │
    ▼               ▼                                    │
┌────────┐    ┌──────────┐                              │
│APPROVE │    │  REJECT  │                              │
└───┬────┘    └────┬─────┘                              │
    │              │       API Call                     │
    │              ├───────────────────────────────────►│
    │              │  POST /workflow-approvals/{id}/reject
    │    API Call  │                                    │
    ├──────────────┼───────────────────────────────────►│
    │              │  POST /workflow-approvals/{id}/approve
    ▼              ▼
┌────────────────────────┐
│   Workflow Continues   │
│   or Fails             │
└────────────────────────┘

Configuration

Config File

Add to your config.yaml:
notifications:
  # Webhook URL to POST when approval is requested
  approval_webhook_url: "https://ntfy.sh/station-approvals"
  
  # Timeout for webhook delivery (default: 10 seconds)
  approval_webhook_timeout: 10

Environment Variables

export STN_APPROVAL_WEBHOOK_URL="https://ntfy.sh/station-approvals"
export STN_APPROVAL_WEBHOOK_TIMEOUT=10

Webhook Payload

When a workflow reaches a human_approval step, Station sends:
{
  "event": "approval.requested",
  "approval_id": "appr-run123-approve_deploy",
  "workflow_id": "deploy-production",
  "workflow_name": "Production Deployment",
  "run_id": "run-abc123",
  "step_name": "approve_deploy",
  "message": "Approve deployment of api-service v2.3.0 to production?",
  "approvers": ["oncall-sre", "platform-leads"],
  "timeout_seconds": 1800,
  "created_at": "2025-01-04T17:30:00Z",
  "approve_url": "http://localhost:8587/workflow-approvals/appr-run123-approve_deploy/approve",
  "reject_url": "http://localhost:8587/workflow-approvals/appr-run123-approve_deploy/reject"
}

Payload Fields

FieldTypeDescription
eventstringAlways "approval.requested"
approval_idstringUnique ID for this approval request
workflow_idstringID of the workflow definition
workflow_namestringHuman-readable workflow name
run_idstringID of this workflow run instance
step_namestringName of the approval step
messagestringHuman-readable message (supports template variables)
approversarrayList of allowed approvers (optional)
timeout_secondsintSeconds until approval expires
created_atstringISO 8601 timestamp
approve_urlstringURL to POST for approval
reject_urlstringURL to POST for rejection

Integration Examples

ntfy.sh

ntfy is a simple HTTP-based pub-sub notification service - perfect for mobile push notifications.
1

Install the ntfy app

Get it on Android or iOS
2

Subscribe to your topic

Open the app and subscribe to station-approvals (or your chosen topic name)
3

Configure Station

notifications:
  approval_webhook_url: https://ntfy.sh/station-approvals
  approval_webhook_timeout: 10
4

Test the webhook

curl -d "Test notification from Station" https://ntfy.sh/station-approvals
For rich notifications with titles, priorities, and action buttons, use a webhook proxy that transforms Station’s JSON payload into ntfy’s format. See the advanced examples below.

Slack

Configure a Slack Incoming Webhook:
notifications:
  approval_webhook_url: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXX"
For interactive approve/reject buttons, you’ll need a small webhook proxy to transform the payload into Slack Block Kit format:
{
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Approval Required*\n\nApprove deployment of `api-service v2.3.0` to production?"
      }
    },
    {
      "type": "actions",
      "elements": [
        {
          "type": "button",
          "text": { "type": "plain_text", "text": "Approve" },
          "style": "primary",
          "url": "http://station:8587/workflow-approvals/appr-abc123/approve"
        },
        {
          "type": "button",
          "text": { "type": "plain_text", "text": "Reject" },
          "style": "danger",
          "url": "http://station:8587/workflow-approvals/appr-abc123/reject"
        }
      ]
    }
  ]
}

PagerDuty

Send approval requests as PagerDuty incidents:
notifications:
  approval_webhook_url: "https://events.pagerduty.com/v2/enqueue"
You’ll need a webhook proxy to transform to PagerDuty Events API v2 format.

Custom Webhook Proxy

Build a simple proxy to transform Station’s payload for any system:
from flask import Flask, request
import requests

app = Flask(__name__)

NTFY_TOPIC = "https://ntfy.sh/station-approvals"

@app.route('/webhook', methods=['POST'])
def handle_approval():
    data = request.json
    
    # Build ntfy message with actions
    message = f"{data['message']}\n\nWorkflow: {data['workflow_name']}"
    
    headers = {
        "Title": "Approval Required",
        "Priority": "high",
        "Tags": "warning",
        "Actions": f"http, Approve, {data['approve_url']}; http, Reject, {data['reject_url']}"
    }
    
    requests.post(NTFY_TOPIC, data=message, headers=headers)
    return {"status": "ok"}

if __name__ == '__main__':
    app.run(port=5000)
Then configure Station to use your proxy:
notifications:
  approval_webhook_url: http://localhost:5000/webhook

Approval API

Once notified, approvers can use the URLs in the payload or call the API directly.

Approve a Request

curl -X POST http://localhost:8587/workflow-approvals/{approval_id}/approve \
  -H "Content-Type: application/json" \
  -d '{"comment": "Reviewed and approved"}'
Response:
{
  "approval_id": "appr-abc123",
  "status": "approved",
  "decided_by": "api-user",
  "decided_at": "2025-01-04T17:35:00Z"
}

Reject a Request

curl -X POST http://localhost:8587/workflow-approvals/{approval_id}/reject \
  -H "Content-Type: application/json" \
  -d '{"reason": "Missing security review"}'

CLI Commands

# List pending approvals
stn workflow approvals list --status pending

# Approve
stn workflow approvals approve appr-abc123 --comment "LGTM"

# Reject  
stn workflow approvals reject appr-abc123 --reason "Needs review"

# Get details
stn workflow approvals get appr-abc123

Audit Logging

All webhook deliveries and approval decisions are logged for compliance and debugging.

Webhook Delivery Logs

Every webhook attempt is recorded with:
  • Request payload sent
  • Response status code
  • Response body (truncated)
  • Duration in milliseconds
  • Error message (if failed)

View Audit Trail

curl http://localhost:8587/workflow-approvals/{approval_id}/audit
Response:
{
  "logs": [
    {
      "event_type": "webhook.attempt",
      "webhook_url": "https://ntfy.sh/station-approvals",
      "created_at": "2025-01-04T17:30:00.100Z"
    },
    {
      "event_type": "webhook.success",
      "response_status": 200,
      "duration_ms": 150,
      "created_at": "2025-01-04T17:30:00.250Z"
    },
    {
      "event_type": "approval.approved",
      "metadata": {
        "decided_by": "alice@example.com",
        "comment": "Looks good"
      },
      "created_at": "2025-01-04T17:35:00Z"
    }
  ]
}

Audit Event Types

Event TypeDescription
webhook.attemptWebhook delivery started
webhook.successWebhook delivered successfully (2xx)
webhook.failureWebhook delivery failed
approval.approvedApproval was granted
approval.rejectedApproval was denied
approval.timeoutApproval expired without decision

Retry Behavior

Station automatically retries failed webhook deliveries:
  • Max retries: 3 attempts
  • Backoff: Exponential (1s, 4s, 9s)
  • Timeout: Configurable per-request (default 10s)
All retry attempts are logged in the audit trail.

Troubleshooting

  1. Verify webhook URL is correct:
curl -X POST "YOUR_WEBHOOK_URL" \
  -H "Content-Type: application/json" \
  -d '{"test": true}'
  1. Check Station logs:
STN_LOG_LEVEL=debug stn serve 2>&1 | grep -i webhook
  1. Check audit logs:
curl http://localhost:8587/workflow-approvals/{id}/audit
Check the audit log for the response body:
SELECT event_type, response_status, error_message 
FROM notification_logs 
WHERE approval_id = 'appr-xxx'
ORDER BY created_at DESC;
  1. Verify Station is accessible from where approvals happen
  2. Check approval hasn’t expired: stn workflow approvals get appr-xxx
  3. Check approval wasn’t already decided
  1. Check workflow run status: stn workflow runs get run-xyz --steps
  2. Check NATS is running: stn status
  3. Check logs for errors

Security Considerations

Always use HTTPS for webhook URLs in production to protect approval payloads.

Recommendations

  1. Use HTTPS - Encrypt webhook traffic
  2. Protect approval API - Run Station on internal network or use authentication
  3. Audit retention - Configure log retention for compliance
  4. Validate approvers - Use the approvers field to restrict who can approve

Comparison: Notifications vs Approval Webhooks

FeatureNotify ToolApproval Webhooks
TriggerAgent calls notify()Workflow hits human_approval step
PurposeAlert users about eventsBlock workflow until human decision
PayloadCustomizable messageStructured approval request
ResponseFire-and-forgetApprove/reject via API
Confignotify: sectionnotifications: section

Next Steps