Human-in-the-loop pattern: 1) AI agent proposes the action with its reasoning. 2) Proposal goes to human reviewer (Slack message with Approve/Reject buttons, or an admin approval queue). 3) Human approves → agent executes via the Paystack API. Human rejects → agent receives rejection and informs the user. Operations requiring human approval: any transfer above NGN 50,000, new bank recipient creation, bulk payouts, refunds to different accounts than the original payment. Operations that can be automated: payment link creation, subscription status queries, transaction lookups, refunds below threshold for verified customers.
| Operation | Automation Level | Rationale |
|---|---|---|
| Transaction lookup | Full auto | Read-only, no financial impact |
| Payment link creation | Full auto | Customer still chooses to pay; reversible |
| Subscription status check | Full auto | Read-only |
| Refund < NGN 5,000 (verified customer) | Auto with audit log | Low value, reversible |
| Refund > NGN 5,000 | Human approval required | Material value, irreversible |
| Transfer to allowlist recipient | Human approval above threshold | Irreversible, financial impact |
| New recipient creation | Always human approval | Expands allowlist — high security impact |
| Bulk payout | Always human approval | Multiplied financial impact |
| Subscription cancellation | Human approval (churn opportunity) | Revenue impact, opportunity to retain |
// Approval gate middleware — wraps any write tool
async function withApproval({ action, params, threshold, agentSessionId, channel = '#payments-approvals' }) {
// Check if this action requires approval based on params
var requiresApproval = action === 'transfer' && params.amount > threshold
|| action === 'refund' && params.amount > 5000_00
|| action === 'create_recipient' // always requires approval
|| action === 'bulk_transfer'; // always requires approval
if (!requiresApproval) {
return { approved: true, auto: true };
}
// Queue for human approval
var requestId = crypto.randomUUID();
await db.pendingApprovals.insert({ requestId, action, params, agentSessionId, status: 'pending', created_at: new Date() });
// Notify via Slack with interactive buttons
await sendSlackInteractiveMessage(channel, {
text: 'AI payment action requires approval',
blocks: buildApprovalMessage(action, params, requestId),
});
// Poll for approval (max 30 minutes, then escalate)
return new Promise((resolve) => {
var pollInterval = setInterval(async () => {
var record = await db.pendingApprovals.get(requestId);
if (record.status === 'approved') {
clearInterval(pollInterval);
resolve({ approved: true, approvedBy: record.approved_by, auto: false });
}
if (record.status === 'rejected') {
clearInterval(pollInterval);
resolve({ approved: false, rejectedBy: record.rejected_by });
}
}, 5000);
// Escalate after 30 minutes without decision
setTimeout(() => {
clearInterval(pollInterval);
resolve({ approved: false, reason: 'approval_timeout' });
}, 30 * 60 * 1000);
});
}
See audit trails for AI financial actions for logging approved actions.
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course