> ## Documentation Index
> Fetch the complete documentation index at: https://docs.useduro.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Virtual Account

> A dedicated Nomba bank account per customer — fund the wallet by transfer, BVN encrypted at rest, inbound transfers auto-credited and idempotent.

Not everyone wants to top up with a card. A customer can request a **dedicated virtual bank account** — a real Nigerian account number, issued by Nomba, that belongs to them. Any bank transfer into it lands in their [wallet](/identity/wallet) automatically. It's the card-free way to keep a renewal funded.

## Requesting one requires a BVN — handled carefully

Nomba issues a virtual account against a **BVN** (Bank Verification Number, 11 digits). Duro has **no BVN validator** of its own, so it treats the BVN as sensitive input from the moment it arrives:

```mermaid theme={null}
flowchart TD
    REQ["POST /virtual-account { bvn }<br/>(11 digits)"] --> ENC["encrypt BVN at rest<br/>(cryptor) → bvnCipher"]
    ENC --> HASH["store sha256(bvn) → bvnHash<br/>@@unique (one BVN per account)"]
    HASH --> NOMBA["Nomba: create virtual account<br/>(accountRef, accountName, bvn)"]
    NOMBA --> STORE["store VirtualAccount<br/>bank name · number · holder"]
    STORE --> DONE["account details returned<br/>(BVN never is)"]
```

* **Encrypted at rest.** The BVN is encrypted with Duro's cryptor and stored as `bvnCipher`. It is **never returned to the browser** — the portal only ever learns whether a BVN is on file (`hasBvn`), never its value.
* **One BVN per account.** A `sha256(bvn)` hash is stored as `bvnHash` with a **unique** constraint, so the same BVN can't be linked to two different accounts. A collision surfaces as *"this BVN is already linked to another account."*
* **Live-mode only.** Virtual accounts are a real banking product — there is no sandbox equivalent, and the feature is unavailable unless the cryptor and the Nomba client are both configured.

<Note>
  Duro deliberately does **not** validate the BVN itself — it has no authority to. It collects it, secures it, and passes it to Nomba, which owns the KYC check. Duro's job is custody: encrypt at rest, never echo it back, and enforce one-BVN-per-account.
</Note>

## Funding — an inbound transfer auto-credits the wallet

Once the account exists, funding it is just a bank transfer. Nomba notifies Duro, and the worker credits the wallet:

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant Bank as Customer's bank
    participant N as Nomba
    participant WH as Worker (nomba webhook)
    participant VA as VirtualAccount lookup
    participant W as Wallet

    Bank->>N: transfer ₦ into the virtual account
    N->>WH: payment_success (transactionType = vact_transfer)
    WH->>VA: find by accountRef (from webhook)
    VA-->>WH: the owning account
    WH->>W: credit wallet (reason: virtual_account_funding)
    Note over W: idempotent on the Nomba transaction id
```

The worker routes on the webhook's `transactionType` of **`vact_transfer`**, correlates it to the right account by the **`accountRef`** carried on the event, and credits the wallet with `reason: virtual_account_funding`. The credit is **idempotent on the Nomba transaction id**, so a redelivered webhook credits the balance exactly once. An amount that resolves to zero kobo, an unresolvable `accountRef`, or a missing transaction id are each logged and skipped rather than crediting blindly.

## Where it fits

A virtual account is the third way money reaches the wallet, sitting beside a card [top-up](/payments/top-up):

<CardGroup cols={3}>
  <Card title="Card top-up" icon="credit-card">
    Inline Nomba checkout, arbitrary amount, card saved on the way through.
  </Card>

  <Card title="Bank transfer" icon="building-columns">
    A transfer into the dedicated virtual account — this page.
  </Card>

  <Card title="Then: wallet-first" icon="bolt">
    However it was funded, the balance renews subscriptions before any card is touched. [→](/billing/wallet-first-billing)
  </Card>
</CardGroup>

Next: the [customer portal](/identity/customer-portal) where a customer requests all of this.
