> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cloudshipai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Approval Webhooks

> Get notified when workflows need human approval via webhooks to Slack, ntfy, PagerDuty, or custom systems

## 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

<Frame>
  ```
  ┌─────────────────────────────────────────────────────────────────────────────┐
  │                        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             │
  └────────────────────────┘
  ```
</Frame>

## Configuration

### Config File

Add to your `config.yaml`:

```yaml theme={null}
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

```bash theme={null}
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:

```json theme={null}
{
  "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

| Field             | Type   | Description                                          |
| ----------------- | ------ | ---------------------------------------------------- |
| `event`           | string | Always `"approval.requested"`                        |
| `approval_id`     | string | Unique ID for this approval request                  |
| `workflow_id`     | string | ID of the workflow definition                        |
| `workflow_name`   | string | Human-readable workflow name                         |
| `run_id`          | string | ID of this workflow run instance                     |
| `step_name`       | string | Name of the approval step                            |
| `message`         | string | Human-readable message (supports template variables) |
| `approvers`       | array  | List of allowed approvers (optional)                 |
| `timeout_seconds` | int    | Seconds until approval expires                       |
| `created_at`      | string | ISO 8601 timestamp                                   |
| `approve_url`     | string | URL to POST for approval                             |
| `reject_url`      | string | URL to POST for rejection                            |

## Integration Examples

### ntfy.sh

[ntfy](https://ntfy.sh) is a simple HTTP-based pub-sub notification service - perfect for mobile push notifications.

<Steps>
  <Step title="Install the ntfy app">
    Get it on [Android](https://play.google.com/store/apps/details?id=io.heckel.ntfy) or [iOS](https://apps.apple.com/us/app/ntfy/id1625396347)
  </Step>

  <Step title="Subscribe to your topic">
    Open the app and subscribe to `station-approvals` (or your chosen topic name)
  </Step>

  <Step title="Configure Station">
    ```yaml theme={null}
    notifications:
      approval_webhook_url: https://ntfy.sh/station-approvals
      approval_webhook_timeout: 10
    ```
  </Step>

  <Step title="Test the webhook">
    ```bash theme={null}
    curl -d "Test notification from Station" https://ntfy.sh/station-approvals
    ```
  </Step>
</Steps>

<Tip>
  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.
</Tip>

### Slack

Configure a Slack Incoming Webhook:

```yaml theme={null}
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:

```json theme={null}
{
  "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:

```yaml theme={null}
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:

```python theme={null}
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:

```yaml theme={null}
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

```bash theme={null}
curl -X POST http://localhost:8587/workflow-approvals/{approval_id}/approve \
  -H "Content-Type: application/json" \
  -d '{"comment": "Reviewed and approved"}'
```

**Response:**

```json theme={null}
{
  "approval_id": "appr-abc123",
  "status": "approved",
  "decided_by": "api-user",
  "decided_at": "2025-01-04T17:35:00Z"
}
```

### Reject a Request

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

### CLI Commands

```bash theme={null}
# 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

```bash theme={null}
curl http://localhost:8587/workflow-approvals/{approval_id}/audit
```

**Response:**

```json theme={null}
{
  "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 Type          | Description                          |
| ------------------- | ------------------------------------ |
| `webhook.attempt`   | Webhook delivery started             |
| `webhook.success`   | Webhook delivered successfully (2xx) |
| `webhook.failure`   | Webhook delivery failed              |
| `approval.approved` | Approval was granted                 |
| `approval.rejected` | Approval was denied                  |
| `approval.timeout`  | Approval 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

<AccordionGroup>
  <Accordion title="Webhook not received">
    1. Verify webhook URL is correct:

    ```bash theme={null}
    curl -X POST "YOUR_WEBHOOK_URL" \
      -H "Content-Type: application/json" \
      -d '{"test": true}'
    ```

    2. Check Station logs:

    ```bash theme={null}
    STN_LOG_LEVEL=debug stn serve 2>&1 | grep -i webhook
    ```

    3. Check audit logs:

    ```bash theme={null}
    curl http://localhost:8587/workflow-approvals/{id}/audit
    ```
  </Accordion>

  <Accordion title="Webhook returns non-2xx">
    Check the audit log for the response body:

    ```sql theme={null}
    SELECT event_type, response_status, error_message 
    FROM notification_logs 
    WHERE approval_id = 'appr-xxx'
    ORDER BY created_at DESC;
    ```
  </Accordion>

  <Accordion title="Approval URL not working">
    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
  </Accordion>

  <Accordion title="Workflow stuck after approval">
    1. Check workflow run status: `stn workflow runs get run-xyz --steps`
    2. Check NATS is running: `stn status`
    3. Check logs for errors
  </Accordion>
</AccordionGroup>

## Security Considerations

<Warning>
  Always use HTTPS for webhook URLs in production to protect approval payloads.
</Warning>

### 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

| Feature  | Notify Tool              | Approval Webhooks                   |
| -------- | ------------------------ | ----------------------------------- |
| Trigger  | Agent calls `notify()`   | Workflow hits `human_approval` step |
| Purpose  | Alert users about events | Block workflow until human decision |
| Payload  | Customizable message     | Structured approval request         |
| Response | Fire-and-forget          | Approve/reject via API              |
| Config   | `notify:` section        | `notifications:` section            |

## Next Steps

* [Workflows](/station/workflows) - Create workflows with approval steps
* [Notifications](/station/notifications) - Agent-initiated notifications
* [Webhooks](/station/webhooks) - Trigger agents from external systems
