Customer wallets are prepaid balances that Monigo can debit at billing time (see Prepaid Billing) or that you can charge programmatically for any purpose. Every wallet operation is recorded with double-entry ledger entries for full auditability.
Core concepts
| Concept | Description |
|---|
| Wallet | A balance in a single currency belonging to one customer. A customer can have multiple wallets (one per currency). |
| Ledger entry | One side of a double-entry accounting record. Every credit/debit creates exactly two entries — one debit and one credit — to keep books balanced. |
| Virtual account | A dedicated bank account (via Paystack, Flutterwave, or Monnify) that automatically credits the wallet when funds are deposited. |
Creating a wallet
Use getOrCreate to ensure a wallet exists for a customer + currency pair. If one already exists, it is returned; otherwise a new wallet with zero balance is created.
curl -X POST https://api.monigo.co/v1/wallets \
-H "Authorization: Bearer mk_live_..." \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cust_uuid",
"currency": "NGN"
}'
Listing wallets
All wallets
The org is inferred from your API key.
resp, err := client.Wallets.List(ctx)
for _, w := range resp.Wallets {
fmt.Printf(" %s — %s %s\n", w.CustomerID, w.Balance, w.Currency)
}
Filter by customer
You can pass an optional customer_id query parameter to filter wallets:
resp, err := client.Wallets.List(ctx, monigo.ListWalletsParams{
CustomerID: "cust_uuid",
})
Or use the dedicated customer wallets endpoint:
resp, err := client.Wallets.ListByCustomer(ctx, "cust_uuid")
Getting a wallet
Fetching a single wallet also returns its virtual accounts.
resp, err := client.Wallets.Get(ctx, "wallet_uuid")
fmt.Printf("Balance: %s\n", resp.Wallet.Balance)
fmt.Printf("Virtual accounts: %d\n", len(resp.VirtualAccounts))
Crediting a wallet
Credit adds funds to the wallet. Every credit creates two ledger entries (debit provider, credit wallet) and fires a customer.wallet.topped_up webhook.
curl -X POST https://api.monigo.co/v1/wallets/{wallet_id}/credit \
-H "Authorization: Bearer mk_live_..." \
-H "Content-Type: application/json" \
-d '{
"amount": "10000.00",
"currency": "NGN",
"description": "Top-up via payment link",
"entry_type": "deposit",
"reference_type": "payment_link",
"reference_id": "pay_abc123",
"idempotency_key": "topup_abc123"
}'
Always provide an idempotency_key when crediting wallets. This prevents double-credits if the request is retried. Use a stable reference from your own system (payment ID, transfer reference, etc.).
Entry types for credits
| Constant | Value | Use case |
|---|
WalletEntryTypeDeposit / WalletEntryType.Deposit | deposit | Customer top-up, payment received |
WalletEntryTypeRefund / WalletEntryType.Refund | refund | Reversing a previous charge |
WalletEntryTypeAdjustment / WalletEntryType.Adjustment | adjustment | Manual balance correction |
Debiting a wallet
Debit removes funds from the wallet. Returns a 402 Payment Required error if the balance is insufficient.
resp, err := client.Wallets.Debit(ctx, walletID, monigo.DebitWalletRequest{
Amount: "500.00",
Currency: "NGN",
Description: "Usage charge for March 2026",
EntryType: monigo.WalletEntryTypeUsage,
ReferenceType: "invoice",
ReferenceID: "inv_abc123",
IdempotencyKey: "debit_inv_abc123",
})
if monigo.IsQuotaExceeded(err) {
// 402 — insufficient balance
log.Println("Wallet balance too low")
}
Entry types for debits
| Constant | Value | Use case |
|---|
WalletEntryTypeUsage / WalletEntryType.Usage | usage | Metered usage charge |
WalletEntryTypeWithdrawal / WalletEntryType.Withdrawal | withdrawal | Customer withdrawal |
WalletEntryTypeAdjustment / WalletEntryType.Adjustment | adjustment | Manual balance correction |
Transaction history
List paginated ledger entries for a wallet.
resp, err := client.Wallets.ListTransactions(ctx, walletID, monigo.ListTransactionsParams{
Limit: 25,
Offset: 0,
})
for _, tx := range resp.Transactions {
fmt.Printf(" %s %s %s — %s (%s → %s)\n",
tx.Direction, tx.Amount, tx.Currency,
tx.Description, tx.BalanceBefore, tx.BalanceAfter)
}
Virtual accounts
Virtual accounts are dedicated bank accounts (via Paystack, Flutterwave, or Monnify) that automatically credit the wallet when a customer deposits funds. This enables self-service top-ups without requiring API calls from your backend.
Create a virtual account
va, err := client.Wallets.CreateVirtualAccount(ctx, walletID, monigo.CreateVirtualAccountRequest{
Provider: monigo.VirtualAccountProviderPaystack,
Currency: "NGN",
})
fmt.Printf("Account: %s at %s (%s)\n", va.AccountNumber, va.BankName, va.AccountName)
List virtual accounts
resp, err := client.Wallets.ListVirtualAccounts(ctx, walletID)
for _, va := range resp.VirtualAccounts {
fmt.Printf(" %s — %s %s (%s)\n", va.Provider, va.AccountNumber, va.BankName, va.Currency)
}
| Provider constant | Value |
|---|
VirtualAccountProviderPaystack / VirtualAccountProvider.Paystack | paystack |
VirtualAccountProviderFlutterwave / VirtualAccountProvider.Flutterwave | flutterwave |
VirtualAccountProviderMonnify / VirtualAccountProvider.Monnify | monnify |
Prepaid billing integration
Wallets are the foundation of Prepaid Billing. When a customer subscribes to a prepaid plan:
- Monigo auto-creates a wallet in the plan’s currency
- At period-end, Monigo debits the wallet atomically and creates a
paid invoice
- If the balance is insufficient, the subscription is paused and a
subscription.prepaid_balance_insufficient webhook fires
- When you credit the wallet and the balance covers the outstanding invoice, Monigo auto-resumes the subscription
This means you only need to handle wallet top-ups — Monigo takes care of billing, pausing, and resuming automatically.
Webhook events
| Event | When fired |
|---|
customer.wallet.topped_up | Wallet credited successfully |
subscription.prepaid_balance_insufficient | Prepaid billing failed — wallet balance too low |
invoice.paid | Prepaid invoice paid via wallet debit |