Wallgent
Guides

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/approvals
const { 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

ParameterTypeDescription
walletIdstringFilter by wallet
statusstringPENDING, APPROVED, REJECTED, or EXPIRED
limitnumberMax results to return
cursorstringPagination cursor

Viewing Approval Details

GET /v1/approvals/:id

Returns 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

FieldTypeDescription
idstringApproval ID
walletIdstringWallet the payment originates from
policyIdstringPolicy that triggered the approval requirement
transactionDetailsobjectAmount, currency, recipient, description
statusstringPENDING, APPROVED, REJECTED, EXPIRED
expiresAtstringISO 8601 expiry timestamp
resolvedAtstringWhen the approval was resolved
resolvedBystringID of the API key that resolved it

Approving a Payment

POST /v1/approvals/:id/approve

Immediately 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/reject

Discards 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

EventDescription
approval.createdA payment was held and is awaiting approval
approval.approvedA reviewer approved the payment; it has now executed
approval.rejectedA reviewer rejected the payment
approval.expiredThe approval window elapsed without a decision

API Endpoints

MethodPathDescription
GET/v1/approvalsList approvals (with status filter)
GET/v1/approvals/:idGet approval details
POST/v1/approvals/:id/approveApprove a pending payment
POST/v1/approvals/:id/rejectReject a pending payment

Permissions

PermissionRequired For
approvals:readList and retrieve approvals
approvals:writeApprove and reject approvals

On this page