Skip to main content
This is the authoritative reference for Kira’s three resource state machines (virtual account, deposit, payout): the status vocabulary, the transitions, and how casing differs across surfaces. Use it to map status values to behavior. For webhook delivery and signing, the full event catalog, and the envelope shapes, see Webhooks.
All values here are code- and live-sandbox-verified. The vocabulary below assumes you pin X-Api-Version: 2026-04-14; the raw-enum default is called out where it differs. See Versioning for how to pin.

Virtual account state machine

The virtual account status vocabulary is version-dependent — it is set by your pinned X-Api-Version. On the 2026-04-14 pin (the version this doc tells you to pin to): approved → declined, plus deactivated.
This pin’s mapping is also code-defined to return rfi for an “awaiting more info” hold on the account itself — but no virtual account reaches that state today; every VA that doesn’t provision resolves straight to declined. As of API version 2026-06-01, rfi names the RFI resource instead — an account-level information request never was, and still isn’t, surfaced as a virtual-account status value.
On the default / newer API versions the raw enum is returned instead: pending → activating → active, plus failed, deactivated. Pin to 2026-04-14 to get the approved/declined vocabulary this doc uses. Underlying transition path (same machine, two vocabularies): pending → activating → active/approved → deactivated; any provisioning step → failed/declined (terminal).
approved ≠ funds-ready. Because approved collapses activating and active, the API returns status: "approved" before funds can move. The pending / activating / active ladder is the raw-enum default, not what the 2026-04-14 pin returns.Detect a truly-active VA via account_number being a real account number — non-null AND != "PENDING-ACT-ACCOUNT" (the sentinel is ACT-only; international / crypto VAs show account_number: null until provisioned) — or by handling the virtual_account.activated webhook (the only “funds-ready” event). On an active ACT VA, GET /v1/virtual-accounts/{id}/balance returns 200 with available_balance (while activating it returns 400).

Webhooks vs poll-only

  • virtual_account.created fires when the VA is provisioned (may arrive while still activating).
  • virtual_account.activated fires when it reaches active — the ONLY funds-ready webhook; clients must handle it.
  • failed / declined and deactivated are poll-only — there is no virtual_account.failed and no virtual_account.deactivated event (and no client-facing deactivate/delete endpoint). Observe them via GET /v1/virtual-accounts/{id}.

Deposit state machine

The coarse inbound deposit status is UPPERCASE. It applies to GET /v1/virtual-accounts/{id}/deposits[/{depositId}].
There is no separate RETURNED deposit status — a return and a refund both collapse to REFUNDED.
Casing: the GET resource status is UPPERCASE (COMPLETED), while flat deposit webhook events carry lowercase data.status (completed) — but casing is inconsistent across surfaces generally (user.* events carry UPPERCASE data.status), so ALWAYS compare case-insensitively.
Refund/return detection (rail-dependent): a returned incoming deposit ALWAYS → status: REFUNDED, delivered via ONE of two event names depending on the raildeposit_funds_received re-sent with data.status: "refunded", OR deposit_returned. Branch on data.status == "refunded" (or resource status == REFUNDED), NOT on the event name.
A simulated deposit credits the sandbox balance. On the fiat wire rail a simulate-deposit credits the VA’s available balance and a payout debits it, so deposit → balance → payout is a real cycle; GET /…/balance reflects the credit (possibly after a few seconds). The balance also includes any opening sandbox float, so a payout can also succeed without a prior deposit, and not every rail moves its balance on a simulated deposit — so treat the virtual_account.deposit_funds_received webhook as the rail-independent signal. The simulated deposit also appears in GET /v1/virtual-accounts/{id}/deposits (don’t rely on the list staying empty).A deposit’s settlement_triggered field is true only for crypto deposits ≥ $1; for fiat it is false because the fiat deposit is already completed (no separate settlement leg). settlement_triggered: false on a fiat deposit is normal — it is NOT an error.

Payout state machine

The payout status is UPPERCASE on GET (but NOT on every surface). It is UPPERCASE in GET /v1/payouts/{id}, in the list, and inside payout.status_changed’s data.data.status. BUT the 201 create response returns status: "created" lowercase (the same payout then reads CREATED on GET), and the flat payout.* webhook events carry lowercase data.status. Compare case-insensitively.
  • A beneficiary-bank RETURN arrives as the payout.returned event carrying data.status: "returned" (lowercase). The payout resource then resolves to a terminal FAILED state on GET /v1/payouts/{id} and carries error_code: "va-payout-bank-returned" — note the error_code lives on the resource, NOT in the webhook payload. Returned funds come back to your balance (minus any return fee) to re-attempt. So returned is a valid payout.* event and data.status value, but it is not a GET resource status; there is no DISPUTED status at all.
  • A provider cancellation arrives (on the ACT rail) as a payout.failed event carrying data.status: "cancelled"; the resource resolves to FAILED with a provider-cancelled error_code. CANCELLED is not a GET resource status, but the payout.failed event payload can carry data.status: "cancelled".
  • EXPIRED is crypto-only and is NOT a valid ?status= list filter.
  • Crypto payouts add a settlement phase before the outbound leg (you submit the on-chain txHash, Kira settles, then the fiat/outbound leg runs).
  • Compliance holds resolve on the resource. A KYT_PENDING / IN_REVIEW hold surfaces via payout.status_changed, but its resolution (cleared or rejected) is reflected on the resource and may not arrive as a separate terminal webhook — confirm the final status with GET /v1/payouts/{id}. The same applies to a deposit held for screening (reconcile via GET /v1/virtual-accounts/{id}/deposits/{depositId}).
Casing is inconsistent across surfaces — ALWAYS compare statuses case-insensitively. GET resource status is UPPERCASE (payouts, deposits; VAs follow the version vocabulary — lowercase approved on this pin). The payout 201 create response is lowercase "created" while GET returns CREATED for the same payout. Flat payout/VA webhook events carry lowercase data.status (created, pending, processing, activating); payout.status_changed carries UPPERCASE at data.data.status; user.* events carry UPPERCASE data.status (CREATED).

Webhook events & delivery

Webhook delivery and signing, the full event catalog (which event fires on each transition, with the resulting status), and the envelope shapes all live in the Webhooks guide. Each event there maps to the status values defined in the state machines above.