Virtual Account Webhooks

Overview

Webhooks provide real-time notifications for virtual account events. Configure a webhook endpoint to receive automatic updates when deposits arrive, payouts are processed, or account status changes.

Webhook Events

EventDescriptionVA Mode
virtual_account.createdVirtual account createdAll
virtual_account.activatedVirtual account is now activeAll
virtual_account.deposit_funds_receivedDeposit received at virtual accountAll
virtual_account.microdeposit_funds_receivedMicrodeposit received (< $1.00 USD)All
virtual_account.deposit_funds_in_transitDeposit settlement started (crypto conversion in progress)Crypto only
virtual_account.deposit_funds_in_destinationDeposit settlement completed (crypto sent to wallet)Crypto only
virtual_account.deposit_funds_failedDeposit settlement failedCrypto only
payout.createdPayout createdBoth
payout.deposit_receivedStablecoins received at deposit addressCrypto payout only
payout.status_changedPayout status transition (pending, processing)Both
payout.completedPayout successfully sent to recipientBoth
payout.failedPayout failedBoth
payout.returnedFunds returned after being sentBoth

Virtual Account Events

virtual_account.created

Sent when a virtual account is created.

{
  "event": "virtual_account.created",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440001",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440002",
    "status": "activating"
  }
}

virtual_account.activated

Sent when a virtual account transitions to active status and is ready to receive deposits.

{
  "event": "virtual_account.activated",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440003",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440002",
    "status": "active"
  }
}

Deposit Events

virtual_account.deposit_funds_received

Sent when USD funds arrive at the virtual account via wire or ACH transfer. This is the primary deposit event for both crypto and fiat mode accounts.

The source field varies depending on the payment rail (wire vs ACH).

Wire Deposit

{
  "event": "virtual_account.deposit_funds_received",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440010",
    "deposit_id": "550e8400-e29b-41d4-a716-446655440011",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440002",
    "amount": "1000.00",
    "currency": "usd",
    "status": "completed",
    "source": {
      "payment_rail": "wire",
      "sender_name": "Acme Corporation",
      "sender_account_number": "9876543210",
      "sender_address": "123 Main St, New York, NY 10001",
      "imad": "20240115MQFNP000001234",
      "omad": "20240115MQFNP000005678",
      "wire_message": "Invoice payment #1234"
    },
    "created_at": "2024-01-15T14:30:00Z"
  }
}

ACH Deposit

{
  "event": "virtual_account.deposit_funds_received",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440012",
    "deposit_id": "550e8400-e29b-41d4-a716-446655440013",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440002",
    "amount": "500.00",
    "currency": "usd",
    "status": "completed",
    "source": {
      "payment_rail": "ach_push",
      "sender_name": "John Smith",
      "sender_account_number": "1234567890",
      "trace_number": "123456789012345",
      "sec_code": "CCD"
    },
    "created_at": "2024-01-15T09:15:00Z"
  }
}

Refunded Deposit

{
  "event": "virtual_account.deposit_funds_received",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440014",
    "deposit_id": "550e8400-e29b-41d4-a716-446655440015",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440002",
    "amount": "200.00",
    "currency": "usd",
    "status": "refunded",
    "source": {
      "payment_rail": "wire",
      "sender_name": "Unknown Sender"
    },
    "created_at": "2024-01-15T16:00:00Z"
  }
}

Source Fields

FieldWireACHDescription
payment_rail"wire""ach_push"How funds were sent
sender_nameYesYesName of the sender
sender_account_numberOptionalOptionalSender's bank account number
sender_addressOptionalNoSender's address
imadOptionalNoInput Message Accountability Data
omadOptionalNoOutput Message Accountability Data
wire_messageOptionalNoWire memo/message
trace_numberNoOptionalACH trace number
sec_codeNoOptionalSEC code (CCD, PPD, WEB)

Deposit Status

StatusDescription
completedDeposit received and processed
refundedDeposit was returned to sender

Microdeposit Event

virtual_account.microdeposit_funds_received

Sent when a deposit under $1.00 USD is received. Microdeposits do not trigger crypto settlement - no deposit_funds_in_transit or deposit_funds_in_destination events are emitted.

{
  "event": "virtual_account.microdeposit_funds_received",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440016",
    "deposit_id": "550e8400-e29b-41d4-a716-446655440017",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440002",
    "amount": "0.50",
    "currency": "usd",
    "status": "completed",
    "source": {
      "payment_rail": "ach_push",
      "sender_name": "Verification Service",
      "trace_number": "123456789012345"
    },
    "created_at": "2024-01-15T10:00:00Z"
  }
}

Note: Microdeposits are typically used for account verification purposes. The payload structure is the same as deposit_funds_received.


Deposit Settlement Events (Crypto Mode Only)

For crypto mode virtual accounts, after a deposit is received, the system converts the funds to stablecoins and sends them to the configured destination wallet. These events track that settlement process.

Note: These events only apply to crypto mode VAs. Fiat mode deposits do not trigger settlement - the balance is simply updated.

virtual_account.deposit_funds_in_transit

Sent when the settlement process begins (internal transfer initiated, crypto conversion queued).

{
  "event": "virtual_account.deposit_funds_in_transit",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440030",
    "deposit_id": "550e8400-e29b-41d4-a716-446655440011",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440002",
    "user_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "pending",
    "source": {
      "amount": "10000.00",
      "currency": "USD"
    },
    "destination": {
      "currency": "USDC",
      "network": "solana",
      "address": "7tQJvFk8XZoaVRjL..."
    },
    "processing_started_at": "2024-01-15T14:31:00Z"
  }
}

virtual_account.deposit_funds_in_destination

Sent when stablecoins have been successfully sent to the destination wallet. Includes a full settlement breakdown showing fees deducted and FX conversion details.

{
  "event": "virtual_account.deposit_funds_in_destination",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440031",
    "deposit_id": "550e8400-e29b-41d4-a716-446655440011",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440002",
    "user_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "source": {
      "amount": "10000.00",
      "currency": "USD"
    },
    "destination": {
      "amount": "9910.09",
      "currency": "USDC",
      "network": "solana",
      "address": "7tQJvFk8XZoaVRjL...",
      "tx_hash": "5KYmFMZ3qvX7h8sN..."
    },
    "settlement": {
      "platform_fees": {
        "base_fee": "15.00",
        "percentage_fee": "12.00",
        "total": "27.00"
      },
      "client_fees": {
        "total": "51.00"
      },
      "total_fees": "78.00",
      "fee_currency": "USD",
      "fx": {
        "commercial_rate": "1.0000",
        "applied_rate": "0.9988",
        "markup_rate": "0.0012",
        "markup_cost": "11.91"
      }
    },
    "completed_at": "2024-01-15T14:35:00Z"
  }
}

Settlement Fields

FieldDescription
settlement.platform_fees.base_feeFixed fee charged by the platform (rail-specific)
settlement.platform_fees.percentage_feePercentage-based fee on deposit amount
settlement.platform_fees.totalSum of platform base + percentage fees
settlement.client_fees.totalFee markup configured by the client
settlement.total_feesTotal fees deducted from deposit (platform + client)
settlement.fee_currencyCurrency in which fees are denominated
settlement.fx.commercial_rateRaw market exchange rate
settlement.fx.applied_rateRate after FX markup: commercial_rate × (1 - markup_rate)
settlement.fx.markup_rateFX markup rate applied to conversion
settlement.fx.markup_costDollar cost of the FX markup on net amount

How destination amount is calculated: destination.amount = (source.amount - total_fees) × applied_rate Example: (10,000.00 - 78.00) × 0.9988 = 9,910.09 USDC

virtual_account.deposit_funds_failed

Sent when the settlement process fails (e.g., crypto transfer failure).

{
  "event": "virtual_account.deposit_funds_failed",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440032",
    "deposit_id": "550e8400-e29b-41d4-a716-446655440011",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440002",
    "user_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "failed",
    "source": {
      "amount": "10000.00",
      "currency": "USD"
    },
    "failure_reason": "Settlement transfer failed",
    "failed_at": "2024-01-15T14:35:00Z"
  }
}

Payout Events

Payout events apply to both fiat and crypto mode payouts. The crypto mode includes additional events like payout.deposit_received.

payout.created

Sent when a payout is created.

Fiat mode - VA balance is debited, wire transfer queued:

{
  "event": "payout.created",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440020",
    "payout_id": "550e8400-e29b-41d4-a716-446655440010",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440003",
    "recipient_id": "550e8400-e29b-41d4-a716-446655440020",
    "amount": "1000.00",
    "currency": "USD",
    "recipient_amount": "977.00",
    "recipient_currency": "USD",
    "fees": {
      "base_fees": {
        "fixed_fee": "15.00",
        "percentage_fee": "5.00"
      },
      "client_markup": {
        "fixed_fee": "2.00",
        "percentage_fee": "1.00"
      },
      "total_fees": "23.00"
    },
    "status": "created",
    "created_at": "2024-01-15T14:30:00Z"
  }
}

Crypto mode - Deposit wallet generated, awaiting stablecoin deposit:

{
  "event": "payout.created",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440021",
    "payout_id": "550e8400-e29b-41d4-a716-446655440010",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440001",
    "recipient_id": "550e8400-e29b-41d4-a716-446655440020",
    "amount": "1000.00",
    "currency": "USD",
    "recipient_amount": "977.00",
    "recipient_currency": "USD",
    "fees": {
      "base_fees": {
        "fixed_fee": "15.00",
        "percentage_fee": "5.00"
      },
      "client_markup": {
        "fixed_fee": "2.00",
        "percentage_fee": "1.00"
      },
      "total_fees": "23.00"
    },
    "status": "created",
    "deposit_instructions": {
      "network": "solana",
      "currency": "USDC",
      "address": "KiraXyz123...",
      "expires_at": "2024-01-16T14:30:00Z"
    },
    "created_at": "2024-01-15T14:30:00Z"
  }
}

payout.deposit_received

Crypto mode only. Sent when stablecoins are detected at the deposit wallet address.

{
  "event": "payout.deposit_received",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440022",
    "payout_id": "550e8400-e29b-41d4-a716-446655440010",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440001",
    "deposit": {
      "tx_hash": "5KYmFMZ3qvX7h8sN...",
      "from_address": "SenderWallet...",
      "amount": "1000.00",
      "token": "USDC",
      "network": "solana"
    },
    "status": "deposit_received",
    "updated_at": "2024-01-15T14:35:00Z"
  }
}

payout.status_changed

Sent when a payout transitions between intermediate statuses (e.g., pending, processing).

{
  "event": "payout.status_changed",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440026",
    "payout_id": "550e8400-e29b-41d4-a716-446655440010",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440003",
    "status": "pending",
    "previous_status": "created",
    "amount": "1000.00",
    "currency": "USD",
    "recipient_amount": "977.00",
    "recipient_currency": "USD",
    "updated_at": "2024-01-15T14:30:30Z"
  }
}

Note: The status field can be PENDING (queued for execution) or PROCESSING (wire/SWIFT transfer being routed).

payout.completed

Sent when the wire/SWIFT transfer has been completed and funds delivered to the recipient.

{
  "event": "payout.completed",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440023",
    "payout_id": "550e8400-e29b-41d4-a716-446655440010",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440003",
    "amount": "1000.00",
    "currency": "USD",
    "recipient_amount": "977.00",
    "recipient_currency": "USD",
    "status": "completed",
    "updated_at": "2024-01-17T10:15:00Z"
  }
}

payout.failed

Sent when a payout fails at any stage of processing.

{
  "event": "payout.failed",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440024",
    "payout_id": "550e8400-e29b-41d4-a716-446655440010",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440003",
    "amount": "1000.00",
    "currency": "USD",
    "recipient_amount": "977.00",
    "recipient_currency": "USD",
    "status": "failed",
    "updated_at": "2024-01-15T14:35:00Z"
  }
}

payout.returned

Sent when funds are returned after being sent.

{
  "event": "payout.returned",
  "data": {
    "event_id": "evt_550e8400-e29b-41d4-a716-446655440025",
    "payout_id": "550e8400-e29b-41d4-a716-446655440010",
    "virtual_account_id": "550e8400-e29b-41d4-a716-446655440003",
    "amount": "1000.00",
    "currency": "USD",
    "recipient_amount": "977.00",
    "recipient_currency": "USD",
    "status": "returned",
    "updated_at": "2024-01-20T09:00:00Z"
  }
}

Event Flow by Account Mode

Fiat Mode - Deposit + Payout

sequenceDiagram
    participant Sender
    participant Banking Partner
    participant Kira
    participant Your Server
    participant Recipient Bank

    Note over Sender,Your Server: Deposit Flow
    Sender->>Banking Partner: Send USD (ACH/Wire)
    Banking Partner->>Kira: Webhook: wire_inbound/ach_credit
    Kira->>Your Server: virtual_account.deposit_funds_received

    Note over Your Server,Recipient Bank: Payout Flow
    Your Server->>Kira: POST /payout
    Kira->>Your Server: payout.created
    Kira->>Your Server: payout.status_changed (PENDING)
    Kira->>Banking Partner: Wire outbound
    Kira->>Your Server: payout.status_changed (PROCESSING)
    Banking Partner->>Recipient Bank: WIRE/SWIFT
    Banking Partner-->>Kira: Completed
    Kira->>Your Server: payout.completed

Crypto Mode - Deposit + Settlement

sequenceDiagram
    participant Sender
    participant Banking Partner
    participant Kira
    participant Your Server
    participant Blockchain

    Sender->>Banking Partner: Send USD (ACH/Wire)
    Banking Partner->>Kira: Webhook: wire_inbound/ach_credit
    Kira->>Your Server: virtual_account.deposit_funds_received

    Note over Kira: Crypto settlement
    Kira->>Your Server: virtual_account.deposit_funds_in_transit
    Kira->>Blockchain: Send stablecoins to destination
    Blockchain-->>Kira: Transaction confirmed
    Kira->>Your Server: virtual_account.deposit_funds_in_destination

Crypto Mode - Payout (Stablecoin Funded)

sequenceDiagram
    participant Your Server
    participant Kira
    participant Blockchain
    participant Banking Partner
    participant Recipient Bank

    Your Server->>Kira: POST /payout (with payment_instructions)
    Kira->>Your Server: payout.created (with deposit_instructions)

    Note over Your Server,Blockchain: Send stablecoins
    Your Server->>Blockchain: Transfer stablecoins
    Blockchain-->>Kira: Deposit detected
    Kira->>Your Server: payout.deposit_received

    Kira->>Your Server: payout.status_changed (PENDING)
    Kira->>Banking Partner: Wire outbound
    Kira->>Your Server: payout.status_changed (PROCESSING)
    Banking Partner->>Recipient Bank: WIRE/SWIFT
    Banking Partner-->>Kira: Completed
    Kira->>Your Server: payout.completed

Best Practices

  1. Always return 200 - Acknowledge receipt immediately, process asynchronously
  2. Handle duplicates - Use event_id or payout_id to detect duplicate deliveries
  3. Process idempotently - Ensure handling the same event twice doesn't cause issues
  4. Log all events - Keep records for debugging and reconciliation
  5. Set up monitoring - Alert on failed webhook deliveries or processing errors
  6. Handle all event types - Even if you only care about some, log unknown types
  7. Verify webhook signatures - See Webhooks Guide for signature verification

Related Documentation