API referenceVirtual accounts

Virtual accounts

Funding accounts merchants receive money into. Read-only on the public API, provisioning new merchant-level VAs is admin-only.

If your business model requires per-end-user VIBANs (one virtual IBAN per app user, common in remittance + e-wallet apps), additional capabilities can be enabled on your account. Contact support@the-technest.com for details.

The virtual_account object

{
  "object": "virtual_account",
  "id": "ckva_xxxxxxxxxxxx",
  "currency": "NGN",
  "env": "live",
  "status": "active",
  "account_number": "8001234567",
  "account_name": "TECHNEST/MERCHANT NAME",
  "bank_name": "Wema Bank",
  "bank_code": "035",
  "provider": "<provider_slug>",
  "provider_ref": "<provider_ref>",
  "is_visible_to_merchant": true,
  "created_at": "2026-01-15T10:00:00Z"
}

Status values

StatusDescription
provisioningCreated at the provider; awaiting activation webhook
activeReady to receive funds
failedProvisioning failed
closedDeactivated

GETList virtual accounts

GET/v1/virtual_accounts

Returns merchant-level VAs. Per-customer VIBANs are excluded, query /v1/customers/{id}/virtual_accounts instead.

Query parameters
limitinteger

1-100, default 50.

starting_afterstring

Cursor.

currencystring

Filter to one currency.

statusstring

Filter: provisioning | active | failed | closed.

Request
curl 'https://api.swappr.me/api/v1/virtual_accounts?currency=NGN&status=active&limit=50' \
  -H "Authorization: Bearer sk_live_..."
Response
{
  "object": "list",
  "has_more": false,
  "data": [
    { "object": "virtual_account", "id": "...", ... },
    ...
  ]
}
200 OK

GETRetrieve a virtual account

GET/v1/virtual_accounts/{id}

Returns the same virtual_account shape as the list endpoint.

Request
curl https://api.swappr.me/api/v1/virtual_accounts/ckva_xxx \
  -H "Authorization: Bearer sk_live_..."
Response
{
  "object": "virtual_account",
  "id": "ckva_xxxxxxxxxxxx",
  "currency": "NGN",
  "env": "live",
  "status": "active",
  "account_number": "8001234567",
  "account_name": "TECHNEST/MERCHANT NAME",
  "bank_name": "Wema Bank",
  "bank_code": "035",
  "provider": "<provider_slug>",
  "provider_ref": "<provider_ref>",
  "is_visible_to_merchant": true,
  "created_at": "2026-01-15T10:00:00Z"
}
200 OK

Sub-merchant collections

Not sure this is the right flow? See Which flow am I in?. For a one-off payment link instead of a standing account, use Collections.

Issue a dedicated account for each of your sub-merchants, so money their customers pay lands with you, tagged to the right sub-merchant. Your sub-merchants never sign up on Swappr.

These accounts are issued in NGN, as a NUBAN. Swappr also collects other currencies for customers you onboard through international accounts. Every account settles into your wallet in the account’s currency. You do not hold a separate balance per sub-merchant. You attribute each inflow using the customer_reference you set, and you run the per-sub-merchant accounting in your own system.

Before you can create these accounts: Technest grants your account access, you accept the sub-merchant collections terms in your dashboard, and the API key carries the Create virtual accounts capability. If any of the three is missing the request returns 403.

POSTCreate a sub-merchant account

POST/v1/virtual_accounts

Creates an NGN account for one of your sub-merchants and returns it. Requires an Idempotency-Key header. Retrying with the same key returns the same account rather than creating a second one.

Body parameters
namestringRequired

The sub-merchant’s business name. Appears on the account and on your reconciliation.

bvnstringRequired

The sub-merchant’s Bank Verification Number. Exactly 11 digits. Validated before the account is created; a malformed BVN is rejected with invalid_bvn and no account is provisioned. The BVN is stored encrypted and is never returned.

currencystringRequired

Must be NGN. A value other than NGN returns unsupported_currency. Other currencies are available for customers you onboard through international accounts.

referencestring

Your own stable identifier for this sub-merchant. It is echoed back as customer_reference and carried on every wallet_funded webhook, so it is how you attribute inflows. Reusing the same reference returns the same sub-merchant’s account. If omitted, we generate one. A reference already used for a non-sub-merchant customer is rejected with reference_conflict.

Request
curl https://api.swappr.me/api/v1/virtual_accounts \
  -X POST \
  -H "Authorization: Bearer sk_live_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Ada Stores",
    "bvn": "22222222222",
    "currency": "NGN",
    "reference": "submerchant_8842"
  }'
Response
{
  "object": "virtual_account",
  "id": "ckva_xxxxxxxxxxxx",
  "currency": "NGN",
  "env": "live",
  "status": "active",
  "account_number": "8001234567",
  "account_name": "ADA STORES",
  "bank_name": "<bank_name>",
  "bank_code": "035",
  "customer_reference": "submerchant_8842",
  "created_at": "2026-01-15T10:00:00Z"
}

A retry with the same Idempotency-Key returns the same account with 200.

201 Created

Receiving funds

When a sub-merchant’s customer pays into the account, we credit your wallet in the account’s currency and fire a wallet_funded webhook. Its customer.customer_reference is the reference you set, so you know which sub-merchant the money is for. The net amount is what lands in your wallet. Sub-merchant accounts hold no balance of their own, so there is nothing to sweep. Reconcile and pay your sub-merchants from your own records.

The wallet_funded webhook is your single signal for money coming in, across every currency and account type: sub-merchant accounts, international customer accounts, and your own funding accounts. Build one handler keyed on customer.customer_reference and it covers them all.

Sub-merchant accounts do not appear in GET /v1/virtual_accounts; that list is your own funding accounts. Attribute and reconcile sub-merchant inflows from the wallet_funded webhook.

Simulate an inbound payment (sandbox)

Testing your wallet_funded handling should not require real money moving. In the sandbox environment you can fire a simulated inbound payment at one of your own sub-merchant accounts. It runs through the same path a real inbound payment takes: your test wallet is credited, the inflow is attributed to the sub-merchant, and a wallet_funded webhook is queued, so you can exercise your webhook endpoint and your reconciliation end to end.

This endpoint is sandbox-only. Authenticate with a sk_test_ key. A sk_live_ key returns 400 sandbox_only, and the account number is only ever matched against your own sandbox accounts.

POSTSimulate a payment

POST/v1/sandbox/simulate-payment

Credits one of your sandbox sub-merchant accounts as if a customer paid into it.

Body parameters
account_numberstringRequired

The account number of one of your own sandbox sub-merchant accounts.

amount_minorintegerRequired

The amount to credit, in the currency minor unit (kobo for NGN). Must be a positive integer. For example, 500000 credits ₦5,000.00.

sender_namestringRequired

The payer name. Shown on the transaction and used for attribution and screening.

sender_account_numberstring

Optional. The payer’s account number.

sender_bank_codestring

Optional. The payer’s bank code.

Request
curl https://api.swappr.me/api/v1/sandbox/simulate-payment \
  -X POST \
  -H "Authorization: Bearer sk_test_..." \
  -H "Content-Type: application/json" \
  -d '{
    "account_number": "8001234567",
    "amount_minor": 500000,
    "sender_name": "Ada Obi"
  }'
Response
{
  "object": "simulated_payment",
  "virtual_account_number": "8001234567",
  "amount_minor": 500000,
  "currency": "NGN",
  "credited": true,
  "transaction_reference": "SIM_8f2c1e90-..."
}

Your sandbox wallet is credited and a wallet_funded webhook is queued, exactly as a real inbound payment would.

200 OK

Errors

StatusCodeMeaning
400sandbox_onlyThe request used a live key or a live environment. Use a sk_test_ key.
400missing_fieldaccount_number or sender_name is missing.
400invalid_fieldamount_minor is not a positive integer, or the body is not valid JSON.
404virtual_account_not_foundNo sandbox account with that number belongs to your account.