Approval Workflows are available on Pro, Business, and Enterprise plans. Upgrade your plan to unlock this feature.

Overview

Approval Workflows allow you to require human approval before your AI agents can execute sensitive actions. This adds a critical safety layer for high-risk operations like:
  • Financial transactions (refunds, transfers, purchases)
  • Data deletion or modification
  • External API calls with irreversible consequences
  • Administrative actions

How It Works

The complete flow:
  1. Agent requests permission via guard.check()
  2. AgentWarden blocks the action and creates a pending approval
  3. Human reviews the request in the dashboard
  4. Human approves or rejects the action
  5. Webhook notifies your server (if configured)
  6. Your server executes the approved action
  7. Action is logged via guard.log()

Setup

1. Configure Permission to Require Approval

In your AgentWarden dashboard:
  1. Go to Agents → Select your agent
  2. Add or edit a permission
  3. ✅ Check “Requires Approval”
  4. Save the permission
Configure permission to require approval

2. Implement Webhook Handler

Your agent must have a webhook endpoint to receive approval notifications:
from flask import Flask, request
from agentwarden import AgentWarden
import os

app = Flask(__name__)
guard = AgentWarden(api_key=os.getenv('AGENTWARDEN_API_KEY'))
AGENT_ID = "your-agent-id"

@app.route('/webhook/approvals', methods=['POST'])
def handle_approval():
    payload = request.get_json()
    
    if payload['event'] == 'approval.approved':
        data = payload['data']
        action = data['action']
        context = data['context']
        
        # Execute the approved action
        try:
            result = execute_action(action, context)
            
            # Log success
            guard.log(AGENT_ID, action, 'success', {
                **context,
                'approval_id': data['approval_id'],
                'result': result
            })
            
        except Exception as e:
            # Log failure
            guard.log(AGENT_ID, action, 'failed', {
                **context,
                'error': str(e)
            })
    
    elif payload['event'] == 'approval.rejected':
        # Log rejection
        data = payload['data']
        guard.log(AGENT_ID, data['action'], 'blocked', {
            **data['context'],
            'reason': 'Rejected by human reviewer'
        })
    
    return {'status': 'received'}, 200

def execute_action(action, context):
    """Execute the approved action"""
    if action == "stripe.refund":
        return process_stripe_refund(context)
    elif action == "database.delete":
        return delete_records(context)
    # ... other actions

if __name__ == '__main__':
    app.run(port=5000)

3. Configure Webhook URL

  1. Go to SettingsWebhooks
  2. Enter your webhook URL
  3. Subscribe to approval.approved and approval.rejected events
  4. Save configuration

4. Implement Agent Logic

Your AI agent should check permissions before executing actions:
from agentwarden import AgentWarden

guard = AgentWarden(api_key="your-api-key")
AGENT_ID = "your-agent-id"

def agent_wants_to_refund(amount, customer_id):
    """AI agent wants to process a refund"""
    
    # Check permission
    result = guard.check(
        agent_id=AGENT_ID,
        action="stripe.refund",
        context={
            "amount": amount,
            "customer_id": customer_id
        }
    )
    
    if result.allowed:
        # Execute immediately
        process_stripe_refund(amount, customer_id)
        guard.log(AGENT_ID, "stripe.refund", "success")
        
    elif result.requires_approval:
        # Action blocked - waiting for human approval
        print(f"⏳ Refund of ${amount} requires human approval")
        print("A reviewer will be notified")
        # Agent continues with other tasks
        
    else:
        # Action not permitted
        print(f"❌ Action blocked: {result.reason}")
        guard.log(AGENT_ID, "stripe.refund", "blocked")

Review and Approve

In the Dashboard

  1. Go to Approvals in your dashboard
  2. Review pending actions with full context
  3. Click Approve or Reject
Approvals dashboard

What Reviewers See

  • Action name: What the agent wants to do
  • Context: All relevant data (amount, IDs, parameters)
  • Agent ID: Which agent made the request
  • Timestamp: When the request was made

Webhook Payloads

When approval events occur, AgentWarden sends webhooks with the following payloads:
{
  "event": "log.pending_approval",
  "timestamp": "2026-02-25T23:51:33.984319",
  "data": {
    "id": "a1b2c3d4-e5f6-7890-ab12-cd34ef567890",
    "agent_id": "agent_abc123def456ghi789",
    "action": "stripe.refund",
    "status": "pending_approval",
    "context": {
      "amount": 500,
      "customer_id": "cus_123",
      "reason": "Awaiting human review"
    },
    "created_at": "2026-02-25T23:51:33.974127+00:00"
  }
}
{
  "event": "approval.approved",
  "timestamp": "2026-02-25T23:51:57.042729",
  "data": {
    "approval_id": "a1b2c3d4-e5f6-7890-ab12-cd34ef567890",
    "agent_id": "agent_abc123def456ghi789",
    "action": "stripe.refund",
    "status": "approved",
    "context": {
      "amount": 500,
      "customer_id": "cus_123"
    },
    "resolved_by": "user_xyz789abc456def123",
    "resolved_at": "2026-02-25T23:51:57.036178+00:00"
  }
}
{
  "event": "approval.rejected",
  "timestamp": "2026-02-25T23:52:30.123456",
  "data": {
    "approval_id": "a1b2c3d4-e5f6-7890-ab12-cd34ef567890",
    "agent_id": "agent_abc123def456ghi789",
    "action": "stripe.refund",
    "status": "rejected",
    "context": {
      "amount": 500
    },
    "resolved_by": "user_xyz789abc456def123",
    "resolved_at": "2026-02-25T23:52:30.118765+00:00"
  }
}

Best Practices

1. Provide Rich Context

Include all information a reviewer needs to make an informed decision:
result = guard.check(
    agent_id=AGENT_ID,
    action="stripe.refund",
    context={
        "amount": 500,
        "currency": "USD",
        "customer_id": "cus_123",
        "customer_email": "customer@example.com",
        "reason": "Product damaged during shipping",
        "order_id": "ord_456",
        "order_date": "2026-02-20",
        "agent_reasoning": "Customer provided photo evidence of damage"
    }
)

2. Use Granular Actions

Define specific actions rather than broad ones: Good:
  • stripe.refund.full
  • stripe.refund.partial
  • database.users.delete
  • database.orders.cancel
Bad:
  • stripe.any
  • database.write

3. Set Amount Limits

Combine approvals with amount limits for financial actions:
# In dashboard: Set max_amount = 100 for stripe.refund

# Refunds under $100 → auto-approved
# Refunds over $100 → require approval

4. Implement Timeouts

Don’t let agents wait forever for approvals:
import time

def agent_with_timeout(action, context, timeout_minutes=30):
    result = guard.check(AGENT_ID, action, context)
    
    if result.requires_approval:
        print(f"Waiting for approval (timeout: {timeout_minutes}min)")
        # Agent continues with other tasks
        # Approval will come via webhook
        
        # Optional: Set a timeout callback
        schedule_timeout(action, context, timeout_minutes)

5. Log Everything

Log all approval events for audit trail:
# When requesting approval
guard.log(AGENT_ID, action, "pending_approval", context)

# When approved (in webhook handler)
guard.log(AGENT_ID, action, "success", {**context, "approved_by": user_id})

# When rejected (in webhook handler)
guard.log(AGENT_ID, action, "blocked", {**context, "rejected_by": user_id})

Example Use Cases

Financial Transactions

# Require approval for refunds over $500
if amount > 500:
    permission = create_permission(
        action="stripe.refund.large",
        requires_approval=True
    )
else:
    permission = create_permission(
        action="stripe.refund.small",
        requires_approval=False
    )

Data Deletion

# Always require approval for deleting user data
result = guard.check(
    agent_id=AGENT_ID,
    action="database.users.delete",
    context={
        "user_id": user_id,
        "user_email": user_email,
        "reason": "GDPR data deletion request",
        "requested_by": agent_reasoning
    }
)

External API Calls

# Require approval for sending emails to large lists
result = guard.check(
    agent_id=AGENT_ID,
    action="email.send.bulk",
    context={
        "recipient_count": len(recipients),
        "subject": email_subject,
        "preview": email_body[:200]
    }
)

Troubleshooting

Approvals Not Creating

  1. Verify permission has “Requires Approval” checked
  2. Check agent is using correct agent_id and action name
  3. Ensure organization has Pro+ plan
  4. Check API logs for errors

Webhook Not Firing

  1. Verify webhook URL is publicly accessible
  2. Check webhook events include approval.approved
  3. Test webhook using “Test Webhook” button
  4. Check server logs for incoming requests

Action Not Executing After Approval

  1. Verify webhook handler is executing the action
  2. Check for errors in webhook handler logs
  3. Ensure guard.log() is being called
  4. Verify action context has all required data

Next Steps