Skip to main content
Transfer approvals provide multi-party authorization for transactions, adding security and compliance controls to your organization’s transaction workflow. Require approval before executing high-value or sensitive transactions.
Transfer approvals apply to on-chain stablecoin transfers (wallet transfers, batch transfers, and bridging) and offramps. Onramps do not support approval workflows.

How Transfer Approvals Work

1

Create transfer with approval

Include requireApproval: true when creating a transfer via API.
2

Transfer enters pending state

Transfer status becomes PENDING_APPROVAL and does not execute.
3

Admin notification

Dashboard admins receive email notifications about the pending approval.
4

Approve or reject

Authorized admins review and either approve or reject the transfer.
5

Execution or cancellation

If approved, transfer executes normally. If rejected, transfer is cancelled.

Implementation Options

Transfer approvals work differently depending on where transactions are initiated:

Dashboard Approvals

When transfers are created in the HIFI Dashboard:
  • Members can initiate wallet transfers
  • Member-initiated transfers require Admin approval before execution
  • Admin-initiated transfers execute without approval
  • Members can initiate offramp transactions, and these will always enter the transfer approval flow (require Admin approval)
  • Email notifications sent for approvals and rejections

API Approvals

When using the API:
  • Include requireApproval: true when creating transfers
  • Transfers enter approval workflow until approved or rejected
  • Supported on wallet transfers, batch transfers, bridging, and offramps
  • Offramp transactions will enter the transfer approval flow only if requireApproval: true is set in the request body
  • Can be integrated into your application’s approval logic

Creating Transfers with Approval

Add the requireApproval parameter to any supported transaction endpoint. Request:
curl -X POST "https://sandbox.hifi.com/v2/wallets/transfers" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "a40ea2aa-7937-4be9-bb1f-b75f1489bcc6",
    "source": {
      "userId": "usr_abc123"
    },
    "destination": {
      "userId": "usr_xyz789"
    },
    "amount": 10000,
    "currency": "usdc",
    "chain": "POLYGON",
    "requireApproval": true
  }'
Response:
{
  "transferType": "WALLET.TRANSFER",
  "transferDetails": {
    "id": "xfr_abc123",
    "requestId": "a40ea2aa-7937-4be9-bb1f-b75f1489bcc6",
    "status": "PENDING_APPROVAL",
    "amount": 10000,
    "currency": "usdc",
    "chain": "POLYGON",
    "receipt": {
      "approval": {
        "id": "apv_xyz789",
        "status": "PENDING",
        "transferId": "xfr_abc123",
        "transferType": "WALLET.TRANSFER",
        "createdAt": "2025-02-03T16:11:36.654998+00:00",
        "votes": []
      }
    }
  }
}

Supported Endpoints

The requireApproval parameter works with:
EndpointTransfer Type
POST /v2/wallets/transfersSingle wallet transfers
POST /v2/wallets/transfers/batchesBatch transfers (up to 50 recipients)
POST /v2/wallets/bridgesCross-chain bridging transfers
POST /v2/offrampsOfframp transfers (stablecoin to fiat)
Bridging and Offramp Note: For bridging transfers and offramps, quotes are generated after approval. This prevents quotes from expiring while awaiting review.

Listing Pending Approvals

Retrieve all transactions awaiting approval using the List Transaction Approvals endpoint. Request:
curl -X GET "https://sandbox.hifi.com/v2/transfer-approvals?limit=20&offset=0" \
  -H "Authorization: Bearer YOUR_API_KEY"
Query Parameters:
  • limit (optional): Number of approvals to return (default: 20, max: 100)
  • offset (optional): Number of approvals to skip for pagination (default: 0)
Response:
{
  "success": true,
  "message": "Pending approvals retrieved successfully",
  "count": 2,
  "approvals": [
    {
      "id": "apv_abc123",
      "transferId": "xfr_xyz789",
      "status": "PENDING",
      "transferType": "WALLET.TRANSFER",
      "createdAt": "2024-01-15T10:30:00.000Z",
      "updatedAt": "2024-01-15T11:00:00.000Z"
    },
    {
      "id": "apv_def456",
      "transferId": "bat_ghi789",
      "status": "PENDING",
      "transferType": "WALLET.TRANSFER.BATCH",
      "createdAt": "2024-01-15T09:15:00.000Z",
      "updatedAt": "2024-01-15T09:15:00.000Z"
    }
  ]
}
Use this endpoint to build approval dashboards or automate approval workflows.

Approving Transfers

Approve pending transfers using the Approve Transfer endpoint. Request:
curl -X POST "https://sandbox.hifi.com/v2/transfer-approvals/apv_abc123/approve" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "comment": "Approved after reviewing transaction details"
  }'
Request Fields:
  • approvalId (required, path): ID of the approval to approve
  • comment (optional): Explanation for the approval decision (max 1000 characters)
Response:
{
  "success": true,
  "message": "Transfer approved and initiated successfully",
  "approvalId": "apv_abc123",
  "status": "APPROVED",
  "timestamp": "2024-01-15T11:00:00.000Z"
}
After approval, the transfer proceeds to execution immediately.

Rejecting Transfers

Reject pending transfers using the Reject Transfer endpoint. Request:
curl -X POST "https://sandbox.hifi.com/v2/transfer-approvals/apv_abc123/reject" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "comment": "Transfer amount exceeds daily limit"
  }'
Request Fields:
  • approvalId (required, path): ID of the approval to reject
  • comment (optional): Reason for rejection (max 1000 characters)
Response:
{
  "success": true,
  "message": "Transfer rejected successfully",
  "approvalId": "apv_abc123",
  "status": "REJECTED",
  "timestamp": "2024-01-15T11:00:00.000Z"
}
Rejected transactions are cancelled and will not execute.

Approval Status Flow

Transactions with requireApproval: true follow this status progression:
StatusDescription
PENDING_APPROVALTransaction awaiting admin approval
APPROVEDTransaction approved and proceeding to execution
REJECTEDTransaction rejected and cancelled
After approval, transactions move to normal execution statuses (CREATED, INITIATED, PENDING, COMPLETED).
Expiration Times: Transactions in PENDING_APPROVAL status will automatically expire if not approved or rejected within the following timeframes: - Onchain transfers (wallet transfers, batch transfers, bridging): Expire after 1 week - Offramps: Expire after 1 day Expired transactions are automatically cancelled and cannot be executed.
Status Updates: Subscribe to TRANSFER.APPROVAL.PENDING, TRANSFER.APPROVAL.APPROVED, and TRANSFER.APPROVAL.REJECTED webhook events to receive real-time approval notifications. See Webhooks for setup instructions.

Notifications

Email Notifications

Admins receive notifications when:
  • A transaction requires approval (Member-initiated)
  • A transaction needs their review
Members receive notifications when:
  • Their transaction request is approved
  • Their transaction request is rejected (with reason if provided)

Webhook Events

Subscribe to approval events for automated workflows:
Event TypeDescription
TRANSFER.APPROVAL.PENDINGTransaction created, awaiting approval
TRANSFER.APPROVAL.APPROVEDTransaction approved, proceeding to execution
TRANSFER.APPROVAL.REJECTEDTransaction rejected and cancelled

Sample Code

Here’s a complete approval workflow implementation:
1

Create a High-Value Transaction That Requires Approval

To trigger an approval workflow for transactions over a certain threshold (e.g., $10k), include requireApproval: true in your request:
async function createHighValueTransfer(amount, destinationUserId) {
  const requireApproval = amount > 10000;
  const transfer = await fetch('https://sandbox.hifi.com/v2/wallets/transfers', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      requestId: crypto.randomUUID(),
      source: { userId: 'usr_treasury' },
      destination: { userId: destinationUserId },
      amount,
      currency: 'usdc',
      chain: 'POLYGON',
      requireApproval
    })
  }).then(r => r.json());

  if (transfer.transferDetails.status === 'PENDING_APPROVAL') {
    notifyApprovers(transfer.transferDetails.id);
  }
  return transfer;
}
If the transaction is flagged for approval, notify approvers (e.g., by email or internal notification).
2

List Pending Approvals (for Admin Review)

Fetch all pending transaction approvals to display in an admin dashboard:
async function fetchPendingApprovals() {
  const approvals = await fetch(
    'https://sandbox.hifi.com/v2/transfer-approvals?limit=50',
    {
      headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
    }
  ).then(r => r.json());
  
  return approvals.approvals.map(approval => ({
    id: approval.id,
    transferId: approval.transferId,
    type: approval.transferType,
    pendingSince: approval.createdAt
  }));
}
3

Approve a Pending Transaction

Admins can approve a pending transaction by submitting an approval action:
async function approveTransfer(approvalId, adminComment) {
  const approval = await fetch(
    `https://sandbox.hifi.com/v2/transfer-approvals/${approvalId}/approve`,
    {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer YOUR_API_KEY',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        comment: adminComment
      })
    }
  ).then(r => r.json());
  
  console.log('Transaction approved:', approval.approvalId);
  return approval;
}
4

React to Approval Webhook Events

Use webhook events to automatically notify stakeholders and update your application’s state:
function handleApprovalWebhook(event) {
  switch (event.eventType) {
    case 'TRANSFER.APPROVAL.PENDING':
      sendSlackNotification({
        message: `Transaction ${event.data.transferId} requires approval`,
        amount: event.data.amount,
        destination: event.data.destination
      });
      break;
    case 'TRANSFER.APPROVAL.APPROVED':
      updateTransferStatus(event.data.transferId, 'APPROVED');
      notifyRequester(event.data.initiatorId, 'approved');
      break;
    case 'TRANSFER.APPROVAL.REJECTED':
      notifyRequester(event.data.initiatorId, 'rejected', event.data.comment);
      break;
  }
}
Subscribe to webhook endpoints to handle TRANSFER.APPROVAL.* events.

Getting Help