Virtual Accounts

Overview

Virtual accounts are U.S. bank accounts that automatically convert incoming USD deposits into cryptocurrency and settle the funds to a designated blockchain wallet. Each virtual account has unique bank account details (account number and routing number) that can be used to receive ACH and wire transfers.

Prerequisites

Before creating a virtual account, ensure:

  1. ✅ User has been created
  2. ✅ User has completed identity verification (KYC/KYB)
  3. ✅ Verification status is verified
  4. ✅ User has a blockchain wallet created (Solana or Polygon)

Create Virtual Account

Endpoint

POST /v1/users/{userId}/virtual-accounts

Headers

HeaderRequiredDescription
AuthorizationYesBearer token from authentication
x-api-keyYesYour API key
Content-TypeYesMust be application/json
idempotency-keyYesUnique identifier to prevent duplicate creation

Request Body

{
  "type": "US_ACH",
  "destination": {
    "currency": "USDC",
    "network": "solana",
    "address": "7tQJvFk8XZoaVRjL..."
  },
  "description": "Primary deposit account",
  "metadata": {
    "customer_reference": "CUST-12345",
    "account_purpose": "freelancer_payments"
  }
}

Field Descriptions

FieldTypeRequiredDescription
typestringYesAccount type: US_ACH (only US_ACH is currently supported)
destinationobjectYesWhere to send converted cryptocurrency
destination.currencystringYesCryptocurrency: USDC
destination.networkstringYesBlockchain: solana or polygon
destination.addressstringYesWallet address on the specified blockchain
descriptionstringNoHuman-readable description of the account
metadataobjectNoCustom key-value pairs for your reference

Supported Configurations

CurrencyNetworkStatus
USDCSolana✅ Supported
USDCPolygon✅ Supported

Example Request

curl -X POST https://api.balampay.com/v1/users/550e8400-e29b-41d4-a716-446655440000/virtual-accounts \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "idempotency-key: va-creation-$(date +%s)" \
  -d '{
    "type": "US_ACH",
    "destination": {
      "currency": "USDC",
      "network": "solana",
      "address": "7tQJvFk8XZoaVRjLGcBdqN3hJq8VqBvGpPQy8R9xYwZ1"
    },
    "description": "Main receiving account for freelance payments"
  }'

Response

{
  "id": "va_789012345",
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "type": "US_ACH",
  "status": "active",
  "destination": {
    "currency": "USDC",
    "network": "solana",
    "address": "7tQJvFk8XZoaVRjLGcBdqN3hJq8VqBvGpPQy8R9xYwZ1"
  },
  "source_deposit_instructions": {
    "currency": "usd",
    "bank_name": "Example Bank",
    "bank_address": "123 Main Street, San Francisco, CA 94105",
    "bank_account_number": "1234567890",
    "bank_routing_number": "021000021",
    "bank_beneficiary_name": "John Doe",
    "bank_beneficiary_address": "456 Oak Ave, San Francisco, CA 94102"
  },
  "description": "Main receiving account for freelance payments",
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T10:30:00Z"
}

Virtual Account Status

StatusDescriptionActions Available
activatingAccount is being createdWait - usually takes 1-2 minutes
activeAccount is ready to receive depositsCan receive ACH/wire transfers
deactivatedAccount has been closedCannot receive deposits

Bank Account Details

The source_deposit_instructions contain the bank account details that users need to receive USD payments:

Bank Name: [Provided by API]
Bank Address: [Provided by API]

Account Number: [Provided by API]
Routing Number: [Provided by API]

Beneficiary Name: [User's name]
Beneficiary Address: [User's address]

Using These Details

For ACH transfers:

  • Domestic transfers from any U.S. bank
  • Typically arrive within 1-3 business days
  • Low or no fees for the sender

For Wire transfers:

  • Same-day or next-day transfers
  • Can be domestic or international
  • Higher fees (typically $15-$50)

Get Virtual Accounts

Endpoint

GET /v1/users/{userId}/virtual-accounts

Example Request

curl -X GET https://api.balampay.com/v1/users/550e8400-e29b-41d4-a716-446655440000/virtual-accounts \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "x-api-key: YOUR_API_KEY"

Response

[
  {
    "id": "va_789012345",
    "user_id": "550e8400-e29b-41d4-a716-446655440000",
    "type": "US_ACH",
    "status": "active",
    "destination": {
      "currency": "USDC",
      "network": "solana",
      "address": "7tQJvFk8XZoaVRjLGcBdqN3hJq8VqBvGpPQy8R9xYwZ1"
    },
    "source_deposit_instructions": {
      "currency": "usd",
      "bank_name": "Example Bank",
      "bank_account_number": "1234567890",
      "bank_routing_number": "021000021",
      "bank_beneficiary_name": "John Doe"
    },
    "description": "Main receiving account",
    "created_at": "2024-01-15T10:30:00Z"
  }
]

How Deposits Work

Step-by-Step Flow

sequenceDiagram
    participant Sender
    participant Sender Bank
    participant Kira
    participant Blockchain
    participant User Wallet

    Note over Sender,User Wallet: ACH/Wire Transfer Flow

    Sender->>Sender Bank: Initiate transfer to virtual account
    Sender Bank->>Kira: Send USD (ACH/Wire)

    Note over Kira: USD received

    Note over Kira: Convert USD to USDC

    Kira->>Blockchain: Send USDC to destination
    Blockchain->>User Wallet: USDC credited

    Kira->>Your Server: Webhook: Deposit notification

Timing

Transfer TypeArrival TimeConversion TimeTotal Time
ACH1-3 business daysInstant1-3 business days
Wire (Domestic)Same day or next dayInstantSame day - 1 business day
Wire (International)1-5 business daysInstant1-5 business days

Deposit Processing

  1. Funds Received: USD arrives at the virtual account
  2. Conversion: Instantly converted to USDC at current market rate
  3. Blockchain Settlement: Cryptocurrency sent to destination wallet
  4. Confirmation: Transaction confirmed on blockchain (seconds to minutes)
  5. Notification: Webhook sent to your server with deposit details

Fees

Kira may collect fees on deposits based on your client configuration. Fees are:

  • Deducted from the deposit amount before conversion
  • Configurable per client
  • Visible in deposit webhooks and API responses

Example with fees:

USD Received: $1,000.00
Fee (2%): $20.00
Net Amount: $980.00
USDC Sent: ~980 USDC (after conversion)

Multiple Virtual Accounts

Users can create multiple virtual accounts for different purposes:

// Account 1: For freelance payments
const account1 = await createVirtualAccount({
  destination: {
    currency: 'USDC',
    network: 'solana',
    address: 'wallet1...'
  },
  description: 'Freelance payments'
});

// Account 2: For business invoices
const account2 = await createVirtualAccount({
  destination: {
    currency: 'USDC',
    network: 'polygon',
    address: 'wallet2...'
  },
  description: 'Business invoices'
});

Each account has unique bank details, allowing users to:

  • Track payments from different sources
  • Route funds to different wallets
  • Use different blockchains for different purposes

Error Responses

Missing or Invalid Request Fields

Status: 400 Bad Request

{
  "error": "Invalid request data",
  "details": [
    {
      "path": "destination.currency",
      "message": "Required",
      "code": "invalid_type"
    }
  ]
}

Missing Idempotency Key

Status: 400 Bad Request

{
  "code": "validation_error",
  "message": "Idempotency key is required"
}

Idempotency Key Reused with Different Data

Status: 400 Bad Request

Note: This returns as a validation error (not 409) for virtual accounts.

{
  "code": "validation_error",
  "message": "Idempotency key has already been used with different request data"
}

Unauthorized

Status: 401 Unauthorized

{
  "code": "unauthorized",
  "message": "No client ID found in claims"
}

User Not Found

Status: 400 Bad Request

{
  "code": "validation_error",
  "message": "User with ID 550e8400-e29b-41d4-a716-446655440000 not found or does not belong to client"
}

Customer Not Found

Status: 400 Bad Request

{
  "code": "validation_error",
  "message": "Customer not found: 550e8400-e29b-41d4-a716-446655440000"
}

Customer Not Active

Status: 400 Bad Request

{
  "code": "validation_error",
  "message": "Customer is not active: 550e8400-e29b-41d4-a716-446655440000. Please activate the customer first."
}

Only US_ACH Supported

Status: 400 Bad Request

{
  "code": "validation_error",
  "message": "Only US_ACH type is supported at the moment"
}

Invalid Wallet Address or Currency/Network Combination

Status: 400 Bad Request

{
  "code": "validation_error",
  "message": "Invalid Solana wallet address format"
}

Internal Server Error

Status: 500 Internal Server Error

{
  "code": "internal_error",
  "message": "An unexpected error occurred"
}

Idempotency

Use the idempotency-key header to safely retry requests:

# First request - creates virtual account
curl -X POST https://api.balampay.com/v1/users/{userId}/virtual-accounts \
  -H "idempotency-key: unique-key-123" \
  -d '{...}'

# Second request with same key and body - returns existing account
curl -X POST https://api.balampay.com/v1/users/{userId}/virtual-accounts \
  -H "idempotency-key: unique-key-123" \
  -d '{...}'

Best Practices

  1. Validate wallet addresses - Double-check wallet addresses before creating accounts
  2. Use descriptive names - Add meaningful descriptions to identify account purposes
  3. Store bank details securely - Save source_deposit_instructions for display to users
  4. Monitor via webhooks - Don't poll; use webhooks for real-time deposit notifications
  5. Test network connectivity - Verify wallet address can receive transactions before creating account
  6. Handle activating state - Wait for active status before showing bank details to users
  7. Communicate clearly - Explain to users how long transfers take and what fees apply

Displaying Bank Details to Users

When showing bank details to users, provide clear instructions:

To receive payments, share these details with the sender:

Bank Name: {bank_name}
Bank Address: {bank_address}

Account Information:
Account Number: {bank_account_number}
Routing Number: {bank_routing_number}

Beneficiary:
Name: {bank_beneficiary_name}
Address: {bank_beneficiary_address}

⏱️ ACH transfers: 1-3 business days
⏱️ Wire transfers: Same day or next day

💡 Once received, USD will be automatically converted to USDC
   and sent to your wallet.

Next Steps

After creating a virtual account:

  1. Track deposits and monitor settlement
  2. Set up webhooks for real-time notifications
  3. Display bank details to your users
  4. Monitor account activity via the deposits API