Skip to main content

Overview

Payins fund your Zuba balance. These deposit methods are live today:
  • NGN bank transfers: transfers into your fixed virtual account are credited automatically; dynamic per-deposit virtual accounts are also available via the API.
  • EUR & GBP bank transfers: transfers into your dedicated named accounts (EUR IBAN via SEPA, GBP sort code + account number via Faster Payments) are credited automatically.
  • USD bank deposits: transfers into your managed USD deposit account are credited automatically.
  • XOF & XAF bank transfers: receiving accounts fund your trade-desk orders. Upload evidence only when the returned settlement has requirements.proofOfPayment: true; other orders reconcile without a proof upload.
  • Crypto deposits: provision an on-chain address and receive USDC or USDT, credited on confirmation. Live accounts apply a per-transfer minimum, below which a transfer is not credited.
Zuba records each accepted deposit in the ledger and credits your balance once funds are received.

NGN Deposits

Fixed virtual account

Your workspace has a dedicated NGN virtual account in your business name (Zuba {Merchant Name}). Transfers sent to it are credited to your NGN balance automatically. No create call is required. Fetch the details with GET /v1/virtual-accounts/me:
Get My Virtual Account Response
The account details are also shown in the dashboard. Contact your account manager if no virtual account has been provisioned yet.

Dynamic virtual accounts (per deposit)

Alternatively, create a deposit with POST /v1/deposits to receive one-off virtual account details for a specific sender. NGN deposits created this way require the sender’s firstName, lastName, email, and phoneNumber to generate the virtual account:
The response contains the virtual account details to display to the sender:
Create Deposit Response (NGN)
The iban and bic field names are shared across deposit methods. For NGN deposits, iban carries the virtual account number and bic carries the bank name. Display the metadata block to the sender: accountNumber, accountName, bankName, and expiresAt are the details they need to complete the transfer before the virtual account expires.

EUR & GBP Bank Deposits

Your workspace can be provisioned with dedicated named accounts in your business name: an EUR IBAN reachable over SEPA and a GBP account number + sort code reachable over Faster Payments. Transfers sent to them are credited to the matching balance automatically (no create call is required) and appear as deposits with method virtual_account in GET /v1/deposits. The account details are shown in the dashboard. Contact your account manager to provision named accounts.

USD Bank Deposits

USD deposits settle into a managed deposit account provisioned for your business. Transfers sent to your account details are credited to your USD balance automatically and appear as deposits with method bank_deposit in GET /v1/deposits. No create call is required. Contact your account manager to provision a USD deposit account.

Requesting an account

Accounts can also be requested over the API with POST /v1/virtual-accounts, for your own workspace or for a customer account you manage (pass its id in Zuba-Account-Id). An account can only be opened once the owner has an approved application carrying everything the receiving bank asks for. Those attributes vary by currency and are not all collected during onboarding, so rather than discovering a gap from a failed request, ask first:
Check what is outstanding
Requirements Response
Branch on ready. When it is false, each entry names what is outstanding and, in resolution, the exact call that supplies it — already resolved down to the specific person where the value belongs to one. Send those, then request the account. Read code rather than description: the codes are stable, the prose is not. A requirement whose resolution is null is one no API call sets, either because it is a detail recorded when the application was reviewed or because it is a state of the account or workspace rather than a value. Its description says what clears it. status distinguishes the cases where nothing is outstanding: ready (the request would succeed), already_exists (the owner already holds an account in this currency, and requesting one returns it), and unavailable (the currency is not offered to this owner, and no value they supply changes that). The check is read-only and safe to call as often as you like — collecting these from your customer at signup avoids a failed request later.

Crypto Deposits

Provision an on-chain deposit address with POST /v1/deposits/crypto-address, then send USDC or USDT to it. The deposit is recorded and credited once the transfer confirms on-chain, and appears with method crypto_deposit.
Live accounts enforce a minimum of 1 USDC or USDT per transfer. A smaller transfer is not credited, does not appear in GET /v1/deposits, and cannot be recovered. Contact your account manager if you have sent one. Amounts do not accumulate: two 0.60 transfers stay uncredited rather than combining to clear the minimum.Transfers funding a trade-desk order are exempt, so a shortfall top-up of any size still reaches your balance.The sandbox enforces no minimum, so sub-unit test transfers are credited there.
network is a CAIP-2 chain identifier: Address behaviour:
  • Addresses are reused. Omitting clientRef returns your single default address for the network; the same call always returns the same address. Pass distinct clientRef values (for example, your end-customer IDs) to provision distinct addresses.
  • The address is asset-agnostic: it receives any supported stablecoin on its network.
  • EVM addresses are valid across all EVM chains.
  • A 409 Conflict means the address for that client/network/reference is frozen or archived and cannot be re-created. Contact support.
Crypto deposits are availability-gated per environment. Contact your account manager to enable them. For the crypto payout side, see Payouts.

Deposit Lifecycle

Deposit Statuses

Checking Deposit Status

Deposit IDs are UUIDs. Fetch a deposit by ID to get its current status:
Deposit status changes are not currently delivered as webhook events. The webhook event catalog covers payout, order, and account events (see the Webhooks guide). Poll GET /v1/deposits/{id} to track a deposit: fetching a pending or processing deposit also triggers an asynchronous status refresh, so polling keeps the record fresh.

Listing Deposits

Retrieve deposits for your account as a paginated list, newest first. Filter with status, and page with limit (1–100, default 20) plus cursor:
Treat nextCursor as an opaque token: pass it back verbatim in the cursor query parameter to fetch the next page, and stop when hasMore is false. An invalid cursor, limit, or status value returns a 400 with field-level details.

Error Handling

Payin endpoints return standard error bodies:
400 Bad Request
Common cases: See the Error Handling guide for retry and recovery strategies.

Best Practices

  • Always include a unique clientRef for idempotency and reconciliation (duplicates are rejected with a 400)
  • Display the full metadata block to the sender: account number, account name, bank name, and expiry
  • Communicate the virtual account expiry (metadata.expiresAt) so the sender transfers in time
  • Store deposit IDs (UUIDs) for status tracking and customer support
  • Poll GET /v1/deposits/{id} with exponential backoff
  • Handle all eight statuses, including in_review, refunding, and refunded
  • Treat in_review as not-yet-credited: funds are held for compliance review
  • Log deposit events for audit and debugging
  • Use HTTPS for all API communications
  • Never log sensitive customer payment data
  • Implement proper access controls for deposit endpoints
  • If you consume webhooks for other events, verify signatures (see Verifying Signatures)

Next Steps

Payouts

Send money globally to beneficiaries

Webhook Setup

Real-time notifications for payout, order, and account events

Ledger System

Track transactions with double-entry accounting

API Reference

Complete API documentation