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

# Building a Neobank

> Complete guide to building a digital bank with Zentra

Learn how to build a complete digital banking application using Zentra's financial infrastructure control plane.

<Warning>
  Treat this guide as architecture-first. The reviewed public surfaces in the current repo are transfers, payments, identity, cards, virtual accounts, billpay, and webhooks. Higher-level wallet or product behaviors should still be treated as application-layer composition unless separately documented for your tenant.
</Warning>

<Note>
  In the examples below, `customer` and `wallet` are application-layer concepts. Implement them in your own backend, or map them to tenant-specific overlays if your environment exposes extra draft APIs. The KYC snippets assume a tenant-gated reviewed identity surface and should still be validated against your enabled environment.
</Note>

## What You'll Build

By the end of this guide, you'll have:

* Customer onboarding with KYC
* Virtual bank accounts for each customer
* Wallet management
* Money transfers
* Virtual card issuing
* Transaction history
* Real-time notifications

## Primitive-First Model

Build your app in layers:

1. primitives: transfers, webhooks, audit-safe references, and tenant-gated identity checks where enabled
2. optional rail add-ons: virtual accounts, cards, and payment collection
3. product logic in your own backend: ledgers, onboarding flows, limits, rewards, savings experiences, and subscription logic

## Prerequisites

Before starting, ensure you have:

* Zentra developer account ([Create account](https://api.usezentra.com/developer/signup))
* Basic knowledge of your chosen programming language
* Development environment set up

## Architecture Overview

```
┌─────────────┐
│   Your App  │
│  (Frontend) │
└──────┬──────┘
       │
       ▼
┌──────────────┐
│  Your Server │
│  (Backend)   │
└──────┬───────┘
       │
       ▼
┌──────────────┐
│  Zentra API  │
└──────────────┘
```

## Step 1: Set Up Authentication

First, set up your API client:

<CodeGroup>
  ```javascript JavaScript theme={null}
  const Zentra = require('@zentra/sdk');

  const client = new Zentra.Client({
    apiKey: process.env.ZENTRA_SECRET_KEY,
    environment: 'sandbox'
  });
  ```

  ```python Python theme={null}
  from zentra import Client

  client = Client(
      api_key=os.getenv('ZENTRA_SECRET_KEY'),
      environment='sandbox'
  )
  ```
</CodeGroup>

## Step 2: Customer Onboarding

When a user signs up, create your own app-level customer record and then run KYC against Zentra's tenant-specific identity surface if your environment exposes it:

```javascript theme={null}
async function onboardCustomer(userData) {
  const customer = await db.customers.create({
    email: userData.email,
    first_name: userData.firstName,
    last_name: userData.lastName,
    phone: userData.phone
  });
  
  // Verify BVN
  const verification = await client.identity.verifyBVN({
    bvn: userData.bvn,
    first_name: userData.firstName,
    last_name: userData.lastName,
    date_of_birth: userData.dob
  });
  
  if (!verification.verified) {
    throw new Error('BVN verification failed');
  }
  await db.customers.markKycVerified(customer.id, verification);
  return customer;
}
```

## Step 3: Create Virtual Account

Create a dedicated account number for the customer:

```javascript theme={null}
async function createBankAccount(userId, railCustomerId, userBvn) {
  const account = await client.virtualAccounts.create({
    customer_id: railCustomerId,
    account_name: 'John Doe',
    preferred_bank: 'wema-bank',
    bvn: userBvn
  });
  
  // Save to your database
  await db.accounts.create({
    user_id: userId,
    account_id: account.id,
    account_number: account.account_number,
    bank_name: account.bank_name
  });
  
  return account;
}
```

## Step 4: Set Up Wallet

Project or initialize the user's balance state in your own backend:

```javascript theme={null}
async function setupWallet(userId) {
  const wallet = await db.wallets.getOrCreate(userId);
  
  // Display balance
  console.log(`Balance: ₦${wallet.balance_minor / 100}`);
  
  return wallet;
}
```

## Step 5: Handle Deposits

Listen for webhook notifications when virtual account is credited:

```javascript theme={null}
app.post('/webhooks/zentra', async (req, res) => {
  const { event, data } = req.body;
  
  if (event === 'virtual_account.credited') {
    // Update user's balance
    await db.transactions.create({
      user_id: data.customer_id,
      type: 'deposit',
      amount_minor: data.amount_minor,
      reference: data.reference,
      status: 'completed'
    });
    
    // Notify user
    await sendNotification(data.customer_id, {
      title: 'Deposit Received',
      message: `₦${data.amount_minor / 100} credited to your account`
    });
  }
  
  res.status(200).send('OK');
});
```

## Step 6: Enable Transfers

Allow users to send money:

```javascript theme={null}
async function transferMoney(userId, transferData) {
  // Check balance
  const wallet = await db.wallets.getOrCreate(userId);
  const totalAmountMinor = transferData.amountMinor + 50; // Amount + fee
  
  if (wallet.balance_minor < totalAmountMinor) {
    throw new Error('Insufficient balance');
  }
  
  // Create transfer
  const transfer = await client.transfers.create({
    amountMinor: transferData.amountMinor,
    recipient_account: transferData.recipientAccount,
    recipient_bank_code: transferData.bankCode,
    recipient_name: transferData.recipientAccountName,
    narration: transferData.narration,
    reference: `TRF_${Date.now()}`
  });
  
  // Save to database
  await db.transactions.create({
    user_id: userId,
    type: 'transfer',
    amount_minor: transferData.amountMinor,
    recipient: transferData.recipientAccountName,
    reference: transfer.reference,
    status: transfer.status
  });
  
  return transfer;
}
```

## Step 7: Issue Virtual Cards

Let users create virtual cards:

```javascript theme={null}
async function issueCard(userId, cardData) {
  const card = await client.cards.create({
    customer_id: userId,
    type: 'virtual',
    currency: 'NGN'
  });

  if (cardData.initialFundingMinor > 0) {
    await client.cards.fund(card.id, {
      amountMinor: cardData.initialFundingMinor,
      currency: 'NGN',
      transactionReference: `card_fund_${card.id}`,
      idempotencyKey: `card_fund_${card.id}`
    });
  }
  
  // Save card details (securely)
  await db.cards.create({
    user_id: userId,
    card_id: card.id,
    last4: card.last4,
    brand: card.brand,
    status: card.status
  });
  
  return {
    id: card.id,
    last4: card.last4,
    status: card.status
  };
}
```

## Step 8: Transaction History

Display user's transaction history:

```javascript theme={null}
async function getTransactions(userId, page = 1) {
  const { data, pagination } = await db.transactions.listByUser(userId, {
    page,
    limit: 20,
    sort: '-created_at'
  });
  
  return {
    transactions: data.map(txn => ({
      id: txn.id,
      type: txn.type,
      amount_minor: txn.amount_minor,
      description: txn.narration,
      status: txn.status,
      date: txn.created_at
    })),
    pagination
  };
}
```

## Step 9: Real-time Balance Updates

Use webhooks to update balances in real-time:

```javascript theme={null}
// When virtual account is credited
case 'virtual_account.credited':
  await updateUserBalance(data.customer_id, data.amount_minor, 'credit');
  break;

// When transfer completes
case 'transfer.completed':
  await updateUserBalance(data.customer_id, data.amount_minor, 'debit');
  break;

// When card transaction occurs
case 'card.transaction':
  await updateUserBalance(data.customer_id, data.amount_minor, 'debit');
  break;
```

## Step 10: Going Live

When ready for production:

1. **Switch to Live Mode**
   ```javascript theme={null}
   const client = new Zentra.Client({
     apiKey: process.env.ZENTRA_LIVE_KEY,
     environment: 'live'
   });
   ```

2. **Complete Compliance**
   * Submit business documents
   * Complete KYC verification
   * Set up webhook endpoints

3. **Test Thoroughly**
   * Run end-to-end tests
   * Test error scenarios
   * Load test your infrastructure

4. **Monitor Everything**
   * Set up error tracking (Sentry)
   * Monitor API usage
   * Track transaction success rates

## Complete Example App

Here's a minimal Express.js app. Notice that user/customer state and transaction history live in your backend, while Zentra handles the reviewed rail and money-movement surfaces:

```javascript theme={null}
const express = require('express');
const Zentra = require('@zentra/sdk');

const app = express();
const client = new Zentra.Client({
  apiKey: process.env.ZENTRA_SECRET_KEY,
  environment: 'sandbox'
});

app.use(express.json());

// Onboard customer
app.post('/api/onboard', async (req, res) => {
  const customer = await db.customers.create(req.body);
  const railCustomerId = await ensureRailCustomerForTenant(customer);
  const account = await client.virtualAccounts.create({
    customer_id: railCustomerId,
    account_name: `${customer.first_name} ${customer.last_name}`,
    bank_code: '058',
    bvn: req.body.bvn
  });
  
  res.json({ customer, account });
});

// Transfer money
app.post('/api/transfer', async (req, res) => {
  const transfer = await client.transfers.create(req.body);
  res.json(transfer);
});

// Get transactions
app.get('/api/transactions', async (req, res) => {
  const userId = req.query.user_id;
  const txns = await db.transactions.listByUser(userId);
  res.json(txns);
});

// Handle webhooks
app.post('/webhooks/zentra', (req, res) => {
  const { event, data } = req.body;
  console.log('Webhook:', event, data);
  // Process event...
  res.status(200).send('OK');
});

app.listen(3000);
```

## Next Steps

<CardGroup cols={2}>
  <Card title="API Reference" icon="book" href="/api-reference/overview">
    Explore all available endpoints
  </Card>

  <Card title="Webhook Guide" icon="webhook" href="/guides/handling-webhooks">
    Master webhook handling
  </Card>

  <Card title="Going Live" icon="rocket" href="/guides/going-live">
    Production checklist
  </Card>

  <Card title="Test Data" icon="flask" href="/resources/test-data">
    Test in sandbox mode
  </Card>
</CardGroup>

## Get Help

Questions? We're here to help:

* 📬 [Contact Support](/support/contact)
* 📈 [Status Page](https://status.usezentra.com)
* 📚 [More Guides](/guides/accepting-payments)
