Human-in-the-Loop Approvals
Require a human to approve payments before they execute using policy-driven approval workflows.
Overview
Approvals let you put a human in the loop before a payment executes. When a wallet has a policy with requireHumanApproval: true, any payment from that wallet is held in a PENDING state and returns an ApprovalRequiredError (HTTP 202) rather than executing immediately.
A reviewer — another API key, a human via the dashboard, or a webhook handler — then approves or rejects the held payment.
Approval lifecycle:
PENDING → APPROVED → (payment executes)
→ REJECTED → (payment discarded)
→ EXPIRED → (approval window elapsed)Triggering an Approval
Approvals are triggered automatically — you do not create them manually. When you call POST /v1/payments and the sending wallet has a policy requiring human approval, the API responds with HTTP 202 and a PENDING_APPROVAL status instead of executing the payment.
import Wallgent from '@wallgent/sdk'
const wg = new Wallgent({ apiKey: process.env.WALLGENT_API_KEY })
const result = await wg.payments.send({
from: 'wal_01J...',
to: 'wal_01J...',
amount: '5000.00',
description: 'Large vendor payment',
})
if (result.status === 'PENDING_APPROVAL') {
console.log('Payment is awaiting human approval:', result.approvalId)
}The approvalId in the response is the ID you use to approve or reject the payment.
Listing Pending Approvals
GET /v1/approvalsconst { data } = await wg.approvals.list({ status: 'PENDING' })
for (const approval of data) {
console.log(
`${approval.id}: ${approval.transactionDetails.amount} ${approval.transactionDetails.currency} → ${approval.transactionDetails.recipient}`
)
}Filter Parameters
| Parameter | Type | Description |
|---|---|---|
walletId | string | Filter by wallet |
status | string | PENDING, APPROVED, REJECTED, or EXPIRED |
limit | number | Max results to return |
cursor | string | Pagination cursor |
Viewing Approval Details
GET /v1/approvals/:idReturns the full approval record including payment details, policy that triggered it, and current status.
const approval = await wg.approvals.retrieve('apr_01J...')
console.log({
walletId: approval.walletId,
policyId: approval.policyId,
amount: approval.transactionDetails.amount,
currency: approval.transactionDetails.currency,
recipient: approval.transactionDetails.recipient,
description: approval.transactionDetails.description,
status: approval.status,
expiresAt: approval.expiresAt,
})Approval Object Fields
| Field | Type | Description |
|---|---|---|
id | string | Approval ID |
walletId | string | Wallet the payment originates from |
policyId | string | Policy that triggered the approval requirement |
transactionDetails | object | Amount, currency, recipient, description |
status | string | PENDING, APPROVED, REJECTED, EXPIRED |
expiresAt | string | ISO 8601 expiry timestamp |
resolvedAt | string | When the approval was resolved |
resolvedBy | string | ID of the API key that resolved it |
Approving a Payment
POST /v1/approvals/:id/approveImmediately executes the held payment.
const result = await wg.approvals.approve('apr_01J...')
console.log('Payment approved and executed. Status:', result.status)Rejecting a Payment
POST /v1/approvals/:id/rejectDiscards the held payment. Accepts an optional reason.
const result = await wg.approvals.reject('apr_01J...', 'Amount exceeds vendor contract limit')
console.log('Payment rejected. Status:', result.status)Webhook Handler Pattern
The recommended production pattern is to handle approvals via a webhook. Your approval system receives the approval.created event, applies your business logic, and approves or rejects programmatically.
Step 1: Create a Policy Requiring Approval
await wg.policies.create({
walletId: 'wal_01J...',
name: 'Large payment review',
maxTransactionAmount: 1000.00,
requireHumanApproval: true,
})Step 2: Set Up a Webhook for approval.created
await wg.webhooks.create({
url: 'https://your-app.com/webhooks/wallgent',
events: ['approval.created', 'approval.expired'],
})Step 3: Handle the Event in Your Webhook Handler
import Wallgent from '@wallgent/sdk'
const wg = new Wallgent({ apiKey: process.env.WALLGENT_REVIEWER_API_KEY })
app.post('/webhooks/wallgent', async (req, res) => {
const event = req.body
if (event.type === 'approval.created') {
const { approvalId, transactionDetails } = event.data
const amount = parseFloat(transactionDetails.amount)
// Apply your business logic
if (amount <= 5000 && isKnownRecipient(transactionDetails.recipient)) {
await wg.approvals.approve(approvalId)
} else {
// Escalate to human reviewer via Slack, email, etc.
await notifyReviewer(approvalId, transactionDetails)
}
}
res.sendStatus(200)
})Webhook Events
| Event | Description |
|---|---|
approval.created | A payment was held and is awaiting approval |
approval.approved | A reviewer approved the payment; it has now executed |
approval.rejected | A reviewer rejected the payment |
approval.expired | The approval window elapsed without a decision |
API Endpoints
| Method | Path | Description |
|---|---|---|
GET | /v1/approvals | List approvals (with status filter) |
GET | /v1/approvals/:id | Get approval details |
POST | /v1/approvals/:id/approve | Approve a pending payment |
POST | /v1/approvals/:id/reject | Reject a pending payment |
Permissions
| Permission | Required For |
|---|---|
approvals:read | List and retrieve approvals |
approvals:write | Approve and reject approvals |