A Virtual IBAN is a dedicated EUR IBAN opened in the name of one of your companies. SEPA payments sent to it land on a separate EUR bank balance. From there you pay out by SEPA to any IBAN or move the money to your own EUR account.
Before You Start #
Virtual IBAN is set up individually for each merchant. Your account manager agrees the setup with you and then sends you:
companyId— the company the IBAN is enabled for;accountId— your account used for Virtual IBAN: it pays the issue fee, if your tariff has one, and receives transfers from the bank balance.
Your tariff (issue, incoming and payout fees) and the operations available to you are also agreed individually; you can always read them with GET …/account. Until the setup is done, every call returns 403 with capability_disabled.
All endpoints use the usual x-client-id / x-client-secret headers. Amounts are EUR with at most two decimals and are sent and returned as strings.
Integration Flow #
- Read the state: what is enabled, your tariff and, once issued, the IBAN and its balance.
- Issue the IBAN and poll until it is
active. - Give the IBAN to your payers. Incoming payments are credited automatically.
- Pay out by SEPA: quote the fee, then create the payout with that fee.
- Or move money to your own EUR account.
| Step | Endpoint |
|---|---|
| Read state | GET /v1/corporate-banking/{companyId}/account |
| Issue | POST /v1/corporate-banking/{companyId}/account |
| Quote a payout | POST /v1/corporate-banking/{companyId}/quote |
| Pay out | POST /v1/corporate-banking/{companyId}/payouts |
| Follow a payout | GET /v1/corporate-banking/{companyId}/payouts/{payoutId} |
| Move to your EUR account | POST /v1/corporate-banking/{companyId}/transfers |
1. Read the State #
GET /v1/corporate-banking/{companyId}/account?fundingAccountId={yourEurAccountId}
The response shows:
capabilities— which ofissue,depositandpayoutare enabled for the company;tariff— the issue, deposit and payout fees, each a percentage plus a fixed amount;minimumDeposit— the smallest incoming payment that still credits something after the deposit fee;purposeCodes— the values accepted asreasonin payouts;account— the IBAN with its bank balance, ornulluntil one has been issued.
fundingAccountId is optional: pass the accountId from your account manager to see its balance before the IBAN is issued.
2. Issue the IBAN #
POST /v1/corporate-banking/{companyId}/account
Content-Type: application/json
{
"accountId": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"owner": {
"name": "Example Trading Ltd",
"addressLine1": "12 King Street",
"city": "Toronto",
"region": "Ontario",
"postcode": "M5H1A1",
"country": "CA",
"nationality": "CA",
"registrationNumber": "1234567890"
}
}
accountIdis the account from your account manager. If your tariff has an issue fee and the account cannot cover it, nothing is issued and the call returnsinsufficient_issuance_funds.owneris the legal company the IBAN is opened for. Text fields accept Latin letters, digits, spaces and/ - ? : ( ) . ' +(addresses also,), without leading or trailing spaces.countryandnationalityare ISO 3166-1 alpha-2 codes.- One IBAN per company: repeating the call returns the same IBAN.
Issuance is asynchronous. The response is 202 with status:
status |
Meaning |
|---|---|
pending |
Being issued. Poll GET …/account again. |
active |
Ready to receive payments. iban, bic and ownerName are filled in. |
failed |
Not issued, and the issue fee was returned. You can issue again. |
3. Receive Payments #
Give your payers the iban, bic and ownerName from the account object. No payment reference is required.
Each incoming SEPA payment is credited to the bank balance automatically, net of the deposit fee, and announced with the usual account webhook (see Webhook Balance Effects). The bank balance is account.balance in the state response.
4. Pay Out by SEPA #
Quote the fee first:
POST /v1/corporate-banking/{companyId}/quote
Content-Type: application/json
{ "amount": "100.00" }
{ "amount": "100.00", "fee": "2.50", "totalDebit": "102.50", "currency": "EUR" }
Then create the payout with the quoted fee as expectedFee:
POST /v1/corporate-banking/{companyId}/payouts
Idempotency-Key: payout-2026-104
Content-Type: application/json
{
"virtualAccountId": "6b0f2b1e-6f1c-4c5e-9a55-0f2a5b7c1d10",
"amount": "100.00",
"expectedFee": "2.50",
"destination": {
"name": "Supplier GmbH",
"iban": "DE89370400440532013000",
"bic": "COBADEFFXXX",
"country": "DE",
"addressLine1": "Hauptstrasse 1",
"city": "Berlin",
"postcode": "10115",
"reason": "SUPP",
"reference": "Invoice 2026-104",
"scheme": "AUTO"
}
}
virtualAccountIdisaccount.idfrom the state response.- If the tariff changed after the quote, the payout is refused with
fee_changedinstead of charging a different fee. Quote again. amountplus the fee is reserved on the bank balance at once. The reserve is spent when the payout settles and released if it fails.
The response is 202; its correlationId is the payout ID. Follow the payout with GET …/payouts/{payoutId}:
status |
Meaning |
|---|---|
settled |
Sent. Final. |
failed |
Rejected; amount and fee returned to the bank balance. Final. |
unknown |
The outcome is being confirmed with the bank. Do not resend — keep polling. |
| any other | In progress, money held. |
Retries #
Retries are safe with the same Idempotency-Key: the same key returns the same payout, while a different body under that key is idempotency_conflict. Never change the key to retry a payout whose outcome you do not know — look it up with GET …/payouts/{payoutId} instead.
5. Move Money to Your EUR Account #
POST /v1/corporate-banking/{companyId}/transfers
Idempotency-Key: sweep-2026-10-07
Content-Type: application/json
{ "toAccountId": "d290f1ee-6c54-4b01-90e6-d701748f0851", "amount": "500.00" }
The transfer is immediate and free. Only this direction is possible: the bank balance is funded by incoming SEPA payments, not by transfers. Not enough money on the bank balance is banking_rejected.
Errors #
Errors are returned as {"code": "..."}. A wrong or missing x-client-id / x-client-secret is a 401 without a body, as on every other endpoint.
| HTTP | code |
What to do |
|---|---|---|
| 400 | invalid_request |
Fix the request. |
| 400 | banking_rejected |
The bank refused the operation, or the bank balance is too low. |
| 400 | insufficient_issuance_funds |
The account cannot cover the issue fee. Contact your account manager. |
| 403 | capability_disabled |
Virtual IBAN, or this operation, is not enabled for the company. Contact your account manager. |
| 404 | not_found |
Unknown company, IBAN or payout. |
| 409 | fee_changed |
The tariff changed. Quote again. |
| 409 | idempotency_conflict |
The Idempotency-Key was already used with a different body. |
| 409 | previous_issue_recovering |
A previous issuance is still being resolved. Retry later. |
| 409 | bank_account_migration_required |
Contact your account manager. |
| 503 | banking_unavailable |
The bank is temporarily unavailable. Retry later. |