Virtual accounts

Receive, identify, and reconcile payments with unique virtual account numbers and vIBANs linked to your main bank account

A virtual account is a unique account number, such as a virtual IBAN (vIBAN), linked to a main bank account at your partner bank.

How it works

Your partner bank holds one main account. It generates and attaches multiple virtual account numbers to it. Each payer receives a different number.

When your bank reports a credit, Mambu Payments reads the virtual account number on it. Mambu Payments then links the transaction to the matching virtual account. You know which payer, invoice, or loan the funds belong to without parsing payment references.

Use virtual account numbers in reconciliation rules and expected payments to match transactions automatically. You can also originate payments from a virtual account.

In Mambu Payments, a virtual account is an internal account with type set to virtual. Manage it with the internal accounts API endpoints, the dashboard, and internal_account webhook events.

Key concepts

TermDefinition
Connected accountThe main bank account at your partner bank. Virtual accounts attach to it.
Internal accountAn account your organization holds for a customer. It has a type of own or virtual.
Virtual account numberThe identifier payers send funds to. It is an IBAN, a UK account number, or any alphanumeric string of up to 35 characters.
Account holderThe person or organization a virtual account is held for. Linking one is optional.
Bank synchronizationThe exchange of creation and lifecycle requests with your bank.

Choose how to provision a virtual account

Mambu Payments supports two provisioning modes.

ModeHow it worksInitial status
Register an existing numberYou provide a number your bank already issued.active
Create at your bankMambu Payments requests the number from your bank.pending_creation

Account numbers are unique across all non-disabled internal accounts in your organization. You can reuse a number after you disable the internal account that holds it.

Register an existing virtual account

To register a number your bank already issued, create an internal account with type set to virtual.

curl --request POST \
     --url https://sandbox.numeral.io/v1/internal_accounts \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'X-API-Key: <api_key>' \
     --data '
{
  "type": "virtual",
  "name": "Dupont Logistics - invoices",
  "holder_name": "Dupont Logistics SAS",
  "account_number": "FR7630004000031234567890143",
  "connected_account_ids": ["a5d1f662-28ff-4215-9bdd-d735cdb5f8d7"],
  "account_holder_id": "de785fd3-da86-45c7-a185-719ebfb2122d",
  "metadata": { "customer_ref": "CUST-1042" }
}
'

The response returns the internal account with status active. Mambu Payments sends an internal_account.created event.

Create a virtual account at your bank

To have your bank issue the number, set synchronized_with_bank to true. For most banks, omit account_number and let the bank allocate it.

curl --request POST \
     --url https://sandbox.numeral.io/v1/internal_accounts \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'X-API-Key: <api_key>' \
     --data '
{
  "type": "virtual",
  "name": "Dupont Logistics - invoices",
  "holder_name": "Dupont Logistics SAS",
  "connected_account_ids": ["a5d1f662-28ff-4215-9bdd-d735cdb5f8d7"],
  "synchronized_with_bank": true
}
'

The response returns status pending_creation. When the bank answers, the status changes to active or creation_rejected. The final account_number arrives in an internal_account.updated event.

Wait for the internal_account.created event before you share the number with your customer.

Track the virtual account lifecycle

A virtual account has one of five statuses.

StatusMeaning
pending_creationThe bank received the creation request and has not confirmed it.
creation_rejectedThe bank refused the creation request.
activeThe virtual account receives and sends payments.
blockedThe virtual account is suspended. You can unblock it.
disabledThe virtual account is closed. You cannot enable it again.

Only bank-synchronized accounts use pending_creation and creation_rejected. The bank's answer sets them. You cannot set them yourself.

Use these endpoints to manage an existing virtual account.

ActionEndpointRequirement
UpdatePOST /internal_accounts/<internal_account_id>None
BlockPOST /internal_accounts/<internal_account_id>/blockStatus is active.
UnblockPOST /internal_accounts/<internal_account_id>/unblockStatus is blocked.
DisablePOST /internal_accounts/<internal_account_id>/disableStatus is not disabled.

Update names, holder details, addresses, currencies, metadata, and custom fields. You cannot change the account number, type, or connected accounts.

Block and disable accept a reason. The values are fraud, duplicate, account_holder_deceased, account_holder_offboarded, and other. The default is other.

Caution: Disabling a virtual account is irreversible. Block the account instead to suspend it temporarily.

Disabling an account holder disables all of its internal accounts. For bank-synchronized accounts, Mambu Payments forwards updates, blocks, unblocks, and disables to your bank if the bank supports them.

Listen to virtual account events

Subscribe to the internal_account webhook topic. Each event carries the full internal account object.

EventSent when
internal_account.pending_creationYou create a bank-synchronized account.
internal_account.createdThe account becomes active.
internal_account.creation_rejectedThe bank refuses the creation request.
internal_account.updatedAny attribute changes, including the number returned by the bank.
internal_account.blockedYou block the account.
internal_account.unblockedYou unblock the account.
internal_account.disabledYou disable the account.

A bank-synchronized account typically produces a pending_creation event, then one or more updated events, then a createdevent.

Receive payments on a virtual account

When your bank reports a credit to a virtual account number, Mambu Payments follows three steps.

  1. Store the number on the transaction.
  2. Search for an internal account of type virtual with that number on the transaction's connected account.
  3. Set internal_account_id on the transaction if exactly one account matches.

The transaction then references the virtual account.

{
  "id": "b6c9d3a2-8f41-4e7b-9a15-2c0e5f7d1b38",
  "object": "transaction",
  "internal_account_id": "0d3b9d33-21d4-47c5-9421-be899ed8a4ba",
  "internal_account": {
    "account_number": "FR7630004000031234567890143",
    "holder_name": "Dupont Logistics SAS"
  }
}

If no virtual account matches, Mambu Payments still records the transaction with the virtual account number. In that case, internal_account_id is empty.

To retrieve the transactions of one virtual account, filter List transactions by internal_account_id or internal_account_number.

Reconcile payments with virtual accounts

Virtual accounts make reconciliation deterministic.

  1. Create one virtual account for each invoice, customer, or loan.
  2. Create an expected payment and set internal_account_id to that virtual account.
  3. Create a reconciliation rule that matches on the virtual account number, amount, direction, and date.

Mambu Payments then reconciles a matching transaction automatically, whatever reference the payer wrote. See Understanding reconciliations.

Send payments from a virtual account

To originate a payment from a virtual account, set originating_account_id on the payment order.

curl --request POST \
     --url https://sandbox.numeral.io/v1/payment_orders \
     --header 'Accept: application/json' \
     --header 'Content-Type: application/json' \
     --header 'X-API-Key: <api_key>' \
     --data '
{
  "connected_account_id": "a5d1f662-28ff-4215-9bdd-d735cdb5f8d7",
  "type": "sepa",
  "direction": "credit",
  "amount": 125000,
  "currency": "EUR",
  "originating_account_id": "0d3b9d33-21d4-47c5-9421-be899ed8a4ba",
  "receiving_account": {
    "account_number": "DE89370400440532013000",
    "holder_name": "Schneider GmbH"
  }
}
'

Mambu Payments uses the virtual account number and holder details as the originating account. The payment order must meet these conditions:

  • The virtual account has status active.
  • The virtual account is attached to the connected_account_id of the payment order.
  • For SEPA direct debits, the virtual account has a creditor_identifier.

In the dashboard, turn on Use virtual account when you create a payment order on a corporate connected account. See Create a payment order.

Check bank support

Virtual account support depends on the bank behind your connected account.

CapabilityDescription
Incoming payment attributionMambu Payments links credits to your virtual accounts when your bank reports the virtual account number in its statements or notifications.
Creation at the bankMambu Payments creates virtual accounts on your bank's side through its API. Set synchronized_with_bank to true.
Update, block, and disable at the bankMambu Payments forwards these actions to your bank if the bank supports them.
Sending paymentsNot all banks support sending payments using virtual accounts.

If your bank does not support creation through its API, create the virtual accounts at the bank and register them in Mambu Payments.

Limits and constraints

Required permissions:

  • counterparties.view_internal_accounts to view virtual accounts.
  • counterparties.create_internal_accounts to create them.
  • counterparties.edit_internal_accounts to update, block, unblock, and disable them.

API keys need the Write right to create and the Modify right to update, block, unblock, and disable.

Related


Did this page help you?